From 9c6722f99a69f038880bac0a3af4051aaaa15f55 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 11 Sep 2023 15:29:44 -0400 Subject: [PATCH 0001/2693] README v0 --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..829204dd --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# nestlog From 8a1f29a44b133c30c72a11f2a796d5ad10cad7f1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 11 Sep 2023 16:09:26 -0400 Subject: [PATCH 0002/2693] initial implementation + example + cmake "build" --- CMakeLists.txt | 7 + example/CMakeLists.txt | 21 +++ example/ex1/CMakeLists.txt | 3 + example/ex1/ex1.cpp | 14 ++ include/nestlog/array.hpp | 25 ++++ include/nestlog/fixed.hpp | 45 ++++++ include/nestlog/log_state.hpp | 227 ++++++++++++++++++++++++++++ include/nestlog/log_streambuf.hpp | 122 +++++++++++++++ include/nestlog/pad.hpp | 41 ++++++ include/nestlog/printer.hpp | 28 ++++ include/nestlog/quoted.hpp | 147 +++++++++++++++++++ include/nestlog/scope.hpp | 236 ++++++++++++++++++++++++++++++ include/nestlog/tag.hpp | 87 +++++++++++ include/nestlog/tostr.hpp | 99 +++++++++++++ include/nestlog/vector.hpp | 28 ++++ 15 files changed, 1130 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 example/CMakeLists.txt create mode 100644 example/ex1/CMakeLists.txt create mode 100644 example/ex1/ex1.cpp create mode 100644 include/nestlog/array.hpp create mode 100644 include/nestlog/fixed.hpp create mode 100644 include/nestlog/log_state.hpp create mode 100644 include/nestlog/log_streambuf.hpp create mode 100644 include/nestlog/pad.hpp create mode 100644 include/nestlog/printer.hpp create mode 100644 include/nestlog/quoted.hpp create mode 100644 include/nestlog/scope.hpp create mode 100644 include/nestlog/tag.hpp create mode 100644 include/nestlog/tostr.hpp create mode 100644 include/nestlog/vector.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..b2a41b6f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.10) + +project(nestlog VERSION 0.1) +enable_language(CXX) +enable_testing() + +add_subdirectory(example) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 00000000..ac2655ae --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,21 @@ +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 "") + +#include(cmake/FindSphinx.cmake) + +add_subdirectory(ex1) + +# ---------------------------------------------------------------- +# make standard directories for std:: includes explicit +# so that +# (1) they appear in compile_commands.json. +# (2) clangd (run from emacs lsp-mode) can find them +# +if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES + ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +endif() diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt new file mode 100644 index 00000000..ce8d9a5e --- /dev/null +++ b/example/ex1/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(ex1 ex1.cpp) + +target_include_directories(ex1 PUBLIC ${PROJECT_SOURCE_DIR}/include) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp new file mode 100644 index 00000000..60922607 --- /dev/null +++ b/example/ex1/ex1.cpp @@ -0,0 +1,14 @@ +#include "nestlog/scope.hpp" + +using namespace xo; + +void A(int x) { + XO_SCOPE(log); + + log("x:", x); +} + +int +main(int argc, char ** argv) { + A(66); +} diff --git a/include/nestlog/array.hpp b/include/nestlog/array.hpp new file mode 100644 index 00000000..03b265e9 --- /dev/null +++ b/include/nestlog/array.hpp @@ -0,0 +1,25 @@ +/* @file array.hpp */ + +#pragma once + +#include +#include + +namespace std { + template + inline std::ostream & + operator<<(std::ostream & os, + std::array const & v) + { + os << "["; + for(size_t i = 0; i < N; ++i) { + if(i > 0) + os << " "; + os << v[i]; + } + os << "]"; + return os; + } /*operator<<*/ +} /*namespace std*/ + +/* end array.hpp */ diff --git a/include/nestlog/fixed.hpp b/include/nestlog/fixed.hpp new file mode 100644 index 00000000..bf1a5526 --- /dev/null +++ b/include/nestlog/fixed.hpp @@ -0,0 +1,45 @@ +/* @file fixed.hpp */ + +#pragma once + +#include + +namespace xo { + /* use: + * ostream os = ...; + * + * os << fixed(3.1415926, 3) + * + * writes + * 3.142 + * + * on os, restoring stream's formatting+precision state + */ + class fixed { + public: + fixed(double x, uint16_t prec) : x_{x}, prec_{prec} {} + + /* print this value */ + double x_; + /* precision */ + uint16_t prec_ = 0; + }; /*fixed*/ + + inline std::ostream & + operator<<(std::ostream & s, fixed const & fx) + { + std::ios::fmtflags orig_flags = s.flags(); + std::streamsize orig_p = s.precision(); + + s.flags(std::ios::fixed); + s.precision(fx.prec_); + s << fx.x_; + + s.flags(orig_flags); + s.precision(orig_p); + + return s; + } /*operator<<*/ +} /*namespace xo*/ + +/* end fixed.hpp */ diff --git a/include/nestlog/log_state.hpp b/include/nestlog/log_state.hpp new file mode 100644 index 00000000..41556b8a --- /dev/null +++ b/include/nestlog/log_state.hpp @@ -0,0 +1,227 @@ +/* @file log_state.hpp */ + +#pragma once + +#include "log_streambuf.hpp" +#include +#include // for std::unique_ptr + +namespace xo { + // track per-thread state associated with nesting logger + // + template + class state_impl { + public: + using log_streambuf_type = log_streambuf>; + + public: + state_impl(); + + std::uint32_t nesting_level() const { return nesting_level_; } + + void incr_nesting() { ++nesting_level_; } + void decr_nesting() { --nesting_level_; } + + std::ostream & ss() { return ss_; } + + /* call on entry to new scope */ + void preamble(std::string_view name1, std::string_view name2); + /* call before each new log entry */ + void indent(char pad_char); + /* call on exit from scope */ + void postamble(std::string_view name1, std::string_view name2); + + /* write collected output to *p_sbuf */ + void flush2sbuf(std::streambuf * p_sbuf); + + /* discard output, reset write pointer to beginning of buffer */ + void reset_stream() { + p_sbuf_phase1_->reset_stream(); + p_sbuf_phase2_->reset_stream(); + } + + private: + /* common implementation for .preamble(), .postamble() */ + void entryexit_aux(std::string_view name1, + std::string_view name2, + char label_char); + + private: + /* current nesting level for this thread */ + uint32_t nesting_level_ = 0; + + /* buffer space for logging + * (before pretty-printing for scope::log() calls that span multiple lines) + * reused across tos() and scope::log() calls + */ + std::unique_ptr p_sbuf_phase1_; + + /* buffer space for handling scope::log() calls that span multiple lines; + * inserts extra characters in effort to indent gracefully + */ + std::unique_ptr p_sbuf_phase2_; + + /* output stream -- always attached to .p_sbuf_phase1 + * stream inserters for application datatypes will target this stream + */ + std::ostream ss_; + }; /*state_impl*/ + + constexpr uint32_t c_default_buf_size = 1024; + + template + state_impl::state_impl() + : p_sbuf_phase1_(new log_streambuf_type(c_default_buf_size)), + p_sbuf_phase2_(new log_streambuf_type(c_default_buf_size)), + ss_(p_sbuf_phase1_.get()) + { + assert(p_sbuf_phase1_.get() == ss_.rdbuf()); + } /*ctor*/ + + template + void + state_impl::indent(char pad_char) + { + //log_streambuf * sbuf = this->p_sbuf_phase1_.get(); + +#ifdef NOT_IN_USE + { + char buf[80]; + ::snprintf(buf, sizeof(buf), "[%02d] ", this->nesting_level_); + + this->ss_ << buf; + //this->p_sbuf_->sputn(buf, strlen(buf)); + } +#endif + + /* indent to nesting level */ + for(uint32_t i = 0, n = this->nesting_level_; iss_ << pad_char; + } + } /*indent*/ + + template + void + state_impl::entryexit_aux(std::string_view name1, + std::string_view name2, + char label_char) + { + log_streambuf_type * sbuf = this->p_sbuf_phase1_.get(); + + sbuf->reset_stream(); + this->indent(' '); + + /* mnemonic for scope entry/exit */ + this->ss_ << label_char; + + /* scope name */ + this->ss_ << name1 << name2 << "\n"; + } /*entryexit_aux*/ + + template + void + state_impl::preamble(std::string_view name1, + std::string_view name2) + { + this->entryexit_aux(name1, name2, '+' /*label_char*/); + } /*preamble*/ + + template + void + state_impl::postamble(std::string_view name1, + std::string_view name2) + { + this->entryexit_aux(name1, name2, '-' /*label_char*/); + } /*postamble*/ + + template + void + state_impl::flush2sbuf(std::streambuf * p_sbuf) + { + log_streambuf_type * sbuf1 = this->p_sbuf_phase1_.get(); + log_streambuf_type * sbuf2 = this->p_sbuf_phase2_.get(); + + /* expecting sbuf to contain 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 clog + */ + 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; + + while(true) { + bool have_nonspace = false; + + /* invariant: s<=p<=e */ + + /* for indenting, looking for first 'space following non-space, on first line', if any */ + + while(p < e) { + if(space_after_nonspace) { + ; + } else { + if(*p != ' ') + have_nonspace = true; + + if(have_nonspace && (*p == ' ')) { + space_after_nonspace = p; + } + } + + if(*p == '\n') { + ++p; + break; + } else { + ++p; + } + } + + /* p=e or *p=\n */ + + /* charseq [s,p) does not contain any newlines, print it */ + 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)); + //} + + /* at least 1 char following newline, need to indent for it + * - minimum indent = nesting level; + * - however if space_after_nonspace defined, indent to that + */ + uint32_t n_indent = this->nesting_level_; + + if(space_after_nonspace) + n_indent += (space_after_nonspace - s); + + for(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()); + + /* reset streams for next message */ + this->reset_stream(); + } /*flush2sbuf*/ +} /*namespace xo*/ + +/* end log_state.hpp */ diff --git a/include/nestlog/log_streambuf.hpp b/include/nestlog/log_streambuf.hpp new file mode 100644 index 00000000..5d06dab7 --- /dev/null +++ b/include/nestlog/log_streambuf.hpp @@ -0,0 +1,122 @@ +/* @file log_streambuf.hpp */ + +#pragma once + +#include +#include +#include // e.g. for std::memcpy() +#include // e.g. for std::memcpy() + +namespace xo { + /* recycling buffer for logging. + * write to self-extending storage array; + */ + template + class log_streambuf : public std::streambuf { + public: + log_streambuf(std::uint32_t buf_z) { + this->buf_v_.resize(buf_z); + this->reset_stream(); + } /*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(); } + std::uint32_t pos() const { return this->pptr() - this->pbase(); } + + 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); + } /*reset_stream*/ + + protected: + 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()); + +#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=" << quoted(string_view(s, n)) << ", n=" << n << std::endl; + + if (this->pptr() + n > this->hi()) { + n = this->hi() - this->pptr(); + std::memcpy(this->pptr(), s, n); + } else { + std::memcpy(this->pptr(), s, n); + } + this->pbump(n); + + return n; + } /*xsputn*/ + + virtual int_type + 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())); + + std::size_t new_z = 2 * this->buf_v_.size(); + + this->buf_v_.resize(new_z); + this->buf_v_[old_n] = new_ch; + + /* '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(); + + this->setp(p_base, p_hi); + this->pbump(old_n + 1); + + return new_ch; + } /*overflow*/ + + /* off. offset, relative to starting point dir. + * dir. + * which. in|out|both + */ + virtual pos_type seekoff(off_type off, + std::ios_base::seekdir dir, + std::ios_base::openmode which) override { + //std::cout << "seekoff: off=" << off << ", dir=" << dir << ", which=" << which << std::endl; + + // Only output stream is supported + if (which != std::ios_base::out) + throw std::runtime_error("log_streambuf: only output mode supported"); + + if (dir == std::ios_base::cur) { + this->pbump(off); + } else if (dir == std::ios_base::end) { + /* .setp(): using for side effect: sets .pptr to .pbase */ + this->setp(this->pbase(), this->epptr()); + this->pbump(off); + } else if (dir == std::ios_base::beg) { + /* .setp(): using for side effect: sets .pptr to .pbase */ + this->setp(this->pbase(), this->epptr()); + this->pbump(this->capacity() + off); + } + + return this->pptr() - this->pbase(); + } /*seekoff*/ + + private: + /* buffered output stored here */ + std::vector buf_v_; + }; /*log_streambuf*/ + +} /*namespace xo*/ + +/* end log_streambuf.hpp */ diff --git a/include/nestlog/pad.hpp b/include/nestlog/pad.hpp new file mode 100644 index 00000000..123c8d5c --- /dev/null +++ b/include/nestlog/pad.hpp @@ -0,0 +1,41 @@ +/* @file pad.hpp */ + +#pragma once + +#include + +namespace xo { + /* use: + * ostream os = ...; + * os << ":" << pad(8) << ":" + * + * writes + * : : + * + * on os + */ + class pad_impl { + public: + pad_impl(int32_t n) : n_pad_(n) {} + + uint32_t n_pad() const { return n_pad_; } + + private: + uint32_t n_pad_ = 0; + }; /*pad_impl*/ + + inline pad_impl + pad(uint32_t n) { return pad_impl(n); } + + inline std::ostream & + operator<<(std::ostream &s, + pad_impl const &pad) + { + for(uint32_t i=0; i + +namespace xo { + namespace print { + /* print an event to a logfile + * intended to be usable as EventSink argument + * to RealizationSimSource + */ + template + class printer { + public: + printer(Stream && os) : os_{std::move(os)} {} + + void operator()(T const & x) { + this->os_ << x; + } + + private: + Stream os_; + }; /*printer*/ + } /*namespace print*/ +} /*namespace xo*/ + +/* end printer.hpp */ diff --git a/include/nestlog/quoted.hpp b/include/nestlog/quoted.hpp new file mode 100644 index 00000000..5e4cc8b4 --- /dev/null +++ b/include/nestlog/quoted.hpp @@ -0,0 +1,147 @@ +/* file quoted.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "nestlog/tostr.hpp" +#include +#include +#include +#include + +namespace xo { + namespace print { + /* use this to avoid template conversion hassles + * since literal strings get treated as arrays + */ + template + char const * ccs(T x) { return x; } + + /* Printing cases: + * 1. T&&: + * move into quoted_impl. T must be moveable! + * 2. T&: + * copy reference into quoted_impl. + * similarly for T const &, copy reference into quoted_impl + */ + + template + class quoted_impl { + public: + quoted_impl(bool unq_flag, T const & x) : unq_flag_{unq_flag}, value_{x} {} + quoted_impl(bool unq_flag, T && x) : unq_flag_{unq_flag}, value_{std::move(x)} {} + + bool unq_flag() const { return unq_flag_; } + T const & value() const { return value_; } + + void print(std::ostream & os) const { + std::string xs = xo::tostr(value_); + + if (xs.empty()) { + /* print empty string as "" */ + os << "\"\""; + } else if ((xs.at(0) == '<') && (xs.at(xs.size() - 1) == '>')) { + /* assume string represents output of a well-formed object printer, + * and already self-escapes + */ + os << xs; + } else if (xs.find_first_of(" \"\n\r\\") == std::string::npos) { + /* no escapes needed, just print xs */ + if (unq_flag_) + os << xs; + else + os << "\"" << xs << "\""; + } else { + /* printed value contains a space + * and/or a must-be-escaped character. + * in any case, need quotes + */ + + os << "\""; + + /* print contents of ss, with escapes: + * \ => \\ + * " => \" + * newline => \n + * cr => \r + */ + for (char ch : xs) { + switch (ch) { + case '"': + /* " => \" */ + os << "\\\""; + break; + case '\n': + /* newline -> \n */ + os << "\\\n"; + break; + case '\r': + /* cr -> \r */ + os << "\\\r"; + break; + case '\\': + /* \ => \\ (mind c++ requires we escape \) */ + os << "\\\\"; + break; + default: + os << ch; + break; + } + } + + os << "\""; + } + } /*print*/ + + private: + /* .unq_flag: if true, omit surrounding " chars + * if printed value satisfies both: + * - no escaped chars + * - no spaces + */ + bool unq_flag_ = false; + /* .value: value to be printed */ + T value_; + }; /*quoted_impl*/ + + template + std::ostream & + operator<<(std::ostream & os, quoted_impl const & x) { + x.print(os); + return os; + } /*operator*/ + + /* 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] + * (in particular: _not_ std::string &, std::string const &, std::string &&) + * - rvalue std::string passed to quoted_impl ctor + * + * 2a. call quoted(x) with std::string & x, then: + * - T deduced to [std::string &] + * - std::string & passed to quoted_impl ctor + * + * 2b. call quoted(x) with std::string const & x, then: + * - T deduced to [std::string const &] + * - std::string const & passed to quoted_impl ctor + */ + template + auto quoted(T && x) { + return quoted_impl(false /*unq_flag*/, std::forward(x)); + } + + inline auto qcstr(char const * x) { + return quoted(x); + } /*qcstr*/ + + template + auto unq(T && x) { + return quoted_impl(true /*unq_flag*/, std::forward(x)); + } + } /*namespace print*/ +} /*namespace xo*/ + +/* end quoted.hpp */ diff --git a/include/nestlog/scope.hpp b/include/nestlog/scope.hpp new file mode 100644 index 00000000..29f5791c --- /dev/null +++ b/include/nestlog/scope.hpp @@ -0,0 +1,236 @@ +/* @file scopẹhpp */ + +#pragma once + +#include "log_state.hpp" +#include "tostr.hpp" +#include "tag.hpp" + +#include +#include +#include // for std::unique_ptr + +namespace xo { + + template + class state_impl; + + /* 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) xo::scope name(__FUNCTION__) + /* like XO_SCOPE(name), but also set enabled flag */ +# define XO_SCOPE2(name, debug_flag) xo::scope name(__FUNCTION__, debug_flag) +# define XO_SCOPE_DISABLED(name) xo::scope name(__FUNCTION__, false) +# define XO_STUB() { XO_SCOPE(logr); logr.log("STUB"); } + + /* nesting logger + * + * Use: + * using xo::scope; + * + * void myfunc() { + * XO_SCOPE(log); //or scope x("myfunc") + * log(a,b,c); + * anotherfunc(); + * log(d,e,f); + * } + * + * void anotherfunc() { + * XO_SCOPE(x); // or scope x("anotherfunc") + * x.log(y); + * } + * + * or: + * void myfunc() { + * bool log_flag = true; + * XO_SCOPE2(log, log_flag); // create local variable 'log' + * log && log(a,b,c); // log iff enabled + * log.end_scope(); // optional protection against compiler destroying 'log' early + * } + * + * output like: + * +myfunc: + * a,b,c + * +anotherfunc: + * y + * -anotherfunc: + * d,e,f + * -myfunc: + */ + template > + class basic_scope { + public: + using state_impl_type = state_impl; + + public: + basic_scope(std::string_view name1); + basic_scope(std::string_view name1, bool enabled_flag); + basic_scope(std::string_view name1, std::string_view name2, bool enabled_flag); + ~basic_scope(); + + bool enabled() const { return !finalized_; } + + operator bool() const { return this->enabled(); } + + /* report current nesting level */ + std::uint32_t nesting_level() const; + + template + bool log(Tn&&... rest) { + if(this->finalized_) { + throw std::runtime_error("basic_scope: attempt to use finalized scope"); + } else { + state_impl_type * logstate = require_indent_thread_local_state(); + + /* log to per-thread stream to prevent data races */ + tosn(logstate2stream(logstate), rest...); + + this->flush2clog(logstate); + } + + return true; + } /*log*/ + + template + bool operator()(Tn&&... rest) { return this->log(rest...); } + + /* call once to end scope before dtor */ + void end_scope(); + + 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ẹ + */ + static state_impl_type * require_indent_thread_local_state(); + + /* establish logging state; use thread-local storage for threadsafety */ + static state_impl_type * require_thread_local_state(); + + /* retrieve permanently-associated ostream for logging-state */ + static std::ostream & logstate2stream(state_impl_type * logstate); + + /* write collected output to std::clog, or chosen streambuf */ + void flush2clog(state_impl_type * logstate, std::streambuf * p_sbuf = std::clog.rdbuf()); + + private: + /* keep logging state separately for each thread */ + static thread_local std::unique_ptr s_threadlocal_state; + + /* name of this scope (part 1) */ + std::string_view name1_ = ""; + /* name of this scope (part 2) */ + std::string_view name2_ = "::"; + /* set once per scope .finalized=true <-> logging disabled */ + bool finalized_ = false; + }; /*basic_scope*/ + + template + basic_scope::basic_scope(std::string_view fn1, + std::string_view fn2, + bool enabled_flag) + : name1_(fn1), + name2_(fn2), + finalized_(!enabled_flag) + { + if(enabled_flag) { + state_impl_type * logstate = basic_scope::require_thread_local_state(); + + logstate->preamble(this->name1_, this->name2_); + logstate->flush2sbuf(std::clog.rdbuf()); + + ///* next call to scope::log() can reset to beginning of buffer space */ + //logstate->ss().seekp(0); + + logstate->incr_nesting(); + } + } /*ctor*/ + + template + basic_scope::basic_scope(std::string_view fn1, bool enabled_flag) + : basic_scope(fn1, "", enabled_flag) + {} + + template + basic_scope::basic_scope(std::string_view fn) + : basic_scope(fn, true /*enabled_flag*/) + {} + + template + basic_scope::~basic_scope() { + if(!this->finalized_) + this->end_scope(); + } /*dtor*/ + + template + thread_local std::unique_ptr> + basic_scope::s_threadlocal_state; + + template + std::uint32_t + basic_scope::nesting_level() const { + return require_thread_local_state()->nesting_level(); + } /*nesting_level*/ + + template + basic_scope::state_impl_type * + basic_scope::require_indent_thread_local_state() + { + state_impl_type * local_state = require_thread_local_state(); + + local_state->reset_stream(); + local_state->indent(' ' /*pad_char*/); + + return local_state; + } /*require_thread_local_stream*/ + + template + basic_scope::state_impl_type * + basic_scope::require_thread_local_state() + { + if(!s_threadlocal_state) { + s_threadlocal_state.reset(new state_impl_type()); + } + + return s_threadlocal_state.get(); + } /*require_thread_local_state*/ + + template + std::ostream & + basic_scope::logstate2stream(state_impl_type * logstate) + { + return logstate->ss(); + } /*logstate2stream*/ + + template + void + basic_scope::flush2clog(state_impl_type * logstate, + std::streambuf * p_sbuf) + { + logstate->flush2sbuf(p_sbuf); + } /*flush2clog*/ + + template + void + basic_scope::end_scope() + { + if(!this->finalized_) { + this->finalized_ = true; + + state_impl_type * logstate + = basic_scope::require_thread_local_state(); + + logstate->decr_nesting(); + + logstate->postamble(this->name1_, this->name2_); + logstate->flush2sbuf(std::clog.rdbuf()); + } + } /*end_scope*/ + + + using scope = basic_scope; + +} /*namespace xo*/ + +/* end scope.hpp */ diff --git a/include/nestlog/tag.hpp b/include/nestlog/tag.hpp new file mode 100644 index 00000000..16a3e21a --- /dev/null +++ b/include/nestlog/tag.hpp @@ -0,0 +1,87 @@ +/* @file tag.hpp */ + +#pragma once + +#include "nestlog/quoted.hpp" +#include + +// STRINGIFY(xyz) -> "xyz" +#define STRINGIFY(x) #x + +// TAG(xyz) -> tag("xyz", xyz) +#define TAG(x) xo::make_tag(STRINGIFY(x), x) +#define TAG2(x, y) xo::make_tag(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 + struct tag_impl { + tag_impl(Name const & n, Value const & v) + : name_{n}, value_{v} {} + + Name const & name() const { return name_; } + Value const & value() const { return value_; } + + 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*/ + + template + tag_impl + xtag(Name && n, Value && v) + { + return tag_impl(n, v); + } /*xtag*/ + + template + tag_impl + xtag(char const * n, Value && v) { + return tag_impl(n, v); + } /*xtag*/ + + inline + tag_impl + xtag_pre(char const * n) { + return tag_impl(n, ""); + } /*xtag_pre*/ + + template + inline std::ostream & + operator<<(std::ostream &s, + tag_impl const & tag) + { + using xo::print::unq; + + if(PrefixSpace) + s << " "; + + s << ":" << tag.name() + << " " << unq(tag.value()); + + return s; + } /*operator<<*/ +} /*namespace xo*/ + +/* end tag.hpp */ diff --git a/include/nestlog/tostr.hpp b/include/nestlog/tostr.hpp new file mode 100644 index 00000000..b2bc927b --- /dev/null +++ b/include/nestlog/tostr.hpp @@ -0,0 +1,99 @@ +/* file tostr.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include +#include +#include + +namespace xo { + /* + * write x to stream s + * note: here x is a universal reference, since + * (a) it's a template type + * (b) requires deduction to establish x's type + * this means: + * x will be an r-value reference or an l-value reference + * depending on calling context + * + * see: + * https://eli.thegreenplace.net/2014/variadic-templates-in-c/ + * http://bajamircea.github.io/coding/cpp/2016/04/07/move-forward.html + * https://en.cppreference.com/w/cpp/language/value_category + * + * has identity == has address + * + * /- has identity -----------------\ + * | | + * | lvalue | + * | glvalue /------------------------------\ + * | | | | + * | | xvalue | | + * | | rvalue | | + * | | glvalue | | + * | | | | + * \--------------------------------/ | + * | rvalue | + * | prvalue | + * | | + * \- can be moved ---------------/ + * + * 1. has identity, but cannot be moved -> it's an lvalue; otherwise it's an rvalue + * e.g: local variable name + * + * 2. can be moved, but no identity -> it's a prvalue (pure right-value); + * otherwise it's a glvalue (generalized left-value) + * e.g: non-reference function return value, or literal constant + * + * 3. has identity and can be moved -> it's an xvalue (strange value) + * e.g: std::move(a) + * + * reminder: + * - std::move() does not move: it converts lvalue to rvalue, so compiler can select + * desired overload + * - std::forward() does not forward: it recovers original value category + * (when starting with a universal reference), so compiler can select + * desired ctor + */ + + // Use: + // tos(s,a,b,c) + // is the same as + // s << a << b << c; + // + template + Stream & tos(Stream & s, T && x) { + s << x; + return s; + } /*tos*/ + + template + Stream &tos(Stream &s, T &&x, Tn &&...rest) { + s << x; + return tos(s, rest...); + } /*tos*/ + + // like tos(..), but append newline + // + template + Stream &tosn(Stream &s, Tn &&...args) { + tos(s, args...); + s << std::endl; + return s; + } /*tosn*/ + + // tostr(args..) writes arguments to temporary stingstream, + // returns its contents + // + template std::string tostr(Tn &&...args) { + std::stringstream ss; + tos(ss, args...); + //ss << std::ends; + return ss.str(); + } /*tostr*/ +} /*namespace xo*/ + +/* end tostr.hpp */ diff --git a/include/nestlog/vector.hpp b/include/nestlog/vector.hpp new file mode 100644 index 00000000..be5f4497 --- /dev/null +++ b/include/nestlog/vector.hpp @@ -0,0 +1,28 @@ +/* file vector.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include +#include + +namespace std { + template + inline std::ostream & + operator<<(std::ostream & os, + std::vector const & v) + { + os << "["; + for(size_t i=0, z=v.size(); i 0) + os << " "; + os << v[i]; + } + os << "]"; + return os; + } /*operator<<*/ +} /*namespace std*/ + +/* end vector.hpp */ From 45988211c7ccf7daa5063170302ffdbf32d3f900 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 11 Sep 2023 16:10:50 -0400 Subject: [PATCH 0003/2693] doc: README.md example --- README.md | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 829204dd..261399f8 100644 --- a/README.md +++ b/README.md @@ -1 +1,31 @@ -# nestlog +# nestlog -- logging with automatic indenting according to call graph + +Nestlog is a lightweight header-only library for console logging. + +## Examples + + ``` + /* examples/ex1/ex1.cpp */ + + #include "nestlog/scope.hpp" + + using namespace xo; + + void A(int x) { + XO_SCOPE(log) // i.e. xo::scope log("A"); + + log(":x ", x); + } + + int + main(int argc, char ** argv) { + A(66); + } + ``` + + output: + ``` + +A + :x 66 + -A + ``` From 7bfde708dd978e753475add85ae3a9a782e05c6c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 11 Sep 2023 16:12:18 -0400 Subject: [PATCH 0004/2693] doc: format tidy --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 261399f8..33e1b52d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ Nestlog is a lightweight header-only library for console logging. ## Examples - ``` /* examples/ex1/ex1.cpp */ #include "nestlog/scope.hpp" @@ -21,11 +20,9 @@ Nestlog is a lightweight header-only library for console logging. main(int argc, char ** argv) { A(66); } - ``` - output: - ``` +output: + +A - :x 66 + :x 66 -A - ``` From c32699ae14d55bbb5d4aa9130d74cfe660c93e6a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 11 Sep 2023 16:24:46 -0400 Subject: [PATCH 0005/2693] nestlog: + ex2 + expand README.md --- README.md | 70 +++++++++++++++++++++++++++++++++++--- example/CMakeLists.txt | 1 + example/ex2/CMakeLists.txt | 3 ++ example/ex2/ex2.cpp | 27 +++++++++++++++ 4 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 example/ex2/CMakeLists.txt create mode 100644 example/ex2/ex2.cpp diff --git a/README.md b/README.md index 33e1b52d..2db7c081 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,16 @@ Nestlog is a lightweight header-only library for console logging. ## Examples +### 1 + /* examples/ex1/ex1.cpp */ #include "nestlog/scope.hpp" - using namespace xo; - void A(int x) { - XO_SCOPE(log) // i.e. xo::scope log("A"); + XO_SCOPE(log); // i.e. xo::scope log("A"); - log(":x ", x); + log("enter ", ":x ", x); } int @@ -24,5 +24,65 @@ Nestlog is a lightweight header-only library for console logging. output: +A - :x 66 + enter :x 66 -A + +### 2 + + /* examples ex2/ex2.cpp */ + + #include "nestlog/scope.hpp" + + int fib(int n) { + XO_SCOPE(log); + + int retval = 1; + + if (n >= 2) + retval = fib(n - 1) + fib(n - 2); + + log("result ", ":retval ", retval); + } + + int + main(int argc, char ** argv) { + XO_SCOPE(log); + + int n = 4; + int fn = fib(n); + + log(":n ", n, " :fib(n) ", fn); + } + +output: + + +main + +fib + +fib + +fib + +fib + result :retval 1 + -fib + +fib + result :retval 1 + -fib + result :retval 2 + -fib + +fib + result :retval 1 + -fib + result :retval 3 + -fib + +fib + +fib + result :retval 1 + -fib + +fib + result :retval 1 + -fib + result :retval 2 + -fib + result :retval 5 + -fib + :n 4 :fib(n) 5 + -main diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index ac2655ae..8a901a22 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -8,6 +8,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") #include(cmake/FindSphinx.cmake) add_subdirectory(ex1) +add_subdirectory(ex2) # ---------------------------------------------------------------- # make standard directories for std:: includes explicit diff --git a/example/ex2/CMakeLists.txt b/example/ex2/CMakeLists.txt new file mode 100644 index 00000000..76ae6f64 --- /dev/null +++ b/example/ex2/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(ex2 ex2.cpp) + +target_include_directories(ex2 PUBLIC ${PROJECT_SOURCE_DIR}/include) diff --git a/example/ex2/ex2.cpp b/example/ex2/ex2.cpp new file mode 100644 index 00000000..6267c045 --- /dev/null +++ b/example/ex2/ex2.cpp @@ -0,0 +1,27 @@ +/* examples ex2/ex2.cpp */ + +#include "nestlog/scope.hpp" + +int +fib(int n) { + XO_SCOPE(log); + + int retval = 1; + + if (n >= 2) + retval = fib(n - 1) + fib(n - 2); + + log("result ", ":retval ", retval); + + return retval; +} + +int +main(int argc, char ** argv) { + XO_SCOPE(log); + + int n = 4; + int fn = fib(n); + + log(":n ", n, " :fib(n) ", fn); +} From 01bd1ffc6f79e5ea5183844372ec115c7c07016c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 11 Sep 2023 16:37:50 -0400 Subject: [PATCH 0006/2693] tweak example 2 output --- README.md | 39 ++++++++++++++++++++++++--------------- example/ex2/ex2.cpp | 6 ++++-- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 2db7c081..4e934ad7 100644 --- a/README.md +++ b/README.md @@ -33,15 +33,20 @@ output: #include "nestlog/scope.hpp" - int fib(int n) { - XO_SCOPE(log); + int + fib(int n) { + XO_SCOPE(log); - int retval = 1; + int retval = 1; - if (n >= 2) - retval = fib(n - 1) + fib(n - 2); + if (n >= 2) { + log(":n ", n); + retval = fib(n - 1) + fib(n - 2); + } - log("result ", ":retval ", retval); + log(":n ", n, " -> :retval ", retval); + + return retval; } int @@ -58,31 +63,35 @@ output: +main +fib + :n 4 +fib + :n 3 +fib + :n 2 +fib - result :retval 1 + :n 1 -> :retval 1 -fib +fib - result :retval 1 + :n 0 -> :retval 1 -fib - result :retval 2 + :n 2 -> :retval 2 -fib +fib - result :retval 1 + :n 1 -> :retval 1 -fib - result :retval 3 + :n 3 -> :retval 3 -fib +fib + :n 2 +fib - result :retval 1 + :n 1 -> :retval 1 -fib +fib - result :retval 1 + :n 0 -> :retval 1 -fib - result :retval 2 + :n 2 -> :retval 2 -fib - result :retval 5 + :n 4 -> :retval 5 -fib :n 4 :fib(n) 5 -main diff --git a/example/ex2/ex2.cpp b/example/ex2/ex2.cpp index 6267c045..dfc2a7bd 100644 --- a/example/ex2/ex2.cpp +++ b/example/ex2/ex2.cpp @@ -8,10 +8,12 @@ fib(int n) { int retval = 1; - if (n >= 2) + if (n >= 2) { + log(":n ", n); retval = fib(n - 1) + fib(n - 2); + } - log("result ", ":retval ", retval); + log(":n ", n, " -> :retval ", retval); return retval; } From 89b493e939926d05a1ab564ce272175fb0c62db9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 12 Sep 2023 12:23:32 -0400 Subject: [PATCH 0007/2693] build: bugfix: missed commit nestlog.cmake --- cmake/nestlog.cmake | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 cmake/nestlog.cmake diff --git a/cmake/nestlog.cmake b/cmake/nestlog.cmake new file mode 100644 index 00000000..30736007 --- /dev/null +++ b/cmake/nestlog.cmake @@ -0,0 +1,31 @@ +# ---------------------------------------------------------------- +# use this in subdirs that compile c++ code +# +macro(xo_include_options target) + # ---------------------------------------------------------------- + # PROJECT_SOURCE_DIR: + # so we can for example write + # #include "nestlog/scope.hpp" + # from anywhere in the project + # PROJECT_BINARY_DIR: + # since generated version file will be in build directory, + # need that build directory to also appear in + # compiler's include path + # + target_include_directories( + ${target} PUBLIC + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_BINARY_DIR} + ) + + # ---------------------------------------------------------------- + # make standard directories for std:: includes explicit + # so that + # (1) they appear in compile_commands.json. + # (2) clangd (run from emacs lsp-mode) can find them + # + if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES + ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) + endif() +endmacro() From 49f8acfabb87f94291b497ae71fdbb4177d2b362 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 12 Sep 2023 12:24:05 -0400 Subject: [PATCH 0008/2693] build: include paths (for lsp integration) --- CMakeLists.txt | 2 ++ example/ex1/CMakeLists.txt | 3 +-- example/ex2/CMakeLists.txt | 3 +-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b2a41b6f..38939895 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,4 +4,6 @@ project(nestlog VERSION 0.1) enable_language(CXX) enable_testing() +include(cmake/nestlog.cmake) + add_subdirectory(example) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt index ce8d9a5e..6af29d79 100644 --- a/example/ex1/CMakeLists.txt +++ b/example/ex1/CMakeLists.txt @@ -1,3 +1,2 @@ add_executable(ex1 ex1.cpp) - -target_include_directories(ex1 PUBLIC ${PROJECT_SOURCE_DIR}/include) +xo_include_options(ex1) diff --git a/example/ex2/CMakeLists.txt b/example/ex2/CMakeLists.txt index 76ae6f64..1ac5735a 100644 --- a/example/ex2/CMakeLists.txt +++ b/example/ex2/CMakeLists.txt @@ -1,3 +1,2 @@ add_executable(ex2 ex2.cpp) - -target_include_directories(ex2 PUBLIC ${PROJECT_SOURCE_DIR}/include) +xo_include_options(ex2) From 5ad143d5b69912658edbb4aa8201938055851a3b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 12 Sep 2023 12:24:33 -0400 Subject: [PATCH 0009/2693] nestlog: control indent level + args with scope entry --- example/ex2/ex2.cpp | 17 ++++--- include/nestlog/log_state.hpp | 22 +++++++--- include/nestlog/log_streambuf.hpp | 3 +- include/nestlog/pad.hpp | 27 ++++++++---- include/nestlog/scope.hpp | 73 +++++++++++++++++++------------ include/nestlog/tostr.hpp | 6 +++ 6 files changed, 98 insertions(+), 50 deletions(-) diff --git a/example/ex2/ex2.cpp b/example/ex2/ex2.cpp index dfc2a7bd..85066b73 100644 --- a/example/ex2/ex2.cpp +++ b/example/ex2/ex2.cpp @@ -2,28 +2,33 @@ #include "nestlog/scope.hpp" +using namespace xo; + int fib(int n) { - XO_SCOPE(log); + scope log(XO_SSETUP0(), ":n ", n); int retval = 1; if (n >= 2) { - log(":n ", n); retval = fib(n - 1) + fib(n - 2); + log(":n ", n); } - - log(":n ", n, " -> :retval ", retval); + log("<- :retval ", retval); return retval; } int main(int argc, char ** argv) { - XO_SCOPE(log); + log_config::indent_width = 4; int n = 4; + + scope log(XO_SSETUP0(), ":n", 4); + int fn = fib(n); - log(":n ", n, " :fib(n) ", fn); + log(":n ", n); + log("<- :fib(n) ", fn); } diff --git a/include/nestlog/log_state.hpp b/include/nestlog/log_state.hpp index 41556b8a..8d096950 100644 --- a/include/nestlog/log_state.hpp +++ b/include/nestlog/log_state.hpp @@ -2,7 +2,9 @@ #pragma once +#include "log_config.hpp" #include "log_streambuf.hpp" +#include "pad.hpp" #include #include // for std::unique_ptr @@ -44,11 +46,12 @@ namespace xo { /* common implementation for .preamble(), .postamble() */ void entryexit_aux(std::string_view name1, std::string_view name2, - char label_char); + char label_char, + bool newline_flag); private: /* current nesting level for this thread */ - uint32_t nesting_level_ = 0; + std::uint32_t nesting_level_ = 0; /* buffer space for logging * (before pretty-printing for scope::log() calls that span multiple lines) @@ -95,16 +98,20 @@ namespace xo { #endif /* indent to nesting level */ + this->ss_ << pad(this->nesting_level_ * log_config::indent_width, pad_char); +#ifdef OBSOLETE for(uint32_t i = 0, n = this->nesting_level_; iss_ << pad_char; } +#endif } /*indent*/ template void state_impl::entryexit_aux(std::string_view name1, std::string_view name2, - char label_char) + char label_char, + bool newline_flag) { log_streambuf_type * sbuf = this->p_sbuf_phase1_.get(); @@ -115,7 +122,10 @@ namespace xo { this->ss_ << label_char; /* scope name */ - this->ss_ << name1 << name2 << "\n"; + this->ss_ << name1 << name2; + + if (newline_flag) + this->ss_ << "\n"; } /*entryexit_aux*/ template @@ -123,7 +133,7 @@ namespace xo { state_impl::preamble(std::string_view name1, std::string_view name2) { - this->entryexit_aux(name1, name2, '+' /*label_char*/); + this->entryexit_aux(name1, name2, '+' /*label_char*/, false /*!newline_flag*/); } /*preamble*/ template @@ -131,7 +141,7 @@ namespace xo { state_impl::postamble(std::string_view name1, std::string_view name2) { - this->entryexit_aux(name1, name2, '-' /*label_char*/); + this->entryexit_aux(name1, name2, '-' /*label_char*/, true /*newline_flag*/); } /*postamble*/ template diff --git a/include/nestlog/log_streambuf.hpp b/include/nestlog/log_streambuf.hpp index 5d06dab7..02b4b183 100644 --- a/include/nestlog/log_streambuf.hpp +++ b/include/nestlog/log_streambuf.hpp @@ -5,7 +5,8 @@ #include #include #include // e.g. for std::memcpy() -#include // e.g. for std::memcpy() +#include +#include namespace xo { /* recycling buffer for logging. diff --git a/include/nestlog/pad.hpp b/include/nestlog/pad.hpp index 123c8d5c..e85d9c25 100644 --- a/include/nestlog/pad.hpp +++ b/include/nestlog/pad.hpp @@ -3,36 +3,47 @@ #pragma once #include +#include namespace xo { /* use: * ostream os = ...; + * + * 1. * os << ":" << pad(8) << ":" * * writes * : : * + * 2. + * os << pad(16, '-') + * + * writes + * ---------------- + * * on os */ class pad_impl { public: - pad_impl(int32_t n) : n_pad_(n) {} + pad_impl(std::uint32_t n, char pad_char) : n_pad_{n}, pad_char_{pad_char} {} - uint32_t n_pad() const { return n_pad_; } + std::uint32_t n_pad() const { return n_pad_; } + char pad_char() const { return pad_char_; } private: - uint32_t n_pad_ = 0; + std::uint32_t n_pad_ = 0; + char pad_char_ = '\0'; }; /*pad_impl*/ inline pad_impl - pad(uint32_t n) { return pad_impl(n); } + pad(std::uint32_t n, char pad_char = ' ') { return pad_impl(n, pad_char); } inline std::ostream & - operator<<(std::ostream &s, - pad_impl const &pad) + operator<<(std::ostream & s, + pad_impl const & pad) { - for(uint32_t i=0; i class state_impl; +//# define XO_SSETUP0() xo::scope_setup(__FUNCTION__) +# define XO_SSETUP0() xo::scope_setup(__PRETTY_FUNCTION__) + /* 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) xo::scope name(__FUNCTION__) +# define XO_SCOPE(name) xo::scope name(xo::scope_setup(__FUNCTION__)) /* like XO_SCOPE(name), but also set enabled flag */ -# define XO_SCOPE2(name, debug_flag) xo::scope name(__FUNCTION__, debug_flag) -# define XO_SCOPE_DISABLED(name) xo::scope name(__FUNCTION__, false) +# define XO_SCOPE2(name, debug_flag) xo::scope name(xo::scope_setup(__FUNCTION__, debug_flag)) +# define XO_SCOPE_DISABLED(name) xo::scope name(xo::scope_setup(__FUNCTION__, false)) # define XO_STUB() { XO_SCOPE(logr); logr.log("STUB"); } + /* convenience class for basic_scope<..> construction (see below). + * use to disambiguate setup from other arguments + */ + struct scope_setup { + scope_setup(std::string_view name1, std::string_view name2, bool enabled_flag) + : name1_{name1}, name2_{name2}, enabled_flag_{enabled_flag} {} + scope_setup(std::string_view name1, bool enabled_flag) + : scope_setup(name1, "", enabled_flag) {} + scope_setup(std::string_view name1) + : scope_setup(name1, true /*enabled_flag*/) {} + + std::string_view name1_ = "<.name1>"; + std::string_view name2_ = "<.name2>"; + bool enabled_flag_ = false; + }; /*scope_setup*/ + /* nesting logger * * Use: @@ -64,9 +83,9 @@ namespace xo { using state_impl_type = state_impl; public: - basic_scope(std::string_view name1); - basic_scope(std::string_view name1, bool enabled_flag); - basic_scope(std::string_view name1, std::string_view name2, bool enabled_flag); + //basic_scope(std::string_view name1, bool enabled_flag); + template + basic_scope(scope_setup setup, Tn&&... rest); ~basic_scope(); bool enabled() const { return !finalized_; } @@ -76,6 +95,8 @@ namespace xo { /* report current nesting level */ std::uint32_t nesting_level() const; + void set_dest_sbuf(std::streambuf * x) { this->dest_sbuf_ = x; } + template bool log(Tn&&... rest) { if(this->finalized_) { @@ -86,7 +107,7 @@ namespace xo { /* log to per-thread stream to prevent data races */ tosn(logstate2stream(logstate), rest...); - this->flush2clog(logstate); + this->flush2sbuf(logstate); } return true; @@ -112,12 +133,14 @@ namespace xo { static std::ostream & logstate2stream(state_impl_type * logstate); /* write collected output to std::clog, or chosen streambuf */ - void flush2clog(state_impl_type * logstate, std::streambuf * p_sbuf = std::clog.rdbuf()); + void flush2sbuf(state_impl_type * logstate); private: /* keep logging state separately for each thread */ static thread_local std::unique_ptr s_threadlocal_state; + /* send indented output to this streambuf (e.g. std::clog.rdbuf()) */ + std::streambuf * dest_sbuf_ = std::clog.rdbuf(); /* name of this scope (part 1) */ std::string_view name1_ = ""; /* name of this scope (part 2) */ @@ -127,17 +150,20 @@ namespace xo { }; /*basic_scope*/ template - basic_scope::basic_scope(std::string_view fn1, - std::string_view fn2, - bool enabled_flag) - : name1_(fn1), - name2_(fn2), - finalized_(!enabled_flag) + template + basic_scope::basic_scope(scope_setup setup, Tn&&... args) + + : name1_{std::move(setup.name1_)}, + name2_{std::move(setup.name2_)}, + finalized_{!setup.enabled_flag_} { - if(enabled_flag) { + if(setup.enabled_flag_) { state_impl_type * logstate = basic_scope::require_thread_local_state(); logstate->preamble(this->name1_, this->name2_); + + tosn(logstate2stream(logstate), " ", args...); + logstate->flush2sbuf(std::clog.rdbuf()); ///* next call to scope::log() can reset to beginning of buffer space */ @@ -147,16 +173,6 @@ namespace xo { } } /*ctor*/ - template - basic_scope::basic_scope(std::string_view fn1, bool enabled_flag) - : basic_scope(fn1, "", enabled_flag) - {} - - template - basic_scope::basic_scope(std::string_view fn) - : basic_scope(fn, true /*enabled_flag*/) - {} - template basic_scope::~basic_scope() { if(!this->finalized_) @@ -205,11 +221,10 @@ namespace xo { template void - basic_scope::flush2clog(state_impl_type * logstate, - std::streambuf * p_sbuf) + basic_scope::flush2sbuf(state_impl_type * logstate) { - logstate->flush2sbuf(p_sbuf); - } /*flush2clog*/ + logstate->flush2sbuf(this->dest_sbuf_); + } /*flush2sbuf*/ template void diff --git a/include/nestlog/tostr.hpp b/include/nestlog/tostr.hpp index b2bc927b..e395cfe3 100644 --- a/include/nestlog/tostr.hpp +++ b/include/nestlog/tostr.hpp @@ -59,6 +59,12 @@ namespace xo { * desired ctor */ + /* no-op terminal case */ + template + Stream & tos(Stream & s) { + return s; + } + // Use: // tos(s,a,b,c) // is the same as From 605f4df41f26909df3f8f2100fc92ff20026538c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 12 Sep 2023 12:34:16 -0400 Subject: [PATCH 0010/2693] nestlog: missed commit log_config.hpp --- include/nestlog/log_config.hpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 include/nestlog/log_config.hpp diff --git a/include/nestlog/log_config.hpp b/include/nestlog/log_config.hpp new file mode 100644 index 00000000..9b1eff12 --- /dev/null +++ b/include/nestlog/log_config.hpp @@ -0,0 +1,22 @@ +/* @file log_config.hpp */ + +#pragma once + +#include + +namespace xo { + /* Tag here b/c we want header-only library */ + template + struct log_config_impl { + /* spaces per indent level */ + static std::uint32_t indent_width; + }; /*log_config_impl*/ + + template + std::uint32_t + log_config_impl::indent_width = 1; + + using log_config = log_config_impl; +} /*namespace xo*/ + +/* end log_config.hpp */ From 7c964ab93cb7e29535dae83caacb2453b6acac8e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 12 Sep 2023 12:34:44 -0400 Subject: [PATCH 0011/2693] nestlog: + trailing args with .end_scope() --- example/ex2/ex2.cpp | 3 ++- include/nestlog/log_state.hpp | 13 ++++--------- include/nestlog/scope.hpp | 15 ++++++++++----- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/example/ex2/ex2.cpp b/example/ex2/ex2.cpp index 85066b73..2084b551 100644 --- a/example/ex2/ex2.cpp +++ b/example/ex2/ex2.cpp @@ -14,7 +14,8 @@ fib(int n) { retval = fib(n - 1) + fib(n - 2); log(":n ", n); } - log("<- :retval ", retval); + + log.end_scope("<- :retval ", retval); return retval; } diff --git a/include/nestlog/log_state.hpp b/include/nestlog/log_state.hpp index 8d096950..ac25e043 100644 --- a/include/nestlog/log_state.hpp +++ b/include/nestlog/log_state.hpp @@ -46,8 +46,7 @@ namespace xo { /* common implementation for .preamble(), .postamble() */ void entryexit_aux(std::string_view name1, std::string_view name2, - char label_char, - bool newline_flag); + char label_char); private: /* current nesting level for this thread */ @@ -110,8 +109,7 @@ namespace xo { void state_impl::entryexit_aux(std::string_view name1, std::string_view name2, - char label_char, - bool newline_flag) + char label_char) { log_streambuf_type * sbuf = this->p_sbuf_phase1_.get(); @@ -123,9 +121,6 @@ namespace xo { /* scope name */ this->ss_ << name1 << name2; - - if (newline_flag) - this->ss_ << "\n"; } /*entryexit_aux*/ template @@ -133,7 +128,7 @@ namespace xo { state_impl::preamble(std::string_view name1, std::string_view name2) { - this->entryexit_aux(name1, name2, '+' /*label_char*/, false /*!newline_flag*/); + this->entryexit_aux(name1, name2, '+' /*label_char*/); } /*preamble*/ template @@ -141,7 +136,7 @@ namespace xo { state_impl::postamble(std::string_view name1, std::string_view name2) { - this->entryexit_aux(name1, name2, '-' /*label_char*/, true /*newline_flag*/); + this->entryexit_aux(name1, name2, '-' /*label_char*/); } /*postamble*/ template diff --git a/include/nestlog/scope.hpp b/include/nestlog/scope.hpp index 249eb23f..0f1af013 100644 --- a/include/nestlog/scope.hpp +++ b/include/nestlog/scope.hpp @@ -105,7 +105,7 @@ namespace xo { state_impl_type * logstate = require_indent_thread_local_state(); /* log to per-thread stream to prevent data races */ - tosn(logstate2stream(logstate), rest...); + tosn(logstate2stream(logstate), std::forward(rest)...); this->flush2sbuf(logstate); } @@ -114,10 +114,11 @@ namespace xo { } /*log*/ template - bool operator()(Tn&&... rest) { return this->log(rest...); } + bool operator()(Tn&&... args) { return this->log(std::forward(args)...); } /* call once to end scope before dtor */ - void end_scope(); + template + void end_scope(Tn&&... args); private: /* establish stream for logging; use thread-local storage for threadsafetỵ @@ -162,7 +163,7 @@ namespace xo { logstate->preamble(this->name1_, this->name2_); - tosn(logstate2stream(logstate), " ", args...); + tosn(logstate2stream(logstate), " ", std::forward(args)...); logstate->flush2sbuf(std::clog.rdbuf()); @@ -227,8 +228,9 @@ namespace xo { } /*flush2sbuf*/ template + template void - basic_scope::end_scope() + basic_scope::end_scope(Tn&&... args) { if(!this->finalized_) { this->finalized_ = true; @@ -239,6 +241,9 @@ namespace xo { logstate->decr_nesting(); logstate->postamble(this->name1_, this->name2_); + + tosn(logstate2stream(logstate), " ", std::forward(args)...); + logstate->flush2sbuf(std::clog.rdbuf()); } } /*end_scope*/ From 2871874dd8faed258639ea322b8f9ba6b45c9cfe Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 12 Sep 2023 15:21:48 -0400 Subject: [PATCH 0012/2693] nestlog: choose function-printing style --- example/CMakeLists.txt | 1 + example/ex2/ex2.cpp | 2 +- example/ex3/CMakeLists.txt | 2 ++ example/ex3/ex3.cpp | 36 ++++++++++++++++++++++++++++++++++ include/nestlog/log_config.hpp | 7 +++++++ include/nestlog/log_state.hpp | 25 ++++++++++++++--------- include/nestlog/scope.hpp | 30 ++++++++++++++++------------ 7 files changed, 80 insertions(+), 23 deletions(-) create mode 100644 example/ex3/CMakeLists.txt create mode 100644 example/ex3/ex3.cpp diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 8a901a22..06512248 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -9,6 +9,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") add_subdirectory(ex1) add_subdirectory(ex2) +add_subdirectory(ex3) # ---------------------------------------------------------------- # make standard directories for std:: includes explicit diff --git a/example/ex2/ex2.cpp b/example/ex2/ex2.cpp index 2084b551..3d246f0e 100644 --- a/example/ex2/ex2.cpp +++ b/example/ex2/ex2.cpp @@ -26,7 +26,7 @@ main(int argc, char ** argv) { int n = 4; - scope log(XO_SSETUP0(), ":n", 4); + scope log(XO_SSETUP0(), ":n ", 4); int fn = fib(n); diff --git a/example/ex3/CMakeLists.txt b/example/ex3/CMakeLists.txt new file mode 100644 index 00000000..d81e0e99 --- /dev/null +++ b/example/ex3/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ex3 ex3.cpp) +xo_include_options(ex3) diff --git a/example/ex3/ex3.cpp b/example/ex3/ex3.cpp new file mode 100644 index 00000000..58d81dc7 --- /dev/null +++ b/example/ex3/ex3.cpp @@ -0,0 +1,36 @@ +/* examples ex3/ex3.cpp */ + +#include "nestlog/scope.hpp" + +using namespace xo; + +int +fib(int n) { + scope log(XO_SSETUP0(), ":n ", n); + + int retval = 1; + + if (n >= 2) { + retval = fib(n - 1) + fib(n - 2); + log(":n ", n); + } + + log.end_scope("<- :retval ", retval); + + return retval; +} + +int +main(int argc, char ** argv) { + log_config::style = FS_Pretty; + log_config::indent_width = 4; + + int n = 4; + + scope log(XO_SSETUP0(), ":n ", 4); + + int fn = fib(n); + + log(":n ", n); + log("<- :fib(n) ", fn); +} diff --git a/include/nestlog/log_config.hpp b/include/nestlog/log_config.hpp index 9b1eff12..ce28665f 100644 --- a/include/nestlog/log_config.hpp +++ b/include/nestlog/log_config.hpp @@ -2,6 +2,7 @@ #pragma once +#include "function.hpp" #include namespace xo { @@ -10,12 +11,18 @@ namespace xo { struct log_config_impl { /* spaces per indent level */ static std::uint32_t indent_width; + /* display style for function names. FS_Simple|FS_Pretty|FS_Streamlined */ + static function_style style; }; /*log_config_impl*/ template std::uint32_t log_config_impl::indent_width = 1; + template + function_style + log_config_impl::style = FS_Streamlined; + using log_config = log_config_impl; } /*namespace xo*/ diff --git a/include/nestlog/log_state.hpp b/include/nestlog/log_state.hpp index ac25e043..59c3b4f6 100644 --- a/include/nestlog/log_state.hpp +++ b/include/nestlog/log_state.hpp @@ -27,11 +27,11 @@ namespace xo { std::ostream & ss() { return ss_; } /* call on entry to new scope */ - void preamble(std::string_view name1, std::string_view name2); + void preamble(function_style style, std::string_view name1, std::string_view name2); /* call before each new log entry */ void indent(char pad_char); /* call on exit from scope */ - void postamble(std::string_view name1, std::string_view name2); + void postamble(function_style style, std::string_view name1, std::string_view name2); /* write collected output to *p_sbuf */ void flush2sbuf(std::streambuf * p_sbuf); @@ -44,7 +44,8 @@ namespace xo { private: /* common implementation for .preamble(), .postamble() */ - void entryexit_aux(std::string_view name1, + void entryexit_aux(function_style style, + std::string_view name1, std::string_view name2, char label_char); @@ -107,7 +108,8 @@ namespace xo { template void - state_impl::entryexit_aux(std::string_view name1, + state_impl::entryexit_aux(function_style style, + std::string_view name1, std::string_view name2, char label_char) { @@ -119,24 +121,29 @@ namespace xo { /* mnemonic for scope entry/exit */ this->ss_ << label_char; + if (log_config::indent_width > 1) + this->ss_ << ' '; + /* scope name */ - this->ss_ << name1 << name2; + this->ss_ << function_name(style, name1) << name2; } /*entryexit_aux*/ template void - state_impl::preamble(std::string_view name1, + state_impl::preamble(function_style style, + std::string_view name1, std::string_view name2) { - this->entryexit_aux(name1, name2, '+' /*label_char*/); + this->entryexit_aux(style, name1, name2, '+' /*label_char*/); } /*preamble*/ template void - state_impl::postamble(std::string_view name1, + state_impl::postamble(function_style style, + std::string_view name1, std::string_view name2) { - this->entryexit_aux(name1, name2, '-' /*label_char*/); + this->entryexit_aux(style, name1, name2, '-' /*label_char*/); } /*postamble*/ template diff --git a/include/nestlog/scope.hpp b/include/nestlog/scope.hpp index 0f1af013..cd3219fe 100644 --- a/include/nestlog/scope.hpp +++ b/include/nestlog/scope.hpp @@ -16,28 +16,29 @@ namespace xo { class state_impl; //# define XO_SSETUP0() xo::scope_setup(__FUNCTION__) -# define XO_SSETUP0() xo::scope_setup(__PRETTY_FUNCTION__) +# define XO_SSETUP0() xo::scope_setup(log_config::style, __PRETTY_FUNCTION__) /* 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) xo::scope name(xo::scope_setup(__FUNCTION__)) +# define XO_SCOPE(name) xo::scope name(xo::scope_setup(log_config::style, __PRETTY_FUNCTION__)) /* like XO_SCOPE(name), but also set enabled flag */ -# define XO_SCOPE2(name, debug_flag) xo::scope name(xo::scope_setup(__FUNCTION__, debug_flag)) -# define XO_SCOPE_DISABLED(name) xo::scope name(xo::scope_setup(__FUNCTION__, false)) +# define XOf_SCOPE2(name, debug_flag) xo::scope name(xo::scope_setup(log_config::style, __PRETTY_FUNCTION__, debug_flag)) +# define XO_SCOPE_DISABLED(name) xo::scope name(xo::scope_setup(log_config::style, __PRETTY_FUNCTION__, false)) # define XO_STUB() { XO_SCOPE(logr); logr.log("STUB"); } /* convenience class for basic_scope<..> construction (see below). * use to disambiguate setup from other arguments */ struct scope_setup { - scope_setup(std::string_view name1, std::string_view name2, bool enabled_flag) - : name1_{name1}, name2_{name2}, enabled_flag_{enabled_flag} {} - scope_setup(std::string_view name1, bool enabled_flag) - : scope_setup(name1, "", enabled_flag) {} - scope_setup(std::string_view name1) - : scope_setup(name1, true /*enabled_flag*/) {} + scope_setup(function_style style, std::string_view name1, std::string_view name2, bool enabled_flag) + : style_{style}, name1_{name1}, name2_{name2}, enabled_flag_{enabled_flag} {} + scope_setup(function_style style, std::string_view name1, bool enabled_flag) + : scope_setup(style, name1, "", enabled_flag) {} + scope_setup(function_style style, std::string_view name1) + : scope_setup(style, name1, true /*enabled_flag*/) {} + function_style style_ = FS_Pretty; std::string_view name1_ = "<.name1>"; std::string_view name2_ = "<.name2>"; bool enabled_flag_ = false; @@ -142,6 +143,8 @@ namespace xo { /* send indented output to this streambuf (e.g. std::clog.rdbuf()) */ std::streambuf * dest_sbuf_ = std::clog.rdbuf(); + /* style for displaying .name1 */ + function_style style_ = FS_Pretty; /* name of this scope (part 1) */ std::string_view name1_ = ""; /* name of this scope (part 2) */ @@ -154,14 +157,15 @@ namespace xo { template basic_scope::basic_scope(scope_setup setup, Tn&&... args) - : name1_{std::move(setup.name1_)}, + : style_{setup.style_}, + name1_{std::move(setup.name1_)}, name2_{std::move(setup.name2_)}, finalized_{!setup.enabled_flag_} { if(setup.enabled_flag_) { state_impl_type * logstate = basic_scope::require_thread_local_state(); - logstate->preamble(this->name1_, this->name2_); + logstate->preamble(this->style_, this->name1_, this->name2_); tosn(logstate2stream(logstate), " ", std::forward(args)...); @@ -240,7 +244,7 @@ namespace xo { logstate->decr_nesting(); - logstate->postamble(this->name1_, this->name2_); + logstate->postamble(this->style_, this->name1_, this->name2_); tosn(logstate2stream(logstate), " ", std::forward(args)...); From 59e7e19ccf232c07442486f325c91303b4edb19b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 12 Sep 2023 15:22:14 -0400 Subject: [PATCH 0013/2693] nestlog: function printer (missed commit) --- include/nestlog/function.hpp | 190 +++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 include/nestlog/function.hpp diff --git a/include/nestlog/function.hpp b/include/nestlog/function.hpp new file mode 100644 index 00000000..da6a3f6e --- /dev/null +++ b/include/nestlog/function.hpp @@ -0,0 +1,190 @@ +/* @file function.hpp */ + +#include +#include + +namespace xo { + enum function_style { + /* literal: print given name, no alterations */ + FS_Literal, + /* pretty: print name, surrounded by [] */ + FS_Pretty, + /* streamlined: remove extraneous detail, try to print something like class::method */ + FS_Streamlined, + /* simple: remove everything except function/method name */ + FS_Simple + }; + + /* Tag to drive header-only expression */ + template + class function_name_impl { + public: + function_name_impl(function_style style, + std::string_view pretty) + : style_{style}, pretty_{pretty} {} + + function_style style() const { return style_; } + std::string_view const & pretty() const { return pretty_; } + + /* e.g. + * std::vector xo::sometemplateclass::fib(int, char**) + * ^ ^ + * p q + */ + static void print_simple(std::ostream & os, std::string_view const & s) { + std::size_t p = exclude_return_type(s); + std::string_view s2 = s.substr(p); + std::size_t q = find_toplevel_sep(s2, true /*last_flag*/); + + print_aux(os, s2.substr(q)); + } /*print_simple*/ + + /* e.g. + * std::vector xo::sometemplateclass::fib(int, char**) + * ^ ^ + * p q + */ + static void print_streamlined(std::ostream & os, std::string_view const & s) { + std::size_t p = exclude_return_type(s); + std::string_view s2 = s.substr(p); + std::size_t q = find_toplevel_sep(s2, false /*!last_flag*/); + + print_aux(os, s2.substr(q)); + } /*print_streamlined*/ + + private: + static std::size_t exclude_return_type(std::string_view const & s) { + /* strategy: + * - scan right-to-left + * - ignore anything between matching <>, () pairs (i.e. anything nested) + * - stop at rightmost toplevel space --> return suffix following that space + */ + std::size_t nesting_level = 0; + + std::size_t z = s.size(); + for (std::size_t rp = 0; rp < z; ++rp) { + std::size_t p = z-1-rp; + char ch = s[p]; + + if (ch == '<' || ch == '(') + ++nesting_level; + + if (nesting_level == 0) { + if (ch == ' ') + return p + 1; + } + + if (ch == '>' || ch == ')') + --nesting_level; + } + + return 0; + } /*exclude_return_type*/ + + /* e.g. + * xo::ns::someclass::somemethod(xo::enum1, std::vector) + * ^ + * return this pos + * + * last_flag: return pos after last :: + * !last_flag: return pos after 2nd-last :: + */ + static std::size_t find_toplevel_sep(std::string_view const & s, bool last_flag) { + /* strategy: + * - scan left-to-right + * - ignore anything between matching <>, () pairs (i.e. anything nested) + * - count :: pairs + * - remember 2nd-last :: pair; reports pos just after it + * + * note: + * - if no :: pairs, or only one such pair, return 0 + */ + std::size_t nesting_level = 0; + + std::size_t pos_after_last_sep = 0; + std::size_t pos_after_2ndlast_sep = 0; + + for (std::size_t p = 0; p < s.size(); ++p) { + char ch = s[p]; + + if (ch == '<' || ch == '(') + ++nesting_level; + + if (nesting_level == 0) { + if ((ch == ':') + && (p+1 < s.size()) + && s[p+1] == ':') + { + pos_after_2ndlast_sep = pos_after_last_sep; + pos_after_last_sep = p+2; + ++p; /* skipping 1st : in separator */ + } + } + + if (ch == '>' || ch == ')') + --nesting_level; + } + + return last_flag ? pos_after_last_sep : pos_after_2ndlast_sep; + } /*find_toplevel_sep*/ + + /* fib(int, char **) --> fib + * quux(std::vector>) -> quux + * foo::bar>() -> foo::bar + */ + static void print_aux(std::ostream & os, std::string_view const & s) { + //std::cerr << "print_aux: s=" << s << std::endl; + + /* strategy: + * - print left-to-right, omit anything between matching <> or () pairs. + * - don't keep track of which is which, so would also match < with ) etc; + * this acceptable since pretty functions won't visit this corner case + */ + std::size_t nesting_level = 0; + + for (char ch : s) { + if (ch == '<' || ch == '(') + ++nesting_level; + + if (nesting_level == 0) + os << ch; + + if (ch == '>' || ch == ')') + --nesting_level; + } + } /*print_aux*/ + + private: + /* FS_Simple | FS_Pretty (= FS_Literal) | FS_Streamlined */ + function_style style_; + /* e.g. __PRETTY_FUNCTION__ */ + std::string_view pretty_; + }; /*function_name_impl*/ + + using function_name = function_name_impl; + + inline std::ostream & + operator<<(std::ostream & os, + function_name const & fn) + { + switch(fn.style()) { + case FS_Literal: + os << fn.pretty(); + break; + case FS_Pretty: + os << "[" << fn.pretty() << "]"; + break; + case FS_Simple: + function_name::print_simple(os, fn.pretty()); + break; + case FS_Streamlined: + /* omit namespace qualifiers and template arguments */ + function_name::print_streamlined(os, fn.pretty()); + break; + } + + return os; + } /*operator<<*/ +} /*namespace xo*/ + +/* end function.hpp */ From 1c6fffd048c89009ad3e913ecd1c8d4d7c9c5797 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 12 Sep 2023 15:36:11 -0400 Subject: [PATCH 0014/2693] nestlog: elaborate example ex3 --- example/ex3/ex3.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/example/ex3/ex3.cpp b/example/ex3/ex3.cpp index 58d81dc7..1b3cc8f7 100644 --- a/example/ex3/ex3.cpp +++ b/example/ex3/ex3.cpp @@ -6,16 +6,16 @@ using namespace xo; int fib(int n) { - scope log(XO_SSETUP0(), ":n ", n); + scope log(XO_ENTER0(), xtag("n", n)); int retval = 1; if (n >= 2) { retval = fib(n - 1) + fib(n - 2); - log(":n ", n); + log(xtag("n", n)); } - log.end_scope("<- :retval ", retval); + log.end_scope("<-", xtag("retval", retval)); return retval; } @@ -27,10 +27,10 @@ main(int argc, char ** argv) { int n = 4; - scope log(XO_SSETUP0(), ":n ", 4); + scope log(XO_ENTER0(), ":n ", 4); int fn = fib(n); - log(":n ", n); - log("<- :fib(n) ", fn); + log(xtag("n", n)); + log("<-", xtag("fib(n)", fn)); } From f9c583973c3e1c2c8d5689680bdad24991267057 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 12 Sep 2023 15:36:37 -0400 Subject: [PATCH 0015/2693] nestlog: + XO_ENTER0(), XO_ENTER1() --- include/nestlog/scope.hpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/include/nestlog/scope.hpp b/include/nestlog/scope.hpp index cd3219fe..bb0d0854 100644 --- a/include/nestlog/scope.hpp +++ b/include/nestlog/scope.hpp @@ -15,6 +15,9 @@ namespace xo { template class state_impl; +# define XO_ENTER0() xo::scope_setup(log_config::style, __PRETTY_FUNCTION__) +# define XO_ENTER1(debug_flag) xo::scope_setup(log_config::style, __PRETTY_FUNCTION__, debug_flag) + //# define XO_SSETUP0() xo::scope_setup(__FUNCTION__) # define XO_SSETUP0() xo::scope_setup(log_config::style, __PRETTY_FUNCTION__) @@ -23,7 +26,7 @@ namespace xo { /* establish scope using current function name */ # define XO_SCOPE(name) xo::scope name(xo::scope_setup(log_config::style, __PRETTY_FUNCTION__)) /* like XO_SCOPE(name), but also set enabled flag */ -# define XOf_SCOPE2(name, debug_flag) xo::scope name(xo::scope_setup(log_config::style, __PRETTY_FUNCTION__, debug_flag)) +# define XO_SCOPE2(name, debug_flag) xo::scope name(xo::scope_setup(log_config::style, __PRETTY_FUNCTION__, debug_flag)) # define XO_SCOPE_DISABLED(name) xo::scope name(xo::scope_setup(log_config::style, __PRETTY_FUNCTION__, false)) # define XO_STUB() { XO_SCOPE(logr); logr.log("STUB"); } @@ -38,6 +41,8 @@ namespace xo { scope_setup(function_style style, std::string_view name1) : scope_setup(style, name1, true /*enabled_flag*/) {} + static scope_setup literal(std::string_view name1, bool enabled_flag = true) { return scope_setup(FS_Literal, name1, enabled_flag); } + function_style style_ = FS_Pretty; std::string_view name1_ = "<.name1>"; std::string_view name2_ = "<.name2>"; From f7c19c338fd8056db7ddd2cbbbe76ece3ca2c3ff Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 12 Sep 2023 16:42:10 -0400 Subject: [PATCH 0016/2693] nestlog: minor scope fixes --- include/nestlog/scope.hpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/include/nestlog/scope.hpp b/include/nestlog/scope.hpp index bb0d0854..2648f483 100644 --- a/include/nestlog/scope.hpp +++ b/include/nestlog/scope.hpp @@ -15,19 +15,19 @@ namespace xo { template class state_impl; -# define XO_ENTER0() xo::scope_setup(log_config::style, __PRETTY_FUNCTION__) -# define XO_ENTER1(debug_flag) xo::scope_setup(log_config::style, __PRETTY_FUNCTION__, debug_flag) +# define XO_ENTER0() xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__) +# define XO_ENTER1(debug_flag) xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__, debug_flag) //# define XO_SSETUP0() xo::scope_setup(__FUNCTION__) -# define XO_SSETUP0() xo::scope_setup(log_config::style, __PRETTY_FUNCTION__) +# define XO_SSETUP0() xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__) /* 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) xo::scope name(xo::scope_setup(log_config::style, __PRETTY_FUNCTION__)) +# define XO_SCOPE(name) xo::scope name(xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__)) /* like XO_SCOPE(name), but also set enabled flag */ -# define XO_SCOPE2(name, debug_flag) xo::scope name(xo::scope_setup(log_config::style, __PRETTY_FUNCTION__, debug_flag)) -# define XO_SCOPE_DISABLED(name) xo::scope name(xo::scope_setup(log_config::style, __PRETTY_FUNCTION__, false)) +# define XO_SCOPE2(name, debug_flag) xo::scope name(xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__, debug_flag)) +# define XO_SCOPE_DISABLED(name) xo::scope name(xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__, false)) # define XO_STUB() { XO_SCOPE(logr); logr.log("STUB"); } /* convenience class for basic_scope<..> construction (see below). @@ -42,6 +42,7 @@ namespace xo { : scope_setup(style, name1, true /*enabled_flag*/) {} 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); } function_style style_ = FS_Pretty; std::string_view name1_ = "<.name1>"; From a150913045861407db7866cfeb96ce53a847d8b5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 14 Sep 2023 17:02:50 -0400 Subject: [PATCH 0017/2693] nestlog: print [file:line] on rhs of log line --- example/ex3/ex3.cpp | 3 ++ include/nestlog/filename.hpp | 70 +++++++++++++++++++++++++++++ include/nestlog/log_config.hpp | 12 +++++ include/nestlog/log_state.hpp | 75 ++++++++++++++++++++++++++----- include/nestlog/log_streambuf.hpp | 36 +++++++-------- include/nestlog/scope.hpp | 52 ++++++++++++++------- 6 files changed, 204 insertions(+), 44 deletions(-) create mode 100644 include/nestlog/filename.hpp diff --git a/example/ex3/ex3.cpp b/example/ex3/ex3.cpp index 1b3cc8f7..05a3c9e4 100644 --- a/example/ex3/ex3.cpp +++ b/example/ex3/ex3.cpp @@ -24,6 +24,7 @@ int main(int argc, char ** argv) { log_config::style = FS_Pretty; log_config::indent_width = 4; + log_config::location_tab = 40; int n = 4; @@ -34,3 +35,5 @@ main(int argc, char ** argv) { log(xtag("n", n)); log("<-", xtag("fib(n)", fn)); } + +/* ex3/ex3.cpp */ diff --git a/include/nestlog/filename.hpp b/include/nestlog/filename.hpp new file mode 100644 index 00000000..ead3b65e --- /dev/null +++ b/include/nestlog/filename.hpp @@ -0,0 +1,70 @@ +/* @file filename.hpp */ + +#pragma once + +#include +#include + +namespace xo { + /* Example: + * os << basename("/path/to/basename.cpp") + * prints + * basename.cpp + * on os + */ + + /* Tag to drive header-only expression */ + template + class basename_impl { + public: + basename_impl(std::string_view path) + : path_{path} {} + + std::string_view const & path() const { return path_; } + + /* /home/roland/proj/nestlog/include/nestlog/filename.hpp + * <-basename-> + */ + static void print_basename(std::ostream & os, std::string_view const & s) { + std::size_t p = exclude_dirname(s); + + os << s.substr(p); + } /*print_basename*/ + + private: + static std::size_t exclude_dirname(std::string_view const & s) { + std::size_t z = s.size(); + + if (z == 0) + return 0; + + if (s[z-1] == '/') { + /* ignore trailing '/' */ + return exclude_dirname(s.substr(0, z-1)); + } + + std::size_t p = s.find_last_of('/'); + + if (p == std::string_view::npos) + return 0; + else + return p + 1; + } /*exclude_dirname*/ + + private: + /* some unix pathname, e.g. [/home/roland/proj/nestlog/include/nestlog/filename.hpp] */ + std::string_view path_; + }; /*basename_impl*/ + + using basename = basename_impl; + + inline std::ostream & + operator<<(std::ostream & os, + basename const & bn) + { + basename::print_basename(os, bn.path()); + return os; + } +} /*xo*/ + +/* end filename.hpp */ diff --git a/include/nestlog/log_config.hpp b/include/nestlog/log_config.hpp index ce28665f..d6da65da 100644 --- a/include/nestlog/log_config.hpp +++ b/include/nestlog/log_config.hpp @@ -13,6 +13,10 @@ namespace xo { static std::uint32_t indent_width; /* display style for function names. FS_Simple|FS_Pretty|FS_Streamlined */ static function_style style; + /* if true, append [file:line] to output */ + static bool location_enabled; + /* when .location_enabled, write [file:line] starting this many chars from left margin */ + static std::uint32_t location_tab; }; /*log_config_impl*/ template @@ -23,6 +27,14 @@ namespace xo { function_style log_config_impl::style = FS_Streamlined; + template + bool + log_config_impl::location_enabled = true; + + template + std::uint32_t + log_config_impl::location_tab = 80; + using log_config = log_config_impl; } /*namespace xo*/ diff --git a/include/nestlog/log_state.hpp b/include/nestlog/log_state.hpp index 59c3b4f6..73b77960 100644 --- a/include/nestlog/log_state.hpp +++ b/include/nestlog/log_state.hpp @@ -5,7 +5,9 @@ #include "log_config.hpp" #include "log_streambuf.hpp" #include "pad.hpp" +#include "filename.hpp" #include +#include #include // for std::unique_ptr namespace xo { @@ -42,6 +44,12 @@ namespace xo { p_sbuf_phase2_->reset_stream(); } + void set_location(std::string_view file, std::uint32_t line) { + this->location_flag_ = true; + this->file_ = std::move(file); + this->line_ = line; + } /*set_location*/ + private: /* common implementation for .preamble(), .postamble() */ void entryexit_aux(function_style style, @@ -59,6 +67,21 @@ 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 + * - ..and reset .location_flag + */ + bool location_flag_ = false; + std::string_view file_; + std::uint32_t line_ = 0; + /* buffer space for handling scope::log() calls that span multiple lines; * inserts extra characters in effort to indent gracefully */ @@ -99,11 +122,6 @@ namespace xo { /* indent to nesting level */ this->ss_ << pad(this->nesting_level_ * log_config::indent_width, pad_char); -#ifdef OBSOLETE - for(uint32_t i = 0, n = this->nesting_level_; iss_ << pad_char; - } -#endif } /*indent*/ template @@ -124,7 +142,7 @@ namespace xo { if (log_config::indent_width > 1) this->ss_ << ' '; - /* scope name */ + /* scope name - note no trailing newline; expect .preamble()/.postamble() caller to supply */ this->ss_ << function_name(style, name1) << name2; } /*entryexit_aux*/ @@ -153,12 +171,15 @@ namespace xo { log_streambuf_type * sbuf1 = this->p_sbuf_phase1_.get(); log_streambuf_type * sbuf2 = this->p_sbuf_phase2_.get(); - /* expecting sbuf to contain one line of output. + /* generally expecting sbuf to contain 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 clog + * 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(); @@ -177,6 +198,8 @@ namespace xo { /* for indenting, looking for first 'space following non-space, on first line', if any */ + std::size_t lpos_on_newline = 0; + while(p < e) { if(space_after_nonspace) { ; @@ -191,18 +214,50 @@ namespace xo { if(*p == '\n') { ++p; + /* reset .pos on newline */ + lpos_on_newline = this->lpos_; + this->lpos_ = 0; + break; } else { ++p; + + /* increment .lpos on non-newline */ + ++(this->lpos_); } } /* p=e or *p=\n */ /* charseq [s,p) does not contain any newlines, print it */ - sbuf2->sputn(s, p - s); + if (lpos_on_newline > 0) { + /* charseq [s,p) does not contain any newlines, print it */ + sbuf2->sputn(s, p - s - 1); - if(p == e) { + if (this->location_flag_) { + /* 'tab' to position 80 */ + sbuf2->sputc(' '); + for (std::uint32_t i = lpos_on_newline + 1; i < log_config::location_tab; ++i) + sbuf2->sputc(' '); + + std::stringstream ss; + ss << "[" << basename(this->file_) << ":" << this->line_ << "]"; + + std::string ss_str = std::move(ss.str()); /*c++20*/ + 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; } diff --git a/include/nestlog/log_streambuf.hpp b/include/nestlog/log_streambuf.hpp index 02b4b183..3a467b82 100644 --- a/include/nestlog/log_streambuf.hpp +++ b/include/nestlog/log_streambuf.hpp @@ -36,30 +36,30 @@ namespace xo { protected: virtual std::streamsize xsputn(char const * s, std::streamsize n) override { - /* s must be an address in [this->lo() .. this->lo() + capacity()] */ + /* s must be an address in [this->lo() .. this->lo() + capacity()] */ - assert(this->hi() >= this->pptr()); + assert(this->hi() >= this->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; + 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=" << quoted(string_view(s, n)) << ", n=" << n << std::endl; + //std::cout << "xsputn: s=" << quoted(string_view(s, n)) << ", n=" << n << std::endl; - if (this->pptr() + n > this->hi()) { - n = this->hi() - this->pptr(); - std::memcpy(this->pptr(), s, n); - } else { - std::memcpy(this->pptr(), s, n); - } - this->pbump(n); + if (this->pptr() + n > this->hi()) { + n = this->hi() - this->pptr(); + std::memcpy(this->pptr(), s, n); + } else { + std::memcpy(this->pptr(), s, n); + } + this->pbump(n); - return n; - } /*xsputn*/ + return n; + } /*xsputn*/ virtual int_type overflow(int_type new_ch) override diff --git a/include/nestlog/scope.hpp b/include/nestlog/scope.hpp index 2648f483..04451aaf 100644 --- a/include/nestlog/scope.hpp +++ b/include/nestlog/scope.hpp @@ -1,8 +1,9 @@ -/* @file scopẹhpp */ +/* @file scope.hpp */ #pragma once #include "log_state.hpp" +#include "filename.hpp" #include "tostr.hpp" #include "tag.hpp" @@ -15,38 +16,44 @@ namespace xo { template class state_impl; -# define XO_ENTER0() xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__) -# define XO_ENTER1(debug_flag) xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__, debug_flag) +# define XO_ENTER0() xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__) +# define XO_ENTER1(debug_flag) xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__, debug_flag) //# define XO_SSETUP0() xo::scope_setup(__FUNCTION__) -# define XO_SSETUP0() xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__) +# define XO_SSETUP0() xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__) /* 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) xo::scope name(xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__)) +# define XO_SCOPE(name) xo::scope name(xo::scope_setup(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__, debug_flag)) -# define XO_SCOPE_DISABLED(name) xo::scope name(xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__, false)) +# 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_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__, false)) # define XO_STUB() { XO_SCOPE(logr); logr.log("STUB"); } /* convenience class for basic_scope<..> construction (see below). * use to disambiguate setup from other arguments */ struct scope_setup { - scope_setup(function_style style, std::string_view name1, std::string_view name2, bool enabled_flag) - : style_{style}, name1_{name1}, name2_{name2}, enabled_flag_{enabled_flag} {} - scope_setup(function_style style, std::string_view name1, bool enabled_flag) - : scope_setup(style, name1, "", enabled_flag) {} - scope_setup(function_style style, std::string_view name1) - : scope_setup(style, name1, true /*enabled_flag*/) {} + scope_setup(function_style style, std::string_view name1, std::string_view name2, + std::string_view file, std::uint32_t line, bool enabled_flag) + : style_{style}, name1_{name1}, name2_{name2}, file_{file}, line_{line}, enabled_flag_{enabled_flag} {} + scope_setup(function_style style, std::string_view name1, std::string_view file, std::uint32_t line, bool enabled_flag) + : scope_setup(style, name1, "", file, line, enabled_flag) {} + scope_setup(function_style style, std::string_view name1, std::string_view file, std::uint32_t line) + : scope_setup(style, name1, file, line, true /*enabled_flag*/) {} - 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); } + //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); } function_style style_ = FS_Pretty; std::string_view name1_ = "<.name1>"; std::string_view name2_ = "<.name2>"; + /* __FILE__ */ + std::string_view file_ = "<.file>"; + /* __LINE__ */ + std::uint32_t line_ = 0; + /* true iff output enabled for this scope */ bool enabled_flag_ = false; }; /*scope_setup*/ @@ -155,6 +162,10 @@ namespace xo { std::string_view name1_ = ""; /* name of this scope (part 2) */ std::string_view name2_ = "::"; + /* captured value of __FILE__ */ + std::string_view file_ = ""; + /* captured value of __LINE__ */ + std::uint32_t line_ = 0; /* set once per scope .finalized=true <-> logging disabled */ bool finalized_ = false; }; /*basic_scope*/ @@ -166,14 +177,23 @@ namespace xo { : style_{setup.style_}, name1_{std::move(setup.name1_)}, name2_{std::move(setup.name2_)}, + file_{std::move(setup.file_)}, + line_{setup.line_}, finalized_{!setup.enabled_flag_} { if(setup.enabled_flag_) { state_impl_type * logstate = basic_scope::require_thread_local_state(); + std::ostream & os = logstate2stream(logstate); logstate->preamble(this->style_, this->name1_, this->name2_); - tosn(logstate2stream(logstate), " ", std::forward(args)...); + 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()); From f7882b1ff21211693aa2ee6cb2b8c2144cba9975 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 14 Sep 2023 17:09:07 -0400 Subject: [PATCH 0018/2693] nestlog: + tag(), use to tidy ex3 --- example/ex3/ex3.cpp | 4 ++-- include/nestlog/tag.hpp | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/example/ex3/ex3.cpp b/example/ex3/ex3.cpp index 05a3c9e4..b4d1e9e1 100644 --- a/example/ex3/ex3.cpp +++ b/example/ex3/ex3.cpp @@ -6,13 +6,13 @@ using namespace xo; int fib(int n) { - scope log(XO_ENTER0(), xtag("n", n)); + scope log(XO_ENTER0(), tag("n", n)); int retval = 1; if (n >= 2) { retval = fib(n - 1) + fib(n - 2); - log(xtag("n", n)); + log(tag("n", n)); } log.end_scope("<-", xtag("retval", retval)); diff --git a/include/nestlog/tag.hpp b/include/nestlog/tag.hpp index 16a3e21a..7ac62023 100644 --- a/include/nestlog/tag.hpp +++ b/include/nestlog/tag.hpp @@ -25,6 +25,8 @@ namespace xo { struct tag_impl { tag_impl(Name const & n, Value const & v) : name_{n}, value_{v} {} + tag_impl(Name && n, Value && v) + : name_{std::move(n)}, value_{std::move(v)} {} Name const & name() const { return name_; } Value const & value() const { return value_; } @@ -67,6 +69,20 @@ namespace xo { return tag_impl(n, ""); } /*xtag_pre*/ + template + tag_impl + tag(Name && n, Value && v) + { + return tag_impl(n, v); + } /*tag*/ + + template + tag_impl + tag(char const * n, Value && v) + { + return tag_impl(n, v); + } /*tag*/ + template inline std::ostream & operator<<(std::ostream &s, From fd2be2a4aec400a83aa689b655e82c5ef072e153 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Sep 2023 13:24:02 -0400 Subject: [PATCH 0019/2693] nestlog: + terminal colors for entry/exit/code location --- example/ex3/ex3.cpp | 6 +- include/nestlog/code_location.hpp | 56 ++++++++++++++ include/nestlog/color.hpp | 123 ++++++++++++++++++++++++++++++ include/nestlog/function.hpp | 29 ++++++- include/nestlog/log_config.hpp | 26 +++++++ include/nestlog/log_state.hpp | 37 +++++++-- 6 files changed, 265 insertions(+), 12 deletions(-) create mode 100644 include/nestlog/code_location.hpp create mode 100644 include/nestlog/color.hpp diff --git a/example/ex3/ex3.cpp b/example/ex3/ex3.cpp index b4d1e9e1..64682a8a 100644 --- a/example/ex3/ex3.cpp +++ b/example/ex3/ex3.cpp @@ -22,9 +22,13 @@ fib(int n) { int main(int argc, char ** argv) { - log_config::style = FS_Pretty; + log_config::style = FS_Streamlined; log_config::indent_width = 4; log_config::location_tab = 40; + log_config::encoding = CE_Xterm; + log_config::function_entry_color = 69; + log_config::function_exit_color = 70; + log_config::code_location_color = 166; int n = 4; diff --git a/include/nestlog/code_location.hpp b/include/nestlog/code_location.hpp new file mode 100644 index 00000000..e343fb58 --- /dev/null +++ b/include/nestlog/code_location.hpp @@ -0,0 +1,56 @@ +/* @file code_location.hpp */ + +#pragma once + +#include "filename.hpp" +#include "color.hpp" + +namespace xo { + /* Example: + * os << code_location("/path/to/foo.cpp", 123) + * writes + * foo.cpp:123 + * on stream os + */ + + /* Tag to drive header-only expression */ + template + class code_location_impl { + public: + code_location_impl(std::string_view file, + std::uint32_t line, + color_encoding encoding = CE_Ansi, + std::uint32_t color = 31 /*red*/) + : file_{file}, line_{line}, encoding_{encoding}, color_{color} {} + + void print_code_location(std::ostream & os) const { + os << "[" + << with_color(encoding_, color_, basename(file_)) + << ":" + << line_ + << "]"; + } /*print_code_location*/ + + private: + /* __FILE__ */ + std::string_view file_; + /* __LINE__ */ + std::uint32_t line_ = 0; + /* color encoding for file,line */ + color_encoding encoding_ = CE_Ansi; + /* color for file,line */ + std::uint32_t color_ = 0; + }; /*code_location_impl*/ + + using code_location = code_location_impl; + + inline std::ostream & + operator<<(std::ostream & os, + code_location const & x) + { + x.print_code_location(os); + return os; + } +} /*namespace xo*/ + +/* end code_location.hpp */ diff --git a/include/nestlog/color.hpp b/include/nestlog/color.hpp new file mode 100644 index 00000000..8f5a4d74 --- /dev/null +++ b/include/nestlog/color.hpp @@ -0,0 +1,123 @@ +/* color.hpp */ + +#pragma once + +#include +//#include // for std::move +#include + +namespace xo { + enum color_encoding { + CE_None, + CE_Ansi, + CE_Xterm, + }; + + enum color_flags { + CF_None = 0x0, + CF_ColorOn = 0x01, + CF_Contents = 0x02, + CF_ColorOff = 0x04, + CF_All = 0x07 + }; + + template + class color_impl { + public: + color_impl(color_flags flags, color_encoding encoding, std::uint32_t color, Contents && contents) + : flags_{flags}, encoding_{encoding}, color_{color}, contents_{std::move(contents)} {} + + std::uint32_t color() const { return color_; } + Contents const & contents() const { return contents_; } + + void print(std::ostream & os) const { + if ((flags_ & CF_ColorOn) && (color_ > 0)) { + switch(encoding_) { + case CE_Ansi: + os << "\033[" << color_ << "m"; + break; + case CE_Xterm: + os << "\033[38;5;" << color_ << "m"; + break; + } + } + + if (flags_ & CF_Contents) + os << contents_; + + if ((flags_ & CF_ColorOff) && (color_ > 0)) + os << "\033[0m"; + } /*print*/ + + private: + color_flags flags_ = CF_None; + + color_encoding encoding_ = CE_Ansi; + /* .encoding = CE_Ansi: + * 0 = no color + * 30 = black + * 31 = red + * 32 = green + * 33 = yellow + * 34 = blue + * 35 = magenta + * 36 = cyan + * + * .encoding = CE_Xterm: + * see [[https://i.stack.imgur.com/KTSQa.png]] + * 0..7 standard colors (muted: grey, red, green, yellow, blue, pink, cyan, white) + * 8..15 high-intensity colors (grey, red, green, yellow, blue, pink, cyan, white) + * 16..51 chooses hue + * 16..51 + (0..5)x36 increases whiteness + */ + std::uint32_t color_ = 0; + + Contents contents_; + }; /*color_impl*/ + + template + color_impl with_ansi_color(std::uint32_t color, Contents && contents) { + return color_impl(CF_All, CE_Ansi, color, std::move(contents)); + } /*with_ansi_color*/ + + template + color_impl with_xterm_color(std::uint32_t color, Contents && contents) { + return color_impl(CF_All, CE_Xterm, color, std::move(contents)); + } /*with_ansi_color*/ + + template + color_impl with_color(color_encoding encoding, std::uint32_t color, Contents && contents) { + return color_impl(CF_All, encoding, color, std::move(contents)); + } /*with_color*/ + + inline color_impl + color_on_ansi(std::uint32_t color) { + return color_impl(CF_ColorOn, CE_Ansi, color, 0); + } /*color_on_ansi*/ + + inline color_impl + color_on_xterm(std::uint32_t color) { + return color_impl(CF_ColorOn, CE_Xterm, color, 0); + } /*color_on_xterm*/ + + inline color_impl + color_on(color_encoding encoding, std::uint32_t color) { + return color_impl(CF_ColorOn, encoding, color, 0); + } /*color_on*/ + + inline color_impl + color_off() { + /* any non-zero value works here for color */ + return color_impl(CF_ColorOff, CE_None, 1 /*color*/, 0); + } /*color_off*/ + + template + inline std::ostream & + operator<<(std::ostream & os, color_impl const & x) { + x.print(os); + return os; + } /*operator<<*/ + +} /*namespace xo*/ + +/* end color.hpp */ diff --git a/include/nestlog/function.hpp b/include/nestlog/function.hpp index da6a3f6e..42c5752f 100644 --- a/include/nestlog/function.hpp +++ b/include/nestlog/function.hpp @@ -1,5 +1,7 @@ /* @file function.hpp */ +#include "color.hpp" + #include #include @@ -19,11 +21,19 @@ namespace xo { template class function_name_impl { public: + /* color: ANSI escape color (lookup Select Graphic Rendition subset) + * 0 = none + * 31 = red + */ function_name_impl(function_style style, - std::string_view pretty) - : style_{style}, pretty_{pretty} {} + color_encoding encoding, + std::uint32_t color, + std::string_view pretty) + : style_{style}, encoding_{encoding}, color_{color}, pretty_{pretty} {} function_style style() const { return style_; } + color_encoding encoding() const { return encoding_; } + std::uint32_t color() const { return color_; } std::string_view const & pretty() const { return pretty_; } /* e.g. @@ -152,11 +162,16 @@ namespace xo { if (ch == '>' || ch == ')') --nesting_level; } + } /*print_aux*/ private: /* FS_Simple | FS_Pretty (= FS_Literal) | FS_Streamlined */ function_style style_; + /* CE_Ansi | CE_Xterm */ + color_encoding encoding_; + /* color, if non-zero */ + std::uint32_t color_; /* e.g. __PRETTY_FUNCTION__ */ std::string_view pretty_; }; /*function_name_impl*/ @@ -167,19 +182,25 @@ namespace xo { operator<<(std::ostream & os, function_name const & fn) { + /* set text color */ + switch(fn.style()) { case FS_Literal: - os << fn.pretty(); + os << with_color(fn.encoding(), fn.color(), fn.pretty()); break; case FS_Pretty: - os << "[" << fn.pretty() << "]"; + os << "[" << with_color(fn.encoding(), fn.color(), fn.pretty()) << "]"; break; case FS_Simple: + os << color_on(fn.encoding(), fn.color()); function_name::print_simple(os, fn.pretty()); + os << color_off(); break; case FS_Streamlined: /* omit namespace qualifiers and template arguments */ + os << color_on(fn.encoding(), fn.color()); function_name::print_streamlined(os, fn.pretty()); + os << color_off(); break; } diff --git a/include/nestlog/log_config.hpp b/include/nestlog/log_config.hpp index d6da65da..3046ef44 100644 --- a/include/nestlog/log_config.hpp +++ b/include/nestlog/log_config.hpp @@ -3,6 +3,7 @@ #pragma once #include "function.hpp" +#include "nestlog/color.hpp" #include namespace xo { @@ -13,10 +14,19 @@ namespace xo { static std::uint32_t indent_width; /* display style for function names. FS_Simple|FS_Pretty|FS_Streamlined */ static function_style style; + /* color encoding */ + static color_encoding encoding; + /* color to use for function name, on entry/exit (xo::scope creation/destruction) + * (ansi color codes, see Select Graphics Rendition subset) + */ + static std::uint32_t function_entry_color; + static std::uint32_t function_exit_color; /* if true, append [file:line] to output */ static bool location_enabled; /* when .location_enabled, write [file:line] starting this many chars from left margin */ static std::uint32_t location_tab; + /* color to use for code location */ + static std::uint32_t code_location_color; }; /*log_config_impl*/ template @@ -27,6 +37,18 @@ namespace xo { function_style log_config_impl::style = FS_Streamlined; + template + color_encoding + log_config_impl::encoding = CE_Ansi; + + template + std::uint32_t + log_config_impl::function_entry_color = 34; + + template + std::uint32_t + log_config_impl::function_exit_color = 32; + template bool log_config_impl::location_enabled = true; @@ -35,6 +57,10 @@ namespace xo { std::uint32_t log_config_impl::location_tab = 80; + template + std::uint32_t + log_config_impl::code_location_color = 31; + using log_config = log_config_impl; } /*namespace xo*/ diff --git a/include/nestlog/log_state.hpp b/include/nestlog/log_state.hpp index 73b77960..fa48adf6 100644 --- a/include/nestlog/log_state.hpp +++ b/include/nestlog/log_state.hpp @@ -6,11 +6,17 @@ #include "log_streambuf.hpp" #include "pad.hpp" #include "filename.hpp" +#include "code_location.hpp" #include #include #include // for std::unique_ptr namespace xo { + enum EntryExit { + EE_Entry, + EE_Exit + }; + // track per-thread state associated with nesting logger // template @@ -55,7 +61,7 @@ namespace xo { void entryexit_aux(function_style style, std::string_view name1, std::string_view name2, - char label_char); + EntryExit entryexit); private: /* current nesting level for this thread */ @@ -129,21 +135,37 @@ namespace xo { state_impl::entryexit_aux(function_style style, std::string_view name1, std::string_view name2, - char label_char) + EntryExit entryexit) { log_streambuf_type * sbuf = this->p_sbuf_phase1_.get(); sbuf->reset_stream(); this->indent(' '); + char ee_label = '\0'; + std::uint32_t fn_color = 0; + + color_encoding encoding = log_config::encoding; + /* mnemonic for scope entry/exit */ - this->ss_ << label_char; + switch(entryexit) { + case EE_Entry: + ee_label = '+'; + fn_color = log_config::function_entry_color; + break; + case EE_Exit: + ee_label = '-'; + fn_color = log_config::function_exit_color; + break; + } + + this->ss_ << ee_label; if (log_config::indent_width > 1) this->ss_ << ' '; /* scope name - note no trailing newline; expect .preamble()/.postamble() caller to supply */ - this->ss_ << function_name(style, name1) << name2; + this->ss_ << function_name(style, encoding, fn_color, name1) << name2; } /*entryexit_aux*/ template @@ -152,7 +174,7 @@ namespace xo { std::string_view name1, std::string_view name2) { - this->entryexit_aux(style, name1, name2, '+' /*label_char*/); + this->entryexit_aux(style, name1, name2, EE_Entry); } /*preamble*/ template @@ -161,7 +183,7 @@ namespace xo { std::string_view name1, std::string_view name2) { - this->entryexit_aux(style, name1, name2, '-' /*label_char*/); + this->entryexit_aux(style, name1, name2, EE_Exit); } /*postamble*/ template @@ -241,7 +263,8 @@ namespace xo { sbuf2->sputc(' '); std::stringstream ss; - ss << "[" << basename(this->file_) << ":" << this->line_ << "]"; + ss << code_location(this->file_, this->line_, + log_config::encoding, log_config::code_location_color); std::string ss_str = std::move(ss.str()); /*c++20*/ sbuf2->sputn(ss_str.c_str(), ss_str.size()); From 5a47539dbbd81881290005fc133d224c0c0629ab Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Sep 2023 15:25:17 -0400 Subject: [PATCH 0020/2693] nestlog: + explicit+max indent width --- example/ex3/ex3.cpp | 8 ++++---- include/nestlog/color.hpp | 8 ++++---- include/nestlog/log_config.hpp | 20 +++++++++++++++++++- include/nestlog/log_state.hpp | 32 ++++++++++++++++++++++++-------- 4 files changed, 51 insertions(+), 17 deletions(-) diff --git a/example/ex3/ex3.cpp b/example/ex3/ex3.cpp index 64682a8a..89d7ad2c 100644 --- a/example/ex3/ex3.cpp +++ b/example/ex3/ex3.cpp @@ -12,10 +12,9 @@ fib(int n) { if (n >= 2) { retval = fib(n - 1) + fib(n - 2); - log(tag("n", n)); } - log.end_scope("<-", xtag("retval", retval)); + log.end_scope(tag("n", n), " <-", xtag("retval", retval)); return retval; } @@ -24,7 +23,8 @@ int main(int argc, char ** argv) { log_config::style = FS_Streamlined; log_config::indent_width = 4; - log_config::location_tab = 40; + log_config::max_indent_width = 14; + log_config::location_tab = 70; log_config::encoding = CE_Xterm; log_config::function_entry_color = 69; log_config::function_exit_color = 70; @@ -36,7 +36,7 @@ main(int argc, char ** argv) { int fn = fib(n); - log(xtag("n", n)); + xtag("n", n); log("<-", xtag("fib(n)", fn)); } diff --git a/include/nestlog/color.hpp b/include/nestlog/color.hpp index 8f5a4d74..2c2419c2 100644 --- a/include/nestlog/color.hpp +++ b/include/nestlog/color.hpp @@ -25,7 +25,7 @@ namespace xo { class color_impl { public: color_impl(color_flags flags, color_encoding encoding, std::uint32_t color, Contents && contents) - : flags_{flags}, encoding_{encoding}, color_{color}, contents_{std::move(contents)} {} + : flags_{flags}, encoding_{encoding}, color_{color}, contents_{std::forward(contents)} {} std::uint32_t color() const { return color_; } Contents const & contents() const { return contents_; } @@ -77,17 +77,17 @@ namespace xo { template color_impl with_ansi_color(std::uint32_t color, Contents && contents) { - return color_impl(CF_All, CE_Ansi, color, std::move(contents)); + return color_impl(CF_All, CE_Ansi, color, std::forward(contents)); } /*with_ansi_color*/ template color_impl with_xterm_color(std::uint32_t color, Contents && contents) { - return color_impl(CF_All, CE_Xterm, color, std::move(contents)); + return color_impl(CF_All, CE_Xterm, color, std::forward(contents)); } /*with_ansi_color*/ template color_impl with_color(color_encoding encoding, std::uint32_t color, Contents && contents) { - return color_impl(CF_All, encoding, color, std::move(contents)); + return color_impl(CF_All, encoding, color, std::forward(contents)); } /*with_color*/ inline color_impl diff --git a/include/nestlog/log_config.hpp b/include/nestlog/log_config.hpp index 3046ef44..cf5ea3aa 100644 --- a/include/nestlog/log_config.hpp +++ b/include/nestlog/log_config.hpp @@ -10,8 +10,14 @@ namespace xo { /* Tag here b/c we want header-only library */ template struct log_config_impl { - /* spaces per indent level */ + /* spaces per nesting level */ static std::uint32_t indent_width; + /* max #of spaces to introduce when indenting */ + static std::uint32_t max_indent_width; + /* if true enable explicit nesting level display [nnn] */ + static bool nesting_level_enabled; + /* color to use for explicit nesting level */ + static std::uint32_t nesting_level_color; /* display style for function names. FS_Simple|FS_Pretty|FS_Streamlined */ static function_style style; /* color encoding */ @@ -33,6 +39,18 @@ namespace xo { std::uint32_t log_config_impl::indent_width = 1; + template + std::uint32_t + log_config_impl::max_indent_width = 32; + + template + bool + log_config_impl::nesting_level_enabled = true; + + template + std::uint32_t + log_config_impl::nesting_level_color = 195; + template function_style log_config_impl::style = FS_Streamlined; diff --git a/include/nestlog/log_state.hpp b/include/nestlog/log_state.hpp index fa48adf6..8273a6ec 100644 --- a/include/nestlog/log_state.hpp +++ b/include/nestlog/log_state.hpp @@ -126,8 +126,14 @@ namespace xo { } #endif - /* indent to nesting level */ - this->ss_ << pad(this->nesting_level_ * log_config::indent_width, pad_char); + /* indent to nesting level. + * + * note: see also flush2sbuf(), need special indent handling for continuation lines + * (when application sends explicit newlines to this logger) + */ + this->ss_ << pad(std::min(this->nesting_level_ * log_config::indent_width, + log_config::max_indent_width), + pad_char); } /*indent*/ template @@ -161,6 +167,15 @@ namespace xo { this->ss_ << ee_label; + if (log_config::nesting_level_enabled) { + this->ss_ + << "(" + << with_color(log_config::encoding, + log_config::nesting_level_color, + this->nesting_level_) + << ")"; + } + if (log_config::indent_width > 1) this->ss_ << ' '; @@ -257,7 +272,7 @@ namespace xo { sbuf2->sputn(s, p - s - 1); if (this->location_flag_) { - /* 'tab' to position 80 */ + /* '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(' '); @@ -280,9 +295,8 @@ namespace xo { sbuf2->sputn(s, p - s); } - if (p == e) { + if (p == e) break; - } // { // char buf[80]; @@ -293,14 +307,16 @@ namespace xo { /* at least 1 char following newline, need to indent for it * - minimum indent = nesting level; - * - however if space_after_nonspace defined, indent to that + * - however if space_after_nonspace defined, also indent for that */ - uint32_t n_indent = this->nesting_level_; + std::uint32_t n_indent = std::min(this->nesting_level_ * log_config::indent_width, + log_config::max_indent_width); + /* this is just to indent for per-line entry/exit label */ if(space_after_nonspace) n_indent += (space_after_nonspace - s); - for(uint32_t i = 0; i < n_indent; ++i) + for(std::uint32_t i = 0; i < n_indent; ++i) sbuf2->sputc(' '); s = p; From ad36c16d9156f14e6af2e15482f731b2feea65bd Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Sep 2023 16:30:34 -0400 Subject: [PATCH 0021/2693] nestlog: + time-of-day column --- example/ex3/ex3.cpp | 6 +- include/nestlog/log_config.hpp | 18 ++ include/nestlog/log_state.hpp | 33 ++- include/nestlog/scope.hpp | 5 + include/nestlog/time.hpp | 362 +++++++++++++++++++++++++++++++++ 5 files changed, 420 insertions(+), 4 deletions(-) create mode 100644 include/nestlog/time.hpp diff --git a/example/ex3/ex3.cpp b/example/ex3/ex3.cpp index 89d7ad2c..a0507147 100644 --- a/example/ex3/ex3.cpp +++ b/example/ex3/ex3.cpp @@ -21,10 +21,12 @@ fib(int n) { int main(int argc, char ** argv) { + log_config::time_enabled = true; + log_config::time_local_flag = true; log_config::style = FS_Streamlined; log_config::indent_width = 4; log_config::max_indent_width = 14; - log_config::location_tab = 70; + log_config::location_tab = 80; log_config::encoding = CE_Xterm; log_config::function_entry_color = 69; log_config::function_exit_color = 70; @@ -36,7 +38,7 @@ main(int argc, char ** argv) { int fn = fib(n); - xtag("n", n); + log(tag("n", n)); log("<-", xtag("fib(n)", fn)); } diff --git a/include/nestlog/log_config.hpp b/include/nestlog/log_config.hpp index cf5ea3aa..fa0d3902 100644 --- a/include/nestlog/log_config.hpp +++ b/include/nestlog/log_config.hpp @@ -10,6 +10,12 @@ namespace xo { /* Tag here b/c we want header-only library */ template struct log_config_impl { + /* true to log local time */ + static bool time_enabled; + /* true to log time-of-day in local coords; false for UTC coords */ + static bool time_local_flag; + /* true to log time-of-day with microsecond precision; false for millisecond precision */ + static bool time_usec_flag; /* spaces per nesting level */ static std::uint32_t indent_width; /* max #of spaces to introduce when indenting */ @@ -35,6 +41,18 @@ namespace xo { static std::uint32_t code_location_color; }; /*log_config_impl*/ + template + bool + log_config_impl::time_enabled = 1; + + template + bool + log_config_impl::time_local_flag = true; + + template + bool + log_config_impl::time_usec_flag = true; + template std::uint32_t log_config_impl::indent_width = 1; diff --git a/include/nestlog/log_state.hpp b/include/nestlog/log_state.hpp index 8273a6ec..06999ccb 100644 --- a/include/nestlog/log_state.hpp +++ b/include/nestlog/log_state.hpp @@ -7,6 +7,7 @@ #include "pad.hpp" #include "filename.hpp" #include "code_location.hpp" +#include "time.hpp" #include #include #include // for std::unique_ptr @@ -146,6 +147,29 @@ namespace xo { log_streambuf_type * sbuf = this->p_sbuf_phase1_.get(); sbuf->reset_stream(); + + /* e.g: + * + * 14:38:19.914 + * ------------- + * 0123456789012 + * 0 1 + * + * (13 chars including trailing space) + */ + if (log_config::time_enabled) { + if (log_config::time_local_flag) { + if (log_config::time_usec_flag) + this->ss_ << xo::time::hms_usec::local(xo::time::time::now()) << " "; + else + this->ss_ << xo::time::hms_msec::local(xo::time::time::now()) << " "; + } else { + if (log_config::time_usec_flag) + this->ss_ << xo::time::hms_usec::utc(xo::time::time::now()) << " "; + else + this->ss_ << xo::time::hms_msec::utc(xo::time::time::now()) << " "; + } + } this->indent(' '); char ee_label = '\0'; @@ -309,8 +333,13 @@ namespace xo { * - minimum indent = nesting level; * - however if space_after_nonspace defined, also indent for that */ - std::uint32_t n_indent = std::min(this->nesting_level_ * log_config::indent_width, - log_config::max_indent_width); + std::uint32_t n_indent = 0; + + if (log_config::time_enabled) + n_indent += 13; /*strlen("14:38:19.974 ")*/ + + n_indent += std::min(this->nesting_level_ * log_config::indent_width, + log_config::max_indent_width); /* this is just to indent for per-line entry/exit label */ if(space_after_nonspace) diff --git a/include/nestlog/scope.hpp b/include/nestlog/scope.hpp index 04451aaf..e343c9a3 100644 --- a/include/nestlog/scope.hpp +++ b/include/nestlog/scope.hpp @@ -118,6 +118,11 @@ namespace xo { } else { state_impl_type * logstate = require_indent_thread_local_state(); + /* indent for timestamp */ + if (log_config::time_enabled) + logstate->ss() << pad(13, ' '); + //logstate->indent(' '); + /* log to per-thread stream to prevent data races */ tosn(logstate2stream(logstate), std::forward(rest)...); diff --git a/include/nestlog/time.hpp b/include/nestlog/time.hpp new file mode 100644 index 00000000..04344195 --- /dev/null +++ b/include/nestlog/time.hpp @@ -0,0 +1,362 @@ +/* @file Time.hpp */ + +#pragma once + +#include +#include +#ifdef NOT_YET +# include +#endif +#include +#include +#include + +namespace xo { + namespace time { + + using utc_nanos = std::chrono::time_point; + + using nanos = std::chrono::nanoseconds; + using microseconds = std::chrono::microseconds; + using milliseconds = std::chrono::milliseconds; + using seconds = std::chrono::seconds; + using hours = std::chrono::hours; + using days = std::chrono::days; + + struct time { + static utc_nanos now() { + return utc_nanos(std::chrono::system_clock::now()); + } + + static utc_nanos epoch() { + return utc_nanos(std::chrono::system_clock::from_time_t(0)); + } /*epoch*/ + + static utc_nanos ymd_hms(uint32_t ymd, uint32_t hms) { + /* e.g. ymd=20220610 -> n_yr=2022, n_mon=06, n_dy=10 */ + + uint32_t n_yr = ymd / 10000; + uint32_t n_mon = (ymd % 10000) / 100; + uint32_t n_dy = ymd % 100; + + uint32_t n_hr = hms / 10000; + uint32_t n_min = (hms % 10000) / 100; + uint32_t n_sec = hms % 100; + + struct tm t; + + t.tm_year = n_yr - 1900; /* 0 means 1900 */ + t.tm_mon = n_mon - 1; /* 0 means january */ + t.tm_mday = n_dy; + + t.tm_hour = n_hr; /* 24 hour clock */ + t.tm_min = n_min; + t.tm_sec = n_sec; + + /* time since epoch */ + time_t epoch_time = timegm(&t); + + return std::chrono::system_clock::from_time_t(epoch_time); + } /*ymd_hms*/ + + /* midnight UTC on date ymd. + * e.g. ymd_midnight(20220707) -> midnight UTC on 7jul22 + */ + static utc_nanos ymd_midnight(uint32_t ymd) { + return ymd_hms(ymd, 0); + } /*ymd_midnight*/ + + static utc_nanos ymd_hms_usec(uint32_t ymd, uint32_t hms, uint32_t usec) { + utc_nanos s = ymd_hms(ymd, hms); + + return s + microseconds(usec); + } /*ymd_hms_usec*/ + + /* .first: UTC midnight on same calendar day as t0 + * .second: elapsed time from .first to t0 (i.e. UTC time-of-day for t0) + */ + static std::pair utc_split_vs_midnight(utc_nanos t0) { + using xo::time::microseconds; + using xo::time::utc_nanos; + + /* use yyyymmdd.hh:mm:ss.nnnnnn */ + + time_t t0_time_t = (std::chrono::system_clock::to_time_t + (std::chrono::time_point_cast(t0))); + + /* convert to std::tm, + * only provides 1-second precision + */ + std::tm t0_tm; + ::gmtime_r(&t0_time_t, &t0_tm); + + /* midnight on the same calendar day as t0_tm */ + std::tm midnight_tm = t0_tm; + { + midnight_tm.tm_hour = 0; + midnight_tm.tm_min = 0; + midnight_tm.tm_sec = 0; + } + + /* convert to UTC epoch seconds */ + time_t midnight_time_t = ::timegm(&midnight_tm); + + utc_nanos t0_midnight = + (std::chrono::time_point_cast( + std::chrono::system_clock::from_time_t(midnight_time_t))); + + nanos t0_tdy = t0 - t0_midnight; + + return std::pair(t0_midnight, t0_tdy); + } /*utc_split_vs_midnight*/ + + /* .first: LOCAL midnight on same calendar day as t0 (but in UTC coords) + * .second: elapsed time from .first to t0 (i.e. LOCAL time-of-day for t0) + */ + static std::pair local_split_vs_midnight(utc_nanos t0) { + using xo::time::microseconds; + using xo::time::utc_nanos; + + /* use yyyymmdd.hh:mm:ss.nnnnnn */ + + time_t t0_time_t = (std::chrono::system_clock::to_time_t + (std::chrono::time_point_cast(t0))); + + /* convert to std::tm, + * only provides 1-second precision + */ + std::tm t0_tm; + ::localtime_r(&t0_time_t, &t0_tm); + + /* midnight on the same calendar day as t0_tm */ + std::tm midnight_tm = t0_tm; + { + midnight_tm.tm_hour = 0; + midnight_tm.tm_min = 0; + midnight_tm.tm_sec = 0; + } + + /* convert local midnight to UTC epoch seconds */ + time_t midnight_time_t = ::timelocal(&midnight_tm); + + utc_nanos t0_midnight = + (std::chrono::time_point_cast( + std::chrono::system_clock::from_time_t(midnight_time_t))); + + nanos t0_tdy = t0 - t0_midnight; + + return std::pair(t0_midnight, t0_tdy); + } /*local_split_vs_midnight*/ + + /* split utc_nanos into + * std::tm + * .tm_year + * .tm_mon (1-12) + * .tm_mday (1-31) + * .tm_hour (0-23) + * .tm_min (0-59) + * .tm_sec (0-59) + * .tm_wday (0=sunday .. 6=saturday) + * .tm_yday (0=1jan .. 365) + * .tm_isdst (daylight savings time flag) + * usec (0-999999) + */ + static std::pair split_tm(utc_nanos t0) { + using xo::time::microseconds; + using xo::time::utc_nanos; + + /* use yyyymmdd.hh:mm:ss.nnnnnn */ + + time_t t0_time_t = (std::chrono::system_clock::to_time_t + (std::chrono::time_point_cast(t0))); + + /* convert to std::tm, un UTC coords, + * only provides 1-second precision + */ + std::tm t0_tm; + ::gmtime_r(&t0_time_t, &t0_tm); + + /* midnight on the same calendar day as t0_tm */ + std::tm midnight_tm = t0_tm; + + midnight_tm.tm_isdst = 0; + midnight_tm.tm_hour = 0; + midnight_tm.tm_min = 0; + midnight_tm.tm_sec = 0; + + /* convert back to epoch seconds */ + time_t midnight_time_t = ::mktime(&midnight_tm); + + utc_nanos t0_midnight = + (std::chrono::time_point_cast( + std::chrono::system_clock::from_time_t(midnight_time_t))); + + uint32_t usec = + (std::chrono::duration_cast( + std::chrono::hh_mm_ss(t0 - t0_midnight).subseconds())) + .count(); + + return std::make_pair(t0_tm, usec); + } /*split_tm*/ + + static void print_hms_msec(nanos dt, std::ostream & os) { + /* use hhmmss.nnn */ + using std::int32_t; + + auto hms = std::chrono::hh_mm_ss(dt); + int32_t h = hms.hours().count(); + int32_t m = hms.minutes().count(); + int32_t s = hms.seconds().count(); + int32_t msec = std::chrono::duration_cast(hms.subseconds()).count(); + + char buf[32]; + snprintf(buf, sizeof(buf), "%02d:%02d:%02d.%03d", h, m, s, msec); + + os << buf; + } /*print_hms_msec*/ + + static void print_utc_hms_msec(utc_nanos t0, std::ostream & os) { + print_hms_msec(utc_split_vs_midnight(t0).second, os); + } /*print_utc_hms_usec*/ + + static void print_hms_usec(nanos dt, std::ostream & os) { + /* use hhmmss.uuuuuu */ + using std::int32_t; + + auto hms = std::chrono::hh_mm_ss(dt); + int32_t h = hms.hours().count(); + int32_t m = hms.minutes().count(); + int32_t s = hms.seconds().count(); + int32_t usec = std::chrono::duration_cast(hms.subseconds()).count(); + + char buf[32]; + snprintf(buf, sizeof(buf), "%02d:%02d:%02d.%06d", h, m, s, usec); + + os << buf; + } /*print_hms_usec*/ + + static void print_utc_ymd_hms_usec(utc_nanos t0, std::ostream & os) { + using xo::time::microseconds; + using xo::time::utc_nanos; + + /* use yyyymmdd.hh:mm:ss.nnnnnn */ + + //std::tm t0_tm; + //uint32_t t0_usec; + + /* (structured binding ftw!) */ + auto [t0_tm, t0_usec] = split_tm(t0); + + /* no std::format in clang11 afaict */ + char usec_buf[7]; + snprintf(usec_buf, sizeof(usec_buf), "%06d", t0_usec); + + + /* control string | example + * ----------------------------+-------------------------- + * %c - locale-specific string | Fri Jun 10 16:29:05 2022 + * %Y - year | 2022 + * %m - month | 06 + * %d - day of month | 10 + * %H - hour | 16 + * %M - minute | 29 + * %S - second | 05 + * %Z - timezone | UTC + */ + os << std::put_time(&t0_tm, "%Y%m%d:%H:%M:%S.") << usec_buf; + } /*print_utc_ymd_hms_usec*/ + + /* print datetime in format compatible with ISO 8601. + * copying the format javascript uses, e.g: + * 2012-04-23T18:25:43.511Z + */ + static void print_iso8601(utc_nanos t0, std::ostream & os) { + auto [t0_tm, t0_usec] = split_tm(t0); + + char msec_buf[8]; + snprintf(msec_buf, sizeof(msec_buf), "%03d", t0_usec / 1000); + + os << std::put_time(&t0_tm, "%Y-%m-%dT%H:%M:%S.") << msec_buf << "Z"; + } /*print_iso8601*/ + }; /*time*/ + + /* stream inserter that displays time in ISO 8601 format: + * 2012-04-23T18:25:43.511Z + */ + struct iso8601 { + iso8601(utc_nanos t0) : t0_{t0} {} + + utc_nanos t0_; + }; /*iso8601*/ + + inline std::ostream & + operator<<(std::ostream & os, + iso8601 x) + { + time::print_iso8601(x.t0_, os); + return os; + } /*operator<<*/ + + /* stream inserter that display time like: + * hh:mm:ss.nnn + */ + struct hms_msec { + hms_msec(nanos dt) : dt_{dt} {} + + static hms_msec utc(utc_nanos t0) { return hms_msec(time::utc_split_vs_midnight(t0).second); } + static hms_msec local(utc_nanos t0) { return hms_msec(time::local_split_vs_midnight(t0).second); } + + nanos dt_; + }; /*hms_msec*/ + + inline std::ostream & + operator<<(std::ostream & os, hms_msec x) + { + time::print_hms_msec(x.dt_, os); + return os; + } /*operator<<*/ + + + /* stream inserter that display time like: + * hh:mm:ss.nnnnnn + */ + struct hms_usec { + hms_usec(nanos dt) : dt_{dt} {} + + static hms_usec utc(utc_nanos t0) { return hms_usec(time::utc_split_vs_midnight(t0).second); } + static hms_usec local(utc_nanos t0) { return hms_usec(time::local_split_vs_midnight(t0).second); } + + nanos dt_; + }; /*hms_msec*/ + + inline std::ostream & + operator<<(std::ostream & os, hms_usec x) + { + time::print_hms_usec(x.dt_, os); + return os; + } /*operator<<*/ + + + } /*namespace time*/ +} /*namespace xo*/ + +namespace std { + namespace chrono { + inline std::ostream & operator<<(std::ostream & os, + xo::time::utc_nanos t0) + { + xo::time::time::print_utc_ymd_hms_usec(t0, os); + return os; + } /*operator<<*/ + + inline std::ostream & operator<<(std::ostream & os, + xo::time::nanos dt) + { + xo::time::time::print_hms_usec(dt, os); + return os; + } /*operator<<*/ + } /*namespace chrono*/ +} /*namespace std*/ + +/* end Time.hpp */ From 3e62cefe43e032926cd5ddba82248ffb5b65d35d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Sep 2023 16:32:35 -0400 Subject: [PATCH 0022/2693] nestlog: minor streamlining --- include/nestlog/log_state.hpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/include/nestlog/log_state.hpp b/include/nestlog/log_state.hpp index 06999ccb..7384cec0 100644 --- a/include/nestlog/log_state.hpp +++ b/include/nestlog/log_state.hpp @@ -158,16 +158,23 @@ namespace xo { * (13 chars including trailing space) */ if (log_config::time_enabled) { + using xo::time::time; + using xo::time::utc_nanos; + using xo::time::hms_msec; + using xo::time::hms_usec; + + utc_nanos now_tm = time::now(); + if (log_config::time_local_flag) { if (log_config::time_usec_flag) - this->ss_ << xo::time::hms_usec::local(xo::time::time::now()) << " "; + this->ss_ << hms_usec::local(now_tm) << " "; else - this->ss_ << xo::time::hms_msec::local(xo::time::time::now()) << " "; + this->ss_ << hms_msec::local(now_tm) << " "; } else { if (log_config::time_usec_flag) - this->ss_ << xo::time::hms_usec::utc(xo::time::time::now()) << " "; + this->ss_ << hms_usec::utc(now_tm) << " "; else - this->ss_ << xo::time::hms_msec::utc(xo::time::time::now()) << " "; + this->ss_ << hms_msec::utc(now_tm) << " "; } } this->indent(' '); From f5a2ee1af515711684dd7a99cdc061987121d75a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Sep 2023 16:41:18 -0400 Subject: [PATCH 0023/2693] nestlog: refactor: streamline indenting for lhs timestamp --- include/nestlog/log_state.hpp | 68 +++++++++++++++++++---------------- include/nestlog/scope.hpp | 6 ++-- 2 files changed, 39 insertions(+), 35 deletions(-) diff --git a/include/nestlog/log_state.hpp b/include/nestlog/log_state.hpp index 7384cec0..fee193ec 100644 --- a/include/nestlog/log_state.hpp +++ b/include/nestlog/log_state.hpp @@ -24,6 +24,7 @@ namespace xo { class state_impl { public: using log_streambuf_type = log_streambuf>; + using utc_nanos = xo::time::utc_nanos; public: state_impl(); @@ -35,6 +36,40 @@ namespace xo { std::ostream & ss() { return ss_; } + void check_print_time(utc_nanos now_tm) { + using xo::time::time; + using xo::time::utc_nanos; + using xo::time::hms_msec; + using xo::time::hms_usec; + + if (log_config::time_local_flag) { + if (log_config::time_usec_flag) + this->ss_ << hms_usec::local(now_tm) << " "; + else + this->ss_ << hms_msec::local(now_tm) << " "; + } else { + if (log_config::time_usec_flag) + this->ss_ << hms_usec::utc(now_tm) << " "; + else + this->ss_ << hms_msec::utc(now_tm) << " "; + } + } /*check_print_time*/ + + /* space budget for time-of-day */ + std::size_t calc_time_indent() const { + if (log_config::time_enabled) { + /*strlen("14:38:19.974 ")*/ + return 13; + } else { + return 0; + } + } /*calc_time_indent*/ + + void time_indent() { + if (log_config::time_enabled) + this->ss_ << pad(this->calc_time_indent(), ' '); + } /*time_indent*/ + /* call on entry to new scope */ void preamble(function_style style, std::string_view name1, std::string_view name2); /* call before each new log entry */ @@ -148,35 +183,7 @@ namespace xo { sbuf->reset_stream(); - /* e.g: - * - * 14:38:19.914 - * ------------- - * 0123456789012 - * 0 1 - * - * (13 chars including trailing space) - */ - if (log_config::time_enabled) { - using xo::time::time; - using xo::time::utc_nanos; - using xo::time::hms_msec; - using xo::time::hms_usec; - - utc_nanos now_tm = time::now(); - - if (log_config::time_local_flag) { - if (log_config::time_usec_flag) - this->ss_ << hms_usec::local(now_tm) << " "; - else - this->ss_ << hms_msec::local(now_tm) << " "; - } else { - if (log_config::time_usec_flag) - this->ss_ << hms_usec::utc(now_tm) << " "; - else - this->ss_ << hms_msec::utc(now_tm) << " "; - } - } + this->check_print_time(xo::time::time::now()); this->indent(' '); char ee_label = '\0'; @@ -342,8 +349,7 @@ namespace xo { */ std::uint32_t n_indent = 0; - if (log_config::time_enabled) - n_indent += 13; /*strlen("14:38:19.974 ")*/ + n_indent += this->calc_time_indent(); n_indent += std::min(this->nesting_level_ * log_config::indent_width, log_config::max_indent_width); diff --git a/include/nestlog/scope.hpp b/include/nestlog/scope.hpp index e343c9a3..848e9d4e 100644 --- a/include/nestlog/scope.hpp +++ b/include/nestlog/scope.hpp @@ -118,10 +118,8 @@ namespace xo { } else { state_impl_type * logstate = require_indent_thread_local_state(); - /* indent for timestamp */ - if (log_config::time_enabled) - logstate->ss() << pad(13, ' '); - //logstate->indent(' '); + /* 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)...); From db9e34d8e9d073f11d1eb9ca9b43639b035383e7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Sep 2023 16:42:46 -0400 Subject: [PATCH 0024/2693] nestlog: bugfix: timestamp indent to account for msec/usec --- include/nestlog/log_state.hpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/include/nestlog/log_state.hpp b/include/nestlog/log_state.hpp index fee193ec..d833307e 100644 --- a/include/nestlog/log_state.hpp +++ b/include/nestlog/log_state.hpp @@ -58,8 +58,13 @@ namespace xo { /* space budget for time-of-day */ std::size_t calc_time_indent() const { if (log_config::time_enabled) { - /*strlen("14:38:19.974 ")*/ - return 13; + if (log_config::time_usec_flag) { + /*strlen("14:38:19.123456 ")*/ + return 16; + } else { + /*strlen("14:38:19.974 ")*/ + return 13; + } } else { return 0; } From 5339e9b124d1922b30c4c2e8b362c8b0fdad46a9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Sep 2023 17:39:35 -0400 Subject: [PATCH 0025/2693] nestlog: log levels --- example/ex1/ex1.cpp | 2 +- example/ex2/ex2.cpp | 4 ++-- example/ex3/ex3.cpp | 9 ++++---- include/nestlog/log_config.hpp | 7 ++++++ include/nestlog/log_level.hpp | 42 ++++++++++++++++++++++++++++++++++ include/nestlog/scope.hpp | 32 ++++++++++++++------------ include/nestlog/time.hpp | 4 ++-- 7 files changed, 76 insertions(+), 24 deletions(-) create mode 100644 include/nestlog/log_level.hpp diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 60922607..db0ce39e 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -3,7 +3,7 @@ using namespace xo; void A(int x) { - XO_SCOPE(log); + XO_SCOPE(log, info); log("x:", x); } diff --git a/example/ex2/ex2.cpp b/example/ex2/ex2.cpp index 3d246f0e..016fe177 100644 --- a/example/ex2/ex2.cpp +++ b/example/ex2/ex2.cpp @@ -6,7 +6,7 @@ using namespace xo; int fib(int n) { - scope log(XO_SSETUP0(), ":n ", n); + scope log(XO_ENTER0(info), ":n ", n); int retval = 1; @@ -26,7 +26,7 @@ main(int argc, char ** argv) { int n = 4; - scope log(XO_SSETUP0(), ":n ", 4); + scope log(XO_ENTER0(info), ":n ", 4); int fn = fib(n); diff --git a/example/ex3/ex3.cpp b/example/ex3/ex3.cpp index a0507147..0d9121dc 100644 --- a/example/ex3/ex3.cpp +++ b/example/ex3/ex3.cpp @@ -6,7 +6,7 @@ using namespace xo; int fib(int n) { - scope log(XO_ENTER0(), tag("n", n)); + scope log(XO_ENTER0(info), tag("n", n)); int retval = 1; @@ -21,6 +21,7 @@ fib(int n) { int main(int argc, char ** argv) { + log_config::min_log_level = log_level::info; log_config::time_enabled = true; log_config::time_local_flag = true; log_config::style = FS_Streamlined; @@ -34,12 +35,12 @@ main(int argc, char ** argv) { int n = 4; - scope log(XO_ENTER0(), ":n ", 4); + scope log(XO_ENTER0(info), ":n ", 4); int fn = fib(n); - log(tag("n", n)); - log("<-", xtag("fib(n)", fn)); + log && log(tag("n", n)); + log && log("<-", xtag("fib(n)", fn)); } /* ex3/ex3.cpp */ diff --git a/include/nestlog/log_config.hpp b/include/nestlog/log_config.hpp index fa0d3902..b0a3780e 100644 --- a/include/nestlog/log_config.hpp +++ b/include/nestlog/log_config.hpp @@ -2,6 +2,7 @@ #pragma once +#include "log_level.hpp" #include "function.hpp" #include "nestlog/color.hpp" #include @@ -10,6 +11,8 @@ namespace xo { /* Tag here b/c we want header-only library */ template struct log_config_impl { + /* display log messages with severity >= .log_level */ + static log_level min_log_level; /* true to log local time */ static bool time_enabled; /* true to log time-of-day in local coords; false for UTC coords */ @@ -41,6 +44,10 @@ namespace xo { static std::uint32_t code_location_color; }; /*log_config_impl*/ + template + log_level + log_config_impl::min_log_level = log_level::default_level; + template bool log_config_impl::time_enabled = 1; diff --git a/include/nestlog/log_level.hpp b/include/nestlog/log_level.hpp new file mode 100644 index 00000000..f5d5f4db --- /dev/null +++ b/include/nestlog/log_level.hpp @@ -0,0 +1,42 @@ +/* @file log_level.hpp */ + +#include + +namespace xo { + enum class log_level : std::uint32_t { + /* control log message severity + * silent > severe > error > warning > info > chatty + */ + chatty, + info, + warning, + error, + severe, + silent, + + default_level = error + }; /*log_level*/ + + inline bool + operator>(log_level x, log_level y) { + return (static_cast(x) > static_cast(y)); + } + + inline bool + operator>=(log_level x, log_level y) { + return (static_cast(x) >= static_cast(y)); + } + + inline bool + operator<(log_level x, log_level y) { + return (static_cast(x) < static_cast(y)); + } + + inline bool + operator<=(log_level x, log_level y) { + return (static_cast(x) <= static_cast(y)); + } + +} /*namespace xo*/ + +/* end log_level.hpp */ diff --git a/include/nestlog/scope.hpp b/include/nestlog/scope.hpp index 848e9d4e..f15515df 100644 --- a/include/nestlog/scope.hpp +++ b/include/nestlog/scope.hpp @@ -16,16 +16,16 @@ namespace xo { template class state_impl; -# define XO_ENTER0() xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__) -# define XO_ENTER1(debug_flag) xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__, debug_flag) +# define XO_ENTER0(lvl) xo::scope_setup(xo::log_level::lvl, xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__) +# define XO_ENTER1(lvl, debug_flag) xo::scope_setup(xo::log_level::lvl, xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__, debug_flag) //# define XO_SSETUP0() xo::scope_setup(__FUNCTION__) -# define XO_SSETUP0() xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__) +//# define XO_SSETUP0(lvl) xo::scope_setup(xo::log_level::lvl, xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__) /* 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) xo::scope name(xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__)) +# define XO_SCOPE(name, lvl) xo::scope name(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_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__, false)) @@ -35,17 +35,21 @@ namespace xo { * use to disambiguate setup from other arguments */ struct scope_setup { - scope_setup(function_style style, std::string_view name1, std::string_view name2, - std::string_view file, std::uint32_t line, bool enabled_flag) - : style_{style}, name1_{name1}, name2_{name2}, file_{file}, line_{line}, enabled_flag_{enabled_flag} {} - scope_setup(function_style style, std::string_view name1, std::string_view file, std::uint32_t line, bool enabled_flag) - : scope_setup(style, name1, "", file, line, enabled_flag) {} - scope_setup(function_style style, std::string_view name1, std::string_view file, std::uint32_t line) - : scope_setup(style, name1, file, line, true /*enabled_flag*/) {} + scope_setup(log_level level, function_style style, std::string_view name1, std::string_view name2, + std::string_view file, std::uint32_t line) + : log_level_{level}, style_{style}, name1_{name1}, name2_{name2}, file_{file}, line_{line} {} + scope_setup(log_level level, function_style style, + std::string_view name1, std::string_view file, std::uint32_t line) + : scope_setup(level, style, name1, "" /*name2*/, file, line) {} + + bool is_enabled() const { return (this->log_level_ >= log_config::min_log_level); } //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 */ + log_level log_level_ = log_level::error; + /* FS_Pretty | FS_Streamlined | FS_Simple */ function_style style_ = FS_Pretty; std::string_view name1_ = "<.name1>"; std::string_view name2_ = "<.name2>"; @@ -53,8 +57,6 @@ namespace xo { std::string_view file_ = "<.file>"; /* __LINE__ */ std::uint32_t line_ = 0; - /* true iff output enabled for this scope */ - bool enabled_flag_ = false; }; /*scope_setup*/ /* nesting logger @@ -182,9 +184,9 @@ namespace xo { name2_{std::move(setup.name2_)}, file_{std::move(setup.file_)}, line_{setup.line_}, - finalized_{!setup.enabled_flag_} + finalized_{!(setup.is_enabled())} { - if(setup.enabled_flag_) { + if(setup.is_enabled()) { state_impl_type * logstate = basic_scope::require_thread_local_state(); std::ostream & os = logstate2stream(logstate); diff --git a/include/nestlog/time.hpp b/include/nestlog/time.hpp index 04344195..1d93dd26 100644 --- a/include/nestlog/time.hpp +++ b/include/nestlog/time.hpp @@ -1,4 +1,4 @@ -/* @file Time.hpp */ +/* @file time.hpp */ #pragma once @@ -359,4 +359,4 @@ namespace std { } /*namespace chrono*/ } /*namespace std*/ -/* end Time.hpp */ +/* end time.hpp */ From beeb927082ced8e34b8cadfb055b2c97bdf18928 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Sep 2023 20:15:58 -0400 Subject: [PATCH 0026/2693] nestlog: streamlining + comments + markdown --- README.md | 81 +++++++++++++++++------------------ example/ex2/ex2.cpp | 7 +-- example/ex3/ex3.cpp | 6 +-- include/nestlog/color.hpp | 14 +++++- include/nestlog/log_level.hpp | 17 +++++++- include/nestlog/scope.hpp | 12 ++++-- 6 files changed, 84 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 4e934ad7..2edcca0b 100644 --- a/README.md +++ b/README.md @@ -33,65 +33,64 @@ output: #include "nestlog/scope.hpp" + using namespace xo; + int fib(int n) { - XO_SCOPE(log); + scope log(XO_ENTER0(info), ":n ", n); int retval = 1; if (n >= 2) { - log(":n ", n); retval = fib(n - 1) + fib(n - 2); + log && log(":n ", n); } - log(":n ", n, " -> :retval ", retval); + log.end_scope("<- :retval ", retval); return retval; } int main(int argc, char ** argv) { - XO_SCOPE(log); + log_config::min_log_level = xo::log_level::info; + log_config::indent_width = 4; - int n = 4; - int fn = fib(n); + int n = 4; - log(":n ", n, " :fib(n) ", fn); + scope log(XO_ENTER0(info), ":n ", 4); + + int fn = fib(n); + + log && log(":n ", n); + log && log("<- :fib(n) ", fn); } output: - +main - +fib - :n 4 - +fib - :n 3 - +fib - :n 2 - +fib - :n 1 -> :retval 1 - -fib - +fib - :n 0 -> :retval 1 - -fib - :n 2 -> :retval 2 - -fib - +fib - :n 1 -> :retval 1 - -fib - :n 3 -> :retval 3 - -fib - +fib - :n 2 - +fib - :n 1 -> :retval 1 - -fib - +fib - :n 0 -> :retval 1 - -fib - :n 2 -> :retval 2 - -fib - :n 4 -> :retval 5 - -fib - :n 4 :fib(n) 5 - -main + 20:13:12.992909 +(0) main :n 4 [ex2.cpp:30] + 20:13:12.992968 +(1) fib :n 4 [ex2.cpp:9] + 20:13:12.992986 +(2) fib :n 3 [ex2.cpp:9] + 20:13:12.992999 +(3) fib :n 2 [ex2.cpp:9] + 20:13:12.993002 +(4) fib :n 1 [ex2.cpp:9] + 20:13:12.993012 -(4) fib <- :retval 1 + 20:13:12.993022 +(4) fib :n 0 [ex2.cpp:9] + 20:13:12.993032 -(4) fib <- :retval 1 + :n 2 + 20:13:12.993049 -(3) fib <- :retval 2 + 20:13:12.993059 +(3) fib :n 1 [ex2.cpp:9] + 20:13:12.993069 -(3) fib <- :retval 1 + :n 3 + 20:13:12.993085 -(2) fib <- :retval 3 + 20:13:12.993095 +(2) fib :n 2 [ex2.cpp:9] + 20:13:12.993105 +(3) fib :n 1 [ex2.cpp:9] + 20:13:12.993115 -(3) fib <- :retval 1 + 20:13:12.993124 +(3) fib :n 0 [ex2.cpp:9] + 20:13:12.993134 -(3) fib <- :retval 1 + :n 2 + 20:13:12.993145 -(2) fib <- :retval 2 + :n 4 + 20:13:12.993155 -(1) fib <- :retval 5 + :n 4 + <- :fib(n) 5 + 20:13:12.993172 -(0) main diff --git a/example/ex2/ex2.cpp b/example/ex2/ex2.cpp index 016fe177..23bb2b3e 100644 --- a/example/ex2/ex2.cpp +++ b/example/ex2/ex2.cpp @@ -12,7 +12,7 @@ fib(int n) { if (n >= 2) { retval = fib(n - 1) + fib(n - 2); - log(":n ", n); + log && log(":n ", n); } log.end_scope("<- :retval ", retval); @@ -22,6 +22,7 @@ fib(int n) { int main(int argc, char ** argv) { + log_config::min_log_level = xo::log_level::info; log_config::indent_width = 4; int n = 4; @@ -30,6 +31,6 @@ main(int argc, char ** argv) { int fn = fib(n); - log(":n ", n); - log("<- :fib(n) ", fn); + log && log(":n ", n); + log && log("<- :fib(n) ", fn); } diff --git a/example/ex3/ex3.cpp b/example/ex3/ex3.cpp index 0d9121dc..d20f0f65 100644 --- a/example/ex3/ex3.cpp +++ b/example/ex3/ex3.cpp @@ -26,14 +26,14 @@ main(int argc, char ** argv) { log_config::time_local_flag = true; log_config::style = FS_Streamlined; log_config::indent_width = 4; - log_config::max_indent_width = 14; - log_config::location_tab = 80; + log_config::max_indent_width = 30; + log_config::location_tab = 100; log_config::encoding = CE_Xterm; log_config::function_entry_color = 69; log_config::function_exit_color = 70; log_config::code_location_color = 166; - int n = 4; + int n = 9; scope log(XO_ENTER0(info), ":n ", 4); diff --git a/include/nestlog/color.hpp b/include/nestlog/color.hpp index 2c2419c2..20ea0bb1 100644 --- a/include/nestlog/color.hpp +++ b/include/nestlog/color.hpp @@ -33,6 +33,8 @@ namespace xo { void print(std::ostream & os) const { if ((flags_ & CF_ColorOn) && (color_ > 0)) { switch(encoding_) { + case CE_None: + break; case CE_Ansi: os << "\033[" << color_ << "m"; break; @@ -45,8 +47,16 @@ namespace xo { if (flags_ & CF_Contents) os << contents_; - if ((flags_ & CF_ColorOff) && (color_ > 0)) - os << "\033[0m"; + if ((flags_ & CF_ColorOff) && (color_ > 0)) { + switch(encoding_) { + case CE_None: + break; + case CE_Ansi: + case CE_Xterm: + os << "\033[0m"; + break; + } + } } /*print*/ private: diff --git a/include/nestlog/log_level.hpp b/include/nestlog/log_level.hpp index f5d5f4db..55c4f2a4 100644 --- a/include/nestlog/log_level.hpp +++ b/include/nestlog/log_level.hpp @@ -5,13 +5,28 @@ namespace xo { enum class log_level : std::uint32_t { /* control log message severity - * silent > severe > error > warning > info > chatty + * silent > always > severe > error > warning > info > chatty > never + * + * never: + * used internally e.g. by XO_ENTER1() + * a log message with this severity will never be printed + * + * always: + * use with XO_ENTER1(): + * scope log(XO_ENTER1(always, mydebug_flag)); + * to log message whenever mydebug_flag is true (for any .min_log_level except silent) + * + * silent: + * use in log_config to suppress all log messages */ + never, + verbose, chatty, info, warning, error, severe, + always, silent, default_level = error diff --git a/include/nestlog/scope.hpp b/include/nestlog/scope.hpp index f15515df..fac759c2 100644 --- a/include/nestlog/scope.hpp +++ b/include/nestlog/scope.hpp @@ -17,7 +17,13 @@ namespace xo { class state_impl; # define XO_ENTER0(lvl) xo::scope_setup(xo::log_level::lvl, xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__) -# define XO_ENTER1(lvl, debug_flag) xo::scope_setup(xo::log_level::lvl, xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__, debug_flag) +# define XO_ENTER1(lvl, debug_flag) XO_ENTER2(lvl, debug_flag, __PRETTY_FUNCTION__) +# define XO_ENTER2(lvl, debug_flag, name1) xo::scope_setup((debug_flag ? xo::log_level::lvl : xo::log_level::never), xo::log_config::style, name1, __FILE__, __LINE__) + +# define XO_DEBUG(debug_flag) XO_ENTER1(always, debug_flag) +# define XO_DEBUG2(debug_flag, name1) XO_ENTER2(always, debug_flag, name1) + +# define XO_LITERAL(lvl, name1, name2) xo::scope_setup(xo::log_level::lvl, FS_Literal, name1, name2, __FILE__, __LINE__) //# define XO_SSETUP0() xo::scope_setup(__FUNCTION__) //# define XO_SSETUP0(lvl) xo::scope_setup(xo::log_level::lvl, xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__) @@ -27,8 +33,8 @@ namespace xo { /* 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__)) /* 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_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__, false)) +//# 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). From 3437ef1b3faa876f7340ebd14cc92753bcdce659 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Sep 2023 20:30:44 -0400 Subject: [PATCH 0027/2693] nestlog: ++ README.md --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 2edcca0b..df0a4a8a 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,18 @@ Nestlog is a lightweight header-only library for console logging. +## Features + +- easy-to-read format uses indenting to show call structure. + indentation has user-controlled upper limit to preserve readability with + deeply nested call graphs +- colorized output using vt100 color codes (ansi or xterm) +- automatically captures + displays timestamp, function name and code location. + supports several function-name formats to reflect tradeoff readability for precision +- application code may issue logging code that contains embedded newlines; + logger preserves indentation. +- logger is 'truthy', so you only pay for formatting for entry points that are enabled. + ## Examples ### 1 From 794196c3f8df9eaa3cd3b635f9f5fdc20c736433 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Sep 2023 12:38:36 -0400 Subject: [PATCH 0028/2693] nestlog: bugfix: color_off() must use CE_Ansi instead of CE_None --- include/nestlog/color.hpp | 2 +- include/nestlog/log_state.hpp | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/include/nestlog/color.hpp b/include/nestlog/color.hpp index 20ea0bb1..6f1d52f8 100644 --- a/include/nestlog/color.hpp +++ b/include/nestlog/color.hpp @@ -118,7 +118,7 @@ namespace xo { inline color_impl color_off() { /* any non-zero value works here for color */ - return color_impl(CF_ColorOff, CE_None, 1 /*color*/, 0); + return color_impl(CF_ColorOff, CE_Ansi, 1 /*color*/, 0); } /*color_off*/ template diff --git a/include/nestlog/log_state.hpp b/include/nestlog/log_state.hpp index d833307e..b878724b 100644 --- a/include/nestlog/log_state.hpp +++ b/include/nestlog/log_state.hpp @@ -211,6 +211,14 @@ namespace xo { this->ss_ << ee_label; if (log_config::nesting_level_enabled) { + /* e.g. + * (^[[38;5;195m7^[[0m) + * <-----a---->b<-c-> + * + * a = color on + * b = level - displayed in color + * c = color off + */ this->ss_ << "(" << with_color(log_config::encoding, From d4207c1f33b521f82609a8218766fa0586623f9e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Sep 2023 14:18:38 -0400 Subject: [PATCH 0029/2693] nestlog: bugfix: suppress timestamp when disabled --- include/nestlog/log_state.hpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/include/nestlog/log_state.hpp b/include/nestlog/log_state.hpp index b878724b..4b894a92 100644 --- a/include/nestlog/log_state.hpp +++ b/include/nestlog/log_state.hpp @@ -42,16 +42,18 @@ namespace xo { using xo::time::hms_msec; using xo::time::hms_usec; - if (log_config::time_local_flag) { - if (log_config::time_usec_flag) - this->ss_ << hms_usec::local(now_tm) << " "; - else - this->ss_ << hms_msec::local(now_tm) << " "; - } else { - if (log_config::time_usec_flag) - this->ss_ << hms_usec::utc(now_tm) << " "; - else - this->ss_ << hms_msec::utc(now_tm) << " "; + if (log_config::time_enabled) { + if (log_config::time_local_flag) { + if (log_config::time_usec_flag) + this->ss_ << hms_usec::local(now_tm) << " "; + else + this->ss_ << hms_msec::local(now_tm) << " "; + } else { + if (log_config::time_usec_flag) + this->ss_ << hms_usec::utc(now_tm) << " "; + else + this->ss_ << hms_msec::utc(now_tm) << " "; + } } } /*check_print_time*/ From 643e0a2edc41fb3ac45a5c8130b7931b0afbbd24 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Sep 2023 14:19:18 -0400 Subject: [PATCH 0030/2693] nestlog: bugfix: indent accounting skips non-printing vt100 escapes --- include/nestlog/log_state.hpp | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/include/nestlog/log_state.hpp b/include/nestlog/log_state.hpp index 4b894a92..9bf6fefb 100644 --- a/include/nestlog/log_state.hpp +++ b/include/nestlog/log_state.hpp @@ -281,6 +281,11 @@ namespace xo { */ 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; @@ -302,18 +307,29 @@ namespace xo { } } - if(*p == '\n') { + if (in_color_escape && (*p != '\n')) { + /* in color escape -> don't advance .lpos */ + if (*p == 'm') + in_color_escape = false; ++p; - /* reset .pos on newline */ + } 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; + lpos_on_newline = this->lpos_; this->lpos_ = 0; + ++p; break; } else { - ++p; - /* increment .lpos on non-newline */ ++(this->lpos_); + ++p; } } @@ -358,7 +374,7 @@ namespace xo { // std::clog.rdbuf()->sputn(buf, strlen(buf)); //} - /* at least 1 char following newline, need to indent for it + /* 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 */ From 5b80fe2d202205b2231e5e822fd7e0a2bf9d3cb3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Sep 2023 14:21:09 -0400 Subject: [PATCH 0031/2693] nestlog: examples/ex3.cpp: minor tweaks --- example/ex3/ex3.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/example/ex3/ex3.cpp b/example/ex3/ex3.cpp index d20f0f65..452998f8 100644 --- a/example/ex3/ex3.cpp +++ b/example/ex3/ex3.cpp @@ -21,19 +21,22 @@ fib(int n) { int main(int argc, char ** argv) { + //std::cerr << "0 1 2 3 4 5 6 7 8 9 10" << std::endl; + //std::cerr << "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" << std::endl; + log_config::min_log_level = log_level::info; log_config::time_enabled = true; log_config::time_local_flag = true; log_config::style = FS_Streamlined; log_config::indent_width = 4; log_config::max_indent_width = 30; - log_config::location_tab = 100; + log_config::location_tab = 80; log_config::encoding = CE_Xterm; log_config::function_entry_color = 69; log_config::function_exit_color = 70; log_config::code_location_color = 166; - int n = 9; + int n = 3; scope log(XO_ENTER0(info), ":n ", 4); From d46c7349502e435edde72546f29c93bc7a8ac104 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Sep 2023 14:21:43 -0400 Subject: [PATCH 0032/2693] nestlog: colorize tag inserter --- include/nestlog/concat.hpp | 37 ++++++++++++++++++++++++++++++++++ include/nestlog/log_config.hpp | 2 +- include/nestlog/tag.hpp | 7 +++++-- include/nestlog/tag_config.hpp | 34 +++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 include/nestlog/concat.hpp create mode 100644 include/nestlog/tag_config.hpp diff --git a/include/nestlog/concat.hpp b/include/nestlog/concat.hpp new file mode 100644 index 00000000..31bd6b43 --- /dev/null +++ b/include/nestlog/concat.hpp @@ -0,0 +1,37 @@ +/* @file concat.hpp */ + +#pragma once + +#include +#include // for std::move() + +namespace xo { + template + struct concat_impl { + public: + concat_impl(T1 && x1, T2 && x2) + : x1_{std::forward(x1)}, x2_{std::forward(x2)} {} + + T1 const & x1() const { return x1_; } + T2 const & x2() const { return x2_; } + + private: + T1 x1_; + T2 x2_; + }; /*concat_impl*/ + + template + concat_impl concat(T1 && x1, T2 && x2) { + return concat_impl(std::move(x1), std::move(x2)); + } /*concat*/ + + template + inline std::ostream & + operator<<(std::ostream & os, concat_impl const & x) { + os << x.x1() << x.x2(); + return os; + } /*operator<<*/ + +} /*namespace xo*/ + +/* end concat.hpp */ diff --git a/include/nestlog/log_config.hpp b/include/nestlog/log_config.hpp index b0a3780e..00e49249 100644 --- a/include/nestlog/log_config.hpp +++ b/include/nestlog/log_config.hpp @@ -4,7 +4,7 @@ #include "log_level.hpp" #include "function.hpp" -#include "nestlog/color.hpp" +#include "color.hpp" #include namespace xo { diff --git a/include/nestlog/tag.hpp b/include/nestlog/tag.hpp index 7ac62023..258b432f 100644 --- a/include/nestlog/tag.hpp +++ b/include/nestlog/tag.hpp @@ -2,7 +2,10 @@ #pragma once -#include "nestlog/quoted.hpp" +#include "tag_config.hpp" +#include "concat.hpp" +#include "quoted.hpp" +#include "color.hpp" #include // STRINGIFY(xyz) -> "xyz" @@ -93,7 +96,7 @@ namespace xo { if(PrefixSpace) s << " "; - s << ":" << tag.name() + s << with_color(tag_config::encoding, tag_config::tag_color, concat((char const *)":", tag.name())) << " " << unq(tag.value()); return s; diff --git a/include/nestlog/tag_config.hpp b/include/nestlog/tag_config.hpp new file mode 100644 index 00000000..d4a67cb7 --- /dev/null +++ b/include/nestlog/tag_config.hpp @@ -0,0 +1,34 @@ +/* @file tag_config.hpp */ + +#pragma once + +#include "color.hpp" +#include + +namespace xo { + /* Tag here b/c we want header-only library */ + template + struct tag_config_impl { + /* color encoding */ + static color_encoding encoding; + /* color to use for tags + * os << tag("foo", foovalue) + * to produces output like + * :foo foovalue + * with :foo using .tag_color + */ + static std::uint32_t tag_color; + }; /*tag_config_impl*/ + + template + color_encoding + tag_config_impl::encoding = CE_Xterm; + + template + std::uint32_t + tag_config_impl::tag_color = 245; + + using tag_config = tag_config_impl; +} /*namespace xo*/ + +/* end tag_config.hpp */ From 7b233acf2941e0ebe3a77768575a83ca92ba67c8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Sep 2023 14:22:07 -0400 Subject: [PATCH 0033/2693] nestlog: + quoted_char inserter --- include/nestlog/quoted_char.hpp | 43 +++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 include/nestlog/quoted_char.hpp diff --git a/include/nestlog/quoted_char.hpp b/include/nestlog/quoted_char.hpp new file mode 100644 index 00000000..010eefa3 --- /dev/null +++ b/include/nestlog/quoted_char.hpp @@ -0,0 +1,43 @@ +/* @file quoted_char.hpp */ + +#pragma once + +#include + +namespace xo { + template + class quoted_char { + public: + quoted_char(CharT ch) : ch_{ch} {} + + void print(std::ostream & os) const { + switch(ch_) { + case '\033': + os << "\\033"; + break; + case '\n': + os << "\\n"; + break; + case '\r': + os << "\\r"; + break; + default: + os << ch_; + } + } + + private: + CharT ch_; + }; /*quoted_char*/ + + template + inline std::ostream & + operator<<(std::ostream & os, quoted_char const & x) { + x.print(os); + return os; + } /*operator<<*/ + +} /*namespace xo*/ + + +/* end quoted_char.hpp */ From efdcacf9b5b791d30e6a012d33bdb6c8c6cee826 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Sep 2023 14:22:51 -0400 Subject: [PATCH 0034/2693] nestlog: README.md tweaks --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index df0a4a8a..6847b7d7 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,20 @@ -# nestlog -- logging with automatic indenting according to call graph +# nestlog -- logging with automatic call-graph indenting Nestlog is a lightweight header-only library for console logging. ## Features +- header-only; nothing to link - easy-to-read format uses indenting to show call structure. indentation has user-controlled upper limit to preserve readability with deeply nested call graphs - colorized output using vt100 color codes (ansi or xterm) - automatically captures + displays timestamp, function name and code location. supports several function-name formats to reflect tradeoff readability for precision -- application code may issue logging code that contains embedded newlines; +- application code may issue logging code that contains embedded newlines and/or color escapes; logger preserves indentation. -- logger is 'truthy', so you only pay for formatting for entry points that are enabled. +- logger is 'truthy' -> only pay for formatting when entry points is enabled. +- also provides family of convenience stream-inserters ## Examples From 3e457e4435467cb3b041d43e98fb679f25a25cb9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Sep 2023 14:36:30 -0400 Subject: [PATCH 0035/2693] nestlog: README: try embedding screen capture --- README.md | 19 +++++++++++++++---- img/ex3.png | Bin 0 -> 47158 bytes 2 files changed, 15 insertions(+), 4 deletions(-) create mode 100755 img/ex3.png diff --git a/README.md b/README.md index 6847b7d7..d54dc2e5 100644 --- a/README.md +++ b/README.md @@ -67,21 +67,32 @@ output: int main(int argc, char ** argv) { - log_config::min_log_level = xo::log_level::info; + log_config::min_log_level = log_level::info; + log_config::time_enabled = true; + log_config::time_local_flag = true; + log_config::style = FS_Streamlined; log_config::indent_width = 4; + log_config::max_indent_width = 30; + log_config::location_tab = 80; + log_config::encoding = CE_Xterm; + log_config::function_entry_color = 69; + log_config::function_exit_color = 70; + log_config::code_location_color = 166; - int n = 4; + int n = 3; scope log(XO_ENTER0(info), ":n ", 4); int fn = fib(n); - log && log(":n ", n); - log && log("<- :fib(n) ", fn); + log && log(tag("n", n)); + log && log("<-", xtag("fib(n)", fn)); } output: + [ex3 output](img/ex3.png) + 20:13:12.992909 +(0) main :n 4 [ex2.cpp:30] 20:13:12.992968 +(1) fib :n 4 [ex2.cpp:9] 20:13:12.992986 +(2) fib :n 3 [ex2.cpp:9] diff --git a/img/ex3.png b/img/ex3.png new file mode 100755 index 0000000000000000000000000000000000000000..6355c003aa7b859469ab7697ca0714d313628c39 GIT binary patch literal 47158 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
R6&ci!@wi{0wF2H%6LuZ*7ft_<3~xa!aZ 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

E>$_e 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 Date: Sat, 16 Sep 2023 14:37:46 -0400 Subject: [PATCH 0036/2693] nestlog: README: img instead of link to img --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d54dc2e5..42232319 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ output: output: - [ex3 output](img/ex3.png) + ![ex3 output](img/ex3.png) 20:13:12.992909 +(0) main :n 4 [ex2.cpp:30] 20:13:12.992968 +(1) fib :n 4 [ex2.cpp:9] From 833a20d8a6e3a61cb3af5c2fe8ea73d95780a494 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Sep 2023 14:46:39 -0400 Subject: [PATCH 0037/2693] nestlog: README + ex2 output image --- README.md | 75 ++++++++++++++++++++++++++++++++-------------------- img/ex2.png | Bin 0 -> 69441 bytes 2 files changed, 46 insertions(+), 29 deletions(-) create mode 100755 img/ex2.png diff --git a/README.md b/README.md index 42232319..27c8e73d 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,48 @@ output: return retval; } + int + main(int argc, char ** argv) { + log_config::min_log_level = xo::log_level::info; + log_config::indent_width = 4; + + int n = 4; + + scope log(XO_ENTER0(info), ":n ", 4); + + int fn = fib(n); + + log && log(":n ", n); + log && log("<- :fib(n) ", fn); + } + +output: +![ex2 output](img/ex2.png) + +### 3 example exposing runtime configuration options + + ``` + /* examples ex3/ex3.cpp */ + + #include "nestlog/scope.hpp" + + using namespace xo; + + int + fib(int n) { + scope log(XO_ENTER0(info), tag("n", n)); + + int retval = 1; + + if (n >= 2) { + retval = fib(n - 1) + fib(n - 2); + } + + log.end_scope(tag("n", n), " <-", xtag("retval", retval)); + + return retval; + } + int main(int argc, char ** argv) { log_config::min_log_level = log_level::info; @@ -89,33 +131,8 @@ output: log && log("<-", xtag("fib(n)", fn)); } + /* ex3/ex3.cpp */ + ``` + output: - - ![ex3 output](img/ex3.png) - - 20:13:12.992909 +(0) main :n 4 [ex2.cpp:30] - 20:13:12.992968 +(1) fib :n 4 [ex2.cpp:9] - 20:13:12.992986 +(2) fib :n 3 [ex2.cpp:9] - 20:13:12.992999 +(3) fib :n 2 [ex2.cpp:9] - 20:13:12.993002 +(4) fib :n 1 [ex2.cpp:9] - 20:13:12.993012 -(4) fib <- :retval 1 - 20:13:12.993022 +(4) fib :n 0 [ex2.cpp:9] - 20:13:12.993032 -(4) fib <- :retval 1 - :n 2 - 20:13:12.993049 -(3) fib <- :retval 2 - 20:13:12.993059 +(3) fib :n 1 [ex2.cpp:9] - 20:13:12.993069 -(3) fib <- :retval 1 - :n 3 - 20:13:12.993085 -(2) fib <- :retval 3 - 20:13:12.993095 +(2) fib :n 2 [ex2.cpp:9] - 20:13:12.993105 +(3) fib :n 1 [ex2.cpp:9] - 20:13:12.993115 -(3) fib <- :retval 1 - 20:13:12.993124 +(3) fib :n 0 [ex2.cpp:9] - 20:13:12.993134 -(3) fib <- :retval 1 - :n 2 - 20:13:12.993145 -(2) fib <- :retval 2 - :n 4 - 20:13:12.993155 -(1) fib <- :retval 5 - :n 4 - <- :fib(n) 5 - 20:13:12.993172 -(0) main +![ex3 output](img/ex3.png) diff --git a/img/ex2.png b/img/ex2.png new file mode 100755 index 0000000000000000000000000000000000000000..8e5546365725205bcee5035ab7239f00a71cd508 GIT binary patch literal 69441 zcmeAS@N?(olHy`uVBq!ia0y~yVD@ETVB+FnV_;zTuerpBfq{XsILO_JVcj{ImkbOH zEa{HEjtmSN`?>!lvNA9*a29w(7BevL9R^{>lFzsfc^?mwS%r(V74I z!{-}|uVZ{t$1XbMw7kon1rNm(g(B+}wYc8A*58yZl<4>~?T0Q~%LIzW&svz+$eXjLe&Puw`%Mpp6i&p>OiW97HmAOJ-euFv#m`FSzdAfKk_yLSo@}u%=Fh#$r!{x`{@V3vO`4aly>$4d zx}<8`xBu(RH}!oyq1?aX(xsr%S64L6+XS3AR!TBWUA9TqEA~&lkm5IXb0d|fzodTL z_LjcWQRVY<`7XU_YEANHnOQ<#e5|j(y#Mb0mxH479xeWJxMhQCC8xuuJ4Us;W~rnv z5v$<7)2#C9NPg7euaCdayOwhE^`*_TG{0`GTcBbd5w!A+X^n27y?*NZ7ezC_{qN69 z5lZr%w|4!TV+k`?yz_6 z9EwkFEe$(nGWE~>D2eEoa#xZsoaT3AI_TfA(KC3mOZ)5Fa>eiKTN@SPj7)3e7TsTA zUFqjK_5K&GfatH2U)iobx#{}SbkpEh*?*s$ztYtkc0_-zpGM8c#Zj5R_RZB^E51?f zb&XW&?wr;8-0Wq)pFL9OTXf#``-;Qu{Hx3FovdR$JJ&jVYsN*Vy?;KPewA+jd!|*{ zn;EmLN>}ar`E2%Gkrn}`b*4r^vwE^8d@+#PF0%JMYo69K8K+IzGF3{_>n=T1HFs*} z-}WU$uTWWM;yu4w`!Ca~-p{nY==(rXXxXkkOc7N7S&sFj^k2R*>ab9j*`nFtu|1ZNQXIJeBE8@35HlyOc^=Iqq z`KG~j!h2pGp0Fy$k!9-YDA}rYs&Cu!jF;Rkh!tLXw<7rd{mXe3^D|?Vcjld`{9N-e z|Lnf+mfl}w(y}}`wZe@|W9?PXYlPZ(YFn>=QJpdOvC^gEUu*Zzv&i>;($-=cIVJb+ z_YGdLKgu5MGUe293`p3PcQq~yNT=}CRiUe0G@~~rwMJ}q>yvr8Orb@fXzj#}OF8#Wnb`NM`Sq{e zi??6={Za4soEHbb6h}MqzdaiN>UD_+2cv?&w!lYjuc>Vzv(DU*{4z~SzNY2V|Ho%0!x~{W6egFFN|9U=6k>(u1w^MFd&pi9?^*v|CbCL7RAD+oRKa>z`UvW%% zZfRw>`YVajUaRlQxqo-;*R1;WarQI+Qvaw)?`*d&joSHZw|dQlqNNpKm;Wxil=o+| zp7pKX{}VeVmo1LiE`46TkMUT-tou^?_g*&E8Bk_tqb3w$B{t&zLxTbru(TBF_ zn#S3CzPsjUT&}KT)gSQgiKPFk+p|*6%rG?S(+$Eyo^}8|9^_h^%>Xrfigg@QTVJa zp8|gc|84wN|K-}>YYsaM9xRS|xb4y7Wo+MdITQt$E==z3f4_8kNfF0;sms!D#I>q| z@_lj?MI_{FJ|6ZL<0+Zvz2!lRO`>+;JR9eR)(s2qEF#Q)VTR(mF0yYObg+D6X z?gEbt4CH?nZ%LZ^e{svFlvA^KE;-L=7MRMV`J2aoXUW|sJcl}47PS7TSh$a^t~tCk zV)^5L^_}~Bzj);ozq^>Zg(2QZw~+VGzaP29F|FqsvS$joOto2F;r2xGa_Y3|r4`2( z{mOs({nB&oQ%hUp-oJ01SMUH7VJ2l(g>TMQfBHPRuj|*vF79^|W9)DH9KoMJai^- zytqC_U>8HnVeap*c3s;q=(rPOFM7@K4r{NMF}uPrW{_xwWtPQl5_vtgsyYHu-O~U zXE$g4=KHl(dWU=Sd^Hi3h(3V_Rpm=R>P|m?C|vsMW7camK3t64FORzBrLc7@jhW6| z>G<3+!aOi~frw+{&Arvz*Qat!Q`_;Zl3A_9V_qVESGd)_uWR#9E)ghTt@(B{{ceO4 zhvIdm@R-a=^Xen~x2d)qdGgV4+k$oxJuiU?-Sa(;EG??#WNKnw!<0IEl~#J)-e(fe zC-BWSS$T@5k@NdD!5OK`Rz(y1K=2eO#5r!(&TDKNocouba9 zbloTMUpDsK4ER`GS+_c5fB1~_!^gN(OQ%1|zkHSdj(p=~-HohiYod%2U%R)wb?n^q z>*3z~WlOZGUlcuzbln+bvwGY61zV(TzwQ)y+t#!E()Wg`VzN9OPygpXicxE+EU%f* zdpdoq*(>%3m5x$7u1?)MeS_G~t+I&@aeBvp@hn*JqwxRV@2lN>%p7fscDNknIkHhk-zTdBpS8F}-ZOgM30jF2u7x(Ph zBftB%Xu=(ymQzK4=XySUJpXfeXYR~&-|2VUg)>ioWtVWs==xT+zI1z^##KjdDGRgf zF0=N$fAu=&iqoP0SB^)$aK82F{}uJ~>X&XXn9q5UYxTlL`AMBSH)~Xd=Ixd5pYGaw z`-$WxzjJ$kNo{}ecm2yfWu9+VuMlJL^O(d{hVC8rd|#I3 zEB9UV+?%_6d;8P#+75FQd|2z2pYZ)PNw_3Nxi((+!O|)}hay>Ntxt23nwTdTNBv*^ zTIIv{OV2O=jhhk6_t>;)96c_x<=M~rfV!?e| zN#*6Q)tA*LFWFq_&s&$uAG%Kd)tCQoNel6>W)`?1gOKk=XEyuVyMGi~>Qbmr%}x2D}%A%Ee;^-(!z`xU zZh2BJs&c)XaWnU$$nsh0?;dWs6n(dEO8taiRxcYuGyeSeXjGSReO+u`j_T(0^I`RW z9?M^Sb=9tSE}O6;kA#7PY3{8MwVDHUI%2CM7{uR|O;-1xR_JU0_oe^Qx$j@c|KGLa zljQ5myGmb&yuY_M)$8;AKTq|I)K0a`wRYzFqtqf$6dI5`P3=rnTbz%vgM{t7(7ub8 ze^xwNoO|c2-nuIvqfVTeKha_vtMQ}63!6EWM6w$dE6>g>Il=I)-sg{Pnyqu3;RegA zXM$$@DAUcSeDJ>25Q zj-1#b>AUOu74LwX!PB%)N+~~e`pZ5=`)+XB+C)9Yp?dayH=&#BV^Mb*Z@#!T?*yk4%^T7k>Jn5nk8L@7eaCTbmN*Tmv{iWp zTd(=M?qr&Gbr;(`eT{QlKfb%N^;hHXxJ^Q>hEwYjin_QL-)WXgczSB;qB93V`#}Ax z+@Ppgd$Q-uXs`&{?y$ayXPu4R=GC#B3s?@s3K?fOCOY+hUuu45veZfAJLe`m&kjhO z^y^@RkZg(**II*vR*7r4!?XW}Xo|0Ni(Mn{b9CMx*&RU#oJt-XVEpWiHPL_Wlme%6%`sV$0j6Q!EjOVeei(9jJmuGI&EKrv>sC?li7ZAgV zLy@$OZ1!(-QNWWB?UghI~OIW)~%_XG=Kj3xVoQDv$VgnH+~I&x9788 z>AO3YUoUy*qgti;?JiLxu{tuM47$#rNlLSX88Shb93`MN$U*^YX9D~N#_N4 zPuH6|_1|lzr@s5w>23QJ9eV4bWbKqKR(c>;ugkhu{oXdRL;B%^gUxw5D|PMue!1NB z>wH>!m8tjTv*!2L9P5={{_ERY?d;?SZQbJfW#_|~bopjBeakUu6H`#V(j%D!MdwXx2pWl}1?>@r{ zRQXSN{p952th2LBH!a$G@QVNce}A7^gw87Y?sfI`L)O*L=iArYB_CHS&z*|Ly?H)q3901~k^uoW2?D1AG$Isu&fEQ7qv4}=_S@Urs=Qi?JZ6>K zGM|{BNv#l(K~1mC|Ej#6{>zg#wZm94nr6w}@QqThbHOfCDaqoAF>Wi7jKW{2}>SdaL z&&IR4;qQhUd#pjDQl96(&vAPdzxet}-;2|-N|)@cGPrcXcJb3V^;Q1@k1I;NWbC?- zU-FAJD$wXmPwmou^481igtf!0ABkT*{V|Ev^R3_1*&YH-fqwh$@c(o?P@8blNT<^* z*t>Mbx~WE!)3)7nkK;AV=wdTj2;`5;=+Fe~;mE_wd{x@1U>r%?& z-`AIVi{H$% z=PS2|h5=+{n|nC&gS&~H_p~=pBma3PPHihwAx^$I{97Pho!AoIo3^S zo2cx5>f7D&`%~BY&dO=N&+Iiz-t6mSo^$5;_wLwox$7+NJ!SiMmR5ez@**$(6&WAX zUj=>s?>*mhPs6#R_OCy$_ny^Z7yoT*b%^Kk`6n%%&rO#OX;wb(!NK&s>uLG)?3uq~ za!gO!O8522TA#Z1{%_XJO-tt-e&3z0y7Tz4wsUi>Pn*5C3rd+D1_3jUWvOO5uW`|F zxxMw!=a#&gR}b37hgQhT@J+Ap~i#+P_6*MGn6w{#PG&ZUOa7r7fI@?Tt- zuPvX}-J{mz;5gAyx+h7uQj|=_xtUq2{UKD%DHr-;r^uv zlMBKOA7;<6h&Ea~|1@J)z?L8Uo936lN)_GtVdfj@1$Umv*ZVc?xHq>daH{#6=%cq< zOlIinzwN2Ks1~(xmg@Da0+z=+A8D-eh-Cx^l>8tr$m{_<4w^fJClv?z-_tt06Ka;AQwPgd=CG32;xM+(~siTON zJ=?07pTECfUFy=J%Ad8A?fPHGKYN}$ZH-u{VQca2(v8j)mR9$k{B+(c`>t=f(T}B` zVbAs4^*>DJPfS>;`SXIeo0qVO!w-R5LRXx7izUvmK3u`jL&r0(!Xsj=km z_~O{`dGm%JceQioWlu^wJ1f*!vC6l9<$>lVPr(I+@9*xu-pkt`X!Vf$RlD!3keWXq zkAG59NO&gEaR2;~o0B~MUNH%n@W3to&g)aN?ma%HuJ)3{dC3?5Q#)r=h@Jgb#8I{X z!#anbtZCBIc6|Yt+h0X@tUkz;9CJfpfr#lPu6A(^S@x|)#!<^#zp{6A$QLY?md#Xn zIY~F*<%?CHKQ9cswTDY`%C;2Nw+*YRYCPuWPg%lubBSTfDscxppJirG4`1+_9DZ8b zG;4~;5gvi#*CJXDF?ApN^73-3ys)&yg3d$xFLK=D7S|I|y^^!~6dp0czw`(~QAuVUP zaH^VyPQrx+j*I>(_Q+a?J=$>8Vo8cZ#?1%n4F^}XbRD`e`{w5K?gM{+yCol!=Q?ZL`6GHyhqnRDTg{k7Sw{KSSM6^T^Ytl_#5ZN)M==eY&ase_dq! zN&eZtJ|>+@o-<{wQ7-oy&yOC;Q~w|37S*eo_-NI;>hMXYEBP)rma}t}Y`ZV(#G&{k zidk$%#hVu{Ybv{OW@39R_5m6TTX?93GA=F`OGr*=H3JSMTc5AHBYA& z&)t@L+e_bWo9}G1sYXc${=T}pTKiOA(YJSZqeUblu1$y+E_-)JGxN}spAXyR#n#Fg zzg3-Yd}GN*#?(W<`~$uvD0;I!PIzZ|5PfPeI@+c|7`^NT1v;5vP6`k5Y>9I$q()m3)6xDNUb0 z%Mv-N_zeGAJ-T1@TGw37@T&Ez^EUP`518iOgTxiPoWu{icQ-aFFI^L~WNL?@A9LyE z$;yi-+{&K0X!^bW7x`Z$MJJmSY_B%D0I)Ny`XgFp33*V-oL7xw+4OFmA&+v(n>-oic! zGu}zso@Vgk?%S(ZoM^zL)ABFH;IsI%F!%SS+Zec4R8Igq_oADy*pQT@^zRiTV zp3nzxx8I+Zs|70EixNID+GmT_9d>T#Te*B*l~!thv+rMt50erfarP|cGA?Y@z4+Ps z?#!=jD<}Echo+w0zxLC)HPeb`xauzq$b7o|t8kGVFg#|S?cm~f-$mqU)fMLUvQ&0KsuXr}k-6NOLm2+M4?{9B| z)&1s7DD+zy^Yc%}^>uUC=(0}VV}7sVaIY%J#Fe#0MMWVdQljRD!hhN{uZTaWVsGUL z)3ndIbVV(#qvLyd!|5kaoxFuXtpX0kB~o86UJ3~g$OJn_v1I~-lfR`Ov>$a{CUqq%zAZZ(UOFbTx4t~k$ovZ0GYXJK^o> z>rcN_W*i!LzC0vxzQ4*8^S`^Q)foHqxBZd^^_0w)O?vb?{@O#)?;W77#a2eK+sB#y zafU$Pz6S2_jlE3I(rl{&f0 zcE!!5jU0V}*wMyrZjkLx7l#@bNSA}YuW?oW>Y}OL?y8US!cK>s7_5SDmw07f}UQdRlp8FrIdN^NAP3aZq%X`62p#E%7^!+u_b7Q_P zxa@HE-IJ3aPgd;w6}o4~xiyogXeOoxCYSC?nzqa^`B+Ey#g~_t-<|Qwfx}%bf6qrZ zb2Vc%zd0Ive+hi}*x(r8bnxQi{EBZk)0ckonf3Pec5kVW5Q&^mIl*PpIqRB@lc589KRwpA{7v@v6&^1g1FJsu+p8-^ zCNlBz|BW?mUAb@4+HI`@P8>UJ zdfj2cv+mV=_BE~glCib!ZsGXT)SU_J5C#{uyvd==IjvP;xn0T?{8?wDX_13 z=@u={UE=GSzc@mBjExB$MGI^+t~arAyDV-i$ZOcfd-T$j+M`{fLiR~VIBqiSH&J%) zo1*I=o4$G8dN-?*7Yb={8a69Ae&;OU>+b5h;%k2Q$*~sod8=n1IrRO`;#CK7&+mAn z$g=YxsQ0u)^VL@8sU>xBM?YKbNIxX_rS70tyFos@XS5tNhhF=%+E4lG`irlxEX(lT zYIP~bRwDDoyo;Y^2#5Y(@mQee39IW1bF05hYc(X(j_nJ&-#<5af2Zhb*^k|?o>pvh zDt|v$%Y32&%i=jTJLNw$H~c;@DfPgit>_)2x=k*%6I@qo`kMV~jNMf-@$E9-*(uU4 z%l+o2+}>APeRCPpRF>85j*gADc9pKqn33?{Kx2@ns;X*HPD4@Rrw6Jp;?!*GSnM}F zmCh`dwUMd*_GV)3mvzn&$D0`+-kne*8!=_lM$cr9Rp|-Q=fjmX)Q>6uoOp6`6VENB|FpOgkOhd-e&rqV{vKk zY2D~;Q;LtBx41bg(|HCsm8|r4NR(PVbqViAp^XB$*SXFMTk&;qH_X0!s$$}+MN@m< zzp5}$y%Z7Kar_GRNA4n?Z^uvH*~{Xq9kQn20r4Fg2K&fGb|)4|`m!`OIVo&8_@m4z z+lSS}_e;QGTdsBf7YulpZQob@{oSGz1`Ub2sp2K;4Jy}V?$N7w(Hp-dBe1nV@=F4b ztX0Ui=xsR@bG6!dB$aNToo#;l+JtrUJ_g*nR$R-NY8J*H@r_}gpm4ajZbm?#m(|}8 zren&ECk>B_N83;ISaQ%p{mis<|+OPNoJ z>X_Gt^o1?a>u`E{++nJA{r`QtrgUt0;<)z8gI%m2KTn!2p47VN%O3~%>dZGTS7c=K zKmA#FkFWfAu;iahD&A3$L}VN^D`De-8bbNbv_w})`qqEg^)?K=4 z{L0+N_By-WzaPn!hJpqMnGbwUd6boWAtB;Z|9}6hDd*P;B{-h4{Tp?(P)*p%<+1)>lS0^*B=w? z?rV6;Zt_h2Fj@80E*+_;%ui9CZoN{fP1_~6^sc?gcev?m!7~5(dcP$K@*C8;K3ce4 zh}}`JFhpZn`8Lt2y_Ifto2NeaIe0kfQ=g>cf-?^o8CO=_oEZP*&U~xVQ{`-eli9+S zERcMkwQ1gKDSLZt;>CB*&$EqYxpO_f{;qBG)R6C|RL+~-n5Hc%@WODz*@jJr zSzf+Pb(cxwajbjj*Ul+FX`|<4AG23Kvy%V+`@3mh&^Ckp=@%?k-eMDWm{ObD(j^wQ zE3NHm(Y&7}SFV7A{t9ct!OW*FM~j~DZg^L5@W%3eTJCGyH<&qEeq!ntz2uO1YuSIf z$xbXQMNZb|H<)r8Tep;^OZ0->qd#&c1NE z8`cSF`906JdR6T1GS7;aOQ)}Dd3?No^}%NL?ywnmn)@x*2(px$zHp&oW4KI#qNtW_j-I zZI?FpJnb$Qt8@b&97tWVwCU7o+u&hfNco$rmE#mh@)HhktTYr_Zd*6=_mlSB3s^g)*1NsTO8@VcMQzz7 z-A_W6$cknz`1B=rqP_)Z(kG4`%>R~$U8`H(oC}Ib!7wq`Sm$epHd$|VH1!sa*=CR- zw|6fC>w%5er)u-KCA2uq_c`7to9e6hMI)JentptotHrBpYom`U)b(6FcqYG&`C{|C zROcC+F#29s#KfM;S?|s*V?Pt2*yDMqMeyE)GxNW0Z79Dp>EILgceNWbe#Sm- z5f*3V{dx4~krc6`?{2b~WjajI3A~;fxZHEH+REJYmYC!NOZm80J1RC=O8k~!PUBt^ zm^LwH&l|_&)1an#QE8)Jcu7F@%J=hxBAPk1q8xut@9Yq{vE-zf#H1t>>HHlVK@IzzVkb_X z47`{T67$*TgS1#ce8X#55v7Qwe>Qkd);&|eoiBS>c9Yox*j!3b@2<4WU6V-eHw3jG z{IYaLl(D^iyjVWBHLKONiB{$B=IDsL-tHEW5>Ot(f@hU$wvpxOJ46m;B^{z~cEdoWR&8=_E^X_OsW?D|NMcg`i zB{#Q}O?p-2tPLBU?PG*RtdEb5zPstQ;HBc@3-fx@WW#2+37FUYskpk> zo&V~JK;=kv_Rlw~#aUhD($36S`0MNI=d+JZn`xZB>hA9H-_LGtd-IYbxczV&@8qJn zHkCnZBR9LPjop3CHagP&@9*!^ANyQxC`t9dYRz=RxhVC3@s^~cT$R^?|LSeqZ+*b{ z$DhyVPm6WGSISkJvTgQCy{$6q7ag6y@IF@qkNT={paoHw0IRlQb8WvSIctP_9A@e*p*ZwZ^Z0_P}_MMk| zDzEu$X@J`k_NR_J`(&+`E%TeJ_0#0cOk;P@;=((d_GDIlef4zN2Y=>;w*{VkkJ_F$ zH|BUtzx}@(w$7V4r`!NfxXebKa5*#A+WhV2#D+;(2d~of`6>$W?MmNgZnIXIX|4LFSm$`Qe{MK?=uWK;lR(=! zGUl#-Q~v$=89DK1c2}0y<=OfBR=V}et-ZEB{{69#;~Y;STFqrIFZ2B@%D3ES<{~x! zd27PfMyX0!zT@TM=1%4A75?5dE%VZWq~aeR60a`zmlxF9%W{|dy5p|}Hn*qTWt;ul zq0`YaKyFI*!6w#4fzA(&9B#K=NIS)MnpwgysU@c|eimp-Kp^^T5?x6%)DUwZI!7ij)vulcqk#ihcrKjgj%3Cbn38tGMYIObI>m91CG z?5cVjpgMPc_{Tkb?`9UT?0paFa~i<96HBregT}56YR+da`Da=+_m)WR$}sm?tJ#%# z?;1zFD_!(TzEq~^XOOetuU^0X4%%L7d1-I7zy7^p`E_>0##@5>SH$&+pPv2`bD|~T z;jN`sKG(fwdKyQ+S6q|6T#UnVUZHzqz!uJI6ftmdb4ftI}60x4*r; zecDWPh02#ZP6Bs!m1ehgUp&yrymXS6m)DaU3=hvgIp}?1uTRwT($jgBzH7pl=5l|T`Jp!vRL{J=zHLj+&7gCV z`=6iQDDZP@V2glLhc4l1l0MipiIeN}r~fKu?w-CNXf7A~J8o{#cU%0l6Qmz4UDY2p zgKN&QP8Pk~M}1jq-#6MQ-!=GKUjH^ktNMT%?}y1(6+bga&;0K-;i8Lxh6alPVv?la z;8@gwxx7-#7@g_@11G+^w|BROBtz4l_@}IQwSDGgORYC*y7wMDOH%B}f9%3S=hWS) zr^RkAOFA(@ans#Bm78-KeQ)-@mQ&+(sJ&lyJ9pChcKN!9M<<^)r{9gRP3UB)Nb|k( zG~shjic{p~i>sDg_E$ZzF|6p!$D`N$U(9fE?k)72p?f~Njwb zELE${o61n+zdx*S&WsC+cMhEPxXD?r-&p*zrORDtZ{|#aE87;#HF(!M{c+l?b-kdm zwVa0CQaT35T6q`thjmG1wjSo5($^d{Q*!O)L%*0^kMQrf)Z1q=q2#34ijtD6KYs>j z=c?Oan@IUlyIb*f`^2BwvoCl}zV2m|bwxu^u4gJ!^kUgI*4O8K=G(=7d#IGwpvI+m zk7F9QxZV`5qa~-d8K+i!?)j6ZeST(B`_U(|#!U_i#_#M_>NuUyuR7&F*XpTtbBAa1 zyn=kG+nXLYezBV(U}XR6#p0;-vsX)A(X6X(F-||{F{hkorudg_H+I~)X(P?%!xH$s zBdnBh#pxf=ytBdN;H<~ItCOd$D{pgB-`it-b7nOtWJ%n`giFp&J{MRr#VHPkzDLG?%xI$*QH2W9p38b>H6HR0`Pl@7L>hH&-p$ z6|Ppf?clbUl?@ENjk#=7WYs*@yK)ptM7R7oq{BZ@Ju;~)%+Pw<9R8Dv9TsmcG9D!+q)J=Ve`A7b_H3^>V4! z!yg|XXMK2ZaFM9m_ZiB3ujd>%%3c4O)i|T%?XA$3os7(ED~`+8$862I>ZNu3%onzW z@*n%{ey#91JZJW7?J&vrGi00Z@6Gu*S!0h{OA|t}STSa6+07`JbvQ7x2+TxKlsdDk3)LH}gi5r}A^d&*q+QTTp(% zXYIdlxARYnE%@sYyKI%beafW4nn{#d&&AiH;92ih{X4dqWvd4a`jlA{mdD|A4R4C*&c8gzIaFB)d1plYjKTC^$ zD$Q)XFDWeY7|)6sv z8b04YORwF(dg{vjkmlyeu7amq7L*%A-`E1K+;}5u$}-mkT#GogrgVcyVwsKcZ&@7= z#1zPfFOibklMMYY&pjpRl*XSt$sn=CT;+3mt?mQa?I{L78>{7YqUx?&JUrs+=v-H5 zXE@V!W3^eMuatnw>h_60*R%LdR`U(IeU3vhTx3U6;zDzcy9WNr_C1%FyVcWbm@{ft zFbF$wD4MccSzbK(?C9&O!SV;Cd;%_Vo!al?vgCl8@3sRMTAuJFRk&R}cA+`zU1iIJ zU5p}2x8JMsetUbn|6Gegr(>2&i=KX79J{;h)n$KsQKXp<58nxsCof)fam9{jGao!} zi%0Fid|;ze2gYg5!ID)oUV%F>lffMrEo)E*X44z29hj8);E9i8Q|A=TnWpB{AsSHi z!LZ>FXq{W>w>OdBdt@U{@YR-S2jtww-Lb^ljQI&47?<0|)l zxmAIS-8Sj*KHL0V?@*`BnHh%8UteE8Z{Dj5+R(DQ{QbAHn{#ho7F@>H&MU3<)T;8+ zlBmsTy-~Z%)>d!ZRQK!qd;Q}xmoYk~`d>AEl=}12)2r*;*Xe%nSs$}A$c|-m+Sygt z7AJq!lm6hR6S>I+RKAJbKWaRibLrcEaj{vJj;`JI!4tXXd&bH zz1ExB{irygaAom>7Z=TJe!tlqR0&%~rbWauvgS{aWn_o!d0S$d-@a$>Q9KnPR0!aOS5Ja*xlWobHEt1!ZAwf$nW?2@AvZh-@N`V zaN?!rMPEQGR-85+Vv&-Y737<^YCX93^V^R@@yV;Syd^8M7B6}w|497GpBt5>0aI5u zc?e%Ue6le3ivP3o^XH4s+`nwaGLyYOpUuwt^5SCF-(O$zd`i!-ORfH}Ecw=!%zcM$ zOwkO!^5o=X@TMc><9i(w6Bp(zEPNDq!N^xCB8N}jZq2VRFBi)!U$xs8bxGM23DhNJ zTXHp~EqkLKwr0W)L1niSVn3EjS{5w&mk1 zR}0*GyrA4ta*3vvR*?DKlF8D-^D5T;OA*=kTrQL~uliUjzv9ET@U43drj-5ZM4v0U z^6!8bVy;9)L?op8{>Qqi`{hm?ilx)vTlQYr@3ua8_9ES@uRMysCM;Q4?RI*vU&!CZ zea;5)IvP&r+<*DB5azF4muJl{(@O$s+l?OiG+f!Nm{Y2lV zw%X9h&A(L`H2j|3u%4&+{50>XgA>zLSOiaJDl@(;I=@Hl+UiT&Sl`_&Sd#rZYFm!x z^AGDD?)|#?2y5c!eZo#0D`yj)Ke2?*pDdDSge(YKY9-2dM_f$nPUhUO17=6OL*>^R zh+6atbH>~{B6~IJK69npuEbyO|J_;{@~y#F>cRK5Eob<1QvYc?x(PcjSRhc5W3X@T z1*u?0Ut!w~1@9ZJrA>UAU-rI(^m4e`5WO63xvJUXx=|sDHmtT1@U>vGyA++NJsf-g5I(PTHB~-*T4@w(37$T_66`mu+YIp^59(X5ZPdamV$Cv4_RB zWL|dLHnZuwtgQFF?{fv5Jnp8Ls{Q1ffCBj>&2;yOgTE`4UMeY13udd@Y**Vg7H9L6p^mTQop87KDjln#& z@6P?EES5XII5K*?D>Ox33TFIkr=cXIcXJP^cXMdkU7IBzR8_PGu@qqC` zt#M(Z=XbsI=0A`B2bYuI?!U*3A;G&Tuo>+)XU+h-4b-gLt7?pL5a+jQ)e>fid~b}P^vpHHLqkz zBWN$t(#5OKoo|oIw0;ya|`YEd#i$gs`Lnul9Q>hf~`>9szK5*pOHHVU~-h=$Cr=x>wFetg#W)9kf2>0EMR zlgrO=Fgae{VYqY?hvNPhGwn)WO?l6wq{dtIV{fLB=I_1V@14H)I-7yxU4@w7nJ*6| zzNbCfv*0`Xx&$-3qjm3#j3kW1L7f@T6{{=mm-|d}=1^Qx$KWlz#`4|w7INT!8zR`Sh z`gt+NcVDl^m!A()y}H-vUzPscmLNWNt0b=Z?z6xhA#Zl%ig}A=!{Rkz%Rl7FBbQ&0*!Isxf8ST)RIk2 zKklA3JOA?3JJ!b&)O==4*c4-SJ@fXqwGoXQa&K)}Ib~z<^K)5WUtJYVb+@ek{!W*R zXG_kL>n(Gw%cGWV)ZP6MwD#+?c-lX2J@K7Y2aPNK{izf@l6cXF?eU{``+nzDmIa+X zl<`@wy7JRW^;F;G@YP?u$B&x%ugLf)H#K-hh4`KX?41~UP$#DFjN6jOr)TkniQoR` zpyT_~y*>uqi`jP0XYIdh(fKFE7T7x9ol$<1ZHND|Y+=xtwd8|?&EG1H_sMGRuf^Jv z$(sZ1$te4##9Y>YoTY9 zyOuoB?N!s_E99)|a=d@az{uVq)_K|iBN_1Y%biRE4Vh`24NMO#PMlTaP!@Q|44HoE z75p+QiF?C@RpArfUjxm)l>T^^9HzIw@fx;eU1GXDo(?|^v*U_x_Wl-P2?QOGo?3ebQX48nMurusiwMVaxt4k&>CSMs%b7j`YKYJKy&6n&mPnwoKT?DK2%$+`~~&ASuW zp=y(LICSrcp($gRb7XZX^8X&b`;%1$&#FSXNJwH_P1A zD(UxNZSoH1YiwIg1VK~50#2&}uj}}R3n7+fIlO>xe0dVpy931`Ihxt(DJPpo3Sn5ny|VNr4!?^m&J?U&M#mFpNz$VbMtIp z&)dk%{rchiyd4kOwwAmMD!m?CzBAou`Ifu)S0j2cRyS-XFWB?)`qiUh^{c(CZ5|&3 z^eXLhnRmP^`La*vg2Jh)JcCB4|&d(WbyGwT@)+Jn1pi8(;czusx zX9LZ`>~$e`7DmEnpMpx+%S*1de?AlC7E+u!{+^Y z`~Q1#HvU*wseUNvR^GB*SBmy@VOh)t-3Qa1n&-o`*ueD9WtW@R*l*+sIC-ez>%Oqu zpJ-sTf1_2bq4(t(hRH`#pMH6HInSq5VTJN!^LrJGr6)@YtNTrfOXB!ZabUrP1rJ_0 zlF@;&M(V&w>hHLJYO40qcOL|tc4Xe%w6xl+I#pdodO>sY?QOZA7`=`z@ti#6u|?L& zNve51p@+5v226^p{TiCN;Qjvpb>?O^sqwqrUA_3TmOeL}TK~tA^CXvWMn#5~Y3s^; zpWf!z&wk6M*mA|FSlKsJS~T>(!2Si#j-OlIyedZ2W!4HgzrNXv>Wu3D{aJJ3kEttU zV@$|m_x`Zg*VmUPuS#YqT61W2U*_dyKe_CKm-z%vR`(BE9ky1eSLQogYisMK)?=O3 zj;BpBFLYPJre$XBL7SFY;0&LZIpa`i=y1C&Df3j{yix;{rCqfP*k$*=+n~4bTw(nB zNaN^K^(AI6E-Y-e*&aj!0zVn@0ZQaTXk*S4WaW_=WjUleZ%yQ4-DE<3U4}d z6dl%n`$O0>_)2!W*2_P8x}KL7oNrNHVe7)qb!boJXBA$iZ-+nKzW;9;C^T9gU|s7~ zv>vk7>&$cx_%4`2<6+3(@uu?d;5l7J#<9Xs9MnX5*52#nUDEN`!xvpcd~s9wja?2ShB{%zvZ z(&jW@m3&{nY3hBpZPG0QN&F(qHb!~g63`apzF4;QgU!On3wAKE zyqlX`x2=>gO6kZk&%dV=-SE-x_xJbW;c8a|nO@0+sXM*m%HZy~%6yHxA^qx+)`i>d zeczO}IMj3be6I<~YoE+`+`V@G1;b8$$zzs@+iYe_etxj8y`7TFehgME@d^fY; z%q&yw4R3Sr?AZA0Vt<{AZDsNEbE5Sh%yMt-Nas{+NtvDKH!p08*H!yWRt_=4Maj zgwET$%hyXsi{{S?5nIv9E$$UNn^Qrq^2tQ2#CIwzpe-$zCMYF5Q*8Kt9~@ZaJ<(a4 zo`-QNfO{{UoU(^_eR`+NE%+{EG2L%Ph04X-GdLY{^=}0}zW3t!GY<|$mFferykZiM z54Bxz*LLmQa_Awe*KxCjM{(#>7UV^<1yh}EuFe`;1t-6ky8%HtKi zsj18%)Yfjvd2_Fz_MA%+fA;xK_zE8V&scggTUF9DYl? zGS1RC_CVqwXOq2_$Ys{s=cYVF>$TOyp-%a(Lm%BkXTuVVADPcBaYCGqp+tz&+XC#{b9Lg0G zwo^%-%%;LU!@|v0C`i6>x8|~Q{;q28cUS&5@LQ#4a)tr1QcS1)X%TqT|0XNyewC(Z zxiwhUh}G_#^#2}1w!kvo{Kf0VUCKkh)?}+Mnsn7>wn^K9{9uWh`?qyId;0Y6jl1Fo z+t~JQo^eg8dF!dur(Jivzqxt&t*zP9S=P*Z&wIRIKD?QoUu>&e$E!kDhXq-tUDM`p=&1j4(LHt6LvV+s;`kehKkrg!KQBrB z-c;xztmfkpsW+t$4?|b<=ijV&*xs_T0KcE%MKYScPxh}l%B&@qsq`_`mwk12ZoR^^Wq%fb zUbXY>O@S+JGgn@{U-vurCexPnE4(k~-8fLzEa0ToXwJOPbi(>c`+XN+TP^nX3v?Gs zP(8Q7x|iY~l}r^TEpJx~}D zr3VAE@+bYir&_yrVU*_YX?$->;(lei@?L)-c5Ojq zn!kSRw@um<_A2z8;Sad}qb&L6+*=D~E#+KSI3t(W$;^SV;7#dF?q)#;g$wG{eaCMQ#%}!PCvOhluIapqr|Q1E2)wy$*YvZc({!WNHisqh z2%YMW{5-o@@@^8(0);*7>@u=Asyl-|r{a#9a&JK-?6Z+@W)i!ICombo7 z$=sCk)xZe3+j1rGK~Q_^2Za@fYx@5hc=4}MnXy%PLE$%PeGd-BmKzOzHYI-5A3qnY z&`;o1Tam%~G+s?9C}FZ%bb?1$;2NF}^H%X*{{@Ux01DU+EUR*(BC;1*{+A!LV?-j5=r; znqtd@Y)(XfhGD82q^HBd*^oSoL-DJa&-<>G?w>z@PA%NMA-FHuc46`Jb0?X0)MY!R z&Ytt$LaWYaiGW9=#@72`}+2F zxLE(49fgb6oSDyKrxNj)g}ZF#(LIOs9T0MAB;u;L?TI$f zG=PZOc|V`Sm`pPrf;R32dS@nasSQ?hMR>FaAJOMQO) zc-(*2xaEqhn(wS7GRFhGr|A?XuM%3;+XPuE_WbDI@AuyyYf~@k4r|-zp<$bv{YYq% zs9M3x=f7em9SXU;z;2oE>}PTl6+YNFY;@akNA%hLzi;!?pUwvDp*$7rn_2bwtoc&) ze8@aXk3(N*#pOnR(-@fxmuH_rnn`)W+*;jy=#bORMd^Iso$@z%HplG(ovde9`>W%^ z6V(@SJNPcKKfSr^joFPDb|%?Vjh`08aEs{(q_S~MXB0C7O{BbtWC!of|KM$0^u*&P z(-ror3m0GWy%u^U^WE39jrnWy>lqU=*IO%^dbab)KKc2z+V1d{K!)>H_x8`u+qE)k zcT)FdpOrVBuWZeUSlDw~&S`>l+&LHXx0gCj{oUtRKDSy)CBy!r+P+Vxw6iWQa!tCp zUh~hbI2md4?c#A2j#l|*IX4uJe|~nhI9Rno^yD08^_km0^UO`@aNo^!##GRUT}s4R zruskMf;S!cmzVX*RDVvfdK?gLdJuGY!kU_3(1=&%s@k2oY|e+ba_3-drho9jIXoa> z!M41+Q4gco#5p+(AUjD4lA$|ET9oZKnF`!_X7$74{msqp-#%_^|G=)wg{61l)GVkq z*>Sx_ivY{|tKAcJv0(Hu_?GVux@axKS#-x{WB&u$^CABdW2Ww%!_oNSwzZW)?9cim zp-c7$?M(hFx+dho-!`R&G7%>0HRmnn+12ivt@QiH*1a1-9^YFLT_L)l){&k6R%OKA z-btx<-t|2FySU$OQlDJOCx#Li#eaLh-`Dx6ylh@2!1)NSyRljrn2CKYV4NI=UV~!6CeOT>g6-Xau z7t-U|pd|_KPK1A9ahl-2)Sz)eb?eDu-^ZbD7jAdW3%G3kD%{L~V0YpUhsuE+hKYNG zU;W;8J8yDLy~Cv!`k4>Q3!n|FV|l zS#uk|uH6erkD>WFQjdZ4#155Tjs5m&y$<|u@9nL=dt+6_WtHpvIsu?%PrJ;79c(_G zP!|2!Fo|`$?T%lGzsu{tmtVak5Hwk0bxS;lbMD^v65B(pOw|~tq3pvqz;$6AxhwJM9ct%Hu-LMwM%aI#BY9`zv;oD_xm-&IB~mI{Si`u!vYgoMS|*0A!wH*{ik2 z@~=yNa2g9d`)Js%|Le`o&8`Ve)$UqBceN+m+)jvI7xLh6-$@-F4eOZCd*lm_B<2Wy z;@<8W(Kuzbl_ zj|Oh6wX#>--11H6X&V#kQqIz^bKln-w~aQA$*bm=?zH|>+k@>98-Ml&Gm6{xBs)p{ zS$9v;;p1&*%YRSx>mwg7+Ma*k?t4zwpDD$lqv6D0a~pRoFIa>2lkC{OXHQHEXg^6; zyaiL(`R{FykN0qv%>|$*r0n!KwCNkJI<^L|M5Zb z-$i%%shMrX#l=Up9?Nb{{;+#`?!3!W?{Xje?K8tbF?DCoy35;gqhAUh-Q_uXf30|GLMCkxn)7?oaI`0{iUtebHWPsJip*%_hHo ze!CwIw>Yl|ueoa47=OEa8~fv_d3(Qx6?z|g&)}*SDx3UAbRToZ;xp!oV%?!9jr$CZ zHTDRu+yAfXr|(3*RFj&IN5x+?vdamqPI`Q-H#L%t`Cae+^CwSEY%p9f!(vhF?y{nu zn#%X(=WV}tgmT^2+%oY;v`<)~_Y83JVwbP!QqFY+0?*FocmyOV_?To|aM=6xTC~t~ z9y`Au6^;k|#1cMSYzg3ZRQ)Mqm&0%OW5FNYKc_x!Y&z7E8O8hK!K1^?#q~c9%OBaQ z1U^_~s?RE$c1>%$rn>K&cJkSM3P>_w<2>bQA|?9pT=cj1wePEyS{!a1xL?hDX_+_k zb+Lw-ixpZNE*#iat`~3poQjArA;QX)@4(wzAaN+vW}rq_`GII z)Q%--pY};FFi2cgzoUKTcP-(jDa-D73P%126H)8E?4s>fCBLvH`A`qb(vvS=9Lr|a zt_#riS$`}=s52)Zm`nIWcJ)QcmT%H;g%9L6lo{@{{k#7EFa4Qc*e6>g$Ov6>jC8bq z8M`Q^A4VRGS`B=zQ64Hmev;vhw}SUWiDj4mfa{Z zW7J64!7n0vhx;C1;*@3T^D30Se4hfE!9D_-*pU6`s@Nh>onU$(qJ2sL3>o3JQ%QV9#?NtXt@$?z+e!4UkO{wd#k}wDC|t~< z)8)JA`11L6QGwgH?v3#<^%lNnEoq)NXU(y`qskLOCz9??*KSVNc0amw61^HtF4k6KZGW_HE=@Okv<+?ud+N0T?@YrNa#I}JH| z4qf@+FPs`3?{U3P(CNfhr)8fCBJxU$t~UEIm+qddeq88m=D^$Iw?Jx}GyzKT+vi|GU@THCWz=w2wm{R*zPE^x& z#ngGnL;ihyuA-vtv*6)E&8s5Ies%d;yKyM0>^{?L5}F$l^4IxK(=2)G^?a*}LIpAx zRasc(b^SW|=jZ3mN}sQbT%EGa{C-VvubgevTJV`&eS# zUR_?^&*2-_^se=_LT$i2$fg;Ej0G|p{HL}i9ApX%T2LUlAlG@LNvivKE0i503+yg5 z_r;Y*Ob>XzBY#t*@H(#!cLzxYm%48^)3+|#D=gH;Cu?=$^Xqg`uFGzWd#o$|=k0pA zEbDET?#GzFoZ2<~)twyt8=NGk+qNi}@43`xKheZ>Z`lgiAznTfO}Cs&7ATyVZSH?b zA;Dh9?5%`i%LK1|&rO28?#^0v?s(Ul%m+zX0w;k~z`p3JUo-ST~%2$QspoV(8;KHW~Z+$ZS7;mOdNxh8Cq*%7ZJ zPFrHNZTw$YXy`HIm1#eAUDX|Cwr}<0gHJq?OBLmU^^fR>l$?CqKYeF4r`FY#ByLrg z{iL}8yfMO;rLpPJ;f&^wZ(FX-;Ntl3;$w>{%e$Nl2D||UU0q!v-{0MR`k3cK;Wp8Vp%!cBKKrFQ288>XBPxS4lvPvka+$8*2EzpuZ}XN5A$ z70We#3`~<>YMOx#+*FGQ*Lj`d_2$Be;Ft>#;oQk{@gF7D{9)8LU+u5FI!~2f?wJ?kN zjxUZ9pEf`HaaS9D7?-isbQjRZ_6?>wh1K8Rl_sx#Q{t_U!G3^i+7Z9lMs%p4jM`{OilhP5quCJQrLH*^YDg3H%9MI79D7*_vMS^C$AR zeVU_L5#e*sP?i43FC$iTnEm?LD*R)2@!_~zut9N-^Q9ZORxkbQfO2n*$vme{8ZuGit?YwYu+%efV zPfjioP=9Rr^>akp+_2J@+LL)_?pNc4ZGIPB7ngjzZ>mL3um2)(4qY=)PvzUk#$(Wf zxxQc=%*CX{woNjiETVkR-p7y?S$D(?A37VEWjbtX=uEzntYr{d6?^ForzhLHWizbo zMQ84L|LpH!i>P;60YU|l{{SoHKn^8>pc zvnJ1fFpd4S;2Ph6zC8z$T1^6GYK-x-riDWT>ZTI@W;o;uRb`~ z?5geaPS8!}nqbpYCioT;yG_h&JOTao|8`_uT{ShNZ{}AnCIQH4Txk~O=HXSw!I6%z zDGsLpe>sKKmgr1BTY76-uD0!?8)mt;LQdV-S)9JL>g%hnO^fI3t^O{?$`)nzDf`g5 zxz=khFW^&jm{o22<$|-(i|XTjvR99F9Vq3uJvQT~{?3?|D`FLX=jYq&%Q9}*pw?{Y zU4FlI`<~+`ji21y_df1|SmNFts4KKoj_HZ2-Lux8A)<6`!}>1M7m8gIgm*8P^J)DP zi=bI2>La_^Lu@X&JVIWc^*prr_GZNuy+*;S_k-3Sv1Y^vPG5F+!@*^;$}JPJSu2bm zL^J=?3YCsnf^y(i>Qc~wTV=m`x;&+J+_qi0pH<@em+p#*QUWJ~4~NonV|<)|+cWkR z*)_X2zVXWqID3iZy2Y7oY>Ltg7%aZ+I@5ZPLqXuhUHa|q?d5)REEFHl3EKJT=f!og){(P$^eXfBd~B2U z1|28j=I7Vf<+k|Itn{Y&vny*E*Odo!BuxEuSj~5v!Mor0tlF$oavcz-Yn7h8RHBM> zGI7hiqB)?kpcVnAb*k9=N`0A1pbpYr?PYRj&!3-u^z9)(%f79b&&{<~=YH^OE$7tS zqJ587{4iX4%0A&T$W2$oGeBCr7ko*uVtE+(Q0+a_RU|>c8WG#V=p?r#;7QoI_8}mYnGD=iscE>APnXS{3}wy}d1zWeq2& zzZbIEn5xFuQJWP zwqk4c^V|5r4}Q!3ccenZuM&+cE6b~QWl>o`_wKah%{fntM#2M#xn8&7^&&XDpNI_$qTy7EX@v`J$~Sm)zV}yL$=e?ceE>(`RqGolq=0 zMewI%Wx%@R<9#bv1~2!r<1K!6CQvtOi@9|^M=o(dm& zxU2N_6Pr)Wi7FcQEmuU*r!)>#e*o=%Pd`YFDGk?(xczl|rGsUaCnSsBD|z6|E6)Dq zi<&Xgp;|JI92zt=H76!Bw@6QB*LVDG^iomg z=ft&*56rF~bgw!;&vsLPzio@XhOPh`@5$?pu9`DTGWkGr1gbkUC3~E{zT5r&l-P|Y z?Gtt}-AgZF`60Nh*XTiI{=Gdrt5e!9$F6)K+s9&=H}8h~hrV>J{|4($#rd^3e0eJ? zJ7L`)`$t-r;#c0;_>U`0Yk`P^po2A6qik5RjQ@PQyS@`^|J=IQZ~rgiUGJ{eaK<%y zFCI8r^Y=3@PLbSho&56Oi^cs%LO(rhm!J0e#S6zj)$jMJAHQLKuw+}JkieGc?RSd0 zWuzahZ1{Fwg`lhwMd z8@=s>nW%Fc&&0RS&dyHl?%%diPBL%*-!egkGd{^PpS4fOd9;;b30LBZwcl;4b}ufH z^JZaCJudUSXI)O*#e>3|*s853Janrr`xg)NYzr7Wj7;2uY#3bUmLqj>=G&NVLMWCp3!o*80{5(}J zat^ki<)3qS!M(`MX)7;X3i|nD);;$GyNn+f9yRk#x8rcW$G=p3e|78GV&6+^A~$F0 zIUG~H$Y1lvFq5S!hTk@{_a2Hp4K^{Q_;bs_q-+tt`p% zVbZuCS2^cm3*YlQYrX%>5&QD_E;IMOKcBnbtjMr8g9H`=ZdcsHK>*0oe*<-dd`F|a1<$k)j{bf@w`^zfm z?sBGwhn~o8W_ZM$A0rg}wNH6}@$++`%l+nlnrnJtN#j<%jI@K*@Arm_m?d{I#U&nB zvv0Y=a!l)?_?`nr6Hmp8rrdHns1*1-<3XOTqczubvn^8t#1al3w@{Q>EaxzzYMt+b zfSG0YSR$6?+^$TB`O?GG68YNT-~`6~w#%lw+uX4heC3rc1zIESo3WIWi`!wn!Wt(I zNCzPJ>$REvrB;7hB)@n)O6H6Ia&vQf_W_pgeIbbb6s>wD#4V0l9`Z%Z{ph!se_GB6-SVCy z%_TBL?aI~ieV=8&=bSCr`BpK8Yws!Prb0!(V}FjVZ0MFdW~j(p7annq=f~THGXFnK z-@m5d;_dDE^Q+BF>rPoJwoJ(G&VU|!^-ku++@o8rN!*E!i_<$)a(g?*(R#bf>$!BwRV&ghb&pl7bO_9)M``2 z@t2Q(Ty&ZB;(Do#j(5lV*DLO@E1$Y}e0tqd(`72#ZKE$7{Bn?8-ov`Fs%h&kHPDcG zUGknwYq#Ii$~*8wcEO1ytCD;6A1Y^f+y7JTNYX~F%43FVn;OjzeZ3y9zhCpx!k#w$ zeLtLRKOA6A3eCB*W24ULgadm&ez&x=6f$5F5q1Y1htxMw|NY;0JDqR?}l3Yi1|FAa5&E9sBH1f?c1$i^(r1prE@j&>b{?94i7!HeR}Y{@wA-5 zTbU`b`O)n~i-bAfuDfo^|KR<_cZY;ps}o|bX0P9?cFlFm^?R!F499E^aT3${sOI(9m-YehzImEA~}DSobs_-|KOz z(CLIJ=e2Sv`?@`^?(g3(Gaqr>Qdl2&a)SqC<#m<({eQPv)&AOYYgg&&U#H{$Eqb^6 zyH-p(REdG{JMlbHSM_ zDr=*+hxyz8-SP_*ZTIT`*P0eSa`_I9J&#bZB{JP&xG2D5*{R0&0=@o!p7i~@ z;8gR;z6k>6_iHw5oHFHHw?FsxwyqCfF8l9J2c6Kg%h}Xh*lyC}W4*z(zrTfof?C=< zF9dQFH_JwP*7k*Tq7%?eG15uR816nn*3V^;ugvKmU0!H)2yt zCurOVba~>FnP*v$R+UYhzv*mMwOg6?iCVAamTS(hT|X;b!#7}wo}GAcvWHQ|;)KXQ z$ISNyzBNBDRw2j6C!_H@W3^Fkd86-aPf)koX=~m!(6Tb_vyf$F4-U8RzeHMAc3n!| zL_0`=!?{VhkWjozf zIBfV|?BKfm@Y(S@JB!!eat584t%EsV^poN5$Nu^$xdH+*4~iz9I+}cHie}cQCnvM6 zt_uBh@Cam2{zc}_>V?N|Epg<3^6+qb=zP1{DUU@=wSAXK71Sx!F4ev_%e-Dg$;Qs9Hc1nd-goI0BdvOM= z!RDmMChesMIRxJC`@Jq_?~lJ?Pj0cj=NEZjq89~<{P$;Po0s>$DcGegy7%xzhU-?# zE0g38TF+bg=kN}#YD+<;i`&af9Cm&B-?7@GereJ1U)m8X8-f^I8B$%-IoC*oR+Clr z?%Y!P=0@PQleu?~%h&6Cce{J&6MMJ02*)3>4Syt6Pw%)Z>HXbZzEDh0V^#0yn&>2ZZa&OrlolTK{p>Wu+ zDcrDetJL)`+sv%mt6L#h&$t2MJHi*IRP5r0_@93qoIeKENzp4@}& z*Z9GE=Q$XcE?ufr;BdlV2EUHo9|400m)M`G?{vTT0I`_NH^A#T8+#(lWVZ#!B@X#& z?aBP}>bS<-7m0pqpt3=)C-jhh%Tlv>@;%X7bEPIP*kTv5Z|^6I#hkL|c1BPCVOe{z z=KFyr{r!J}L`1jf2Utvsz3Q;5>f9A6(JSkue2#P6V*X^j@`d)4bTa%KeGYuM%ZTtFnN#roY2=STT_zlPnsCSvwh5j2|Vow1ZtUg_|P zz{M^aMw7J@W@o-vw0rlWm8C$_aDlpLvT9Gm#)5}VKRJ?rB}FG5o}K>0Ji79n2#2-W z`x&u3OznlqUa7p!b%qCif>)2-oN4mnA^)t8d``Z*=7sOS-xL!IS{3(!$x&jRpIMmT z?dR-bomTG5<=LF4KpipPqeqV>r5?6WlzBXtCGpixv&N+-x2#q>U?j>mMgFPrx+^mb zMJ6{$T-cVh%;m`8y}}F4tk&;&tEeUnYDMWyhprs+`l6x?xhTan_;{7!A!n;UQ^dar zIqhi3{{vb&)?oZj?IfZ<*07)vymU;*A+C|BP@=lw&!MZF%yTAh^nA>E$a})la}T~p zPspD9_xJbHzr40BsjfTonNM-oxq`Vt7o%lFZ;Rji)clR}y@H5_hVP2i?`xi0hk2zf zi^==VJl$#iZCR!%3qod?nTQr#JZz!1G*$}4!P#v-&b37 zjz9XTjp)i-hi6HJyKyK!xurB~`tQ{zWR7ve)`tv1p+f8-+fSY z`?I*;ZWUM2ct^?Xkn_$roN z|75Sr&P3UDoPTb~Z<#K!p5?mv4#vmdo0KN5>NEVuwO{m>{O8?AA-%G+*DXgs{Cd59 zdKv4>0Jp>9@ihzODjqOyymh$h#~I`EJJN-`c>c+~E%Wp1>oGiZMDXpct&@|VoUYzq z{cflGEiW7I7f%i?F-_2|@|&NjPQSZD-*Lgg`_;{g8oovOYd5X{?_>YU^i&U262yKu=O|G7YUT2vD2qcj z_5m9{)Eu%sbAD6d&V(S2_bq~cw@+~@?+H25f9cef!liou-p}w+n#h|~B-0!J`}5oU z`sh05E$j@x3nYE=xEEY`_`Nao_@BHNZ#?Ja^6lqe7QntNwsF&I)~81{H=)hfpN#GtEw52V%7Tzqs&IRf;fy_X?3k0dzE$k@ZwB{gBMZ{+{d;9pke?FaF zxLoFVRO<6{b0@Pd%g$!o+nu|7rN_zo)pPXCcNw+_IC*?KXfBj}sq*K|^L58e^*5im z>~F7I)FHm+Eqr0u5iSke9}*6URs|0ltg64QsXoT1bEIqk?uTvCS1v8}PJQ+1^wKSN zeUOg(-T!W$XT#&}wg1AVmQ}dPhsNI7^5fU^QV*Rw;Pc?Qb?)`G?iJ*dvw3lBQqG5$ z%jbt($<5V|4?26P|p%nwB9vW@(m!jgq&0HT@}XT9 zzAvOC=UyuFy-ro{X)D6kMx9LBUXfaH6qKC$vr>!?fS&H|c+_!MY zYc5gUn~Oi~i+FZSrQs5bEA+My#+zB76JS&3`#gQP;m)fktJvqVy(&}p@AE%)`s(^e zN3(+Wg!H}EPE4&s+!q3CdpkHdq1+d8P2d)ES=ZuR(wB9yJw+_*3XfaR>wLh7kzc9h zYU%51J2izKNL)93__*J`?EHk)l6(AbZ_Ay`W-l6$$Dre~Qc7)(|93s<4|o3ly1suJ z+sA*u-vZdbZ1Qk=d}Qx})LAAkLT#aoxr*kjdS~YN{ZQ!yJB{;Z zsoM`evkI6Iz&C~cY1D>RNwcjUJ9|WK2BN_H=zfef< zteij0yGvdw-S(YrcG9fFZ?4tJZMDC@ZSrN+;iA{~n>|Ux!+`s%L)rA^7LSwEQJ!OFtC60c{LPjBPULVrM?IQt_wN1`XB8db~B6iQtyJ zMhK)Ozd&4C%C_nHE7x_oAO1Of^h^Kut$KQJX^iFQ2XwU(-6K=6{U5;3*lG`!0qcvl9SjD^xg#j@Pciw~@>kbiLn^O~hy!qtB zW3QZSmc@0%3moNmL)KER{rRdXbqU&^<2j9HaWJvA9!Q2R<;s}#qOrA_KgMrCqLfd5 z$@h16T~9IyJo&4o4{h5|75rkRAGKwLQw3L*0k)YHZpG6Pn|Z)TW@X-+dA{ei65Gqg z`|@|cH9L9zFu%Rd$0x6t-_P4vb>I`{2fmp*Hn8kHeUs@_py%@WUJXwYO~J&I%~2~4mU2IOuBxwpgv<5i z&qlj%Sg%q1vv2Bz;Drh|L1!KGOi}xymi(p%+v!=y*x;vU{rvd4@GIg@lC-oh#mPnw z4jx=0_+syU=nizL{l5(P75=#FbvW5nEgJK8!{6Qee_IExf$T;XQoYIk`09lM#e^pI z2eOyNrj@)eGCN`a&pvdr?RKvKt#yzif={;aP242>E{^H>B~HW&qi260Q#3Z1R{jSakWNTMI9$^ZT?<=-H)~^8er8 zN4qk;tx|8>yq8c94L`slkQy(&a8c{b-J)@RvmH1TpS)rtYIi!m8%!>2H|jYPf7|8r zr%!cn-f>GrGjj_q+j=Zv?Wa#gg6|M3w|=6m+*;@+b1mPI-}K)#?z9UpZf#x7;m-_R zxwYnQ(<#HvcN`x&qIDZ`G#Tr@Z#rANYg6^XiuuS(x5Nx^P3P!J|1?c}es1oyOdg5( zEUU%R?s=Uv7V&x_*mbG&dhBw!suzlqEA}NH@9R0XeZnu+c?Ul~Ki~a`w>!;9Lbw7v z-7`6h&1-h~&C`?p?KHEk_9Awn=xwWXa_#=UV$Pve=PeH`5!l#P8@k4kKd@!a4c4bq z>Q#?VyyQ}MD#~WTqZz#($ETahuF>1}w=rVYr~ZFCX5F60>zF%tX4K!c+wV=%b*OIJ z-hzlIa`~lY|sBU-zS;SQ?#lzJuz#G6I!@lz-OCpOB$Cr~OZ*Bx$6KR^t z65Vh#Y;RZi_Po1VY%7!3@TEQdB%L7(>y|9ur*nH$W+w!7oZ?iT()F}_by1GN$LUe^;o8CVLA-o^*Sc=V-`V_d z$DMD-+YG?9-Y!=BCvK_R)P^1FN;z}6WO-4`gqw$FsQ5@cBQxUQs6-Gh7V z-P7)y`dJhPUYHO+$KNL{@Xb8mgvo3ywc3YI{0V1*U3?<&h*7UJB1bBWZ^r439I+Bh z*~5f3d+13yYdrz2m{Pm2vg4eyeq8^uUmVvGn!3UQ{-r7@v6^LWVr^R@ar6|!`4zV$ zK8KvO?H6zgVszyUJB7J#{Tg>O(!TX@c1uf3EveqF?0KAZdGEin?zrcu*c5t-f40fZ zLpOAWcY?ONK=-X*M%%Z31$3~NiZN(CR|Mly_SgOniO3zFly-Q>=W1;E-KX!!`|g{|p2 z=m%XC%btPc}q%=6~Fy1jk9PTJ1G$8K9IJ|=z7QT?$Y@vNxMja0GET)P{@TR`2O z)pE8~;5!nn3}&3axBG=EzB>|S%yMSHZ<5b?du!{vo2P!g1kJ{!&r%DO&AY#EZ`8a? zSNV6A9W>rs^wjI?z3TTXBR8joM!TGQ7HjsPG6p);pi|bJZYI3Iz_r~{tIlKPje{GH+`QyX|=G1IfK}ukVD4jmRC>b4VYB-_jP=CQ7Pm~lg=;KPhAk_T)I5R zHwJVBu-@O8`QAY@vZVI--}~PEqxm_btntkkb54dygYM>335M)Xzxv|h;;X;DzP>x- zl|-Y=>JQ79o0wztf_7H;%rtWKoo5rdHfpPv-HaXP8ag^tbgQ@u^}j^SHhE!cgXp~c zL+`usgVKy(`zN=SI=AzE;`tfb`azC; z@9C2YzJY-gL6?}^neoBm?3AwBc~O#0qNgC&AwKmfxvbZ_;?DcmCD(VVJ2idd%Dtey ze$OH?-KdbKe@~g!<;wQ;&c4m9zemCAHPULX7Zpa&7rdzwjft2m*k8t#b9dL)N4u6C z=IT;>G+8uRoDj%%#>B0LT1gIv3EgO%4#aOxwxL-noBq| zOa7+RjJ#r54qosbhaB<(ZVO!;ElgG7*~ONoSJt|0N^Vi~_m9WrPwx%f{z9km zDr=Wc9e2dP9`FYBrN0;V+X-bW-s1Rh`~JVMM-TfrH{>@3b{KMU+*%9WpU%_pdK+Ya zdR`I73h9Ww&(F_~SLRCHcB=q1VKuYhQX{wBl^+_n6OJ8yaCk0P%@M;eCi$ElAqQ5a z80@`xcX#>v7c*O$CVC|@IezDw{CpN$s=shjSfVZW`Y|oqfWbLvw zpu288e8J1Q2u>wDytm%{;`_M7&oPrtvahX~wcyF~V<`;=O`ggZl6fAgiQY50_p5P} zN!gnS!K90~yFaRRe7;s3yGlODS^~5U-FsKLrF6obpqE!>uq^$$@YCC=$0dDV6r8)e z>&W?+-OqNji`D#mI(^ZK2Y(w6C0g0#e-Or=$bSU&u5$^%fkLE%*2XS~wH!#G$yvP5^$L zq0_{fHVn6Ys`)<_y6dYncy}_|u9VPDWa(+;7C+@Kp_jhx5WJ5dQ08+lk+tbS;u^b^ zYiv)vzq7OWDtMO%uSw>G!|#2YH!XZFe@O6=qKV^-yZh_+SN-{T+_iFb_E+aD_T;G_ zxZ@N!4u8FTOzY8iJ$2@8m${1@cW-4$ZS|JEwd~QOq{{0FSC1OrQx;e@FIzZbme1}i zCY|6!1HGzaQ>8XvTNr$-VH>lts*>Ehxg2lifbMOV=Ui*zn8^5IY0HOR25q(j*L-AO zALZbG`9b*HMQ^T$O#429&)z+^W46j<#Qp0{ko(sQ&VJ?+={S6Y`H6U#lFyF`KD$iZ zetOD@voWqOe0*$EeDGK2b#e<*l516?|J+M$uDqw{a$Ry=uvS&~;-ue)qU$c$J>1)f z*pTjZ^30Uex%Cf%UX<>3Hkh_D#YcJ~*Nzv7GQyW0l((jyE#X@D*1`HS+T;RgLHJ7F zMU|D6po`XbOh3@X63*+hCIGb9s}{7_%U_mpo%p?iKR*f!eI5N(?e@e8w118MAo%6i zPG%pE3!pve^VoPK6sGa7xy#gg;>@Eqe#1xdwO;~3_YF_Qyg7s~6Zz(lR(4!BhotgC zItO8yeKMAdK&zuX7uRQBT^DN&ImD`f!(2lCYyH3T|2M9ZFM8W#m=D^~VI#gWEn-XM z^u0DepG-E|4LZF_<9vqNlb*8c8D3|W2eJBa3BG)`!oyv5i_#LW(v*#Tmyds3+-Y)g z{p%lXdC)!Se#}et{(5o$@yx53ebqi>k!G23v%unLM{VK>wz|i9n;X%l8ccE<8NFoduR)LNf1i&HVP@hU^N?_zOz zF;O3BS+|#s0ep;dGYreX4dSniTD~n40uHPdvHcM+<4{C!!onu)8}k1?v`cM01YXpW zB8^z$<>2-jb{N!&txSmabpxUGUZD95uxS$*ES_2@E(tsE%MW=U0&CIz`v0|hMWBrm z6Ri9pm-N^vwp@__Ez7sGw0w29{C+5CNmtg54GX1=QaXxc+~3~XdfL|&Gzk*)9kdo` z(K6rJ%Z~NQuCDvGd48wUl*SbCtZ;*VazYRcv~id2x|z*3VB* zK}}xBsxObb&7gy;R>9YgPX?W?I_LI|!o|P-d_M15`D^z6pVHrREI=#Ae%@|sVhY^1 z!T{6_ShCJ}X{W%Vx*vz-yQXGdT@{+=6AB$#TP>y^rxSf)nXmNCwujO8e@%Ik?kHvih%=-t)e$Snm&o#c6z z@SQ&D&(poa@wJ*~fg4aifw(=Ro* zX1(9@*{}579m}tmy!BU}xBq{~HhA-sn&W{Vlf9kXo4jQvL#w9MnZS?EUPt-VX7!>k z_R7jM;<|kFdD14;tB%s3i`h+UeiWFi{eEC6VNtN)&EuP=w#;QV1NXy5E(dM1 zxm5fMyv^oEz@gL^NFA|mK1cpj+#<8`YaUBqI-CcZa&nzuo~dvG)D;UP+!aejbj6&u zqjkmJT$*qz`O*^4Q-v~HQ%(w*FViZ1e$MxIj>H0$y}w?qR=RSp{{P>+oC>Aw88}va zN!zV1dr;}%F6d-aSGj+(FSth*!y5v*l>X(*nElZGHUFVYy;2@9O*-{71$n6#BlP-} zi^^Tq3!h(F;@JNJy41^M#TTIx{TTL_&7X3D%cOJGF}Jcjb*Kzj$I30{p|2~V$E;&7 z;N^2Ov62Dh&@c{DiSv)vIyc-)3~o2e^mq%JLi%a@XN?mpD{JO_UVV?|=dZzgYrdBd z+*`wZ?xVIHSy!3;Cewg>&#VNJzP-7r{9B++LB_eWX+w_3K0Y1plQ)(e zydgH_|HF3qbpj>_`(&-Ps%GValJtJoW6D1hKkrs=e!dzq3FKG4bWyp5(32fRuIYNq zGKDANW`RxZuM%_DZy&RxL06OSG=C)~%fG?ExzWLzt-CvL(}Eug>yK^vbJ1PCbG6Dn zbB0vrkMH+>pC^-ipJj1Jf{?%#;q7;dyhV?SPpjDBCC&Q#!NKN7MePl{8d)p<{rOq6 zZrO)yaDR1mYr&;1ZWeY9PO}+>&H^eyj0=;Vo|>xUa9}Pk>W-Qq4GF}K8t|3#ZvGoz z+J4iPmar;Wk+VJLrqb;Y9!VpYZL`hur|BN}!Stozv7Fn5n0+;sq6d$u@$R(QBi0b| zuxLZ2nUNw|Kg{kCmOV9X+1{W%HSaB_*xSsn@GE@tmS<&W)wk^Ep4HM;MGGtr#0tGX z^LqV$y}7(;XKvW}lSbEiWKr|shH}Zvr#n1jO>LH+>L(@ zF|{=QKe@y^dCK7@g^2*A}?Vr%Q99v!(I92r)K^J%}|150eoxvYa zrV$V@DHk%%Py6=5~<`=DejsTOO#9M1Z4!H6^o-u)KzqU>`+m_UTzWSf1 zxvz7U zc+bJzpp(HYeN7)L-wC^HD;@V?T_R*3&b&^a6F;@fdzT&gqZ)c(iNM6XzOIrhj@nME zLfpk(Thz+f&+-udyQ{A~mGfh*?9|^0E|$N3Tw1^7*pw&R(z&ey?0!68uH+B!->~B< zOI>G#;VtAx2xz$MreH~JnoZyQr?S0RN&o$87lLuA2hOG zY38@nsOR2aU32E+#=md#>t8SP;91Zn)Km;w=XB(&3i82VB1-$KZ^p1Y35clm_%;*? z^}4yv{5nHR3R=&8X*<5yPb%-S zsu64Z9C=zqiNooY!3@Ut`+mO*{`vFgrgvdCG>>nLJHNB|dCJ;(w`MuMPr99{74+%8 z*7MW7d7$kkMPHlqzwy`qaQ?2rDCT}#s$Rv~=I@uwO5&9VceQ-1IJ?>y)^ta`BOcUI zn+TpcNeRp#=_oMJ1@T9pnCIQmVC-(2{N^KUwb*j-YO#c4z0&HduO=7z;=CO`vU4Bk zV#S8%zu)hVPZjjTw;dk5UhMkG$?8{UnPy+PwKeq8BFSUE#3N<0!b|qiz01pd zi-R>x>;L^(bH#WgGuNMOxwn@z`l4P&^6cE)+23v&I9#X)o%+QH-Lrx3)UUJFptYk{ z)bL$L5_CQFVwTtC+2|L=OP{n|*KQZrI5;MQjT5Q^%cRIuQy|rXqUGkP8(z*fbztr^qN0Ji}mb zDM!LX=eaFw(}O;jytv?4XKcSZ0QHu5qkp!Kckb%cBjSLsd*4B4s?9LZpSQ&EKwU)6 zJx3=F#Us-Q+@F|*_x{A6M)!$a5_UUQKIJ0#8j^(Rda)_HGcT*%WV^UBSpDX<{QGg+ zRywF2{KVu~fUzFz(0ttM!3uiVnGbDJ{Au;#qu((#-ecPq{NcC%v*FJ5h#8=RGoSff zG2Q?Qy9M_QrPJi{FLqLgA>L=O&w3S zOl&CGnI0$nIyCb(Q>|J}@{Ntj?l*sJ)zyvOHl_I3d&`?$xz1`|eKpjbxMqBk$L961SB>`>9?Uc?oZftP z^F+?nvu>*=`PolZI39J<@VI!i{la7+UQ?Va!N6CN`AtVd>3D#yOR+gKvY3-W$^c&NlMb=owq)GFVy*Voog{_S|6 zJ%Mw&ore8M+XHM!+w;@h9#49~E+^w7#przpcEE7OYxYx@Zfs0;Rdii%eXLLMT=e-B z_YJ=K9Qyq6xV*P>W5di+hI#qI(_uXd&`L0doy+4_Tlo3B5XgIwy5vpE?&$}1mN*?% z?!33fha)Tc!yl>dZuaKK4X&iv3OjM^{MAUcyW(H((vgbFY@9*!gm#hEtk?X0h^jDvz#s_`(<)GJ;l&PKLu8Piy zIhZq#mszc8MX}53T?|)34_z@m2Jn6HOzpgNr?^dgh`TS|l&5fpA#-QKpi4jdh&CH_p7u4RQx>u{=G&trO+FL{@PnxvmuMYMEJ`Mjm?yOY$u;)-~aRH=jW~h z?O&B7QTiRfEU4p=aCyr552m69Q!)3f2Tp~z$U5*#a}DguKo=?e_cAbPp8w?(zw>ue-6QxkbQhxb^xeFa5pp7w>O0)>6xSkoSGbzpx}aU9(u_7!fG~zAwHBa$kJ!;gTC0 zPBDlu9FLp6(?Eiem&x5|#$T@Nc?-nFqZ>H6_JCJ`J^9C=mo_8uJofwICF5HRg*5Eb z&U)+ZE?nLe)VcpVn{$Aa%>;&Lz7cu`)pGArkEJH?DkPV31fPHM>EL^wa8G;t*eib* zI=5d5_P3q7$^F=~n+D*syd>ON&2P+NnCw)c-qZ24{Ig8>FO%6nyw~nuJ$0r1N;Y=& zZl!6Qj_(t;K^lf00{E|tuXB7v?#i!|uS#RZN@tm5PP$d*#<6qJ0aG6Cy^b&2Dy4oW zPLYeSF`UOc!!Q>V&nqP_oa{W8slRg1hF8K7&74|Mjz6b&c8J_qa#E~drvJMgE#E_O zd2Clao9E84QraO9)H}SgulDzqPGR*UQ8P|WJw zKfFf0Y4IMlZd&Xxo#7`MT>I?#^V3O1iOU_}+hP7*TXD)v{I*~4#7Y0dEaUS^l0nH<)TGA`(G0C=6iLmua9gGzoQIVck!vbmVO^nlm3`?V<=dN$dnx>|+#1eMKSB&sX=xjtr)d`C&>5C)VuP#$G z>6N#S`}*RdbJ)G(yG&-Bzqgx1o!q&HW6St**Ez#>o1LdbsTilYs5kl|?m1aqH?t_18@;os=acU3s+|amixqGw3CY3W_L~EM{{& zgI=;o=`HcvhyVQieEHhLJ3EUn$F;}oeGqwLTkdR=A1fP*m=hGet{Ob7*;Rg%LG|VJ z{r|R_bbP;Gzh7E<-lp|;7b`7gcYS%%{@K}$7;o?P{$>muO2?xq4`SP?9;2MOJ-gA@@#hgs*8)=SATtdeYfFN!zPok zhrY+RWL~DyPM3@;xOcj2U3<8xsp-J$3E8vxWUV?R@0{0a`EKzd){iZ-tE=mZule04 z$6D0qt+tnJeZSK^wDI=5!kf+>J06DS>hmm>tyjwIs(KrsI(L5f$31-SW)`q)wXc|; zuyQu68?)&uXi@M^qnh)XOa7Tw&Ala(yE4pu)@pWT-n+(8X~m0P$(PDB{Y-Kc{MGBX z-$C0;Eidhj_Se5REWgf<*mz5D|BARiaqW`ydl>x~Hl3K=A?+`tG>tRUUe`XknlSm; zgYRp3v_HpluF~{1``3tlE@NtoTqei6=^l9S_F;TBs$^Mnb?3bUc`<#>|q;fOdsg*;{wIV|G7?JXo=$%AB%&SWSGt z95}R>T~Gaj0WV~K`l1vD4T-v`;*hh!Krac|x?;F|#rK}8%+vfE%1<0=ReceAPAs6$%j$0kla6~p(V34& zulcJ8Wg490L|#*NF8u$xQ?q%B&bKJXMql4im|WQGdR~&gTUBPBTD{fN%}cmyvf;C% z(-dCwo#U0ai(#8S9l2Yw@VHcyOw6wZe|~)3@eBEg;a@wkUGg4Kp7`p*d#fj}9zE{2 zpVr3BC~U*9CtpoZ=jFYpITPyt)UQ&j&YQX*BHY?<(j`wq1(c$W-|Tbt{o?JKZ?7j&lZ!V^C08bEten!d&@ z^PjKxTjE7d1KRcOb2Z^x*ll#w-p>$ZX;PCDm;jpZScJSO#oteO8DH}{=XH7tJ03^6 z7+h{oJ~O}bbJIQf9XYpti2UAr`ecGqhn4%5RoQ*%C6Pv?DBdE8>c;YS0bq2X073OfXn$%9cHK`t$%z7-4UoB5QF ziyw7;+02mS+&|cFM$}2ber)h-VfbC{%Qvoc-jTX=Hy`X0_rMvTOWc#0-tYUJSD7Dl z)*%!BCGHV_e;&c!vB1BitU#=brPnU%TR=)LkV@(M)$&sTDT0(NDPj znr7HAy=(Zta_ZLmFB4`T6Zo>0NBJ}J71I<>o$0~SA)8GO&Sh&plsJ`9gQ2haPEL~n zlOW5-6wtM~*^CEwHR?uy=6R2<2314NR}7D_Z7%xBy9Mi=h#84*1Yma}PFLu71iKS) z!%FAbuP3Y)w<({o;K2EOiSyhdub}t0v-~J|zlo7cY@T+{;wJ5Q-v&=bE%lxNt!eky zp4xcuNPO1c*tI@?JxwhvBDIt13(mJaF1jr*-E>QA-|`lLpzQ}AZT3!X-E>SSZEf>X zj?Ir}-h7|nIO~l`So>C^FXumU3vD+FQ%Rn;%zCPF%Ne<2Dv5_U@;#oN{}pw>I`3q{ zC+6>V5nKMOD?HTM-PHEyQB9IjchdJ9rr9P6FQO&gp6wU1@SSDinQN`wBEVm;@^I4S z4MhiH`dnWe(y-kyX@+ajMTw#_GKwt|R&$nUE&6<+`}*RQc1@-;79gJxyimn$qfr89 zKk|N(mv6V<5BvS?ZLqrEoC$?~D`S5C$+*65?yoPG{g0}??=`Rogn| zoZX#s;PZ}@lav0}SAS}7cir16hO>`91UTGVq@mQZ;zpMl*FLG#o{M22HiC=p z`jtC33$Z7E@Vsm{>w0eQMt8kC)%}b5uFFldWanj_bBy`R#oJl0TlA%P_pm=vTGa4} zhl3;Z_p|T&i$x=Mm!+lVty<7dbBllKqU3#+0+~};)^2qNdmumQ5pZK)FZq4;)XJ_AC z;yF3Ta_{f=`|p>&yK{4uqw8fJDH9DJ!TD`xn!jIg=3jcRty^4wo5{ZF?{~`&YZmD! z?_gAQS!#6NC;v^vH;zf5^Lx10t=si#RY6XfsB?3|i_=aQHrEH<+x4h{^LL%Rk2cqg zS<=TpJ~=u0u%hRoe+PPJF56*w8Rng)W#aKQ7ked*kLgBkQjxhl;rxWpV)u?sC|q#L zb)}Y^u)1G~u$oT+x47OGZne*#3$)grpX>DOsBHH9w%u*g=6NN}?EGarWB&d!^Zfj% z+GGBfGnv*hS?ZTy)v;dz=3GO<Zf!lyD`R0GGdXtM;^M9H)A`yo zT0ez;dogL%!_Z#-%XU!@SJewHc`f93q9eJt_=53yo5yKqXQeL9J9<1kxYPQ&;hWVv zZNJ?}7Tcm1yX#6@`k5Jm%d{RW>zwblL7KNfb85QHbImm|J2$v$-tSl3Jc8F_L&V-jYHmCRVRU=_?IpJDQCo+eQnLg^C@;G zdW23)u>9J!zg=i)Qt#4_SK}__IlsF!Npn&6w5pREduv;tPn24{NVHsM`k{aKRt7IG z(3l%j|6J?**+}=RyJD6fO5gS3vyW;0$=!eb#doSDn1L zF}IChK5yy$vw{I;S1n^)T$V&^yW`lwZ7TlMX{eIrAs76@e>|E>Xmx6!oDSI1bo^*s`w%>I1(c66AuZ)#H0 ztGHw6)6JbLv#eLj^pp`}_4jvMdDv(2oR3e~`C;5C1@FO&*H>)Z`4Sjl)iyq&*)>*JQk z-3^O(tdf$teeUTrH`Te}w)a(Rj$7^LZRF;N(D?W;=3%q>ys1k{w7Tv8eeA#XeD}x4 z^8arzna!H={f5~c8KniwtiRtW&i!*UeSU7Opx{Jh$7c&Pqr8l&zGR#h`5DCZ>*+n) zCy&m>dIT&z^4L#nX2tx2Gc1dj#kOSbv&cNXW)$9P`8y>d+$-5~UD^A4xl40w)Q=b(SJ{;F+_`g-TD{)~S=HdkA6ixWv`p7t zyU5XT{AcqtAxZzQe!Hd>efDy3QRzP8C_FQ_VEQEMlO@T=`?eN-`D<(vX!rTTj2%2y zGb@e6uk`N!ey>`W`T6SEBHE_wi>6ujZh0NbCCMw5^dX{r)frb~k)FplnWs%(% z)O#{pZ?5w@6LEVDKed@9O0Tc2-Mngc=26+x^X}h2>9SoScFt-W9n1o-3)8z zS;>a#FS%`ucez|Ma<~({{hzcKgj;-IQmw z+T}4FlJ%d@n(vOz-+MJQJUn`3PkoxYR$g4@^qm_^`yW-#2VH~vq-yEUdGGG-j^>p3 z%O`7f!|L11%g1ehzu8>+`drn#x?eBDez~j^IQ@FJ`MnC|(pe_ACp1h-RXKcVskeCf z-O}qfn;);=|8Ey-NKww?nFqM_b{y!N9i$tu%yQb|y#0U6vSZ(!RG(im+boyscv`Tj zT8(?~LfffT86t}w9htNGg=mSR!jo@o%H`~jn;!mo-fA+{p6l3u1sQvl=d0GQE!b&x z?(Og83Wxo-?lmfy{P6Ae``aFOe_d9x!}7@7dW&n7ah=Q$JgCFCi*z(ft|iF?~xGto(J2tNPGWu;RTm25y4WIW)~#5v;WKCE%lEgpuYczz9O3_s_niC`BiHk~f9#)IHtBtn z%{%d7wlL4em^t77oRt-LVkplf`^@!Z#k2*QFYiR};dOCw*>qxS{(ZaZSP?;M57Z4oF-B>*Pt;5Zp1)#1Tp%A%;W@*>l{ zso`-iqqgVezGpmr>xsqt*EcsNx9?s3JYegB@3r4gu1h-q|MjYUv0{CKf`XY#-moFDotNjS6-_y}?;^2d)Z%(?#RXP?IH}|jE>~=Y2*0Emc^!%Ls+uPReHTb-C`@L1)FL_UU zc+UELN&1{Zx13p9mif+pX7}Sk^Nm!$-S2u&ZsK+ClgWI&we)q^_Kb^4-w$!?w*>R& z$lsgLUvxR;W?S9OlQ+J-y?s5l{_odsbBl74_eX39?#|!W?+O3A_tyHITKLZ2%HbdHx|^-Ldj0&aJKtx&-MoGG{hIKd>Al%s zpFVy#gQ-c4X_@>V{kR>~PegVU%SoGNg}kYBc5&hBFZz5qa_-#KW!InG+9Cfe>gI=Q zHNC%=|H=EFRkxm3e&wcTKWkTBPSnoa_3PE@wOnVvy}f`5zx2 zzg=f4UQ=ekBo#Kt`qsT>e!C3m^N^-}qI2rRw3(OJR7+*6!bM}UK z*xIO6^*=!zc@CU`D}{wGOIuH~HTrel_WPW<0_!2o{NMjCtFB9W-=^?_^PF<@v#4+x zgNN+%eRBj1WLMspmwc?p(Dp&UFXG3@WSN=b@dc`;UJ8a>PmxXO9U;bp( zArbdwTYg?Wywvf+mq*?Dw_GbvPEvh1qphax&t-r6t#y-5PF6pjc1+E0&I>i484twt zVlo6RA?<(vBPaKC7}ZL7<(TE&dBMgn_ois)ud7*;o*n%@!7dXP4h@}O6KW&Yx69Yv zU=n50>(7{d;L`fve|~<>{j+p>TvqLor90njI=yCone?W!Cw>G~dAt}|=LPKUEk@0pw5=KM7<_k8%x#onvhwr{_l zuXA>t#Y{hajC z=gxwHnU!_(vS0tJP8I*2^|bKa%`U%px4Pt8f}3oj*IoKM|Dc6cy_>qguYAW}?adA+ z`)7y!e*aEMX2gavgE%khSG*)}_v!DF!Y>vURe2lJc`|xV@`Y3zP_xJX$y}#I<|Mm1k zZjbmtS4`hMHC0>p5a_({9Obt+Hy>}aZQ|a~6X^N*)61WNk4!=vCOoUYVR>y$Wb*u- z-actlWSh^d4F6+0MME@OhxfKXZ?1Eb#j%N;kLz#D{2A+VVBew79=)RV$7N41-LSv> z)fG({ujS9!o$vhX3y)%FQn#0>owxGU?WOC^F8jU4J1F|;n(mr5s9_i6s=>DZ%;^HbZu>P`uSNKWMBS>)hL~`Sorun z{jUlcZ2Nc1PY=Dc@lFUw-?uOS)|O6@5q$FF;rreHHciktQ0%v5+Vo{d_sG|L)An{3 z-M6a#zx=voe?Js>fkN!o*6ioY{O9MDpL?-5dVAhAf8}?34)!jcR^%usII%*xcx{tI z=KA0Jn;1HTlzwI3sVh%3xYus`f7<=$|5Q1wIM!+GlzgEhX_lie6#aF-m&)-?w zDySJN&0<>f{>q>F8=Gvxc3*Cq_g{#|ee3Da9lx7D*3JAc^u@jPd~ju9X4m1JJF-)6 z{dx5;B<#YGyD6_OEuR{)>#&Jj*0YD7_Evwtb*xA1rf%Gx8_$l*W*ZfSyj1?tseQI{ zcKEf_JU?0MvYdc365+iklaDXF$noyp`qq)?fDFk z+NAR`1YH+h`U1L(-MaAN#RJS1_x|iyBV|2pw$p3f|Fg6AKg)TYv1eh`Z~N=KpL2r4 zM1Sw+vKM{wt@i0B(bnD7Pd%)z7oPd~s<-f}%;bW58rN=sit8x8RjUJ;9Uv9J6KkTWFt7whTI(-ks)YY9yd+#<@J@jCYJ z!}CoqbGr{MG|Aip4TedU8D00@%Gg$IN$sk6%=M+VWQOSb>X}BVUA4;uS^GVvq^s7r z_4(LJPkGjTdD|4h*tBWeS5Dg*^G2ucW~N|EkD4`y*VML+d+yAxxBN%< zxeH_>=0YN zscve^G?iboUCv9(99{~}MxO#Mxn%lnsowU@;i`3h`nr!c?>b_%vqh4!_g(wDZ`+>P z8M-f5T^FhR_iTz~M$$vWnjZzbC;OO}y@`-^zkl7cN6C4!$1fHJi^BZNFPKd++Yn-pA29l^-PjIct9Z z4e0jt@YvYg^6_4JU0W@3UiePQWnb0n+GoF)yuWwX>%fch7Z(m*+vq>v z?%mDw`D>%guYLY<*?;@)6D~J(*1x}3@woR2s26%kAmrE6o$q$Ne&hHRG(xiGdGwPL zhw}e{ZYvk72woJ~lg^~S;{lU(p4V1~na?8ncK!W!`|Vsmf4iScZg0s9wlJIhe)r!? zq3_}zuPPJ!_E|%!b;aL<0^zrxiEy_RZN0xU=Is941xN24`t;`gAK~fCO&2Bn`NyYx z=lk9N-BV4r{`+IjyXx)Ii6!e6XOm2MxCU!J+d5>(eebbPD&DgMb^ zuep=USsjmD-D&o2OUzW;)+@>!VQj4PwVPfazP|3-rGUHdzIA>37IXRky!%hjt;%@n zwlnNq{jShD096qS3u{qq~n5P<#QqeL#LOOxJqBwnxT8zRn<|de}UD5|H3TU|BYWMQ4ihh`N-HMaZ2Y&S zE_BbkeZPbx1OM6Is`Sjfp*S&3`Vs@n`K>>mT0Wmsob&V1D&|ucTmR~#xOv-esSt8!_zsV1In$2gzRk+|F$MdY{`4RayK0p3m`gga&j9>BV&bDzn zDtt?{FrD3O`+v#7%H)@qmUcep6%@>zS@HAD;;q(iWxcmVxqAw(ef;y)H+!Q7XYI9}RnlgB3STR9A>h|17ji$B|!k z!#~xXb9h82ba$M%*fP88&V{b7YZ+ETANkarwyZS0FXlF7f|_se!-*pD5tUbZtVGV+ zn68+ot`x-8cu9Nxo=2eJgYQ?v<8K!GEnW5LsO7^J;WtlDhMfF8z5eI)f;FJ_Ak(J}BU8UqeUGN{h@(CZhi>9808wWj_T3ctW~FCKoPw&EivM^#ygK zl$1D)%q{i6%p#pY%TNALpw+SaQdD;jE~u zrk2z1zlk|}+v;j(M~6pc$R!n}l>%Qr{NrR=23k*(zwf8ooJoB8n;!r7v-_Fy{l((` zU7yd}+s_Hww@SKb-PJQ(*Q??du9gt;<1mb=tUMRmd)hMlminTr_rrIDT`uABIn=$= zd+(VYnjinJ4qLnGbp?BSM~8>^fYjJA?f!k8aJqeeE8zbM?22)8jVrl;5jdZvD8&IO*~d)7)Do z-w(3OAJHqmRhsWJE%(Iizme zYwq;t)93c&-rrYy_v`ie{FhH}9IM-yaFFTyv)TE(s^9Hge$e~LrFZQQW>h?x=zizp zaru1x-MZ!fl@eCT%q;$Se$MK<0TJ{5YB&A6{%xb*S6Rndy$83bew{vZL8rs3-#fQH zl+b(@uXJOMK3__`gjvpv+GlfO-c^eGmhy=U3Tj?^VD|cIczkYX#NK_2cTbp{o|AU>vd3rJ zHOi+C|Jo2Y+dRK)vYPLvr(fRf|6g}~XL0&!Hc?;Wj0+14TzBQ3cy5?{OywN+66NGG z|0lmxJzRLF@OW$R(+IZ$X0Hyf)Oj=S-KcvZsABv5PO(@_Z0Xg|hd176wXq$%Fuip_ z`u|0HN{=!MfBS1UBTU4=)Vy!)$;s;N&XZdHHcE#_=IuC|xvF?aM~BBoHpk1|`uj2@ z->7;|Ye|-RlqXlgou7QdBH-zw5YhflVf9^{!fHEOxy5g>POeM_oyz#!Px14kzODX; zV-NRAo9{Z<%)UEjU0v<$C6ym-m-t6LGkHDFZPt6mge2Pt`D?Eqw##pGXzkwT&MECU zG5-6{&(ClF@YdgZ{Q?CJO0a*U1kO_2-Qb)N9ifdz8+ui`{+aKqK>E!<$d7tipd6zD(!{(uhy(*IOwSclC$C+r^^meu+Eo(tFs_ zV-a7-SM_XW`kghAo7Y@f`$Ohx$fI+mJioK@`fb0>U|s7yU2p3{N#nGZm0UYMK3OjK z#*I0~YnkNvN-pj2b!&dd)gSIUFyZ6TMngyAmp9}(^d|GK`nzS>HQ0nukx-`5$&lNJ zRYkxD2$4%IwX=hu=>NOf}J;KkrP` zoMl`BmumQDwwY#MyOC}rpW?=ub7D@_(fiITgXiqX?J)VQRD7gNbwSeZ$rnE!+{kg9 z|Gctkn5%ZCz0NVa8{|xK6s&xleLt^vPsLS9~V4zw((OpSc@?3h)> zoxe}lZP_0!y*+Qoi$&dQ{@==8A9>&VR$J!tqs76XG2(RJ8+|1;%g?)Zw|zt8yaUZ!g^ zlUpC;-`i6uW@0F_*Yfg>GClQ*2RkKeRh^tfGJ|tH=#4O|Nne8yK66BdCu~A zgo8=>yBKf(m7hTS-Z})8bF2=sKdw!dzoU>mLr-c(&6kVr-{xNYYX||U? z>#Ueg9_<#ty~#8@b8<(AN3n)>#-pa=vWK_qxNs~{idSR;=Z)Rv>r*XGq*VN4I^8;- zhwbV`j=D$hG>)Cser;sFyieBJ?1aYA(Bw}idM;$Jl=qyx4qz2J!xyCW{ZdZs4?(zo1kUAOS(UOz%!)x77 zhKQ}PfBk%Q(B&-I-iC@f_8NQBY_3|rj5>8zTTpPKjuE>ri_QZ*)$&O%kM+rBKYsCh zS@PnVXA2DrB}3t?-Ih43a^a7enr%0)DTUl!BKd2}qrTMh@0(K=Mfd2P3!OK!c=e=v-iR-x$}k-+wM=DWLh8GQJlTI?Cl+4f1AK?*Y}hC?KWl> zUSfN$q@>gyl)5@>ZJltPS&05Zmd;gDCTjwMgOerS1^e4xeYNs(;8&$vxi>c*UA3%W z`6TXqk;fJF+~Nl_Y|Ae&h3v2UtMj?!T4egp+j+as-n;AM@FaTYW2ZAeX6Ns_dAI8I z+I72j-SYAZTN9CZdGVuXQ_E&bM}OCt#|t_>(Q94!toJYP?cF`K;o|!X3!B#_&$p|+ zv-!MT^!9gap1)i^KTp+Z%9@RP_EkUbHP`W-l{+IXE+@UoYyPucuh(s6KOSHIw{-O` z<>O}aIQKkg;(oJ3^D0y7tiJ}YzTM8>e(mYJnopkFGcPYI$T15|K2&ZZddp&EyPKkx z-tTMdb#166fmUJc55NPWZ!r=%aJdx?i`rre{CCv#*}Z=ugOPZDw=H zdv(8at;-_wz1n9x-}%}8c*gUoD}K3Mjj7XTni+I^#`E<*)>_>A`#vl>Iy!%*Gb$*g-3Tp6d9^E$Ifwlq~v#O{(&VGQsTF=*YCadZ+g|c7@LzR7mFslvo&xlUbg%5 z!MWPsXJrGY=Vz_3teKnFSc^a1QMvS|oRKVxhRt%9f}qIA%!4r{ckBOso*%ue;|%|Q z`L`b3CbuV4mcG7rcGa_^XS=VhJ7F==Qi->G;r}no?RVb)_icM3m*upH4^^B`?{ie< zKl-h3*Zm#ot5tKmRVYoYb`JRkZe-qua#{Sc~2^AJF*W6TW#%0GHE+DcpR4 z(()`3KY1Mwd})u$c3Gt9(%iUAto$G7&WyC@-v4!!_p?7=7#>%7v~4fw(z0tO_V7_*)~8i6M|%;Y*nDj*tv7R=qkjGV_ICB|i!9eB*EU}=^_scpdHTQkpXb=GFL` zthkU<5>vV^=){@NXZSN36kh(c5fl`hIL*aza!1Ocxiwy;oHHIpZPaN^FId6l)>%01 zzp$X7pyeGA*kp#rYg=d={WurAwGNlXGSvZBbmhs84jG|6_cQP8D9n3tVWHU`q+$LW z8#+Mk=cNlgUHCeedQab#dwbi?soLSY^!NQZ6kGXps+fcC3Zf;KB{q*$o_GIp7=jXpK+Bt{SN<6-1W3Tdu$_I_?Z;GRq&n=7E`=H|? zE4SE=qut`WkIUEF$Y?7mDP8*XVy2p4A)=Y`X2;{c&6%LtjsxuvUM{!)n`vP>_tB*# z=jK|s&vyaM1evk=Dm{9)?{}VdYSfN`M2ltn%HH1E`SSAe-Im4ABINJxD&1VS6O{a4 zuaDSx=ohF9qdT=pP0;Y@)lZAe^6q5tnjiSi&(`bIBV&1K)z6oeYM`l|mm(&eClpJM ziiSsIUj6p=wz0jqs*;jYk%dNvkf5`{$To$t{>N;^LKuHe7rpv?w+Lc z#)4;3a_=a1gAS{jCAoan{du<4It`!Zg2Qy#0#BDa%Y0|A@!$OD?9$xZ+pfL8y?s5a z|HVGFv#uaNY3*I^2{Oz@rO|5^nBgJ>vRF`1P|$P10+?JA$f<&Y6Ir-06{#I}joP5S zZpWc}$yaujW>;Ijng9RK^8&BO6@hOnpU-_>_U6XJc~!48L1&@12hR#ebkDYSfclD# zs=i9PB97ho(hRe!nYe)s3|`SO06@mF~*uV`sCcPI7P7`^J+iCMw*F1Hu_5u>%-Yp=KFe%+)2^hX z`sMS>FYY`bv`64VOwGrmW$kyX zKOAJ&RepbYP2ApFms0jx&mv-uY7=yh%40k4Paij(%s77AF9DNt(le#D->X`E`f1U- ziSBZo$FG>z{`%tfowvW|jr_k4?A&eZArn)g={S04-<~3RXRakb{~Y}5mw7G_xdeNEg-EZx9%U#SNb5J=mPp!b+ zKf5H$q}M-5zDa5SyD2sgE_v&3wUfHtmJs>q*-^8TyXG$a`Ss1s%kvz&v_)rGm1Z^9 zHuDse-0x80-T(7IBlGEhw#Bk@SO48nqBa{>r|d?)<+J&f&m_&Wujy##=IRwb7i+6E z-F{rI`b=Jc4r1lm0+EyckDs}GImTwLE^&S%*Z;XC|BkUXDjvT0pZWEXd(f zj9gUDo?ia^klgZ>a#Q?2+omLe=ZsGBOte(RJqdTb6Z`y z`bYDT9Ut?F+RUW{6~!mT#pxjW&P=9`NacI7Wdn2dfy|rJY;vx`@P>^ zt-3XF-uvas6JqvMY}B3ofBrL(a&YpvG*#53kntH`T2#!B#|FLa^WN`#F1Nk>ecb6e z6JCD&!#cl*LsD+lV!=Nm_aA<8al*$tm+pCPy|yszYvaRBQYAM8zd8Sub#&Wb zYBAx^_WO0&ja?pxCFxNVXeleoZ!fJef-_afN6mT&rTz5Z`F zci)e_g+HH8-+gy?`E~ARKjQKx6ezbsx?KBQcJ4HJUZSa8+s^FF zk7=yiS#G?t>e1@!0Y{H28y_&XwmWQG{LJUH$b~sYx7uecK7Hns*;S+I-Djr9mqjgC zexMlPxbl+0wCU{sx98rTc4JoT%3O)FFI3O2*4(yP1-w>HL{&Uvb|o4kDZmdQ?y+BkpHJC*vpQm zxn;xmtzx;?LjF57?gAlQ_UTidL~Z9KEAj5vR?U`sZq)QxeO|?)bzS$==hq1N>Ap>v z=d86=zN@1{rdKO^Yu4AVV^-8UFc?ypND0fF2 zpSgPVYFN;wq@!H3{oUQQEIoDYe6l&RzrVbE zJg@fK&0VGGw*K~iOSACcZm_gKYa5`Jf{6q8@M z?beNbf=BC|UKb%vysX;KdH-TaM(`3jsn!)1hR?OCSYrh^cJ4iR;_ks62^Rk=6@8!f zH+`IYo&-wj``)O_^6yS4sVvy`|yz5cVp zr@hrV+n@XYX8Qb{p!ty$PWM^0hm{kQ4*PxGuwznhv+H7ki)_k8clk{u1aqG(%=_~~ zVaBaTcfS9R$nNzpX?<4nPw(9wr_Fz5g8%S6Fb-ww7K|x46S(See|X+Hk?Eg4{0>Ov z|My+RqRWo?WR=j8g1FAGv&{d`SGG>lcy_Qj^#7mp|8tgh&*@*Z-h|=2O(2)suAtw3 z$*`7B%_`DYf*Z&Vk!wpk6-On(}9LFLk+z^#)G zbT(*uXL6~Ags_5E&CT8_;s#pem9ZSw5_XJypD6@t91B{8k=RaN;Q2(O?0RhZ&EK;e z>aVT}Ej+s6X-)!}E%jGryUoM{?6%T5!pIW@Y^NGQWtE;Dft^V>N@o&;`+44EQ(c5xn{{Hc} z|L#%o_&0UGF3)cZzWnm?^8T;W_2ctuLGIV=Z&I6h_`$sDcZHkN&zEtF>+u}Fa(!K_ z_4n8D|8Je0X?)ywzTMrYpDVAgjox06WyKOJZJx)|4zhAd4A(3dzR6336q@F)joyAM z{r>r`SCzBP^Z5|ceNHA!4?o@!nVT=rRCj${?Ay(t)o&(#j+0L$UYg@uAAD-c>8E!- zJUo0C)a<`|V`Flz)L-zX36SG5=YSjsx?xf51gLYPJ8!C#X;#SIzVsCi@x9Iml+p&{YgyS=3>+Q*8yUn zejT4yrVyv#mpirJ?}{0$jo*K-_VZcuhf_)_0(JkFe>$nY+g-l)%G~;YKOau{6nb|; z@!GVo9oIf{#U7nd;MDQ()al}NSuy8!KG0tUop$O_w|u+hvQ9vrU(Nqjp{rl*+Wl^q z_8d@JaqnVN^-WRA+w;-QJmrMI^{v_A+jDLjnWuvn<;DB}ov6LN=4a7qkqI~F*j8`j zUGBQ9=+2J9cT3UhC7E4^R)eQx#ZwcGD7^VK4bYjp-;E|p4POlcR)S+nI)P(0W|xG8gfnA}--cSqr3 zX`6}-r=PyM?(Q{9{J!(AC8>U!-(@cODaWLCt@&%>Ub(s-iQ1}4#qR8{e((+*i%Bt-fXKky7A&a#!sS-Lkj0p0e@DNc5f5yMA%!t$uHI=P6D< zwW>=zuYOo{tUXhHZNb(39G6Nt)l-gqJYo3ljPdy$k(<*ZU#5LDybkha`;`QpS-{4!{u4r&kesB z-aq0eKsPo*_a?_FdlEbJPk$-KXUX+!t zl9JL{<>+^sE^ci;-ctW|DjJzBopADDq>zzGNbRDKlb_39|Nj0yzWn(#9w+c<>Zwg< z&g#xO`S6`Z`P;vD-|Q$o>?$6c^7ndN_1Rx>m&|OYxq|l4oSnH+Hymx2>ESHU7Mk-> zXR=lD7XFfZx#Cdx@463jTbmw*+-Q>tN7_Tv{_Ms%oA9t#5o!U6_wHCcbYlz+=X^G= z{$J(nck};SKc7>qqcF?5eBILJF6D1;ZOuE7}^YZL#2; zK=se!S8tfvg#8Ta+_7$_mnR?FdEK{jL$YNLA~wyuY5!#1q@OFxx8%dy?f0V=9Qx>d zATTDB_w3fWlg~-d%NLsWnejM7BV@x&#m9rv=b3`6rXH35|HJ+D=es{X_SfeyO8cIw z&XLZmD|p-l>EnQ>m1bY+64hQ~rFXULC3qiCn_J$Fhizh&!VWGjPckN2W^_%P)FWqm z%jo&Pcj|wO-mjbV^P!&ZimFG2%V(ZxJl_DCBs0=9c|XU@SPZsQErspxrpu>fC5__e zt_#VOIBi|~t7Lob?QKWGm>+L@xbf6_DJ?7i-IyZj@1+to$1Kra^_!-bp~W}w zu9=xO>PI&4pVH#i+jp0YKp&P7%~)B$M3J3w$tYI zn$34|x8F^BzfDo$)7m=|0~0~Z&~o1IS-EcAwjjw>D@D3zhu=!g%elL_qVK`7*?C$! z<1+Uxum1injLB`kb35O*zS>`33eVg9&bhm5^StHr>vm1G3*2<<)30~E=Jz(3WNu5G z_4MSXckasbDtA9<;?CKxarIErwYSB;-)@ga?d}*pw4TNFw)KkhVgXV0XECnp7jQEv zZa0_MX)Phn^W0v4$;v;02LIR}{!p|Gugz^ey|pm!zCEktKTvmPqGkQ>x7%~?r&tI6 zx+(LhPyZad&2i_e=Bsvosunyk<<>Eq`lE&iKR)g6zjfeGVTZ7v#lg17bua$t?R+A1JKrL0 zXJ_%v)ajAQqM0A<|JXMMP1lRPQ+ho%oi_ngVu}TTR{wkk?Vl0tJ7Xj1Jb_XD^YvxE z(%(Ow*1x?YDMjr>PQgRHa|M%5Dn9m42(szzIkEM6T=xId#SAHWC*EA%G}WE0lSdme z&D8G)E*PQHOf#SF08cZeviR66cUiW4)22&JJ74Vj|L3{=b|2-L&j0LhO%%)A_E5k6 z=jj_yE1ymc&xyHY^xMSU*yveG#!uZ0ALY~eQG04RQZlXPy-JK+-R!E$>U(v2*p2Rd z-2|>Dx7Zs4Kg?X0mbp-*Wr3!+&bnTHrZs=s6cTbh){*R5FzSwwQaOpHYeejooeUxNM zW!>-F_uDjGTwIn|KDo6$fBxQeeJ27tf|%o$`LxXy5}noCxB6Tt*UZZQlVYS4JgWZr zfcgr3GF-DQO?Tqe|~He%-H33s5)ErH_qk z){_|*;Eju;eb(6X#|?v*xjwa@5bLR%k6oWFS`tCi|EJiy91sDDk=%MpmMxr z_xpXf&(5>GtsAq$KqfdIGk9`EQ{Cl=iS{EX|LO~u2lx}VRu^)B6-);y$ho!U|GD1*rqLLQ0D@^1>5iHsqQz3KnViU#EK0P_P&U?DvyUYIe zyvL_J@6ujZ0J`mZ_p@2qcfP&7{rX6B3` zzgs#Tbl1h5RiUfbSpWa?`LHG{Prt17wY!DKW%Z_?ZcXL}^`2ut{s`iz0?plB-&LBu zz3y+-_p|2rTY@h=Y(4Pj{z>)uZ{%vfTx2_~37UD^a`|+wD_=9{E=a@VV>|lo|J`5` z0^N=Ux;N>UL?d0n@=9k^doc8Uk`TZ>iUM7^h zy><2PyYl^;|LJbOb15NG+Bq|PW$Fyu>TS{cYAP*2o5n7E(#V*U%%`e)xQ#bkphL#1 zTTHj8`rDhr$H)8mkEcvF%TQ8~w5!>%`(c~(o$&Zt(LUYz{5A9Her0YiczCEF$Le3b zxPDyB`dtrqzu!08k^wwjJ0--{zmIcD--jC5p7y7HmQSaIgx)@Nux3S``vigZr;(e} zd@VxcuYRkUYP)MmRt0DxTKDXwrQW&@kE$1gw$d++EOa~xnwR>Wer86XxxL--g)SM_ z*Tv>NIMA4yZ_lK*?|3=a3{YXPH28vwyD5`+6X!hHMOJs7ot>Qv+BV*)YQny_9JbTl zcGankHYF@Fpsh60eRk^LokK>KR4w1_c)VsJ$0b3Nz(pm`=a#nxbN~AGHv8)TN!O;n zPmG+d8=b~@?WlNs&Uwe9H+K{+etl(Su*HqKr5FG&RebR=Lv0D z=-hs7Hv4~n0~1hZ*{61j$=BD{*T0ig^;&hqulFy%V|;Qbb9W$<`(ZJbSWq`v*Z%Wt z^ZdAz_dx~1lBEHjlV!H<`}-}Ln?>W{!kuq|ma5zMKlU#5Dt*f0qro@Bp!{8odDaz; z*FWNzgzaOr60&%F+ubi}#ogYPdwAg&j-(pVozK>2ZBIJN741Lo@uL5$zBK$An^I|%=60qHc5^P^1JR-zu)^^ z=YnEva6;Uf=l=D-f-Tf+y=HQ}so2(8x=@irYJJ7)wcBUC-7Wy??{a~PSCO@9*RGi5 z-b(okn*RS48Q42v!D;gV$CG{~H$Fe+WXcpRcy(oE9%$a-3e&x%M~uD8?-U+)oL(pR z=E4aU{ZmU{C5M3)P081MXsl&FZCmujqk7%zMs_)lV9>bH7N0Cz|0f#mo|l{X?Ow3) zN~MH8l&gBN(8rN~sW(ee#Ll9nO){Wm56?6*ge=_x-u6#p;heE)%9cQ5-xG?&kLbZc8~w*I}E*K4=G+G{g8b;sv()^{b-=LpVLTMH@|`R#rv zl+C=nt~z;B&f$s5?zgIP@9w&)8@1)c-VcYkA6{X;oeo;%F=vWeru4GddB?W-2Pe&Z zc7A^T=~FdcnK{2J)?9vH_ucySXQ8l5a(P$FpTyKHF8)1z#S+khgC`!>7c6=CYW4bU zTK>1U=kp&oadDfg70S%U!*P5{`kx;k!%jK4%{XHz`3+R#dMWH-M_NtX##1VmInDCy zuH_S-RY$l_YyX*54XPmUdieM%24i|klE)K$9g0m zPt%DM>dWj1VygM|a``oXqsmVy+q14}Y46l9LmHmZKAUjw#?Ip9Uo|Zg;-GX#T6@`E;k{^{Tkm)l%^< zf}Nh&U;bWWwJXfHxnw^F-(0C20`DQ&Uc~BC{tdx54>XMDm zeNb}-V`ybU;_{S4P+oj`YHDfdhBjX5G|30H)!%MxaDR3~-#O&co|-D7>xHvcDK?ti zcXrs3c2?@Q&r8mO%#KV4TR=Mxt&VoyZaeX5wp#A|Wk>#0$Z7W7xBFRRSN2;Zxe7eM zl2deW|J60;c6T*ePXB*t=bv=P1toC;XIeh`yuG%^clqgj&P%+!Obhm}uK#fbKJfB+ zm4#yXk97rhQx}U`bar%fYzg7bZ(Z&X;KXLi`#*+9RdH#6)|P~WOx0qouac)|Ri-@9 zjD2x`|NhiPj%>R^U@LFM)+U)HALB94zP4u9s}htmM)oeroe>tCBR{Qq(!L#fYj?bL zW|+IBmd~xu`$Dcp&AOl0_pH7fVE^GH`^UTgTpb-mN<#Lo{+WF5=`U7crKP@07H-{h zEb7eZt%dJ?|BuSP?|Ps0WU=hwPEfMC^k|BxN#Lr3#%h_WQ{xPBudE1s2O6RW9sF@X zJ!)^>$45ua?7jR>R0PZRo~ey_>3L_*CcWa~S2s2$H*2L_d{wekz~h1j(^8M@>+5)r zi=+WI1gZ$e5;Cf`dR7RSbe z3bUv+XB>mKh$MV@adDdE_g?l3-&LI=Ki19D3|@94T}t zt!af1V8O*MK^W2no*Q$Ddkj(ztn$F;rZ-t*O|2!w;R)UqT zwr|$kn6s0dzS``%b$(Wv95`BvV25NJ-mE-D;o=%oXgB}sm0tC9_+Z!RzIul{y3{q?oE zUqKhDtSxDsWIFA7dH$Y{ZA(2j^UnTdp37z9TzZgI{6>w})*I8)e!Faxtq8w5+28J^ zS?(>7zT6ns^>bER7^m|Z?T|fK@L^|K=DkoJzGL$ve$06-Bcox@!?gUt_H&_ZnQbhW z+1Q$oiywQspN~o7U%AHx*8h+9Uw+HGuedx>?&ta3vUQ)_TNO4u>t%KfzdGa4pKR{8 zO`v+#gB!FTCM(wDyU$8yjwp$9L3g!Jr6-*HA`YH_tZtvg`)h`Ie%xN4U!CgnGCs4P z|9rtJCT6ja=VveF)4S^bRVs?dWW{Cfd;amb{PyW*KFtw&?Z3L<=<9c@XA25^{D1MB z_h#Pnw%=oZ&ks5s{O8Sug@^Kgz~>-4V`sg6ENF0X$*Ql)r&OAMP1z7pcP=c?9j5yir4o}xYx#iJ`L2lnW#1)`~JSYuOv? Date: Sat, 16 Sep 2023 15:03:35 -0400 Subject: [PATCH 0038/2693] nestlog: README: tweak ex1 --- README.md | 31 +++++++++++++++++-------------- img/ex1.png | Bin 0 -> 15819 bytes 2 files changed, 17 insertions(+), 14 deletions(-) create mode 100755 img/ex1.png diff --git a/README.md b/README.md index 27c8e73d..4943e8f0 100644 --- a/README.md +++ b/README.md @@ -20,26 +20,29 @@ Nestlog is a lightweight header-only library for console logging. ### 1 - /* examples/ex1/ex1.cpp */ +``` +#include "nestlog/scope.hpp" - #include "nestlog/scope.hpp" +using namespace xo; - void A(int x) { - XO_SCOPE(log); // i.e. xo::scope log("A"); +void inner(int x) { + scope log(XO_ENTER0(always), ":x ", x); +} - log("enter ", ":x ", x); - } +void outer(int y) { + scope log(XO_ENTER0(always), ":y ", y); - int - main(int argc, char ** argv) { - A(66); - } + inner(2*y); +} + +int +main(int argc, char ** argv) { + outer(123); +} +``` output: - - +A - enter :x 66 - -A +![ex12 output](img/ex1.png) ### 2 diff --git a/img/ex1.png b/img/ex1.png new file mode 100755 index 0000000000000000000000000000000000000000..89e37ca42146167d845242a26d5413ead97b05b8 GIT binary patch literal 15819 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 tE?sawaN;;=D1ul*ju Date: Sat, 16 Sep 2023 15:08:37 -0400 Subject: [PATCH 0039/2693] nestlog: README improvements --- README.md | 71 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 4943e8f0..8145c91a 100644 --- a/README.md +++ b/README.md @@ -42,53 +42,60 @@ main(int argc, char ** argv) { ``` output: -![ex12 output](img/ex1.png) +![ex1 output](img/ex1.png) + +- nestlog types are provided in the `xo` namespace. + macros are prefixed with `XO_` +- indentation reflects call structure. We don't see anything for `main()`, + since we didn't put any logging there ### 2 - /* examples ex2/ex2.cpp */ +``` +/* examples ex2/ex2.cpp */ - #include "nestlog/scope.hpp" +#include "nestlog/scope.hpp" - using namespace xo; +using namespace xo; - int - fib(int n) { - scope log(XO_ENTER0(info), ":n ", n); +int +fib(int n) { + scope log(XO_ENTER0(info), ":n ", n); - int retval = 1; - - if (n >= 2) { - retval = fib(n - 1) + fib(n - 2); - log && log(":n ", n); - } - - log.end_scope("<- :retval ", retval); - - return retval; - } - - int - main(int argc, char ** argv) { - log_config::min_log_level = xo::log_level::info; - log_config::indent_width = 4; - - int n = 4; - - scope log(XO_ENTER0(info), ":n ", 4); - - int fn = fib(n); + int retval = 1; + if (n >= 2) { + retval = fib(n - 1) + fib(n - 2); log && log(":n ", n); - log && log("<- :fib(n) ", fn); } + log.end_scope("<- :retval ", retval); + + return retval; +} + +int +main(int argc, char ** argv) { + log_config::min_log_level = xo::log_level::info; + log_config::indent_width = 4; + + int n = 4; + + scope log(XO_ENTER0(info), ":n ", 4); + + int fn = fib(n); + + log && log(":n ", n); + log && log("<- :fib(n) ", fn); +} +``` + output: ![ex2 output](img/ex2.png) ### 3 example exposing runtime configuration options - ``` +``` /* examples ex3/ex3.cpp */ #include "nestlog/scope.hpp" @@ -135,7 +142,7 @@ output: } /* ex3/ex3.cpp */ - ``` +``` output: ![ex3 output](img/ex3.png) From 0260ca79c1737d2bfbbaf95300ccb1a315e3d0f0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Sep 2023 15:16:10 -0400 Subject: [PATCH 0040/2693] nestlog: expand ex2 --- README.md | 5 ++++- example/ex1/ex1.cpp | 12 ++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8145c91a..1c2df2a3 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ output: - indentation reflects call structure. We don't see anything for `main()`, since we didn't put any logging there -### 2 +### 2 slightly more elaborate ``` /* examples ex2/ex2.cpp */ @@ -89,6 +89,9 @@ main(int argc, char ** argv) { log && log("<- :fib(n) ", fn); } ``` +- global configuration settings live in the `xo::log_config` class. see [log_config.hpp](include/nestlog/log_config.hpp) +- the recommended form `log && log(...)` tests whether logging at this site is enabled /before/ evaluating/formatting the log message; + when logging is disabled, this saves the cost of computing and formatting that message. output: ![ex2 output](img/ex2.png) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index db0ce39e..1258e2a5 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -2,13 +2,17 @@ using namespace xo; -void A(int x) { - XO_SCOPE(log, info); +void inner(int x) { + scope log(XO_ENTER0(always), ":x ", x); +} - log("x:", x); +void outer(int y) { + scope log(XO_ENTER0(always), ":y ", y); + + inner(2*y); } int main(int argc, char ** argv) { - A(66); + outer(123); } From 335083579e5b33db98146865ca88198870a69fe4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Sep 2023 15:20:16 -0400 Subject: [PATCH 0041/2693] nestlog: README: format nits --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1c2df2a3..587893f9 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ output: - indentation reflects call structure. We don't see anything for `main()`, since we didn't put any logging there -### 2 slightly more elaborate +### 2 slightly more elaborate example ``` /* examples ex2/ex2.cpp */ @@ -89,13 +89,13 @@ main(int argc, char ** argv) { log && log("<- :fib(n) ", fn); } ``` +output: +![ex2 output](img/ex2.png) + - global configuration settings live in the `xo::log_config` class. see [log_config.hpp](include/nestlog/log_config.hpp) - the recommended form `log && log(...)` tests whether logging at this site is enabled /before/ evaluating/formatting the log message; when logging is disabled, this saves the cost of computing and formatting that message. -output: -![ex2 output](img/ex2.png) - ### 3 example exposing runtime configuration options ``` From 8268eef8b288c62a9d5552e481567e4315bf06cb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Sep 2023 15:20:29 -0400 Subject: [PATCH 0042/2693] nestlog: fix defualt indenting --- include/nestlog/log_config.hpp | 4 ++-- include/nestlog/log_state.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/nestlog/log_config.hpp b/include/nestlog/log_config.hpp index 00e49249..76ecf63f 100644 --- a/include/nestlog/log_config.hpp +++ b/include/nestlog/log_config.hpp @@ -19,7 +19,7 @@ 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; - /* spaces per nesting level */ + /* spaces per nesting level. 0 -> no indenting */ static std::uint32_t indent_width; /* max #of spaces to introduce when indenting */ static std::uint32_t max_indent_width; @@ -62,7 +62,7 @@ namespace xo { template std::uint32_t - log_config_impl::indent_width = 1; + log_config_impl::indent_width = 2; template std::uint32_t diff --git a/include/nestlog/log_state.hpp b/include/nestlog/log_state.hpp index 9bf6fefb..01566380 100644 --- a/include/nestlog/log_state.hpp +++ b/include/nestlog/log_state.hpp @@ -229,7 +229,7 @@ namespace xo { << ")"; } - if (log_config::indent_width > 1) + if (log_config::indent_width > 0) this->ss_ << ' '; /* scope name - note no trailing newline; expect .preamble()/.postamble() caller to supply */ From 61441f25137acd3d7e83917933a3e38bc306a30e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Sep 2023 15:24:25 -0400 Subject: [PATCH 0043/2693] include/nestlog -> include/indentlog --- include/{nestlog => indentlog}/array.hpp | 0 include/{nestlog => indentlog}/code_location.hpp | 0 include/{nestlog => indentlog}/color.hpp | 0 include/{nestlog => indentlog}/concat.hpp | 0 include/{nestlog => indentlog}/filename.hpp | 0 include/{nestlog => indentlog}/fixed.hpp | 0 include/{nestlog => indentlog}/function.hpp | 0 include/{nestlog => indentlog}/log_config.hpp | 0 include/{nestlog => indentlog}/log_level.hpp | 0 include/{nestlog => indentlog}/log_state.hpp | 0 include/{nestlog => indentlog}/log_streambuf.hpp | 0 include/{nestlog => indentlog}/pad.hpp | 0 include/{nestlog => indentlog}/printer.hpp | 0 include/{nestlog => indentlog}/quoted.hpp | 0 include/{nestlog => indentlog}/quoted_char.hpp | 0 include/{nestlog => indentlog}/scope.hpp | 0 include/{nestlog => indentlog}/tag.hpp | 0 include/{nestlog => indentlog}/tag_config.hpp | 0 include/{nestlog => indentlog}/time.hpp | 0 include/{nestlog => indentlog}/tostr.hpp | 0 include/{nestlog => indentlog}/vector.hpp | 0 21 files changed, 0 insertions(+), 0 deletions(-) rename include/{nestlog => indentlog}/array.hpp (100%) rename include/{nestlog => indentlog}/code_location.hpp (100%) rename include/{nestlog => indentlog}/color.hpp (100%) rename include/{nestlog => indentlog}/concat.hpp (100%) rename include/{nestlog => indentlog}/filename.hpp (100%) rename include/{nestlog => indentlog}/fixed.hpp (100%) rename include/{nestlog => indentlog}/function.hpp (100%) rename include/{nestlog => indentlog}/log_config.hpp (100%) rename include/{nestlog => indentlog}/log_level.hpp (100%) rename include/{nestlog => indentlog}/log_state.hpp (100%) rename include/{nestlog => indentlog}/log_streambuf.hpp (100%) rename include/{nestlog => indentlog}/pad.hpp (100%) rename include/{nestlog => indentlog}/printer.hpp (100%) rename include/{nestlog => indentlog}/quoted.hpp (100%) rename include/{nestlog => indentlog}/quoted_char.hpp (100%) rename include/{nestlog => indentlog}/scope.hpp (100%) rename include/{nestlog => indentlog}/tag.hpp (100%) rename include/{nestlog => indentlog}/tag_config.hpp (100%) rename include/{nestlog => indentlog}/time.hpp (100%) rename include/{nestlog => indentlog}/tostr.hpp (100%) rename include/{nestlog => indentlog}/vector.hpp (100%) diff --git a/include/nestlog/array.hpp b/include/indentlog/array.hpp similarity index 100% rename from include/nestlog/array.hpp rename to include/indentlog/array.hpp diff --git a/include/nestlog/code_location.hpp b/include/indentlog/code_location.hpp similarity index 100% rename from include/nestlog/code_location.hpp rename to include/indentlog/code_location.hpp diff --git a/include/nestlog/color.hpp b/include/indentlog/color.hpp similarity index 100% rename from include/nestlog/color.hpp rename to include/indentlog/color.hpp diff --git a/include/nestlog/concat.hpp b/include/indentlog/concat.hpp similarity index 100% rename from include/nestlog/concat.hpp rename to include/indentlog/concat.hpp diff --git a/include/nestlog/filename.hpp b/include/indentlog/filename.hpp similarity index 100% rename from include/nestlog/filename.hpp rename to include/indentlog/filename.hpp diff --git a/include/nestlog/fixed.hpp b/include/indentlog/fixed.hpp similarity index 100% rename from include/nestlog/fixed.hpp rename to include/indentlog/fixed.hpp diff --git a/include/nestlog/function.hpp b/include/indentlog/function.hpp similarity index 100% rename from include/nestlog/function.hpp rename to include/indentlog/function.hpp diff --git a/include/nestlog/log_config.hpp b/include/indentlog/log_config.hpp similarity index 100% rename from include/nestlog/log_config.hpp rename to include/indentlog/log_config.hpp diff --git a/include/nestlog/log_level.hpp b/include/indentlog/log_level.hpp similarity index 100% rename from include/nestlog/log_level.hpp rename to include/indentlog/log_level.hpp diff --git a/include/nestlog/log_state.hpp b/include/indentlog/log_state.hpp similarity index 100% rename from include/nestlog/log_state.hpp rename to include/indentlog/log_state.hpp diff --git a/include/nestlog/log_streambuf.hpp b/include/indentlog/log_streambuf.hpp similarity index 100% rename from include/nestlog/log_streambuf.hpp rename to include/indentlog/log_streambuf.hpp diff --git a/include/nestlog/pad.hpp b/include/indentlog/pad.hpp similarity index 100% rename from include/nestlog/pad.hpp rename to include/indentlog/pad.hpp diff --git a/include/nestlog/printer.hpp b/include/indentlog/printer.hpp similarity index 100% rename from include/nestlog/printer.hpp rename to include/indentlog/printer.hpp diff --git a/include/nestlog/quoted.hpp b/include/indentlog/quoted.hpp similarity index 100% rename from include/nestlog/quoted.hpp rename to include/indentlog/quoted.hpp diff --git a/include/nestlog/quoted_char.hpp b/include/indentlog/quoted_char.hpp similarity index 100% rename from include/nestlog/quoted_char.hpp rename to include/indentlog/quoted_char.hpp diff --git a/include/nestlog/scope.hpp b/include/indentlog/scope.hpp similarity index 100% rename from include/nestlog/scope.hpp rename to include/indentlog/scope.hpp diff --git a/include/nestlog/tag.hpp b/include/indentlog/tag.hpp similarity index 100% rename from include/nestlog/tag.hpp rename to include/indentlog/tag.hpp diff --git a/include/nestlog/tag_config.hpp b/include/indentlog/tag_config.hpp similarity index 100% rename from include/nestlog/tag_config.hpp rename to include/indentlog/tag_config.hpp diff --git a/include/nestlog/time.hpp b/include/indentlog/time.hpp similarity index 100% rename from include/nestlog/time.hpp rename to include/indentlog/time.hpp diff --git a/include/nestlog/tostr.hpp b/include/indentlog/tostr.hpp similarity index 100% rename from include/nestlog/tostr.hpp rename to include/indentlog/tostr.hpp diff --git a/include/nestlog/vector.hpp b/include/indentlog/vector.hpp similarity index 100% rename from include/nestlog/vector.hpp rename to include/indentlog/vector.hpp From f71c009a0f959f5eb0355899a5ce2667af7b21e3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Sep 2023 15:25:17 -0400 Subject: [PATCH 0044/2693] indentlog: update README --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 587893f9..0948f28f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# nestlog -- logging with automatic call-graph indenting +# indentlog -- logging with automatic call-graph indenting -Nestlog is a lightweight header-only library for console logging. +Indentlog is a lightweight header-only library for console logging. ## Features @@ -21,7 +21,7 @@ Nestlog is a lightweight header-only library for console logging. ### 1 ``` -#include "nestlog/scope.hpp" +#include "indentlog/scope.hpp" using namespace xo; @@ -44,7 +44,7 @@ main(int argc, char ** argv) { output: ![ex1 output](img/ex1.png) -- nestlog types are provided in the `xo` namespace. +- indentlog types are provided in the `xo` namespace. macros are prefixed with `XO_` - indentation reflects call structure. We don't see anything for `main()`, since we didn't put any logging there @@ -54,7 +54,7 @@ output: ``` /* examples ex2/ex2.cpp */ -#include "nestlog/scope.hpp" +#include "indentlog/scope.hpp" using namespace xo; @@ -92,7 +92,7 @@ main(int argc, char ** argv) { output: ![ex2 output](img/ex2.png) -- global configuration settings live in the `xo::log_config` class. see [log_config.hpp](include/nestlog/log_config.hpp) +- global configuration settings live in the `xo::log_config` class. see [log_config.hpp](include/indentlog/log_config.hpp) - the recommended form `log && log(...)` tests whether logging at this site is enabled /before/ evaluating/formatting the log message; when logging is disabled, this saves the cost of computing and formatting that message. @@ -101,7 +101,7 @@ output: ``` /* examples ex3/ex3.cpp */ - #include "nestlog/scope.hpp" + #include "indentlog/scope.hpp" using namespace xo; From 2f78963bed5d70cbbbcd7c425c39505a35813bd8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Sep 2023 17:45:27 -0400 Subject: [PATCH 0045/2693] indentlog: function: special-case printing operator() method --- include/indentlog/function.hpp | 80 +++++++++++++++++++++++++++++----- 1 file changed, 68 insertions(+), 12 deletions(-) diff --git a/include/indentlog/function.hpp b/include/indentlog/function.hpp index 42c5752f..e6e6600f 100644 --- a/include/indentlog/function.hpp +++ b/include/indentlog/function.hpp @@ -50,16 +50,30 @@ namespace xo { } /*print_simple*/ /* e.g. - * std::vector xo::sometemplateclass::fib(int, char**) - * ^ ^ - * p q + * <----------------------------------- s2 ---------------------------------------> + * <--------------------- s3 -----------------> + * <----------------- s4 -----------------> + * std::vector xo::sometemplateclass::fib(int, char**) const + * ^ ^ ^ + * q r p + * + * sometemplateclass ::fib <- .print_aux() + * */ static void print_streamlined(std::ostream & os, std::string_view const & s) { - std::size_t p = exclude_return_type(s); - std::string_view s2 = s.substr(p); - std::size_t q = find_toplevel_sep(s2, false /*!last_flag*/); + std::size_t p = exclude_const_suffix(s); + std::string_view s2 = s.substr(0, p); /*no const suffix */ + std::size_t q = exclude_return_type(s2); + std::string_view s3 = s2.substr(q); /*no return type*/ + std::size_t r = find_toplevel_sep(s3, false /*!last_flag*/); + std::string_view s4 = s3.substr(r); /*no namespace qualifier (unless function)*/ - print_aux(os, s2.substr(q)); + //std::cerr << "print_streamlined: s=[" << s << "], p=" << p << std::endl; + //std::cerr << "print_streamlined: s2=[" << s2 << "], q=" << q << std::endl; + //std::cerr << "print_streamlined: s3=[" << s3 << "], r=" << r << std::endl; + //std::cerr << "print_streamlined: s4=[" << s4 << "]" << std::endl; + + print_aux(os, s4); } /*print_streamlined*/ private: @@ -91,10 +105,23 @@ namespace xo { return 0; } /*exclude_return_type*/ + static std::size_t exclude_const_suffix(std::string_view const & s) { + constexpr std::uint32_t c_prefix_z = 6 /*strlen(" const")*/; + + if ((s.size() > c_prefix_z) + && (s.substr(s.size() - c_prefix_z) == " const")) + { + return s.size() - c_prefix_z; + } + + return s.size(); + } /*exclude_const_suffix*/ + /* e.g. * xo::ns::someclass::somemethod(xo::enum1, std::vector) * ^ * return this pos + * (pos just after 2nd-last non-nested separator) * * last_flag: return pos after last :: * !last_flag: return pos after 2nd-last :: @@ -135,7 +162,9 @@ namespace xo { --nesting_level; } - return last_flag ? pos_after_last_sep : pos_after_2ndlast_sep; + std::size_t retval = (last_flag ? pos_after_last_sep : pos_after_2ndlast_sep); + + return retval; } /*find_toplevel_sep*/ /* fib(int, char **) --> fib @@ -143,7 +172,7 @@ namespace xo { * foo::bar>() -> foo::bar */ static void print_aux(std::ostream & os, std::string_view const & s) { - //std::cerr << "print_aux: s=" << s << std::endl; + //std::cerr << "print_aux: s=[" << s << "]" << std::endl; /* strategy: * - print left-to-right, omit anything between matching <> or () pairs. @@ -152,17 +181,44 @@ namespace xo { */ std::size_t nesting_level = 0; + /* index of next match within string 'operator()'. + * if we would print 'operator', and it's followed by trailing paren pair, + * then don't exclude the trailing () + */ + std::int32_t match_operator_ix = 0; + constexpr char const * c_target_str = "operator("; + for (char ch : s) { - if (ch == '<' || ch == '(') + //std::cerr << "print_aux: ch=[" << ch << "]" << ", nesting_level=" << nesting_level << ", match_operator_ix=" << match_operator_ix << std::endl; + + /* looking for match on 'operator(' at nesting level 0 */ + if ((nesting_level == 0) && (ch == c_target_str[match_operator_ix]) && (match_operator_ix < 9)) + ++match_operator_ix; + else + match_operator_ix = 0; + + /* don't increment nesting level if immediately after 'operator' */ + if (ch == '<') { ++nesting_level; + } else if (ch == '(') { + if ((nesting_level == 0) && (match_operator_ix == 9)) { + /* special case: + * 012345678 + * operator( + * at toplevel; don't count the '(' here toward nesting level + */ + ; + } else { + ++nesting_level; + } + } if (nesting_level == 0) os << ch; - if (ch == '>' || ch == ')') + if (nesting_level > 0 && ((ch == '>') || (ch == ')'))) --nesting_level; } - } /*print_aux*/ private: From 238edb1619068fb28f5b73b3c525a69e49558b24 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Sep 2023 17:46:09 -0400 Subject: [PATCH 0046/2693] indentlog: bugfix: rename include paths --- example/ex1/ex1.cpp | 2 +- example/ex2/ex2.cpp | 2 +- example/ex3/ex3.cpp | 2 +- include/indentlog/quoted.hpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 1258e2a5..91e321e2 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -1,4 +1,4 @@ -#include "nestlog/scope.hpp" +#include "indentlog/scope.hpp" using namespace xo; diff --git a/example/ex2/ex2.cpp b/example/ex2/ex2.cpp index 23bb2b3e..f329b9d4 100644 --- a/example/ex2/ex2.cpp +++ b/example/ex2/ex2.cpp @@ -1,6 +1,6 @@ /* examples ex2/ex2.cpp */ -#include "nestlog/scope.hpp" +#include "indentlog/scope.hpp" using namespace xo; diff --git a/example/ex3/ex3.cpp b/example/ex3/ex3.cpp index 452998f8..281a6e33 100644 --- a/example/ex3/ex3.cpp +++ b/example/ex3/ex3.cpp @@ -1,6 +1,6 @@ /* examples ex3/ex3.cpp */ -#include "nestlog/scope.hpp" +#include "indentlog/scope.hpp" using namespace xo; diff --git a/include/indentlog/quoted.hpp b/include/indentlog/quoted.hpp index 5e4cc8b4..15d7a397 100644 --- a/include/indentlog/quoted.hpp +++ b/include/indentlog/quoted.hpp @@ -5,7 +5,7 @@ #pragma once -#include "nestlog/tostr.hpp" +#include "tostr.hpp" #include #include #include From 4785ae6805b5bf9864883b824c1b67582924d974 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Sep 2023 17:46:35 -0400 Subject: [PATCH 0047/2693] indentlog: + example/ex4 --- example/CMakeLists.txt | 1 + example/ex4/CMakeLists.txt | 2 ++ example/ex4/ex4.cpp | 44 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 example/ex4/CMakeLists.txt create mode 100644 example/ex4/ex4.cpp diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 06512248..7f8eb99d 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -10,6 +10,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") add_subdirectory(ex1) add_subdirectory(ex2) add_subdirectory(ex3) +add_subdirectory(ex4) # ---------------------------------------------------------------- # make standard directories for std:: includes explicit diff --git a/example/ex4/CMakeLists.txt b/example/ex4/CMakeLists.txt new file mode 100644 index 00000000..02c1f301 --- /dev/null +++ b/example/ex4/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ex4 ex4.cpp) +xo_include_options(ex4) diff --git a/example/ex4/ex4.cpp b/example/ex4/ex4.cpp new file mode 100644 index 00000000..82e361db --- /dev/null +++ b/example/ex4/ex4.cpp @@ -0,0 +1,44 @@ +/* @file ex4.cpp */ + +#include "indentlog/scope.hpp" + +using namespace xo; + +class Quadratic { +public: + Quadratic(double a, double b, double c) : a_{a}, b_{b}, c_{c} {} + + double operator() (double x) const { + scope log(XO_ENTER0(info), tag("a", a_), xtag("b", b_), xtag("c", c_), xtag("x", x)); + + double retval = (a_ * x * x) + (b_ * x) + c_; + + log.end_scope("<-", xtag("retval", retval)); + + return retval; + } + +private: + double a_ = 0.0;; + double b_ = 0.0; + double c_ = 0.0; +}; + +int +main(int argc, char ** argv) { + //log_config::style = FS_Pretty; + log_config::style = FS_Streamlined; + log_config::min_log_level = log_level::info; + + scope log(XO_ENTER0(info)); + + Quadratic quadratic(2.0, -5.0, 7.0); + + double x = 3.0; + double r = quadratic(3.0); + + log && log(tag("x", x)); + log && log("<-", xtag("quadratic(x)", r)); +} + +/* end ex4.cpp */ From 814879fd7b85178ee5c9aa8691a78ed0f14a6b38 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Sep 2023 17:52:38 -0400 Subject: [PATCH 0048/2693] indentlog: ex4 exercise signature styles --- README.md | 129 ++++++++++++++++++++++++++++++++------------ example/ex4/ex4.cpp | 15 ++++-- img/ex4.png | Bin 0 -> 39790 bytes 3 files changed, 107 insertions(+), 37 deletions(-) create mode 100755 img/ex4.png diff --git a/README.md b/README.md index 0948f28f..5d65bc86 100644 --- a/README.md +++ b/README.md @@ -99,53 +99,114 @@ output: ### 3 example exposing runtime configuration options ``` - /* examples ex3/ex3.cpp */ +/* examples ex3/ex3.cpp */ - #include "indentlog/scope.hpp" +#include "indentlog/scope.hpp" - using namespace xo; +using namespace xo; - int - fib(int n) { - scope log(XO_ENTER0(info), tag("n", n)); +int +fib(int n) { + scope log(XO_ENTER0(info), tag("n", n)); - int retval = 1; + int retval = 1; - if (n >= 2) { - retval = fib(n - 1) + fib(n - 2); - } - - log.end_scope(tag("n", n), " <-", xtag("retval", retval)); - - return retval; + if (n >= 2) { + retval = fib(n - 1) + fib(n - 2); } - int - main(int argc, char ** argv) { - log_config::min_log_level = log_level::info; - log_config::time_enabled = true; - log_config::time_local_flag = true; - log_config::style = FS_Streamlined; - log_config::indent_width = 4; - log_config::max_indent_width = 30; - log_config::location_tab = 80; - log_config::encoding = CE_Xterm; - log_config::function_entry_color = 69; - log_config::function_exit_color = 70; - log_config::code_location_color = 166; + log.end_scope(tag("n", n), " <-", xtag("retval", retval)); - int n = 3; + return retval; +} - scope log(XO_ENTER0(info), ":n ", 4); +int +main(int argc, char ** argv) { + log_config::min_log_level = log_level::info; + log_config::time_enabled = true; + log_config::time_local_flag = true; + log_config::style = FS_Streamlined; + log_config::indent_width = 4; + log_config::max_indent_width = 30; + log_config::location_tab = 80; + log_config::encoding = CE_Xterm; + log_config::function_entry_color = 69; + log_config::function_exit_color = 70; + log_config::code_location_color = 166; - int fn = fib(n); + int n = 3; - log && log(tag("n", n)); - log && log("<-", xtag("fib(n)", fn)); - } + scope log(XO_ENTER0(info), ":n ", 4); - /* ex3/ex3.cpp */ + int fn = fib(n); + + log && log(tag("n", n)); + log && log("<-", xtag("fib(n)", fn)); +} + +/* ex3/ex3.cpp */ ``` output: ![ex3 output](img/ex3.png) + +### 4 example: function signatures + +``` +/* @file ex4.cpp */ + +#include "indentlog/scope.hpp" + +using namespace xo; + +class Quadratic { +public: + Quadratic(double a, double b, double c) : a_{a}, b_{b}, c_{c} {} + + double operator() (double x) const { + scope log(XO_ENTER0(info), tag("a", a_), xtag("b", b_), xtag("c", c_), xtag("x", x)); + + double retval = (a_ * x * x) + (b_ * x) + c_; + + log.end_scope("<-", xtag("retval", retval)); + + return retval; + } + +private: + double a_ = 0.0;; + double b_ = 0.0; + double c_ = 0.0; +}; + +int +main(int argc, char ** argv) { + //log_config::style = FS_Pretty; + log_config::style = FS_Streamlined; + log_config::min_log_level = log_level::info; + + scope log(XO_ENTER0(info)); + + Quadratic quadratic(2.0, -5.0, 7.0); + + double x = 3.0; + double r = 0.0; + + log_config::style = FS_Pretty; + + r = quadratic(x); + + log_config::style = FS_Streamlined; + + r = quadratic(x); + + log_config::style = FS_Simple; + + r = quadratic(x); +} + +/* end ex4.cpp */ +``` + +output: +![ex4 output](img/ex4.png) diff --git a/example/ex4/ex4.cpp b/example/ex4/ex4.cpp index 82e361db..05ca39e0 100644 --- a/example/ex4/ex4.cpp +++ b/example/ex4/ex4.cpp @@ -35,10 +35,19 @@ main(int argc, char ** argv) { Quadratic quadratic(2.0, -5.0, 7.0); double x = 3.0; - double r = quadratic(3.0); + double r = 0.0; - log && log(tag("x", x)); - log && log("<-", xtag("quadratic(x)", r)); + log_config::style = FS_Pretty; + + r = quadratic(x); + + log_config::style = FS_Streamlined; + + r = quadratic(x); + + log_config::style = FS_Simple; + + r = quadratic(x); } /* end ex4.cpp */ diff --git a/img/ex4.png b/img/ex4.png new file mode 100755 index 0000000000000000000000000000000000000000..efbeb8206b23c3d2e70bff94949cb0024db4a85e GIT binary patch literal 39790 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�%R`1>fYum72Pkd_p49>W5 zdzIa6ev`39YKd6O!w&1^4! ziSW91GgaPOJ$4uT_cBVwzdYD9b){?!!`&-0FYswTcx`m+O3903J?&EXf4Qb}J^b3yWp+NStQba%VHk##E+Txyje#t*OsD z`zo*9{&eTVQL{VyD&&kVO)5P7=jM+em(TxXc8|$1(F=)p2`=Uinj~b<%~@b{va_%2 zL1yTihpv9JUrulfu+4P29cz2_)g{@WPlp5cY<7#A=iBD&yHw{^GlNRd zruTB)rnx>0+ZWI9To|La@BPIHUk&LSK?hp$1ci?sar(BGV@BnFzX@ml+5LX!ofaRV zbFcfIpw4;sExH#ro;TFl9-lF5W`((OO!Be2I{Q`E%n4h@*tXfq_S`;q%ivo^ttOJQ z)jpM9b1_;nf0_ThhPBq;Jfh#MSjt&7Pfqym3=$6@b(e2;9-+%J*?f(i5*=b|<6Q%fvIBvZu2@vu(+bV1Il+ z+|hmULvU8ma8N#OvgV0&MKLInWFWbtksMk&ON>DzkKxcnS9FM zHrAdqeEE7I_cq6**LrI6<{SM^Tzj;^JfhBj(|OTJJc|>LibuwpMSS@o-z~2c`P}d6 zsm#?fmlnxf;+U#&P#=jfc`u{rJ&)wV8`*klLC|=s-gzpL;6H7WQ9=+7mI)%$o zP3hAKQ$b
{li3%9({`Na0D;P=B>-N_r<_eQ>a` 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 Date: Sat, 16 Sep 2023 17:53:45 -0400 Subject: [PATCH 0049/2693] indentlog: README: tweak formatting --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5d65bc86..1db16b19 100644 --- a/README.md +++ b/README.md @@ -209,4 +209,5 @@ main(int argc, char ** argv) { ``` output: + ![ex4 output](img/ex4.png) From 77d58d6a3e63d0d62bd28c9acadb5d9a1cf6c5c7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Sep 2023 17:57:54 -0400 Subject: [PATCH 0050/2693] indentlog: update ex4 screen capture for consistency --- img/ex4.png | Bin 39790 -> 40417 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/img/ex4.png b/img/ex4.png index efbeb8206b23c3d2e70bff94949cb0024db4a85e..56e7796dbf38af02ed4e92fd22016d7b7efe6f02 100755 GIT binary patch literal 40417 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 F1OS6AJmCNU literal 39790 zcmeAS@N?(olHy`uVBq!ia0y~yV7kk|z|hUX#=yX^w_eqdfq{XsILO_JVcj{ImkbOH zEa{HEjtmSN`?>!lvNA9*a29w(7BevL9R^{>O?wTm=eHz!c=GAWPx}nq* zRF-nzMS-(%f`gOLrgS6Y|DWgVEZ=r!ukp=@FEu}7jK0s?`FxILx#j-qcg6GEjWdpz zaEEL-piwKt!NLTxhJz*6QTuPGOa2GvoMQ?uOP_c|@9KA3>{yaj@3Ne?!EN>AZE=-6 zb&Vf5IbKe+a&>F@?5ZFD*8Ab^;m0234lXKfcQ%NKP3DZg_h;>vt<@P@t9+uqiTqx2 z@RzvUro1ncj?8)JA!)+5@87|iyE$fym;GVktNX?lAMUi;TtT_s=#kOq6-+w&;Crzk;Orm#cmqs67#t%5>xnUaf@z4&W7o2Bm^ddU0#z{$D) zrv*(tDOs(oB=q~ios+d0YgRe<98-AWx2JWv>ry_4hkw`wW#%&c-{8SBZ=VRO@pR|q zmp^}bXY{*GbA?TO>(>AGZsZ>HJ)i`qXI18V!0M(tS1!Fi)+;pCl?U15t|liyapK9}M)Xdmn`^(svF?cGu)X66sgyYpNN0u=uJ`E%MpYVH2~6AKu3r2fn4k&otRcI@9| z{9Hgnpg~jkxRS0ap=+|$1sj)5 zTh0khSL^)t2;J3Wc02sw^?CQYzfH;llkFHDe#~59v5|je^Ks>c`HYj#OX(d;zQnGi zqQ?5Xn}5;y51AgaCSq25<<|Os?RY=C|HYPnF@H`ix~*>^Q|Fl2;c><(BZupB@%pU^ ztdoBAyEu5LC{6mc;ZNJaL`tXuTY?z>Nq%?#f8;NITn3}qWzcjP{dIWyOi_g~$q8Hn4Rq=Sva<9aUby)9599OW+S44@Uq5)`-ns90w-v7!xC>1H z;g)eTr@!E_i}_Id^&X36!b z(%Zg}ug~kz{k2-weTV1oZ+-jz59e>Y%9+nKKcBr;pZNT@zp>)VbSJy!+tK~|o+kRl zeY0MkdPdj2Ic$0J$s4CyKOX%QoRPc2x#)kOgvHnDpuBkt?;B-Y(#^=vKK3#9|D)S| zbwBN1of9=Slsj@Z{8#VX?uHE+^RCHeEZLQDisi`BV-MnAU;CW3|o`t~+ zKYvu6+q`LyIG7`!?+-RR++H0hUidtS;;TRwC7loEUcMrzD(Q8XRe8u;l+tRe$AeIV@oCb z{cHN#!jsi|96bsY@zKT1TKjG`AGn~w z!s2?sUZbb`#+E;v9`Z&L{ipp&JFZ?My`A^^?@ckXA$z7z==8qUA^F0(ickEeqhR5l zIJWqBJIBceKUfv^4 zujycKb6vdb&gR<=Li>f+OSeuu{CTNvm>Zvb*Rf@3b9D4di(ZDuL>*%-af;#D^n1zY zT+Nf6HHvN`JO>-dzs&sR zXg&PRg(%{T^c8RC&b`;bq81a(*2{KAOy=eny;WZQg^gZGbB>hyMCom@vc1)-Yf)Un z?k(Lgr7?Nu_eoRs6yE81Jp1kZ1?v{ENII);yklyy^|$?=IbRfocWBtxKJX6ZS2KBN znERn($#efFPPcN59(_1go+B6h$Y^H9-aNbSONxFyNSl^ddeOY(*xeeYv;daX+5bes z{)wo3lkWSh%D!_4U*dj49XnpTI>(F^cHPSHd)nK&pDc8pTe!IW@xQ0rlS>jA|9w4K zWp{95#e;j+JLTU_kFOWMD*jej_GIdV&fujIkrVn~2CF69d-GDlK8;0QQa$nS=lSaz z`uiFE|D7+sv8kAUnV&>=qVwkuPp8Rdw^;fksKHR^vvi77kXW;z{C;q=GvGUoKC&GW{^G&tiD|TrMK1?)t z@ioTebW$uN!#R9-yEu4JG?b$tP%dEkLj#jctT42oF8gq>MqP@Vr z{ncX6{_Z{~<$6m493<6`9w%-1b52#mzFh8KU0uy>&t=I~Ou9Nd{4ci|eOtMZd!u{R z>@{f)F*^7nhqD|V~=x-;*9_UB2n zpWkb6H9z~rG5XJ6jSanXzuU#MU;p>9zQ(fTi9}6pP0qfJdlSPhruayh3o-utcS$Va zrfm0u%P&8kSrtBE=ESbn&uJc-ukI8-Pby1_Ipn>5fA5D40c-v|{JvDg8XPv~S=Ypg zocr5(-@d)A%}pnMmx~u;*RnqMy14o^m5*(|l`F(g5A>cq@%791>Fbx9@AvOL!PB~8 z?wh!Y=IVT=OMNZ2E%ds@p~BDU8nVm2FMpL=h?_$E^qZgWS^i*qeX%~PXUkKg3wJuU zsmr%Monu$yDRfuh_u(dMSNferi0|L4oP z^`3YhxGD9@r4_wTzOAx}*Uc6E!V=f{uKeFlvHid9ZeFo3LfJiQyQ84kOU}+|bN1D4 zc(>VCcHd$brx=|!o>QrnbGF$g3MO-Le|X29ee>YDL*3c;4o%^FyhNn*-?m?+YyN(B zn=;R?J9Pb?)`QJ#4(qRbuRQg1<-=LeC+vxdo_DhIFc`-sqIQx!3)(IL%@8)lXX@FRMS6G|x2Z`�%R`1>fYum72Pkd_p49>W5 zdzIa6ev`39YKd6O!w&1^4! ziSW91GgaPOJ$4uT_cBVwzdYD9b){?!!`&-0FYswTcx`m+O3903J?&EXf4Qb}J^b3yWp+NStQba%VHk##E+Txyje#t*OsD z`zo*9{&eTVQL{VyD&&kVO)5P7=jM+em(TxXc8|$1(F=)p2`=Uinj~b<%~@b{va_%2 zL1yTihpv9JUrulfu+4P29cz2_)g{@WPlp5cY<7#A=iBD&yHw{^GlNRd zruTB)rnx>0+ZWI9To|La@BPIHUk&LSK?hp$1ci?sar(BGV@BnFzX@ml+5LX!ofaRV zbFcfIpw4;sExH#ro;TFl9-lF5W`((OO!Be2I{Q`E%n4h@*tXfq_S`;q%ivo^ttOJQ z)jpM9b1_;nf0_ThhPBq;Jfh#MSjt&7Pfqym3=$6@b(e2;9-+%J*?f(i5*=b|<6Q%fvIBvZu2@vu(+bV1Il+ z+|hmULvU8ma8N#OvgV0&MKLInWFWbtksMk&ON>DzkKxcnS9FM zHrAdqeEE7I_cq6**LrI6<{SM^Tzj;^JfhBj(|OTJJc|>LibuwpMSS@o-z~2c`P}d6 zsm#?fmlnxf;+U#&P#=jfc`u{rJ&)wV8`*klLC|=s-gzpL;6H7WQ9=+7mI)%$o zP3hAKQ$b
{li3%9({`Na0D;P=B>-N_r<_eQ>a` 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 Date: Sat, 16 Sep 2023 18:04:22 -0400 Subject: [PATCH 0051/2693] indentlog: bugfix for 'simple' function sig style --- img/ex4.png | Bin 40417 -> 43138 bytes include/indentlog/function.hpp | 18 +++++++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/img/ex4.png b/img/ex4.png index 56e7796dbf38af02ed4e92fd22016d7b7efe6f02..38035dc6daee003f08c9018b68b5339bc304cc5f 100755 GIT binary patch literal 43138 zcmeAS@N?(olHy`uVBq!ia0y~yVE)Cxz_6Wzje&t-(FxT)1_lPk;vjb?hIQv;UNSH+ zu%tWsIx;Y9?C1WI$jZRLz**oCS*S}&e?@n^ur%U4UaOUp~=S}k8y zq&~ksbmg*;{CE3bhb`9VS_DO2@73PRi@FAiK+#L)6${ow<+>JW)OMU{s=LbHBl+R> zHopCRLA6oO>(-|%{>72}>u#P;-kkRv+Z6uJnty}+L-js)CeO-i&zBdK9N)ZA?OnKk z<+Ez7>`iqU%!lqzR(Vo&?)UE97N1TipPgHNFVn48>gjKYHD7A1)?C!zm*#U>=Dg_t zXVqy-Htc?z^6x^~w{stCuL+bC70y+(VQ^wM^OpS)BmDyIwUhx&rv%6j;=fr*6 zURu!fexk$Ghu`X-{49Qd=c4F&|L}X_Y^IM|?#O-3`SJD6`}?TWt8M?UV^ggE^5SCJxjB~m{8M*+IK+LHzwSXO$MNU$ z>-Rm40Y}|Z<24^Gf0aJJ-S>a)KTmzOT#311F>?gws`-Qm-MFmwd*=LqQfv#?zdQ4( zQuEE!InTGg{Q1%=>Q&{s_ZMBS%)S1vZmVW>tLFDVU)ZX$|0W!4yy}o>n#Hv5+yBw0;e z{HST4akDsYkFn|2dUnUtkJmjs^z2|p*01}?clYjVXWg7`V)Zxgn{ANNErr=}|NrOA zPW$??u7Sa`lI`U7v&`2!^|kuE4%MVx;>tQ;#WnZcpR{e0|5V>Utp2Kwb!)2Q=EPU? zjCNaf>{);Cm*t`T0tb#hmD%5^tatwDJo$pO=bqoEq<0w<-F;^Lu5Q}y_kpDnKBu3G zFJzCA;G7=t&*Iio^^o&{PajE451U=pCy|n&IlWhTjpxyld0S)xqoTv>o;N+*EKs>$ z=vnr0Tl)*^_?W+&|e|&cGQ#h{&oo{)&;F#6Qm_)50`?|mG zSN-df-$z8fkhS)z6F&dzIIn=|pF1-`*>8XTz4!AvCdZo7y4y|8+kT(({xa9BA2&Cr zceDL_*e-AO|8e~v{{LMQcmDZ)zkdIYm~*QY9#y_tx%|x9?c4H_yX9+sB=%@FgLABx z`U|06yJZW0e$8ROG{3ylOzlQsPLZmYZII5%;a1xm6!m;9=@$ zNML2MdZRG6__ksHqN-hMo7XS4J|0x|T0^{a^8bCj@4rXSy=f3-fBF2ft((Q&FMp1c zwQTI$wWzLZepO+|{r>3n)*YvJ7KPltKW*CWFaP52A6|av^J9(gGS}v0t~fzJ#Wgi)B5K{ckFq*V_K`p*E72>EsOa!ukih&3F*yMdY^L^1U|jF!uIn1 z8I!hrUoG?YlxX4prX#lYPv2$U`qsYHa|gS{E%BAJz2CN8c)t}o0{VRT*y!oBcDpxn*wl15>p-Bo`{5>DrW*R29&G^Z@f9K;q>oaZAc^P^;9x#3UJ^%mb`dOi?!)9i$+v#>L zv6=VH;hdLT*^Jl=*3`((C+BpDr88n`~LfP`PgN+>%d60{<{ox{Cd+#3FBx{>L>Tf4=UJl%KO!o&a7ajvYQ`^`;JPEmUM zkFMWSJiYuQw{81VjqvjmpKkiD(^qt!ZAUtDuU2W}%@VO&ch>U>Rqs!FSAXgACSP zcKPG=?JMlgS(mA1++OZ8Q^~1j<&|wsWp@hQjq~r>6hH1Y@0w6|nAd#9_j}dn-|VZ- zPX69*|Htv!S7U{OqoUy+HUB@#|4*Ez7yGK+AnneMLL*%VND-<1Lda1*SD8s^N3#2Z zW>0s)x8hPy7Az2mS)g@l=eg|9#rHP;ubsyxlPfy+j=>X4PBE>;$vrDKN&B40&(l}Q z@X~hNaE8z70tZ9Mn(+Av{hQyc6#euv=kia!!%79q?zL54u;v%mvu^T?+4b%_hMvhdwuDo{Ij{~uAgGkKmVD3uhQM4iZ51{?QB=<&897Pz8_E!`YN+- zzL)K1P^bp!dG9Q;jGEUwqe|~*voN3Jnir4K?Yj7;YCrqw`CCtYtG$DD(}dSP?_CYH zix}~>I7}-y=+ns!Ir{22*Mp5;lkW@bNLX>dzx?Wd;^(Z}mIrnfB&G?Z;S03^6qyx$AeP9 zlyjFBv({eRw|FVvS@!c>QT}K81b>9jfAzD~cm3(cohDJiVXFLm8b>lkXXZ!unItYc z<@0>bmdhOJn~X~BZ(G)#kV|F>erC%)vF2)J{c_f`>S3&WTjrJ=n;Ab_^yFqy4e2Bu z0WHR0!4>@vx*j+_JaVzD~s(O&<; zlCsv)37gvQojvo^tD^4GzSEv>``7R%Tt2X>?VxGQ=lH$ZvWlmzB|_~dY>N?HJOAw4 ztsTE6w*JVG+sJd^iClQ?jPl;(onJ*KDa6m+DAu$@_YI%T2M6PV2M5ZEKg)9bcW&o1 zEq;DZRCjOjjMwamdiJyGw*1Wa+MK!QZ2Cew9p<`ko9B00KjO&$GV}41lart3LrSy0 zpc{$T_x@X&FzameXJNIMYzcA}PDPvFUUoTay7rg*`x{U4TUpMmRLS{l^L`FrRbsaN zy4bbudrP+kpWfL$*}zo&cEz(Bd@uLkD%MUA*!FY(+oSLI7-SyUvTeiSQnT|rUODIf zzg#wDcHG9yy$@SGC#V0q8v6UD#kS4$>ytn4TKIjt<_*8}*IT?7X60AcYD=FhyQlg# z(`?@KKizhjhZY#FS@b?#SpV0p-)pYKrF>35{qRiOVUL@u-%f1o-(xG6^0{f-`~Gcq z^EStYP0)GsVCS6#>FY+-GTHqLS1!H6SzNu-|EF%wl;ctR8O>s~eOzqS1=c0Id(U(3 zZn3;%V;{fCzf;X~S;b%Tiel~Afc6|l=s(%ON z-rAa)-ZP!2`swj0z3-={g}*yJ{oeg6TklxUt`C3z^xi>*!mrGkw?QT7E$v7AR=4tUFtruL( zuQ@aC_v#bI{r6^;FFrZ7@`2dA=@0&97Vn>+#XRBrj!o{+tW_@d~_ME%f zKyiQiM3?O|k~h4`^sHiUR=MgwD}C#uub+$0uV4JOSZm+5_b1QA*){A>ZBR7v)zs}4 zujYOIeExRx&ZMU`XPyaOc${Fp_jdmNyFAinXXaEso4Kerz1sF%*I*ynxeL`E*ll;f4;0E2(#k z)^6TltDv@rLxe+&@6%-I7rOhVsx)Z5bo6kxNSko*Y_Z?xoG)%wk3V0Y`#!;cwfy#} z@dDSRI-6S=!+p2cmjvE>zvr``;E9`h`g?BeTLkLLdF3l@cC+76zNPqwrY-lDyrcbV z60UcKzSibtQV-UQFqBr5?@f5T_@8U{6UoU*pVrjA;Zy#a!>Tby$0oD2X3K%R^Z(}k z$O&Ppt()|+^XuVl%@5z7oW%dwIVS>n)&R<@<{R$2%P?aF!da{k{ z7o^t$A}_TEI6{&zNO9mV0j(F>w$tSQJYauzEjoXy?)tKusp9v|W#43$TH<{XZT5@6m59ulp*OSN+a#>$m%r zVVrqMUsQmG;{qJhGmhH)XmdA2-zui_OC#uHVvqCAP%~AT#m&^XY)8*Fj2`HG~ zDR3@+GSS^@SLA{i6O$uvFYzs#x{Pn}p*_D|t$ub&d;J&ZE!WPc&CV5*UKaE*MCjFz zKI?ZAOcuT4o>FZlGv)oV*?E&5_edHW-Cw;f;p-1;pM~#YgEv39Un36*d!xX=d)Gah za);sbyi6ng=~8Fj?UX9ctlO}2*N?e#zK31<;=1`;_)G71`~Uw-Tfg_4(bAxk-$SSD zb53lJ{r+ouQ>Iz|+4`&ByPhwZ>}Rz5?Y7xJA9d@WIWbZBY`c72MqK4n(f_NCmtK!e z2em9eA7JM9a1IL(SJ%I|z_IyE_WHfg+O_zl?ke|L9J2ZSX7jU)?((L|$NNm#WlJVh z{{Q>^?4zUIs%K)$@2cuQOYXN-%iL+uGp+9D)9FHLytDImt-M+L{q9-iew(Cu6^}SS ze>$z-J=?y1pNSCPr;?XTr_bs&yX9eWZ2kU!yCSz_1TNc>(c)+Q)?}sgtHVoutFEZW zRKMMt`ufAO+4*MCIU8F!w(FhQ_xoM;u^!3GMgDp_p9lqqhNjm2zJ1?o#=pP6XWM?e z;p{WzUd?A;&gN~^pU;~6_p}>L?^fp)k11%}`D|A9626$3&2#Gi{Y-1-H8ZgPdD8#Z zmHoQ7vqCBAmT{uAH6S8HefS`_(v^&NgT)_1$#+ue5${3SNi=evCUpTgn; zjO-l=vh2&Q%{X+Y`u*NdlPq;j^n+$>K4+y})+f}Y>twQDectNz`=VO5IrCW_;ZJlC zuKn}z`0VO;JKYa0U~Q}YcGJDjanhxt3m=5~iyhzU?{`-2QIpfPJ7iUPI_ulxXWiGi3fj%VJ#Y$NT+ak!MtEoSDW12(@zh5u)~y1qN8FXPeO`!cd|JAOXeU-Wso?_DGQDsn*CpMXN&(S(Bq9UkcFgFy^Kfou@ZUD#N1{?{#^KMLrwkv zuD6>b8~=)5<9}YoKl|ybGxaw=ggutU@1XNLre zyGx%xVc*)a^Tt2lIN8iY7L$Ix%KWt`Ffo9mEVMo}u*zyC%U=^G-C21)w=V8b^7wR# zNqI)%wfOp4%RXsyzdpwwZ+E}nr(M68)%}I@4rh*%me0mHHw^x-|E99^>5adnCJE`zcj(@^XtbRpL1Fs{<^Tx`S0c&k=1UqU#KEKl|81_M6)GmYZGwuyXIe1uZ9UzxI9YV_f<=UjMpG=>2nMf5qaJ^f)hRUCUD` z+*u;!Ht#`vD5%(6s^2uBzD4v+y7=oeXC9S5YYl3)y!uNq=kTVW%{w=52{QSj9$jyt zYky&lmgw2FOT%6t=j~B<*ni2>xb~9mb*?8%zIYUFof*5zTzKt-`niOm z*zxzS;y8ApHz1lwGE2r18l-7D^nhY*B z4coEaeE$|F>*AM|`?6=9k-K(3^TYp$ER+5y$C-?bCQl~&OcnhXeo~!ZSJuZvC z%>mVELZaed1b1C{_Bm-|#Qod(`*rK~R0JyIv`HGJcvuU+E=n`!XwW%#>_yz1r;Z^} z2M#bSv-4qoP;pE$y<^&s6^r|_CiCmC&9LA9d|q`|!~T+&L7bCW-6ZXQzcK!=Gs~p( zL>1GK)(`y)dS==&b3EJ>oc5{uqO17KU%#p{U7A0B`0aHhnD3hPf{({GwlM{;J)b_U z&qg|y*U47x+qp0MGC!EyzaOn)#QpPEzwf$()pmCRLG}KWeZ0%wWbB$-X`Q!7^a*=} zq9sSuCnxO&`GmuZQu*~a9M2buEK(4-~T&pFj)K2$AT9WTVMzvgn9 zd*Aj_Wn3nX^bE(qVD`HJcY{ zqTySGZ-qZubo9rAKb2v(kNX<13a+$tJKOEQXzj$h^NPOlS6UaIpRsiRDvjFCF0NmG z8NBCzrfvP)$h>%V_l`g_mctJY6>%Jxk;cCD;Sa<4a*h&j&6$dX3Nsg;JjP?g(WJ(d zF7W4&)V~4=_mC)#3kIpDL{1v>owY4UIaf2yD&+#>!RfUT`ulz)ojmQ#Z~G)Z*Z{?zcW5xe5HKG`i@gg1Y68Bd7XC_x7`{0qJMn;-po=j z&$9Nn-?v*YjGCFHTOY|xJw5xw1(#U{eLMD5%`mCGxbEG0EiZrd%ipSI^e^tbm$iCr z%E6vz&(Z{U+?dy!t9|2E&aEw(X|LCAH#0Rg-Sqb6!*+SS=-G+8WanA;v(5agCpho$ z?YIqlrKJk`n%Viqa?PUGotmw^J;{?|swP)Md#()%d`!=EsL= zHk*IUoo(MY!Ovp)`~-<_JNOUn`Iy{qyDc{A&)GZ{$KztfPp1D-%vrwH`IGZpnesb@ z&sv2246a_i8YF+-=5tRByVyr*`?@_GJ3*bemh@O{cgFD zh45BW#n%#VcD-J=@%HdSO?{C>ClcJBV^E*tNsEz6v{H?^*IU*^g`v76tB zFRQ$ex6C48(x2E>lDV_mHav4U_fLDqvV*&4KWns(t{0nfa%Rlq^iQ{+9sFkgSAFY| zncC}&c0SvGx^myW)5VuJ$e-Hy>-Du*T`9NuS(odthpf%`ZKDlJg_o)wCOX&a=T+~W z8ur0f<@))Y%+{}2x9cU+e~U!l-2Zu-{?0ohsytG^H-BzUnfLF%=30~6&$G|WTXt+u zij-?X-TEsjX;Z_t7p~cN{##Yx?-%hYk7rGkPYAoI$FraJQpV%Q<@Ou$)|@?Uw(OM8 zwwL}V!nd}}o%cRHYt0_NeH}Ad|L|?7`=5Kc@}$4EuY`M0ysdA%i5AbHx|sT@zZc$q ztyw$Mr^!z39!Er8s85_h+=RNnzhuL=5dull|1=8wmuPamq_SpItL_IV~*+wYb| zTfa4TQe)C(%KO2ZvcH1G^4VQs|Ahep2WQm2lU+WisLO=y%xyJ!NAbGM$(NiSxHO;P ze*dXw!D+7FeA})ret6(dyTO_V{~mSgZ{vKNprE3+)*z7AUg}#>>4~OBo;?a0`#+zv z-XvdFq42cb{?9_oHyaMC{Ci*j|F_VzV|o$onU56viaw?^78wZ$t3F}wwSJm*p!ubQ zY^9lu$*-K<$9FNboOF_Mw7PjQg_Xj=2K)Ir&-N{;{bKlBz1MQ-uCnVfb;qI^>(!;x zvhSZY@6BbYW#hEl@_hMo_EV+#jt8&mO8quTl<}Nt|xiAE`^_%Tk|h; zsnqX7ck8dO-8X&Fno5brxt7JtOn&j?eK7yL_R^sTtgl@gqTfS?c~gBGJgtJwnk}-= z-Q%i$V!ZI{ooD&B{T!ECUi!`Wbb8uN#{j9&s;uRUzOWc&ei2=|+hNCXuP1-M-=BV$ zcgA-4bVdQzV7U#44#fPw=q~@Y7BV%uYyPTLS{of)g5Sp65jl2`=~h+CkDW`a&MP;W zB<>P6uQjQV->@lQ%D#@QFAxnPt|wjcue{Jcb<4#y{BAg?ed8J^vh;oNJkfinIX`3` z(&)NmiaOKEAqv+UD6*7)VH2dg3(6^rz9^O$+ATeQc$!(x(xB*Q?dqjzcbDZqpIdHr zzvgrAo5TO7@Bedj|D?J5c5Lknij+DwC-qaQ{LEGP&wto?`7g^n@o!n-i-X5R*k;X2 zEuVR)e0@j#;S<6>pRF$YtSel(^53Vp;E2EJAN!T{=I!&k_Uonhi)(RRmkPJYUp2l^ zw&wc+soJIf%?r-CGb+{}Y+`-(dj0-y>z6$KeBOTg{oG9_)t*g@&NJNoW>a_l(#L0J zo1Z^3)7bsap;qqN`x#+AbMjvuk9#mbwC}3){q(zk|3y{{*mzVQJ!0SWy36K^%C1%O z7ViA7vhsY93IDybTn_59ewFHftev8>WbJdW#tl*1|CuVyc^)3=xOHXe#+5aXg+40# zmkPg|QZ4NsWD{6&^|wcLz&_s(x0=sAQ}q7(#q5-Ad(Q7yxBj2wk@+6#XZtmzuxpab zw7Mxlm0M(A+ooK4k-zilv}Z3C_nS>t^EF!R);meM<}k1Mk8s-$_V4$6K4W$}Cz*{$ zLgDv9(4-Kkdn~vZYxj6vGIIAgP`O3G)a%77hJ*QsdCg7SAMQ{4nlr<@33p96Pjh>z6{m@^HBHD)$*S-Z{}bu3Y=PaofD|54F4aS}gA^T(#dm{a0<(zW2A~=Dofu z{VyL>T(H%YY&mKA=j{GR504h_?|uHg^COGniFaM!FLM5Td8=i$_NRRnUq9z>t8;U| z=AW-}y7cwu$BA5q~=VhzaCqDa-;UiOW$8}9(j7b@b}y8-%r%dtgbV=owNDK zzbq-MOZ~4jmOo*e@!e-~<<2V!W#_NX@c!I<_Uh61Pxfq|wCw)uueI-W-`=?8rPljT zv0X+f?M!^_*QxI>&-2#bd&Os_k*gd>oC`~cT*KybR;T$V@|kS>(|2F^eER&_v~xC} z&zMPtUXn7)nK2`o{qP5c@)mcwN|#54dH??W{3pu)spY|m!#>VOxs8&JaQx4*I${6x zR>@`G+WFr=gQTgiKdf54PDs&SqOIoRA((MKT@-2x6cW|PtxsjDXaF? z9r_Yo(>deQqPxeY)aEYwufBZsg!|kXGM=|58E^GobX$!5ec;(m_0tcoK34keYi;z$ zM}}te{;M9J=M%n#Gw$ynqiuCl<~c6?9c9~FRJ(s3n^efJ`R6XmFYUi1+*)Jt@5ke# zt1f*FzhCz|_vQLa+uy8wGhwxv`;*_ZIhQ^7{e}0^iNfizWs&pi?q4_iv@_=1|IIf2 z_WyQp)b!haoALg#TwI!z;xl98ThH9OBu;WSKaf&ip}0UVYKupmzfE|2?N*aCnwYXe@;D|S`}F$@PZr6#ch}#_T<&>q)2uZ+ z#9OyKDkyz%N_3sG+ry4_!HWqJud*K<=@epv1nhU4cX26|b-QMlUXKlbWf(C{!QbYi z%gyJO9I5d;iej89f9lbe>WnvGd0#ip_@d;~;^=&)lym#f%gYLXO#COx|Ht<2 zobS=;MbfE$QTy*r)qQz2w_tL{jZIB#+B4+!@4sAlhvVnf`+wX_S~!F<%mu$mw;QZ@ zG*x+5%;(!vPEWo1RCSfl(^of-YQOB=@4RmJr@;8WqRhhiJKD_TW9A6?A9!=KwfJ0S z*x`ct;km!!*FMa-x5#Inq|C#r4RMF2a0kDWEDpB6^SQZu)$0PAgXzlAw$%?Oz5W?t z`8@x5(^sB{>826u9{lw=#V?X=KJB@%<5n4)^E)4D1#dYY`=eG*-2cnwTl1C_HBQm| z5VLcW+ui@Cws3y@ZtD~DcZuTJ>cD@M-2ENgqYGs?67&jXM4Mwa7eTNR&)y*_u6KZ~bBe+f#{@zhAFUf4rmc z@e_%b`}=;s+x^LWRbZXwSxGyd%xa~R$m$^i;XEqe^%UWHr zU#-LYSZ_jK{n<&k*X+J(!hh%H{!3hQy;zRd{5_tukNfLmy{0bpC5X)JttCc>;8C+&&wiBdJ1@43=|%|va-`%|2yF8+c(p! z82^>^W%G&iiL-w_S?l&$GxOFS5%!w%S9*7!iR0z$H+{S}`KyGb+e8tyx5n3={oMFa z!+x41-}{>uYTs_22s_X3c4f2st*URQN^^2{?)eiFWM&_*X6}Qt%l$bsKk2k*_Nwpd z&64+uY+YfaR%lB#)ir167m{fF)Q z|E_&Mxp#-n=RJS^ecwO*b-P^EiXXo7?e3bGn9PX(|Lc0uJkeigGCm5b%&&g8Q*Wb# z!wGj```r=gX=3}0^`}UkS$T~=QF_|cS!d@iotoyXtuR05@53#B95$~CG1)#X?4)JR zgX~Z7FPF@;dpR$gwJ4}+$&OoR^sdhC3_D?OZhnFLf$^>>#*1^Tz2|Y7U;X!Pe$iTH zkqK%%phWy@@6@{cQ%${(ia$<1$J=YHZ>(?FTYP7aqWSr@z{Ls&+rD*auQRwFTOQlp z=^rqGck^+%>JuMtrq7?6JYmO{12eaMHoTd!xHlzbvWegp*)yR(A2joy@io7zQuxr| zF@KvUv%cJ+B%v8k)vqcxS$3Olxh^MfW_mjPrgkoC!?K0!r~iEYCx2ex?RN9^+f%HcR97<^HLbLeGCgzj zM=6Q1!!!yXxM2zLWp){2qgE9ID!Xth<=^Fdna) zfBws*Pjjn}U9xMR`Ye3^uc=?e7yYv3Xv=t409ry(`MB5IWyhPGJ{$?Q`;naqX^mffTk>aLo?>KAIbdn9Fk1K7}o-u3n#zVDxe=ZfhHPie2 z#kx|^K;GWB^*W}r+}AER)4(A0$#dB$uK%l31P&P5g&WLgIcI$;F-$=HUa|Lm&$zs; z_r+Ph&cE?lfw{P{c%}XS`3385Kc3fU<2=h?g39|6mV4!FldXR3)6SmO#r=z;G2?S( zr)1@li3jxat{8dUwn;f5P`RRMit$HBh17?3Zk&(Z);uz~ZL?>FXyU3vAA5|?O=z3k zraOb*uJ3wW^;#XT{oQ_Y{D^n#?D z@if*;OOxeIf?X{fm3OHrPnToA{4-lv zx~hw3QimL>brY9Kgv-4)Rew6XPDQ(pv&A4M-Ctuo4{Mp9(FFvPpPv zB+F^cy!z3lro49|&n!IJ+H%qB;nGs~?Mt3&JiUJAfoFP0z1gLWYOgGL&uE@k@|gaq z`Op4am!$lk-dfQW^wCUl){bAE&gFZ>BSI2H8(cdx<1%Np$?7oGPs{YW%Xxoaa=&fZ zvJbkEn_TXEdV2crYT3n`YuENQ?W`7S`rD%M+~fB5)gqB>+Lt!mulw!!|5^F|-*ewz zHceO;uerg-;`yB7C;7{KN?0?#T%5&zSLasL|6R$hdb|3C=GdRl`n`j5nZjC=<0~pZ zdVblbzuo;q?eR+yFXx7YTqti!sd)F})z#H!4>Vrvx8e^HG1+m@)hnYyawkvLijT>D zqD=<3@>!m!Ji6qh+!t9TU^j`Q%khv?atL^|^wEK?Ur!|4pUGJmeYf+L{nYqqackwh zqvG*8l?P>bJi7O&EoIF-1#Xc@$h_=%rqrWt{`sG_@46r5mSVrRJ~Pi+XlfGZs4G;z zc%##2F?WM=76K~_Qjh+z>atkqW>WVp>Vl7`_|nQA<^8{GE!%us45zC8TVSTw5x(x) zcR^nLTgPvyZr?WJ>NDs4zR~BOdQ1+RaW3QklYO^;6+V0Q)Wg``TF>WJde6?sjum_Y zF|t!D%(NoU%T|^@ILn+;)BDd*wIfEo*Amnz%f*26R6m`%ZEn|+Edp(t?7Sa7 zJUkrz>CB_@JUek-nTIiTO^Jung3o+RQn8&QR=-)=@j~s#qvDg|Keb<&w~g;4e`(R3 z!s92+ifS&b>`$Lp>9%gUC-jzEpY4&dgzMAKrC#NF zU#a)>_i3fXk{{h$XU}ZAxct`kiV2C_>udIkr+;pE>+|`5?2^y7_neE{G;8a1&GR?U zy-iP=ciZGmyV<&fYS;gi-wwQc*QDoXXU6-i|KIfAo+($2XWst(k@Sz+%Qh+Y!C&Wo z&w28OeM{kuhm)3R@@z;v<{c5esO;krM~hRpO`fnX3z0~A9C_TN&*t2-Z2riY(^L1x z?5wyyt8GKV)+rh@nCo%MP`qsSWdQ;xTe%ofQ$uB=UQAF*mvD*3mw|geE&p8|Yc*Ef} z_Z`3OtT-9+_inVvsymwle^z~aS07`mKK*rb$j`zltK2>5i zf2XvYicPxTZSvLm=E7gEuel!M5MPtNtbfOk!d~Tnp076CvU~qdlKH*mo%?M!Z9iG^ z9Qxjn&viSZR3vPPyRn!2gU9{RYh!;byWD@``R7fO`RkQ4)@?p#_2~Ha&$ILQeRNiz zymbB4wjDOiO)9nXlQwE(zT5l#UeUkV_y4Gs)?J=-<^RhL%bK8_8qBo~vSl|M#f+Zq zyWGF{y&&Igwq^ZEi6*Yw`Jd}YRTpT!*K-bc>N2+J&$-hRPkcKwE?`YU$7#Bc6MyHS=l z^{>yz+X}sAc6HyGUmv@*CntH;nUvZ8T53YQZ@m0jx9r%di%+^|L>_q0@cR3Gv$@m% zY=L$`f9>^YKl^FlQoa-?maQfA`yMWs8@XNZI&s%zY^SZR_J?w}@u*p%%pQlr%+}7V zEg3GSuPVwr$3?K*DZf|g{;i9je@RQHm$vWCo#Kn#`=|AL-Ps!<9Ka?uS3^-gV~d1| z;`teJt?yZj#U9UlzIPk z?-u5(E~^mx@7P8M*&S8OWc2uUifhN`s;?0LouByr`nPTW=5>fVO+K`w@>oTk#Do*K z(;Igw>2G4VJt;cJZm(BGk+9{0X`MdvsF{q3Uq^ImTJ_(b~uv>$7nb{0AQGD|FeZ#Okp_+Q%m z`Lfn`)hfOg$K?E-qI|ac;Z^^i&m||Z{9U$?{Z_#@yC0biTLQiRP5<-u!@R?79nUws zHD~=H=khgt(}4xnP0D{87EG0$R>85Mru@bK4PCbP<$kGgZ2dC#QdZOEFIz7OOiy2s z|9Ia~4J(Ni=^HI{#I<>AXBYjQucI=Rdm-n-tRo$QLVf>q`zn9S3VsRvB*1^!Eb{Pz z#YdjbOmDa2XZrj1dv$d2rT$xayWc)Bk7N_C`*B!))?q$tpI1MB-~Yew%T0k#)|c9K zuJ`{s!S&?y8i#(d+Inq{#Z&KJ<2wHNjrGr+Uyr}}cj%77=e42-)_)Q|lKjrv>9g9h zggSYlS+Z*`^&j}RXm8&8CGTH1TH0rx(Y(5K_p?vN_qcvo-!zE+owqMDNBM=Z$A^Ws z_O|u`>#DiA*5zHiv7Ga<$E5N}Pb{v#e_GqV<$bm4EyJa+Pt5&vdFIkri)tsn-*xqS z(3E}5Y942FlRmUMPMT?)o)$HC+ChEZJ zq!jVif3BT6kL~x1#k^^$b=p@u?%!g&m9y>VVW9>`{;=xuXD@S3ADdRBaOlDPTeH~N zKR()d?$~Er`+4(kR@};Wzdij#kbNz$Ptx5!L%tRt)85c2pS~Qe6RCa@EqQOw?LTXu z3p&m+xZAlcd2hRqRITgFT!lNvUO)f+et%kz^UUr3%?uq(y-e3+X1q?`bxm^Bs#Qm% zngqk1KUyZcX6=+ue_eKmt&+Q)#I){_dchM(b*1uKRxFXBY9# zcRME-2A$1Po!6DOez`_h5N8qZ{L1K}l@nka2WJLitfK4!FJJ6hq~Rs+^!$Z(t=zWc zMCl2)ZbjXEZv6UNmG%2Qn}0r=ouBgm&z9eO?x6YCrz@;X(!J(OUj8#nui(10MvZ;? zr{Fj3TjI}|oYkAP$o}#Y1KV`2+qbHJ_8R)07xB$(d@k=EV^ceI&DYD*FXXNiaSfdE z&T0Gg;9n*juhQq1PUCpREv93Tzwc+-v$tO*6lR!YPO^Nlp!v*k`T86&{kS*rXOAtL zotIU1zqZ^s|DMg~bJp*d+!9;%?fxe<^Aj^3?cV3!y5eW%J(05WGoOVovYyT%XDa^O z_p1G~Bc-l#RYI|$=O^4gaqaoDuoXXNzZZ0_n|9Vc+(hx$0LRcIjI2Qiq7O zR#xd}PR-HO(}_&CUHgFRr{J3Yoc`!r2D>LNpQL&8&5Y?H{vunqmj})+Xv`JPiQB!{ z_e7BW-rl0~i3QQCo;^!5E_&i2*$@^r(;%^_Mjy0F=Kk_eZ+1NHd-i0qzgh5dKT-3X z8yoz1pJ;b2`r=}g`DOc^BJbia7u~xr7-wD4Fit-wQ;c=OYX)q>Yhu1s!lTG-IWsF? zENnk>nBV?QylL9>*fPW8GQ~YN%WmgBwbAmCT4b_TU}4uYPvuE&{-?<=D`DEgx zJTVRN>dnI2y{%`2&9ds@YQ9kMbZYo7MVm>Ny0o`H{&qWm`Yxa1`_)QMexI{`FLKao z)ttyvM?n)_kyfe3X=hH{eGOJc>YLaiVtKMpIC*n)36VLX!SAF-M ztGLQ@`)B>aPn8+XzUyBdn4_7KetC`QvM*~zmEtyM+Fi3{$`8M493q=~+SD}t)%i`O zv1-3w)o)`uq;xs=={xT&S3b`)JYT}{&7#J+B^o1U$GzUlwLS*-^Q8xYPtEOrV?TW1MH%LNmAmA@S;uOQbO`>t zS~8*j)X|Jmndvb_o;R(YFrR2QkZ7y9SXi*clUw5GGM#gQW_C_j*q7{lZ#z-g&%&{9 z`<r=eA z*9L6N_w%`VT}>+g^}d6=OCN{4ea$`%)TcNouJm86(G`?wH1pl% zj=NQ_*DhPR($i;xp>6fkso{TC%k>;d>Z&|?lqsKzwd|B zA-0`A9(Akk{e0d&ew|{8a)ROw``>Ri3q9+v|Fih#Q~i3)XE$0RcN8paS-E<(@%p{r zX0;TmEPd*;YJSBd&Xda53pw`iZP|FFvFy*A&F800j5syzD38pyyXEnl@0Q*6j9T*W z5@>oM`crFy@6Gl5er0{+zOJZzZ2A1UT|fT+d2XN2X`Fk@#JK89#^=}Z|DzshPM>C7 z|M%LqVu2~OrZ+v3VYR+}W zJ59d@mOfiD92ZG8G;Z+KtGQ?41}lc(?bTf4*O=_^YK_qt%|x)rwe zm$l>bmhgU__Gd{>RFHo96W_X~>26mU=bbdx%lP)@(9Z0GMr@AW5w?fhHb3Nazjwwf z`rCw)#?8uC)3>jkZt(Qbl?ymly3D;k#`q;jVztAJAUPD7VlMN2klNjv|;hw^*(XZ zD|Oz?d)-A}A46xq#8)qF&XKa;be%uD_L$G+a5GQkr^PQGRByPMHoNpx$^3r@I@RZC z{GEK5-`-}^`cO@Qkbc{55u1yio-#}O>S$cC<%x#Fg@ViKlE!I0mEZ4PJ9OE7N&i99 zI`e`D4l{+Guahmk68K5FAnmK;R$hs@7lHNY59l6@_#)Zxf=V+8_ZDucdz1c zZ|W`C>xTr=US3)%Yj)TF@^p_^m;b#h-~ZWJvuja^*zcwO{8H19H=LN?EpK)sf!Xp{?H5T2 z$L8}7T9ZNZv5)iXe@9z7x!c!eOTB8U{8_m8tE{H@`3K;2yr+U{Q|>)EcFXRCaM**| zJt@{-{X`cW#Y-%DI`Zr%(IS7I!1)MVUdorTLL(g-!1#Ha#+m7K_&3aY;OGW|m)G^s=I_cm6C4 zpLR)TcA0(jwA^B$zDABUd@-@py;uJ%-S7A3diVahk7pfjJ$%%$Ht71Y`Q`Udax=MJ zwZHkrDRi#%+v+3c?VX81_Q?mIA8%XyCnC+_l&$pL*z-?MO*ONhu(^oie0r3}hrC+vqJ%I395P*5CVOQcdzp zb;XQkuTm~uYKFPk|QaL13D3y9<${qF-XTS12ex`lD_`L1De)oVA`yO>^pDDc_ ztNzh(M=;(=Isyz}L<*(!Q6X@3qHWF{R-bIT_22TYs#a<{ORd4$xFo%gtA?Y>iS zJ95k1Y_X)teoNM$iO6W=~Mmx|Caf|LyKMOMN}OAIb~e*3FL zKD>{U=zC=D!q;^9iut)ur=HA#O!*vG`@QLa@BIclrsoIqE5mEF>%Xp!_q?xi=Xg=t z2VP#@MZNBtjm6WZ`dxIA=T@Az^Fzz?9VsV;K+V|GrBnEST;9)^9@YPN*5k|m_ERnH z9$=38H*3}`v%j*3gzewG+x>o;#fj)iPAgWw$e14%D6({Oiu5Xf#$NGTHDYbf(PuKW z>S{_BbzCyzmo#!Qx#k$QCgS0LQC?onXW{28P+*7+l zS|@=o(x0{1oGs^3O#V^M%{gnIP0-!u^tY-dNwA@|-fF_4#y1USI(s&4FA2#0J9)|l z!DY<-nH!2Z)H7VJvv9bF9)EsBM11=FW2f7rU37A7*$dUSUoqu=b#Z6=jV)#?Yi_!J z{w+Rxov54ar1u9Z1(2p;uFamqK5zb-^KQ?ozP-amn)uM z_4oR_NZyHCV>(MhXB!;+RSuq&@rbtn^k>(7*FE78+zxMj-di5gYDh2k{qT1CeJ}ae ze4ASZ>LzS2SLLfR6j3%y%>mVrgS7B+JtnzsM%x5@p1?$73zi(j!m?>>k7PzW1a`XTun=ME&* z)K07WXQ;erFZYa$-wYGDWAr>fIP8S(7e=CKR@BYRv!t>gwvH_gk0EEnY5g;?vqUesgBlUvTw(>vL%4`+oWU%>MLk z@^7R+-A&hJ{IgNwfNs2;vDPi#fcufZwsbAhn7U|D(>`I9!^Ovc3K&k3ZvCy%`cV0) zTQ=iCk=)_{=U(~GF9lmJ)+sBf3r-i4Dm=5Y>BB+}y<_Z&3k|h;l(lqO3TGIlb|KAr z7`-^a-V#_WR;8$)_t5f^(t6L#*2L@?8$uoO+YV0j>bti)eM05`smr#Udd>2L>GIbb z;kG$O+OsrR67I*JslFJY`ewImkFl6`Kw??%&r*@XLmX-wGmbT-t=ge+Hd*GVYu!!f z%WMXai4Tt*F^e9q-C9|BhD&r`WbVhw+n#zxpMN@G%bJ2oQ^IDPn^9MM|7goC^KgCl zSwC+UCSI~jJ|oM2ZMKbaSV3vXqnj@RY>#I|MxS~*`PtphX2$j_-!I=YzG!p%eoJPj z`lGNTcB>Vwg4UhMb#j>+zQ)&M1#Bi{BU^$O^JVE2YX|Q;Q(AUB-!SRJ-{-gcF7zKb z^S!U~Lu}{l{QH6e=h%6rRJ)()T&mm-8SiG;2Jx>!$<#UiFqX|ii8D$8r!M+ zuH4v|oGKczt@dz_&Kk|MFEeXD2zuOg(&js}^MY5gj7`OcjLnjB=bC1Ro$h+t(=kcx zkmjr$pBrKmVur?7cfurhcmX zX3@4k_YRNZvbP?$3xeXL^;ZW!XS=ok`?ilajKt!%GBw42+w^qP$x~vn-)zD+ByhNsa((W%SbqCWknhM9ep5XsM9<@{+Ku~5vxHBGe>Yv_oAK$9 z*KIYGtNxK4*QTj|-0Kl&A5`XDu$?>XH2*c@%kD9McC5-YlZ$RYY9A>sBfULP)vlfK zhg?b7)!Kv!^M8K$!uVrb%3WR!-8DJq7ae^l`d)C`&wYh&?(g5v@#V{MfxKoBF4oU` z-`D!H6tZNOaVDAmsjW%hC-n{fA|iDKWIS(zWR$uIJ_Jl*5+@=y`il_oP;YJHAo zuE;o%@Z`kAOB&x^Uq5g2^U35#$Fo+MoY3iMFW7aj>UC#A@$KC0TQApLuDHygT+$|O zWfx`Aab~x=oK#&)=(eunIYp;5x6Pk4`KSLAf;mXg?P8Qz~ob*cGD}Jg^ z-JG`*q8dB<3OCoPx~|^4W9w$m=c0-&=j>(6TMj)__geOIf3)?A9Nnq%Z_7<)7bh8c z9}hO!&>I(1l2g3x2FJ8X$DFEP{@3n0rq%O=oAJal^~BugH%ec<O`%oTWcv-jImaqH%-{_txe%iMA}gvt;5G zwF&FFws1auwOl6iUbo5D$!-_)-{jm6KJ|Ecql13#mBqQAW8Ycw1Zg_jdxUfTY)I&G z@R_f?qSMXOAyVK|qtV8ra;G{KG*ShcUV2}2SCY9Ozvf9(Gt-~52~18lnU}D6wZo;>)@@w=w@#buj$fI- zguY}--dM2i5v00Q`~G2;s+vxOKld>s2$V8cO?XZB6e<_i}a z%w;w{tq^!kG~0N_w(GAOJj@G>}{@R zF|GSz;liBt-!*ghb*ZrGr~0~|Os;P7Sdh;{rysxO_ry0JvB4*>{z~j(#pDD z5;Jm>t>pgjEyxW6jnR57ma26PtP)8&w|o8ix7CU%`__Nq2w4mnOTM%@u~No0aEcuB z>Zt4SVN-N>&WaCL+^>G=!+KZIPmsM9RR-WO>dz5*Dct#CPrG`$LAI_0f(2eW zuRuB2jw80m|K;ZmYh`asUcGkhRNc|Y?bmNtKA)R_en>O*w6a4QT#{x)TKNBs|a$OzGzcnuB|XH1 zX~To<58I??Dfin1T{_G3bkRb^yy;~IYfVZeMB8j{uHW;?%f7{3^->dPe00y>Z?`vo z{Nl1By!>9}@|uvv@A9#1gV4XaCR@sFZq}@8H*Zh*#BAgHV!QvWS*rh^wZDiG__BKc zFaO}*M|=1AKV2dJI~cTy$@s-x*_!zkVMv=CHeD%BcWj8?`>}S}tM}(;*3F;y{`a=_ z=T%?Z{@i7&sdD+w=-uXYntK_Esid%A!^Jn?p(&;k0C+XZ@KhGhiPImkKy6Wk7 zCZBsWZ~j@c+d3a-uhu9mNx%OR&oKozH*Nduw|l)*?b7v=JQvrWP;Bpz{2U%%`}Fsg z=a;R^se7>1Jf2Q$yo6Ws$Q@6D7N~=A) zanQhoP4?kLce#lbb3o?_)E{lOQM96b9n4=Z|4K4fcgEHU#wl1Cf`sD&*eM+-CWq`;P;o(OC~!0sug8X;65G@7IIrSS z=ce<;0B0t-I|*?WM*t5ykz#X5a5{oBQ)+h9~#x`-Z*FQS7`IcCEknqwe?g z@b@z^*Uzxn-@9|$^Y8A3+UE7Qu7GwWgf>^)y77CjLG)f7wHuBxdnVocVg1DFp~d?6 z^`X|Gdb-P)3V3ymcWl`4z=To!oaOqSx0y0^OCqX`m%1Xw6!@A8^H;b<`{>qc__wgywJ@Y+(HYUA2W+XAy&H4Q2 zIoj)tTz}u$nQpGVr+s>S-OihZ$7O5hhkbY~)o1qK-Tv3ak|&#r-tBz8?RVUQgPp(P zmMChT?_1nw<#i~gw<`{E-t!q?p`zkhd5?YEn{-=7>2_Rslz*ONPV(T{r% zUPOO+fB*iUpXX(xUn}aJzhYi_)Kbl7hQjyen)JqeyK}boPb9Fu6HU&@R)e`U5rJu^wZRCh$sy~E*^$zlEd ze>TZ9ui5|aSCNc)-W-|c54X$j*M|RdTkNx()lux>S;2GuLF!tXy6noW8J&fWrx_C- zG&Z(nuiJUd2XvIe!oD7XAajHAcQKZ34_{4SY+YfcQp@sw*Dfo|@(K3h_Bk64vK1}a z*sZLon||!cBcHD2>Q4J!`W%~?ytH!O;SW4~pgjcM?i>Gy73r**q3+IJJjeU$Vpjuy z?{yD@4G$iF`dWO(veIQUAx& znRc%d>}%v;Q<+mBGk@CkxhsB^ZHrJ3`TO^K(YgEQAWN+s6xPa@Pt0gj^_@NaQN#P0 zy9%e@T%Fr`Jn3x3`v_aNzCe{$#{;=Cg+02Lk53YtP*xGJ0JL}Er<%;u<3@)c&b%al zIcce*tx3<%=VA}7qHXOZ0?*53zP_n_edETbpFS0P-u&aRv8>5l-(Gde$<22HV{XfS z&8z+SbowIOqZ^O0U0x=zVDqG>)f3y6*c@Kq*nH&dJlkltj+5qdudGvg`Fm>Xk|Mc=zCjs7vzNl|EV^e9rtWQEUD8{m%F(s4=hl-A=je#sy#O z1U~g|X!`pmY3|I(Kj-!YWEm`vvTwrmyXnKQW(O6?E}VR8(q= ze>!vg>-4&dr^@`pAjs2?^>?;YYAwW50jB7L)eVZe>lM%NLqe*Zz|~KVi!r z%NMGV_6vDi9fR^d`R+K>6@I(LQNjJkvnX$g=xfUkUVc(le!X~S(bn_lV;lc`cvJB+ zQuxoS@com@G*-QJGjM5c-u?UC?z7VQdlV0)rdt=(=nLpAzU1Q^G*4D7ko{%hWPj9o$Djq3g?MgJ<9ml%zXU*|9{_84d#Chv2qHU z7bsz%aWKT3t)rrB#-eGZiD3`seYksk7yswPHLpaL2Kk)58u4)7y+4<@-kw?d{KvXX zqw0uy^E-c!2Q99=!gY#=V}auQlvQg!?Dt}0lzCjt`q}frp2ae0tc8zkw||-YfmJ zb)H>p{|>?b8oD1RC^|nm$`f?XhR<<&>u-s~e(A5>mpv{sBur}G$$#AVoQ1QMYzbG> z&jrbqZ?|5bW&8b3uvMV%>5AfK!%ptTIezzFE}yS=sA9vLsb1Q? zHg{P+`~Bp25VPFC!hGxc=7zSJrfE+t)I-d^e;1oq*vyub_`D!`(b0$Nluh`m$~SRz z3e3M|RoKeyAe*E|+4AsB)GOWw4ZP}csTFnwwaJ+BAv+9X$J8YbP z%x-OniM^ZkDXK3aTKuU@=KlD|4sG%K2mI~-Zt0!Sqi|Pn;mwC^D+TviZ8eU-rsqUBGp1EBpyjH>f0M|)JfhpoqjDHxU#dNDH8g1(~ zUodHHJF)q2!@<{P^OiO`GP6lFHFIrHFmY{gY-3auwDXm4kBPU{o|^Ssbg6l8pria- zBjvk$LNdA}w?237Q2VoFvfn4?sqNG6)PCq}=U;uxw1k%$90d$y4HEed}g#)6pNBCgn#w_ z{pgwCHS?=b*UT?weKL2T&M7`;`N=$3O=M}%f{X6iawVHH`v1P}6Wy6%XlGGB@9MWgom1}m9m!Yr z{7|X7AAU30ckAb_OP5!ySn!_Zz=t~@udWWC4LU`HJMNgqi#06bKNem-w4y#>bC3LI zK|#NZ9!assOXruwHCT8ZB^h~Q{+FdFynHga`F4kR}|-3Th+|i7FYA} z=p=C;&BiS!vePenO@2O)i&<>$#b2$3=M_Hx+dLuu?>w2=?7kBhFSbaSG$V?A>juZJ zzbC3+e{O3N?=5;eDcbdS*Xj1U+1meN`|jo2T5URBIcv+I9gW?64bNtR7rb+vU*4~z z*Rw?Qgm(R6-sf>FiF2D3IQDTI;5+pA`I#4UwbLC1mrVLPXIo4r`T}@ag{+U}_u^T+ zx9mMvBcHfm*`-$BYO!P64%XL)KCE4!(=D$5tyYVjd28lAaEfnUy`}L;yygup?VR3g zf|K5{e*SEDQ=NBTX6E~7p;*%ii`MLgtZg^>;@B=`%TfH(@FsL+`$4CghSUS8KMy|Y z`>cD>B2RFmQ2mGRju&>lPg&oqXKb+{P~%+S&OMy`zDvL-*eqYDeXG*-Vh5|kNg9ar|%?9>Dx*-<2|I*Bx7W{tH zt*^5G3I9UdT-izTub%99+&4M*$qSxf`SUiPdt%nRL}f($`?B1AYHzyl!P+11cE4X@ zn0|1b!5ZBSuh;GNs#{#fqw(FQJ@IK;nR0?%)-=BAeVHpo_n2>Pdd}~&(%{Ix($_PN zO*}qx*}>`)P8p%vd(yX79w=K@`G8N!!gT%S>1&eSWrh}qMf~=ew|tqR&a>3k`ri+8 zrS|OB*%H1g@uQT!PonDTFaFz|jH8w8eeJ`Rw@F<<&ZTckVrVYP$2B19R#`tBCY`!D+>3>!*LN z*}Gfc)v!5Vs%(Yk)xPbSwfDP^w`s1)s@8vH_9OhV!K;+nCx4Y)m+?&W`uT5@z}zRR z%Zx7@{0gb|XFF+DsAuncxGuwMTeGuVky(E4VZP0E>@!!rk24Cb|1QRJ`X*n*;|+(O z?wfgtA=>;&b#=9>e(B_W^CrK~yj7(azRm<=ZMxr@zn|8f*m3DV*Ov^p%;R?dc3fT5 zXg2ek=KS-g`4s>3F4%d_>UD?w`~T+mYaX}zwQ1UA?ibJOIBl%pacP-Yx2tFI`@P?% zeZK$anfc1kv!|KdU$3JwwcE(RVb5Vc>l24|Y-z9Q&agk6c=>>ch0ZsL0}rDw2TuCr zb2mWV$GiH=MR(QxTRw99edzd3C1OR{pC2DT{n;4Cc>n%HW%pU^@--W-zx7zWMzW$j zBfr9Y@w}NR2g>~G-+aGi?VJ0Rx4-Fczpv9 z>)!AEKJO-I2?-;Q-8!W6WO$k7W8yqojutHbek*%@XTaG%Qjau6xRg>hedvEYCD?D` zu?EY(ppj_(+@L6qu8io4hq3bnS41Y>Kge~k=D|xJfwrr)(1qhBPT+;(KVCLWS?z2! z$)t*7$NN2>``(Dk%Xx4LPYt{DH_0zkeco$|x-#gmO&m0vS`kV#t_@IC$a{)5XhU;GP;aDE`K=42-oR_m3(uqzgs*h}-M-<_SNJM#f%#K% zFNg@9DLy)NS=q~6vo+ge#S~r`D@8ln+Za#Il@e4W2der|gIM`e!f=HA4|Cmt`& z%bNDL{rzlNwn9Of%+pO_(Gg{e2QEFk(6+hD_ATFAZ71Eg^-;bC_qjbj@AYtwnb-L3 z=2mr{1x7bcBo#))hf3|6`gC7>q=%OH+;pq!%5FUq3ThS?p1J+LW5Ia`(~lFbpNmic zuh%YNf%s<~LJYoN>(lyCt=f2DCVG8kS ztW{fH{El{%So>#Nn8m#G)`#cU+NXUNf5tX5e=S4Z)cp!_#rFkgnH#cxj`7GS+u&u- z^t=1puG;3>c>Xg#J(K-buDfeGeg4O=1NTq1-Mm%)**96*alT^{|J$E0e>N7~jSeU% z?LG!-H3h}nda@PPZd>!#=DYH{S&P1~7TN!Mc_t|IVjT-?t#(?>ne#~>TKk@ynQ5F_ zml`GElPJqvkS$rl*7C_p`v>2j2MUw3f6RWr`isd8clq9`|NWDfGk;A@`P=Yyy|_06 zo9zwOgTCGg+VwNjxJg**!f7zXK>4mmeKJ&wQ6YqV$UmsuQnQ*Ue4y%Jm#^Vjo4#dcBK7TIg)c4P4 z4y`^uYt{bh%Q{o$v47`qv|mvhfBQgDnywp+j|IrU(+tZ9EQh3J33 z#tIs1ezbDcDxudQCRfiL^|gJYyI`$*$NcD3-))pUgqK+tUbbug&Zy1p`m%e4TYTUy z$uo1M-8R|pF}xc8H(mO3?b7rGpy6;4SI=eUSzkm`bDdagT_c%KG*$oIK6UNaM`3qi zhY7j9&DC7cMQ{WW4mUX+Dimj z=gmNSS3!3?iK!=1bYiye5^!9D#Wa$*2kFoa`H$5@i#C!7D z)7I~QRJ32n=e8bRTePfl-MjGFQ@4DL(G+;Ly6>FiW~1~jZtrW6{_zXW_b2eCR^PeQ zcC*)JD<6}FoUodY!R)+UD}TP(e7DbPt6vnKo7-SnAj6?$hw;^8y#|2~QTUj<%{ zo^3oc?C$-d)4FFby36Z2onH~S_{<#3;xoMFcOJxlJ@9|e=X1ulb2j%%8YDElN&a_; zTYn4p^fqq&JpoqwM>gl&H2QqOnSW-xeBF)xS!X~uq4Zfkli2)r+wD`8Au~)CeEfU! zeBHD8TZ{gFy*_*S{JLBAp9=`Amp)v#COocksr;eE%9*Z5ix0Aj&#?V|$M{`EeGhngd9~H4Y1vuluFQ}8Qu}D-zjSR8J)^r}@AkP&t9qxyqjhD@bn~Ozdo%yI zI#qq!xz18MzsR|F<^Ef3(eHk1Cpo(8uDTi;{?l$M`@wnnf-(gM7@xh}e%}l72GQIlu`@MeDRhPM@;Y*{Z)psX6h4c+*~G-zeksb2`%ePnQYLEYYu0QlC@MB$L1M zshDM(<&Ou=r|r)3S#9CB`Or|3$8Yn&;l8=8(v$adjL&{vv!OQrxmurfF0V`Z8@Fa{&EK!rpY@vG z6Sy9Espfw5>+Rr!$ga5=&3t#6amUwd(Z0dKlg~u{`*4{5toHgnkK(^NbT?*xF})sh zxk$Eg^~Q}G=a$Vmcc<6hW)>f z{m-WF|Fd*PvT)kNL#;x~)~!3Y=i9App<@%Lh&35Jt$fs}uF@ZKT=9(cg*IXKTZPZ6 z?Y~@b4%)V+c-wc(1Tt46K|DWQW;eksk=QS6|a`!!Z_H0_Z0dI!;>i$!+pM|fT@Vi0u)BiM| z!n%_pYR`P~g0=trKPOysahJH-&ClU`il4b(kI7-x``uw#oclcdYrkdi4cqijv!D1B zHuCPjwVQ8I@86BQpZzU^xAx~J?R7mj#qOie_vXSU=35`O-}-F)_UAz}d*$32+n=bX zUUoVhQ|or`iF!q*z`32^f$~`HX@(rPUcHhuo5SmG@hL6r)~0r!Ly|Hp?RMwxZP65W z`&2S1JndB4tQ?DI`@UK8Z&fG$EuMX&+C6sC{I9pKm+r1um184cd41RJd5SAzGCwo7 z7>3=;uG{vvmdp9?sp4xXpL6HG?sJGPdDN-yC0+gFVY_#Sy0k+?6B%eJs zRr~ggtL$eVT%2Uw(cL!tTfvzbhR^PnM{Cb|e}}*Rhx5*3@7efdG%i=&*$Ns_uVX&9 z#x5huzVMMt=Hb<=S4Y>e+yA=QuM%hSx#oCN<)in#=Jz6&@fGUwBu>+byp(wPugTkm zzs3H1S#F>EQ|!-U`+v#(|HVMZv7fr-q1IbBi;tyARPC+1OkqpOy*X)YiAJcaj%BsC zux5ma>qezqi!G1+*j!NeBIn4&Dbtv9*=FU$L^wC5$sC-|*mH8SdiR6-f~Of@u}!kt zxQ^>*=v-sm-Ht8IN;~yBhn=2N?*krzQ^=~mU(BB`@C1z)JNpaa7 z=XSoY=PymXe?j zF1HOd^6kHE5dA~EW|sYzwOrF~Z22A8Sa5qzOR@sfYGxKEjp<>ZU&q<_IXuaKzT;WJ z!-7JEC(27^)tT93-rA#ZBkb>F-}9&axOZOM=l}RfAKABk?$KKHL+?Mj%h#r)ou8*0Rm4)z@^^Y%mFF$aTU9K%1!_M*t@@Vd z<)97L9LEnF*!-CtbR>^M^(hDE4Rcw~2|hW=Y%rncLjU1@8^ewZqDwOQ?{06XzC3#$ ztO)hI;LlvV`{jj%N6)SDx8JYZ{YUoLS@HeP=T+Cp_b-@GdsH-h#_M&v{T?+J9M?D@ zSN%ruxJGVvOw;mw_J3)v{2cXC71m*1IEa4Bb$|*XTr4kA?+ORN`y{o%jz#U zcc?U2Pxq1I3jeOq_HpEW7Edf5-dU`G2QyX=95T!MTd3s&964p2(SCGPqc$M@l#lU+mC0nxSPMpIL3k(0h{F7PS*vk{nfL- z+Zt`m(m4BDZ&}~x2M1DiexJ*Dehqjq`J3sVxdqS#yy*#|&(|EjlVEtCv!z039>;k> z-(SyW=j&bHsB?I}nbe$2+rI2RfBU~7k4l*NGk!`YaBRETqF#6BZux!P!U@}#IKEw{ z^e*~GqHF z&L3KK?;@|%6sGNaf29BKmzeX?+$s5QE%=p~ zvLzoKnlO!L(~s$z+cS1LOJ#1k;`n6GwcpYkjybGIKh;{yaUtXM;uA6F|JnL_-zu`a zQ>^&Ya8eDe2vH#Df z)6p`A%$=&lV*YUacz0q->jMTB?eCv^Ex%p~J~~lvM{@b4%lp6?V37)F%yq4S_9W$; z#W&NB#+jI89P+n%sbU{eDR3f|y{|%G&0I5UFXvu{nwzQ9Pu@BBA?stN!wO!m6D4*p zIjnTIH}}ot78h6RT;P`S>&weY`RTp~ChjQUv6!;jSw~o6N`Y(~o5Bl&3VdvR}09+w!n1^`0=E zdeg6Yo~PCQ9NwMmaJz55IKOc7X`KY|CD!jV#a>A&oG7sW_47E-pNIVQ6BvVU&Aqxh z{PcEaU+d?20mi3)-&F zKB7>vNkF8zrS;efgT5781QsNonxZ)=U1nDGgA;Rp>Xe>Xz&uyq#7`&ABks5+Yh56jNFDJ;qpBc3{uvbZZ);BGw zdr`@= z9qO1i+fnTQCZBQ#>38o1-zUs%R#5s8#dzc3Ugzj{XCULLDraqZg~QKho}S9aCKELK z)9q?CG0AV{vW?r?zi}>VQ{+=)1W;?GDum72dz;^>82SyL(@zYzjy1(l`AtgCLce=bt>n28)sUDpUN!hZwHrOejuh< zl)%*)HtX?&gUzn9d()Dyt%=-pOMii{g6x&S0rnjh*bNSKx-|toDOCQ%`cryLZ#fQGp?N8dREZ^;ToZ?z% z)pF&y@p&8N>z9|XLJoAUz9^j7%OrNP>sBV?DckdjH*ymgQ`1<{2LiUz_m!H2a#Cb6v03G@SdB&&G7*%`H3krBA~;Hq~a|JalH> zb1t)GkM|Mk6vbLY}5H{zFyz7X-}_oedNJ?^KJVc=Kr>C^Uu9oQf@n4JAC(_n#{jW zKMM_3IL3HdOIxhs+f;b#_idZw%Y+5Pqa+ei>}=lhzt6rV>wD?Ysc$-ayrRWpKFTG| zZVc=D`}@0i27AEZbzW05nLSMpJKGijC^Ups&Ykq%A-ztH=vN;x+)|uIy z#~GG1o0Q#1WY2t_ot>S^?a!Y1x%$ylse0v*^`N6OzTYjsztyv!l_^rBe@n)4i+<^v z4kh2}8`1C%lIMlvM`ri$iJp=HT9LZ&#r5^`D?go7KiWUFwR26Bg9R=r-Ue&|E) z_Pf{oe)}G8KgaEqzGJdOd;d9>&+j_h{e%x`MIW{FvwSLYTm0O~JC2_OOWv%NSJBj6 z!}wy0BFkT=24ArEwvjW8GBhDs_<#| zn4Z_$%GsXWCs(K+9ye&4>gxKd4SW%j9**SmkL zQ~yu~PEWJ`SaftZYJN)Gx9z#%*E42qt(kFmcT`7m?(#ap&JxU$bZhqlwOQYKzX>YJ zXK>$0>Sw&Bz@wwg^4WI5Ty>dTj%juW9U1?n889l&Gx<4BwA=Oba{Iq0`4e3N+!dC; zJ-DdL#`1^lA-lI`_x0CsWbF2w%$T{aHcv}(2YkJ#=l$~Qa^J4O3aKY`e=g5lx+-bz z&j0J8YxP}US|>!Ea*E>FXW(H}{LE*j^c*oZ#~Icv7hL^yNdHu0M;H8p*Q|39V_3m5r z#Z2JcMYp~G|NY+N&6Kt|a!V^@d<=3jwzXq#?aQ*WGyi;uS z70arN&{3)?W6{{s_`oTjOl-r9zD^fS)!z2(dgzJwohE;>16ymrEiI9y*B3SITeOCA zn)J;s-MnmDh^TAels=F+r~?EVidghT5pD2G@7VG$m31GQ**Qsh zJD-YeKCQR=NBA|qS-<^-4({IVFk^l6e<{IFpI5I@xc_Bs{ZX^9UhgCQHdlRjTsGmq zcXt1x11p!$)A9=xaecY-%xt9dw+GFV&u3S|<4wb116!?+3%~BUnKoN>+11Mr;_iZu zz7Fi&wLf!SI{S6d@eH6N4vdqJ^_=;>XM2$SH>S%Lk0gv=+&%eiO}6`P!3W{_HTzfo z`u*N_zkhvJ?V`)tSJz~R<^RZE7Gisg=f{f-(8zK9(&vFI&9r#^PV9fZZugTLQ&TdH ztC+&eClj2H^!!}Ce&46sTg5MzPM@V2yzGRxYjRt|DesbN zk?B7bFEv88?}Nr4&&*7pr>VcOnZWAJ3lA3&xq5S!WpUcg3W1!99lH(k_y672ve53D z%9D>jpU$-Tvz3{_9_UX0rWNUQfDjqPt%#p8n(711Xkf)UUxe3W{h2Dk# zVK=}2Vdl!nZ{ESD_zikrzj^)po6nz!=@x@dK{zf~edc>6|6C!f z;4|rIM=telTR4#e^Z4kJQxz9wGZww%xLbVQR_Ixu^*e$8wcl<+u6?Tc@vt3q@zYw} z{mu_ut^36OZ@KI@c~7e8@4{Z*%%cxqU6`sJ)83OJ#6N2;{P^foOGEYM#ptO&)X+ZH z`p~jLR6Fd%#^syd?RxEH%l1Bbg8ICQMQ=XKv}{@*v-48U#-n2U{LMgTpRc*|Ds^+x zBl)wZ&dxXMJ>36geZ_*a$F8m~U)pjId~o!J<^`$z$C`KUW;U+=e*5a<_Y<=9bUs=& zJ?v1P!Q`uh-+_`yLEPLso)S$G-^3YGuR~OWlK22C6 zplhg?VadQ_V{oD1tm*X`Cnu|WD_@x?==6&1*}1ve$2DSPjTQ)mwlC=yhzk!_f6n*m z`J;ATvl|Y5CS`#F{8PG*J62v)xj4b@`<>!Nsjp53uvvX8ocm)9Uz^fongno2yxS^}4@@#F;zN z$Nm&|7sN*Vo%ZATYw=Sy!7WRET%DJP@aMmMk8X9Z`2F+g^!ToCBFA^}`CN5uIOjFZ zOpDjEvdyuXt#h_*^)?=_N%>Z8v93Y0XJTCunS0oPUktn=^0B-1uiN+kUHg90EoNS2 zT;2cQ_fLOy=C=*`v&?^fo|Kf-nf3L5vn|hi+G|0m%KJF6rJF|?O#Du-Gazaty$6DtE1jLF#BNobA8DrpZO){w=!93 z+6KksrcHl!)UjA}Z>+jjnDfj%2ia$<%r!d#Y zO0~r8Mar?W^(DnqB`Udv-WbFc9u-D;9!_Xcf2ifX38{1{(*M5t%Wb5?io|9=k z_YwHIJd-7z3*IK|JI^j(laROTrCKLjVdXYJo{ z@yqWoHLs&LF_n32j+RLIRbSMWv4%4|!f?H>1WQrN`&;_|wwj)KRqk{7L1vL`?vCYe z;}nbUWl#HiC`7;hMo6T475HT13v)h4$NcyAGdGRDr~7bj*!ea7J6ckNS_m$uA|^w7Hg?RLKYTiK_N>J07+Wy_qq z?DU*px31tq@$$K4lNg`q>@HyOJZ0Q`g~!W4w~J9}p6oK$)L0&oDzDqCHN37HnT98~ z{;vA$t-Zx~`n<=xmz{i*_;>EMfBj3{KCcj+A}QeDYk>#XcuVfqZ^_X{g z-uH2Rc{(KKg1d$5Z2xo?Uaq;ae;#eo54`nE(YftJx8#yJE0vhExW6xa+W}hUm~)vS zL9P?F%(0>5LetL$lk4Hj9Jhd%IX-nZJ`7prSnSow{ct{?h1xuhuLn0R_w2quy_TA!qAh37^XdMK42B#C z{C->XNx4R`I~$c*Zm;-$uloG=vwku26z}*cDTIe_pA|Vx`A40rrF+Qin-jkoc*N|y zFr)t0%jLN(DJ(DhIL`8!FX;SmS8~!j)@Gq66TeOUW}=xRWV`fjsL0angHvREw;Sgl z_ubO;he2C~-7z38gY^S*PQ;cs4_UwcIO}wI%7#n6R(Q_?e!V(fwKrygM(x}#u3s6g z7p*=7ExhU8?6jQ4_lKwY+$Zf?{Q5hMx2wrnS^CYEUAl1D<-<%e8P-;tqzri-HV8VL zSRVXr%N;(e>3Xn*j75|3mrFhdEo3|oS;%-}9rHN`7Fn+|umgcTKf27^v-qzS`1ss2 zWxJ(c&3(@N-0IiC%0rGD!V(J9Y%;D$M)c(SDzCo8?(?!R`2W`JYh=`K|AWp7A~G9nt3p0f6$tj+rk50aRYQ8jCfUZ1KifX;^nX|5VUtc2(e*Tg!4(0lt?#Bf+)yQ;%~ozsyYI z`Ahj;W?cSx?930)hkn3L)qNSTDdW<$=zQH-OW>#K zG9OYgv2kbPHToS3K2^75!UZqrj`fQhz!$EGR&M#T#I>E5*QDFb^7|?6T-N!JQ*~ue zK0MsM{mIOu@9*phvC3*G{>jtBlEygTk zU$>`Z+f04$if@zC4HPoJH>_*>{o!u;eb3}`k1`V@7AIVfEuUKC{&e%5&oXVHTW
n!L0kM5ccz|5W|pPBe>t`hQWM%Mb46%7E@}J9siu@vm_wx5V`U^3!9D(Edp~I3B-J>1zvIKKoIF=dy37<2`=#9RMu{ ztbAqnkv?UO_5HL-o+tqXO)W4Sh&*s-nw%VWR znPqI9#5X@g@6-W?7kt)l6uLKl;=7dh@m$;CWsRBP5uJt&;31RmXZ?cat(1P%oEZ+< zkoshL8S6rOn~vEo{-9pmNxrudx%O{UuKeD8iECf`lV3A+*KUZ*-GB7)o9~s{zsv8{ zbxgl)$GBjly|ul^vzPzpU-H^qyYJYW%&ars%K}-Rf6u9}1DzOq{MGOO39SED)PB+5 zRCsp7{g0vFqR;0yo80>G<1YUi>Fm#&dRH1v&iy6)DO5Lc`Sag1rL^5A>nEi#?q^)CYy|Ahep ziBnx?$Uv`>d#Mh)N-n_V^HJ{i(5vLavX^akJ>F+UsM#8SozzRf`qUzcf$UK4B-KWGCzB6ijOkK0Z&vDE(g zTv~6+n|xl-F-+`UbW!(7)9UW~B4PjIe}6BMJ}(l<(eL_RV9DyRJt~Kct1o_zZpnI3 zwKB~)_;2a!totq6qHQytDgKU7{Kp=4KrWf}*>0Bw`q%POJ_qDK{%qHwG-3LyRO#K% z9Id_Q`D{EGQvdp@|IQ-QKAVL(buve;nxyQ~z5Y+_{*THv8}1uC(~*8(7-VjG`rqO2 zXZe`hIr?nAJ9;aw=@dw)jL$HSI6AMqtz2s1G&A+JY8Kwo;De6eZ>nySR7h#)KKf|G zm6ax2&Og0%2DDt2Pg-`Dpt75X#XfF}XK%}0F1Q~)Y<=ED^_sy8jl#-jHy5sTSg`;9 zzu!muzoqYfyKVCQ6v>z$5BckFDBq5A4crv$ci}&lb5qwC{Mm`)+|+s8oM-vMUb-jL zUyR$nb|uN@roLLOeo|rG`D5ndm+Fdl$`@W2S*kzb_ecNqX{)-nITwNN)B7T8@HnYX ztXZFD(wRAy#+S8j^E7RE$FieTco&!0(dK{hlkeTi%u;*JzNUU}1d~DP!uyZ7qnD{} zm?|I5$#nbk)qiW(zT5$tL-`V7;CXzBjmL#Uo6HZ%{S0d}e{NR)y3%EWnooIC=KkAT zPabU8I_rma?L_#2sF!|&4n$q`0D2(mSq<=ks7<%0F1XnKYQ6mA$G149acBJIF0D7) z`OBYYH{Xs-mZh%>jZzn+-PHH}T9mKL1seBz$r}L)z?=v-h zzW-TOKgWD$Z;e05>7en@TcG1kYo%Deg6=G<`Fz$q4Rp)iS@Zj6_Uw(z&0RYa!>z`x0r^z4OwfEbOrM#P^E45}$+WvXo zBjdzFESq00n?38i-S0EsSK3&=+p*a4=ab22udEC{$}4Sl=Fe08`j6|iz=Nh5wv9fk z-z~mq^XtW8*B6qeSyN^jr}yFC9$xUURou&cQpGgg=rp;i7mA-h9OnNW-kXu$k9AF4 zli2r{%jbVPZxbV%v+1N-8p^GC_ud?NdkK1*{08~gQ^Vt4wtslx5p}TQ(W_&iOXJel zy?@tK-D*~MZ%^gKR1Ykx;r32lwYX@R0q6iwt+Jne^`OOV^UaTL@5%hr>Z{-P(CkiR zGrMfeHzVll?5!n3sob~MW`+lAJo_T)h^th~LJ~Nf(8nAe=T>1O`e*BhIkq*MA z-~X`vxL~2x=QGB)XT(hwn|to}yWQ)3C9L{UH*_DKS@?eI^*AMg%bK$A9o-8{yB|;T z*3*n`FM0Cv0{h_!`YhG%v$mw2n-gg!=aB4PId}1&k{v5I&pXAbK1bnrbly(azC=E| z9}hO2-`u zjxQqY|Nr^C$ds?SVHaX!wo;7IjRy`$_q*RIJTCia|9QLLCk#D;qWbvl{{%cL)LXIj zX?aG`#lFQy?cZ*>tdbw;8+KQL1wPi7v9E*uY_$Q;SJT2rE}YCyFKLPU>s{A!N&I%s zROUP*)BE}V|2+3A|FU}$XqWaYL%-kujtKitsh!sxV6Pp%&Llc#W9$3Lyjvb`-TuV) z?c2)W;@misr;an~`&L`sYulVyaK_!eS$0XR&E@FFiFMVd^Z#3_Uyq4+RLXK#(A`4T z^Ke{o<(j|d`fH9PuV?kt3cvNoA+N`9ss9^!O_MKY*WY};?wnb|*w5ypOXhC9T`!a@?O&}}{3k#zs0>n%yKgDUvjv#eC?$rmF$!Eu^u$@0u>x0u9>Qn z7tD8C@%PW?^GBEb?NIK^cFag6F^$vLOmB_uqRpPqKh4TsXE<5iU+mz1m}U6XHU50tzJF@6f2)y!gP^&@#L@|D z6>RN%vajYZWoHy;-0`JVG${VVr{;zpg*$B0rfM&JzfV<(sf(6=ci@`ACuwQ_&wD1d zXzq(N3T3aZyYOe>^Z2Nw$1l_WZa+J7+40<%_0xP3pI`gZb$ZW_!ln9qO0xX&a`|S( ze}DWv-@4xVxT*WjqNQi~62p{a-G5f6BSEZ$~3z`lSE&s^5E7)NOygZuhLa<@aOPZT{BsnH{!p zD|ACI!!6Eha~1dxNQupO{P=^i|3k-iuZhoOeLm|3Kx&Lzlc4LiVv_P+T3ssqyyI?7 z;g*w*O-|oL!($Y0nLTPQP&b7v*GivX8`kIe=n_-e&Y5N^WmreBKWOH+ zGcdnfGWkgB;U0SlHC`T*3!c7AoBu4aDw&n~$xB^g#pZ&$Q@pz?1YS*kv{3n-NoL>N zvRj^3w?X%V*UW#%tjK%1$Krah;|kr{Y@fruu9v?AoHY4&Y4+!N_t%A9XpON?mioV4 zv?YEs(~ykffr zzuo=Vk3D}u8+yyF?wDB`|7dGtGfq1rA$jKT-fy?Ei|+CUTryKUviPT(&2d%lX*156 z-;Y^mtk7=%mdWCBmCSXS`E|cm9(l@Jhd%eZx8cZzPjd_}SNaVPgf`G=3c zT=t)Su4==Z%uA2HKYhgch>dB6{%*B|yDXsN6#ZS#T#A0GD>%*dwCp9&zF55~_2*mf z-Jc-+i1X_?8M*ciJ=<)$7A@zP3K?LE_`!F%(mYmSfA9C-htw{9JTh@xOy8sVdlx@> ze&*Vr67l-`)aE5B8NDl-)hKuZA0vPN#5j|H^2cdAx5!Z<@TX!7U?` zhw5`nCdsd|;dpZH&MdUmRZV^mL!V!62#^-Z5oCEW#aefJv)?Aj>Z-CVtgEXIu`>&B z=+F4;Y@mCo(~Eo3qfVpB`}zC-E_p1QYI;#N;JuA}p&rLIE}nBOtgYt`++xX=y7+?K z=jG9}dYcZCm+y+lIq6_xEp8TbbUVaCq;l=kmr4qFl`-Hm_HQub=jf%h&pO zo`UXa<7?1GR6i>S`$*xy+8X`f8>n~jh9k22)_{A9X)E#ud@Poo!rrfa(vRY0$2 zI%h7MWfBy)<6NnP)os7mEF8-+CN+bGcKrL1hIW$0q+0m&o02AMxF8{VOmM0glgWlf zc9q8#?qu&fGxK~|!L3a5s>$kP$ZkS~9&z6(T zU2A1)33xxMQq;SKnO0Jz7u$YUAF(b<{yG1ha9L)pN-`A zSLU6Z^6wd|=9@Iu4_e8e!j_$!niEld?oQpA_uZds8@HY0o__yI+spL+H^(k6w z$0%orn{BU)zE#2&xj*$<{tVGQ(@(8uJ(n77^}FU7*SFTex)G&yk$=2yyVNB z9oo7)S%&iS(&weOw7%HI6t*sXqyN42{r3NEeE-T)#P;3d+xz?RELFD@TW%|~IvuQe zUwvQLQRZOPmlqd7_t2RAtN~p`^?C)@!wl<=$=kZV&o1DpvV@$Yx2;vy?uOYV$6J5q zRqQ-=pX2K5HJkf>)PBEPo_i_zt&rGJW!*B}go6im9BkLPw5K@}rS&(X;_|~HZkJve z%OILRfZJd&~hWy__BE-!e>Z@j3vyfOKn?Z*p^t`Vu$ zYR5H~&rv#L^4a2~`-P3$>n4EC(p$jXEAR$3kTG}pVej<020G8aKI+!r#`$=`WvS_5 z5>`)^c+2kGka7C-RmEG5Rwp^sAln$@&&{#ZTC4Lh!SLYT<9qJw{<=EZF(3hQklv3t zgZ~!qD(haYTz=$y2SXmJc?cAG}^`@W?WQjcUB{lx%)i{Zpb?3rtKN@fxSX9pO>u9-cPmyav^ut4%y4LMluitf{Dfy>|O7>-T$t!_J;(y8Y{{|>(dT7_}?z1hta z;o{S}+f(E;UTEDgTel(qT!?HkYwgspqELHZG8HZV_vKYk=*n|<%AcRFU0-pbN3QnE z#k5OHJkzeM2uwRS$MRX5bl!*cRS)X#RX(4YyY1#N9tnd3YofNE+VkNM_uE5DA0Fb? zHwj+uHx;eBy?5_L)b6&W+X|j$vmdaowt_?6j)!f}PAK^Ip|!-Ok%fWqq`L zW!+jUzg`VL3+`)s#IJ+*b-&Jewl7@h((cE7)=O*xf3_E$Z)^T@ujI0C(Hz5Mw`bK? zMMM4B-x)0pItjXYW70Hk@C7bU zr^kEEFv;2Va#`BHUYI8FL|-u^!5xTk08_CGym|0-WnX(sb$-maV3>-U~{b#?XG+4=ik#zz*5#BY74 z|F098IW)XlPp0hp|KoB0Y-TRVUxH79fnoPFQ<{MG99Vhi6+aOV5yEO9YIzW&e0XY;DxO*F`8Oaz_!559$+V*}_8 z_TQ)9U*K5W>2BZ_p`Jg*lsj+V4&hge3m{jpZ@GM=`Ixmt+pk#`g^OA^Cxz&}OOLVL z?J9No&UY{H+L@n^x4eIMbH~SH($Syx6g@p59@N`S3mscI)ByTsKXvRxNq&R`tdMEYDa<lMKEZVZZRL%?@`D+}TewR};Pn zMqCVGSFG3jr{MY2$$9?PdAp@f9r*Lh;?CwqQ}>v8hYM<7Tv+%o(yZuP`dR%cK6Tx5 zG<6M^ed!c^SH6vDhkmp8^r=s*)_rN^-M&`zmVe&wEYtmQM!w%G^#o^!)#aS)KJCBU zAhxv@n!^JxO$K)YkEEZUr~B;cjH!z`_x=5LTlu;F?wv0)mNv7rUesQOm zras$mH=O%sl(ctxa~l-0Kf9Yet^cMCq8~Wn=e;AX$)Vp9W|$aUXt*%*NN9NM)QI+s z6&DjGhquImckZlGp26NxP3c+ziwTj+GmcniETWRm(Crjf^2M2VAy1FyZF4V`0B}^X+E(zetnJR}(6~A8Gy2 zzo6#V%jKsToHo39dg|)Ls&^V|xDPmZ&++yye`cw>v$to)4eQIl|Cq)7&~$6 z&8E|5!sBbNrd|EHML~w0HKpaD`Mrw8uF>xf#N27^=AP^lAZ=UwtK{VN%e#tI^kkYU zcUm1WVtja)^AwMR!jI%{?=`~Lt@(OK(qs0QM==7+6pr{FR>%pN_tE>5@#@dzoQHc? zTHfaR9{tSaa=&n5EUZ{ew42=KMl{9$cO(~vMEyo7I-NO4Jx;1{tq%ZyK>w0!!F^P=y2FKy^MxNi45ty1Bw){e7m*b@z%%XD`9ez*H}?*8d6b$jQW%%48l_2U`y zZ+HIPwb`BWNqFslmiO=P82lD`o6=fay}p=LI&IS4ZLPMlFMm#*7QJx(eZNiT{vA7- z`6_qL>b)jj^^>3YB%0?xSoCx1wZ^7Z%zM6c->d3`g`h_2NtGALT73;q!#1QJEn`_x z_xIOK_4zeHf9_Skx83~lnDpsG6&zDRXMAu>+kUq!+WM{eAuT3dro0G;5voyuMyMs)5c7u+Hb6Hgz>L6(7qwu zCVD3xaz7-;k;2SY$7cUsS0-|IFWVO*yJ>?QlSTE9{h!ZSKbsbvmuWu1Np_mHc%oas zoUYo3!mo2m?t{;rUai6-H$j}uW=7w|iRp(MC!F6_4QeDCM0zN{m_9A4vTj1{#5Y_2 zq-ek4|N2MpUvcu=_focsdw;d;(e(M+TEo5nVSZG;ry+~+>jPz>IhtMd&w)!H@5r_pT+^TC#_e(7O{)Cf{t1s-mv)xXZ)8h Yo~0>%zhZqF0|Nttr>mdKI;Vst01^NH9zAmrFEp@$(+#~>m_a%PIrGuDqxcAF~sb8>ok**OgC4*x!s9hdUE@5^+{ z$>$mzCHQ>yDbGqdlQ`*w5R>AX9huMK?=Rn7R$aY2eN}mS_~u7H>q1_=eK%{}g{xP; zu3o;%%Dm#kgGqrRu23{ZN?ATn6vh)-x-8(}Dvhp15OhUFD*`SzWtI!;YN&)pSJ31N z?rXd9rHnr`_Vp#lF8%l9-QT#AZhsdZ`?CMoj5|9%9EfWAApP&6*q-$*?56_s&PCO& za?hXAyR~qC*3vWAwX%)tS1><%Ke_0{&zKM8`Gq$U+hAnM^_B{M)OEz@p>eSvgi}K^;U!$6_=HV{qm=l);x*tDYzI^58-r&v0W?Wj7 zP@fxEzohr{J~RG4ZvH6?-wFJdFZ^e5qx_4@&zC2EH(B@#XPbta9jbZv|MWY1@xUi_ zvUh&gm8!pGe!u>W{n|fYM5}h?tU3EfbDvmc{~kfPHHHFfdV71*PEJyNCaOuOx%aap4Pxfy3{?7i*xu|~~ zfv-0^GKW42V(a{TBBj1|?Vl@eQ;!~teCXFROH10;<<{RjHDhP|F*rH{WtS_qhM8&D1?!*ji_D%VXf>CHM+V}GbCN@W*U@9{q>w{QFo zu24{US?9V|{NlroC$^bol@luB7ym5Is8DL!J+1hUTJIK%a4q`>ZJVdxR6VF*T-kGLX-Mb^TTXAR0 zQ;$uHc7CxI|2n_*$nVfJyX2^!jeeC9JD2bK>i^~D1F4UD3ZKXR`xyPbd`ry)wU}tT z$J@KbjugG@x_tTPobRo_L4b&D6j{4cTj6~pfO;0^bK6*RWr%F_P) z;c%GZ@dHM?5)LwL?6_BUJNH@rzw`ek=NTGZ|GI2;p3wButgKUBKls0D^}0#54d zOzX;w1P3Z#$kjuq1UCO^+omXT&U;b0&8-KSl^gyDnCo!){?A&!+3`X_6Pxxuuhl)7 zXEY-2#+sB9gi)hv(B%17Ws05UiJAM<(VEg73OPC;nj(m z(p$H@Ro(XE-uS66rmSoEEvmZqPspz2`@ZrQf8WV&@k;#U?D;o@4F!GwJ!B6I|FvLm zjmzJu)oisVqujri{Co1|GsE3&_ij~jC(5q(uA0TnZxirr?Y#nLmzmK5t0UaxYrh13 z-aP;B8Jq8Siqp=`u{5fY_;GgY^|)-adlkvW=PaL}*!chJ{(sXWcNQ((`DBuJiu|$u z?X5R2315o_<+n)jmFp6=9$J5qOY@oP#;M8M44);)Mc)?KKILIQkL1#aZihKfGxJVM zyPYTSFs1NXx5;y<<}c@*AL}>$*m8Dy*V)~w=dyE>CtGZsnmp0s@(cHZ3%mQ}cOJX{ z=Hpx^ep`l*3l2(%3z^hJ=5+jcx@N`O@;Zf>*r>-3=Cc{_o>x(Mo9=!o_4BekHua`s zn#aAh-f?l^*UvdK^s3KaS`lJ)U@EUp%oEnbhMO-utSg;(<@eO0|EH(Q&rjbO^p|-__x(pH zD?aaM|MzIxMJ1)LTOZ^fNxT$uw&?r<{m54S8cIii{>bd;677zct|CQX7 z>)-UX_*jpmlAHI^KFKYI`K)IwpI3G2&!6Y^;u|Y}KAk@MdR+C{H=DexQ@_Xm{~B-f zUNo`#AglO^um8Tr|G!%E>7@EX{^Vo5(&~qM!ByDD#jBb`U-LV$-`S>pAz;zLW>co_ zxGe{zV>?t%?sSWIefVJ6f!;<}p>BrWtrkai%IJ7FGxsY_@Dq8yeS`X`h;^$RP0sR3 zHJPMjDy*|Rm-qa3taezY$)%2-!%6nbF8WKL{~eCXZEQiaiW52uuklM|l#^v_<#(9?Es=h5jmgxc5an5cH7wNF}d*1UsEZ@HUFo}HQb?CJD) zzrRy>#lqT{-S76A-;^RRfO>E5`zBT?PS~KgXiFR%lb@SB}dzf`VApMi_uBOZ_6W?a+ zem}7~?%lrM6H*@bPYyHFdb)YZ=^)M63k!296r9rCKG-b1cvm#h@seD%nep<~K0iO! zPCI?o-l+as+d&O>tImS8_1x!5TXoJS1pRBs-&LhtpK?%D+FfTW&(otHdE*kIGyfG@ zc7&INIEHI{e>vmtx7+7=q)b*^z7xu>RCZ%S;3yzkx)0Sk8Z6y^H=e{{CF5OnBw%W1n`Vs%bo!_j!-)y^Tf4lZ} zz_I+-^|vOUX12F8NLjz3xzz03j$h3=)~C#VM9tfhXe<8XW2f=k!@FwVm~Puzzdrr( zuEG!7HE$dX{=G$IVV-|=-KMYU(hG&Y$$hixZ&!D&`IB&P2OS=7Qp z@STL;?PsGu?R>bq>^|4rW7@Yb`^7zZyLZBlcUg1yvKwu`_00d`G|@k$JL0EoZu$Lm z!j^o|IQ>cA^p*_@s|XJ4{n;&2>IZmQLNi z%`U6<*seaQi`;7#y318A;W#E9Ut?%<>s;mYx$4^A&&{9zDIp^5{Jh+4xAUxxQ%(p( z#&quQKDy}Vm*DEfx)A~Xc0W^Q9zW&eZM??d@2~6o^|(zp{t$Yo)Ui14;v!cg{%Jps zw)1O)x(7+OnICQoPk0)4{WV`iht-?@&GqvWq%Guc9`m(0vNdy_ZJzYAzvuYA)h0fT z7h@~*JF+S_CG~IFa-C0~UO)K0=9HFLbMo7N7cMxngk3n|r5MI8ohD>g7j;;%?c%XC zF^O+yYPL-Kzri}SZM~8IM9bnOojVL7pUVi7jGu^;z=bw+7tM^W|4}Dtv zdrpmR2m6QZ98V42tf~3n7_hPKPvLV#tuNGv+bHNPWN$K-5zi@L5*dYhI|daibv&B3^877O2!Tvmq=70Ih|-(2Y7bp$x$o!M{8_c%?_U4%`uh1h6_0zL?f>_B{~_b4 z4|jLO|Gsto%%;@SPZt0A|M&iX*?Ez_O?oZwKDoXp_0_(JZtq2)Dy8dE_Jw5+cb^Tc z`?h)hO#6R<`?+t<2KB(EY)hSTxBPx=c>85%~_ZGgNyE3Ip_hjCAV6%HVHy_xFPfH)HTt3g}``z;S zJ6gHL+uWz#d$e?VT-NFTQATMRbrq2jpq@kV5kdDED}$GxGJn0}=Jfcw$_lfc-!)E| zUXMx6+x>Q1!Frp_>t3&Vi#|Hd$=UPq*s~3X`Ha1%>2RL^Rd~+w`HboDb&>1d9_njr zePH?Ng!0)Xo|B&({95V3yJDCsJqpKRKVd^Qh?N`_(_T9NxU`>fEiNx@&6L z^|@a+vdd{4*0KNnX7dLRqc0IV?s3bovMrpTqM=pz`|bAAhu_}$eBNGu9_Ile^Za`@ znmH}b70T{?Jm-HoGA=)mx9{h(hZD~!lx_Gnk?m1qg|g3~^Il8;Je+mp`9*j6sT=Hm zy-+T`mASlhgV3)ZjaxRH2>;-@yMMlpLY;i&6Ty{xCR@DQ@z_Uix^Z|+p=-qw<$am# zmwx5{|CS$A_;&mKx`!9Ho^B}j58MbIF7dj0(=I%p?<~K8u&<)cro0E2`PQdJ$FB6< z!}2<|{BA0!%zd}#^SKWmMm$H4eCm2^-qE`M&r|(}H_QC&|9;tYi1l90XWy5dMS59^ zkIO~;&isxD1r^4e#wU-&>*?rJai|>fYR-PVZvVfkPWG3J%+ro`i7w=?fArHh;Xni5 z&t{40_j|uTu}M5T*E+mKO_q&M-hM)vevJPl|68?xLMHQ?-Eer9_$AK$qwj8J`#JZE zPV1`5?%Tq1_xhR{pVBYyc-Xq>sL`u`X|Fl{*y`UIqi?W&&tH+4>=BxZxlb- zQ@q-I?@77065@RiVN zvfB86-rw_Q`8oR~j4#SHw=N6ab~iC>-TW5!nu3;Qg|e7HiL7;7-ktgLz|7u$TfK02 zd%wK<&TUme)-sFUe#qK(b?U7n-W$|74{G!rJ>J#cQ1WK$^|(#74;tB1q*E97noWAU zqwsOcY!A2DmdPn)O=Fsd*03~E%q*T zam4n0EC0VKUsn3zy3e|gvcVC5#V_0B)K{xjq&}H-Z~OCnLHo+;eW`nG&&=wrQ+qB` z8n0vU*WSWE#*$<8jeOyq)3}#sUMYMS2`;m)xq|wd;v&~)ba$UVyLhQu@g&=}j26|U zVaDsXT=LoA^xmaf#_uG*(P}T(;?T^gZw~*su<2KddHa8%cadQcLYLkiy_)8@@}kei zq_1o4EI8nN;@8qSlDW5?k4V-VzkR=Na<=%b2anlnN*uSy{`+v4Uwzx*-Iv3{vu3Z- z+10Y3G)(_^pY=P9rY6s4g?A$As+=C}c)0wG_uZ7qzGwKX-z4bmeq*$8iPbXS*=hGG z9`j0Co!VN!!{7I-a0+)fJF{ajCyU|r|V&@XYuQY zC)exM@qbsfh{on#SAV6>sd78i-_6js%Uo~PapBOb@F(xv{PRtIhRV8g zE9m^z{k79V^4;&3rOQeWT;U4~`?Eo-;ONvw?&h^CY{S=G{m=aD{WPDGT@jGri%okb zwZ#mrE z3a%*>5bxl4Cbi5r&2RH9+aC{_ldjo+yOEq){c7d%t{YZ=J{)$IX9QK=)8ndE-pt*8 z_m)`nA^%Gk&TrZ9qBBu0MfHd6!#UOOb}rkvF;GrNu4mn(DfZ6od~Zd*{|I1{w=Z0j z==#xfcmBUA%{z~WI9aHlTl{%)?Mts))lx?^oy+e|xHnC)y5;!tlV;pMk8Sr|r+FfK zdnl;BOTF8sxG6&J?uJO2rjiAs)qw&v+-oPy$^D!3wMT!+k$kbJg}3jjwg~Jyf9ti& zQG+JSUHtElw8<$?V?VZJMZdgzRJ3f(t%q0hR%>r{?dQ0*_xru*Z5`*=Bq;XqIl6V& zeZNy|BxAzm>^yhBiT%@#s5crHEYk0$&Mwi_nUX%Qa#@E@n@;)E+HW`AxAEJ4nUK@S zezr3_%=GcXVrR)YQ!gL)mwCtPq+g|9bLrmHKb}q1&U*^Ir|V5kzWDQGpu{s@cNd{p zPiuqn?dk`*cBMVCxoq?zJE>AM-E}#*k>e${VMEfC$a41P`8io-ZI6_)wCaaSF@hOOl@?Sp}PC zbP9=^dDh8zCMMdpNE)Yk=<~e0;LJZYIcKKyY!}sq)26M{$o$#7W42MM*ADTP)>4zK zr(Zq_t7_`Z-%@T`zl-_s9!aiwZr8T#{(PppG--QOf~)RR_E~k^>$9ZA=EUEvY%~O? z$0;ToV)`~U33X>)Nxa%~DCYXgWqxZ`M??xP<=g&1Xxgp$&)Pezk8KNVcsq?#o87%$ zo>lur_}iUcO68Mpn7Tjzq^7sA_K!u)t@8mZW%=ytrtEWFxNzaa`G@ugC$4+2CUWzc zCnqPTCY5AoujXji-FD;9o*$387p`Y$d%kIki8j~q^Oh1^r@l|x@aO1Xg$X;%m`aXx z2tJ$O%s271?Z+d+xrd_!gMUaHr}ao0rFgL0f2^FCnfUQLulbz|2X&*ioOrY8wB9G_ z^uNhZH*pH9b$qy)xBKlGPW3qo{D*Y6-?^0eHRf5UT?7Du&KHlXP_&V9 zsM?UTt3>eHkB^Vf{`vX&_MuN7AA9T;x6Ax~cD|0l{imm=pWf5tZ~xcC<`!RZTKT=o zbWZim)JHm*?C)gnKiTze*XuLv@-+f-YkfBUSGm0D-aMoKCig#e{<*v1$#qa6>s$CB zi*m8f<+#Yv&`a$=QE* zzj-d*Y_Xl^|F)dh_WALxyEFez&iS!^)-F;1Hpq0s%WMO!-fhCUSC(u$<)!_A@61>8 z=*Wwa`M3EW=gdyp^w&E7_9SoLz&{C&f8**loJsy$zjSKa@0#YBnNnvSFNk7N&BJx{xY%mT;mupFHvV?}{o$B& zK2N{K*1qYjqLZz(KPw3_9z0PuY31F>d+&BWm%FL<@W|og^7T5mHJ;C}-&bSb$y&AH z)!8|g!C`xQ>;6nXI&B93Ny|SQ41(|UFOG?@Q@HT+n)<^Ht=a2#ss(PS{qeAUldkx+ zZ@2UHZ!h`t^QnHl=F5VYNndYQMA+5IyoHXjL zz6({~+O%Zuscs_|%T7JBP+hLm^LfGr<|SUsDe26VENjS&u!=voF{(fzZS4n{+DUVc zJ8ryxgzq=FeNa=mbN`i$bvo+|Ue9h?bFRDe%acC}#wv4OcP{<>6Iy-f~v~+*=T&Gf~v~Lc-SYMRvJl+o09drWjqcX_s>6+Ze+O4HBlsK>+&8=+ve*N5?o*E3apHn zA7B5s^z%ve`6u>VTk`O5W{<3O*gnV81`n*??NGJ>xBR*;{l0Sb>P)AF3eLfEy=O~Z z4NusipCpoPv3O25?g!T=1EC_Sal< z3j`JL;9_N0H%b==qZ<>Mo~T(l&EWm@_48|FtX@Aa{BiEXrqt7Cc+Kxj`1LkI{@PL1 zw^LtTGI>@qIsWssmA@bD`_K_|8r0r1;kQ08PhM|RtOs?6_I&7= z#CpGGomj^@Q|VYGCFA|4xYkYkCTj5cZ1+=%!YJdyd3SD`oQhTNoL42cl3(jOr(^Af zyA~-Y=kIe{T6E(1wV1#6=ki@BojTdi%5%=5Dazi~UMuagH_LtAv>|Wn)v#wB%6$gG z%l%9jyZ2A)h^zbg^x@4X^ZGTe#}xPOe7o)Tp}n7$#A>fpKYElqaO1~g(z6fqS)b8~ z+@xaj`ncuuImvN#KT~&ZQ0)!eKlwYd&E zXPD>D3(4{P8Mo5L{oxqVAa}|rzs(+iKefGQB^Y@3av#hRt zbGBi7KQG@A4bz+i>A1^J@}(|+o&R^;tHKVa3g5;4M!K)xW_tZ>Y<&8(yV~dQqxd$v zs%i61r7uXlZ}Z8+_{Y_kW$SB9cUK+Sx@^OorSL7?-n$Fbp`cBIF&&xMm?`(>A)A{^j zXXt*b#JtdX(-rFLGC!x+&bD{I@#m+3&E@iwL40$kF1n_>W*x6VZdAel%ePdn9_YLp z|Myk+rnjD(wistO>g1k?S~M&7#JVkucj4A^Z1kL~yCc8mBtpI86y=La7KF%uU~Nmq*&^P(pwKCF?yBV$zk`K&pA ze+k>frm1}<0{lmG6NT!+w4U-`SF!8xKJ@Fw;{G$4%jc?nEcx-n^8KFAXL=-!Z$0XX zuPgrba(TO5)9W+yYOz-|kALv?{@MR$>1P?ymSD?+jQRV1F8i{`;<)q6 z2jv}_K9}L$Q=3>lojfPK>615HdT?vU>-GEN9y#uM;Tf?p$@RtI`4#_oBn%cf|5MPq zU;WB06qjrdCjTaWST=Wyqr{Sn`BR9QPhWYI^?M{oIGx~;jU|GPONCqp1P*J+Cx z|I%Uw5BH*7^WIO;n;s@UeThlvsr2$#&iq4Li|#G$|5Fsg)&2X3<(kzG)@fev|je^&KVwV!%whIG)oQ~Y|G>mOh5oYtH3Q|5>E z{PXT7?dq+?&!%@wznZaY(f7?UH4AR!2W!qR?9LRL@;s(+s^+aC@%nk%vFbb5lr4+d z<#0(vGhA=2=lA_qrB1(U`-AoBYd%!!X-$8gU{SKg=JWdguBM z^V`S#0rgH|`?l*py)`p(#guiiWjBw`DLf{b`~As}$NlEP%X~P`{|a8oJF|6*#pL@( zM4!*FC|q=h|IysmU_Z;H;kPG5xYfr?B)-${G5P%Y@R@^Q6;^J~xt?ABCUNen+2o?q zXZP3s-}zJVq2TPWY96s!VK>X#BX`yb$$dO1$k%kE&^2+z&P%(DKR4h0^XJAt-x%3B z>z^DC^m{+y`_x-|J4UkMBo@&98XWX|{Rq`BIsSTkQXQIBa@* z_H%`|A#XCBMYN_qS2-~|@o-yf_0^@BcPuY8G*^`&iBT;N}f7M;Rxl zMn`;{k|TSjFnNVSZ&`ei?|bik4?gw2_n37p=gZ~~ISxx}^w!j!T=rD`+js8NIK}S6 z5BME>Hv*yg%t$&+nDC(!!3s z!cDnv4?#{N?O(mR4vdXZ!gx#qK_$MwLKFS#BS#~uCQ zu<`zpgncX3TRbECF4xU{Vznys4!_OomMT=fmBr3)S3u~%B9otd$T zZMMa}+!H!Wj?Vn_X}SGh&vWu67aWUnuC0k&wsPgkKmWe(pZ<5v7dL64ja{iFj)lED zckH-h6P0D8elxklEg~q(^s|@tSr+Xtep`+|obpFu^QsV&@Tp-Z?RxliSNoS0|ImLp zFSS)w@orp9uGkc*vm)AI=TGr+*|e;QKQ;5>wt1@Oe$39S|2{Wn_XRe2`vAZ9Kekk_ z{<|j}G_K;JGgexyz~s4E+U#M=)_(aN zFBWw#IrLmclv%W{q_6pX>4M#v>&1>V_c6)HiXGTndiQ5&%b-&cuZmI zkKBk)*&oBxW&cI7*SP)(`mY9o~R4AyuszQ?sxyvr$W- zT{Nk%?oPoqjm*T0uMbPS_4-hKDAB_uIeD=Nm)s$wk*W=x^7aisY`$D@UYfKZysxW0 zS?^qoy5B8<-mK+&R~u|^VHbSvQdjcNGk)n6gSc%qmyS&QlF6-ObnzH}eX`)Iu2QA@ zLL8dxy;eG+kqR+VD=t3FH`%l?U@b4l?1eRX9rLqQb{|cu`E1!`?H{+fK&UswgFjpP zW|;81>bs|wyM|bHgx?AXk~%rnVh!wuDg0)Drg z^Pa}GNbblDUvT$OQh&sWN7H8LEIBIq=&e9YTy5=dfm2>Cw>dq}J-mJHj8oLEjMe-8 z|NAX_M6PLr_M`KHxz1(nMc4?o} z&D&J;k^SNCHGBO+c$8->{{6u(=Y?TXWr9!FuOEd=Ud~+;^15=pnypgA_j}e6E(^6+ z&FSe(GyG=y`~<p#^-5%8?H_@Btwb)#x))OyHyu!_J;bTr@t{}i#g^Ac3Nzc^ zE!n%epuf}nT?P-^;ll z*7sR-hpl_bd^3!xTYCP949PF1l4+WHYO7Yc3S8T|S7_SeDe=~G8SX0X&G~V0_IDHR zGds^Had`hc_%L2JbHCr$S1VQpeYH}Yx#iEth1=(G>)eUR-1uSkwW-Tee=sV=Z1$;t z;al-tw)9G1+MgdE(>^{r`e>S7tkk@>Qd{~XCa`ixhKE(W)#6!v)@jS!2~TA9y*-mP z(Y56Fgez@tI3?I(bgLHgcD9OYr(HR_y)<`@=*}l*UL|#!tC=6G&1$-GDQEJt-f4^9 z@7>9K=kvKMOl~>XxNMaoBF!Wp3a=G-wB+jsg)I`xq*}Qo1ch3Y11J5lm~?2-b&d#M zamV(st;cvNy+O8NWUXZ_M_(VX*tc4yYroR%ooUL3$DkkFSkr8nzYarnHs zCZATvdGf0BeeWp{xLu}iArbV>s&>C$hCEaJyM5D7&-eOaBYt(y`zx+IQ`2sk%-3u> z@aX=|!-{YBePi+L{`29TvqEBR@!=-HwXu!m@}I3UG@pTi}&d?dC^uYDhFS+$9`^J&o((J5zak7m7+ z2|KHCru68iu2NBL?L}7}ip3lX`hEG2K;iKgW#1clGdC0!eEA-@p{efbywxg2=AE&{ zC3^W`M_)P1+w;luPI9~Sd7hb9S#Cf^M#hijAKr&Z_?=qv`s8Hw(@XAMyA~#B9i6kW zwesUpap!nOp7W_Uv$RhBuw-{O{&9_8Y{L$D7M1JI9?BUewOBrx;QXXYN8(AF{f7?^ z`r9*4Oi+~lAGV4gTFkhF{q%Zo@x+VXR`vh>Y}=sqb74_>)BkJH`MP^2 zCm-+2JuTmMdWW3l{0);Xo%`-F`O>}FknvMySN;H*XKL^Fd_GrXKl#zjr8dl~pKd&4 zRP49w)2AiEjYY0MdycE$|FTb1U~|snZn@)&as&K~&K~C5!n?6|$;6QDN*Qmaeqv5B zl#p6?v~IG`aK-KSs8c6@AM>< z5AScgE|pZYwthSBx>%pfGwMaMz4r6}UwL_hL+AAcZ<}88-nzJj@9yy{y8QFjtO z;kS1=WYXGr?L5oJ?BFXetui0*MQyZLvX~&RfnE_j+ENcye!(@dS=jFgX;Wyo1Y&)VOz98o@3va z4KcqAKM1eCb})YX$E}6iZ!bE#_|U2JCwuOAA5!68g-<}Vjs1xb^f>NDL$89Oioo?P&fbI zs>@qX#V+6Lw?KJ$%v=Kl#n|RUC3_X?{=9BE$XR-bHMjB&$E%hPRdZjp9P=r4y8I$| zN%5-t%lA*KIl)#u;k3QD-B0IR3;%uob~NRK_}TQfrMq80p5=2qL!V{=T)EjC;#sQ`}-fi z9$h|n#%X8CM7CQDxAXV!)yXG*xs=`oXTMY}#!4;Ed+i`-?7VQ2Tt_p%U4+c>nNRv0 z9wjDUs*&MxTDqan@|lESo99pGTY?jMq}5|vog7^S%9^?H5L_*b(U$S`IGK_-i*yn|F)F zk=3OOa!=f5)8ajSXx9F%rheS3|7S_>@K&`u61~p-PPmO8Z(clCcFvAg#cxu-Z|zWu zY446Wy6BmQ-}QZFv9rB)Ma*&gl=foYLDN^qJ3KmmDZZWhX=Z-1S+8xd3-<)IL%n8c zp3wr=;x=xudbas5y8(WP^PSAIm7$rcV=HQ+!Y$)DxXgETf9La zX-|2^{|&G7-pk!m>oAtuyl>jh_jTWQZ%Vz({YbhrwzE2F4&%XB_l}lqx%}|VA_pCX z(A=6^Gb1&o>q-6ZXr8F--erF)-{RTUGS>~$vYpcubuo+2L?_B4yTC?QxniJKh>?X_KejH&{KSMfa-v*~o ziwYht-u^aUpnmqurk}w+e=N^bu>I@@rAC_reOzo ztCH&{Xa6#=S#?FKrd{;4O!2RMYZ79ge71^66MVnM?{(YWPnIHfPnndm?A}i)wec+M zLJy?Z1f28F-(>dMP@jACqA#WbDrGUxzh-(TWZH{rhU|%pG^R$1-wurM7hLmwx>PeW z=L7arKVSM@OS4@j7Umk5#ni@W*2}eT@rm}Iiw`$Vz2%_01~g-KsrJ{R*3#TXO`(BP zW;u!#OB$SgGV7lI9OvbbIjowU0VpflY(L2&mcqHd)I;5`)y|^*T1Th(+^5?$Hgn&* zr2bvQd;jOt=BG9Fr@rql^?Z?@JH_jY>=fgr{XLnrZmS>fb4WZs=j8bf`WOCLUf#4I zqw8VG>qBeiX!>maUNqBC_kWNt?_=5o-5)SxT~-3np>p0W5K+- zUoVv`?pHpS{rsp~KkeTdUZ?U`S5~I||NDJ9e+{U+{qSba4*od%uUCRUP0XAB^v3VM zM)^g}y_)-^mr+Mk$xNYC=}uaqL?PPUT6{hyb;i?%GZ6>N`MwEUA~(d-+4?i?~Z zr(F|fW`C9In(`+#`mMCPBXqR$P+xng2;?ve_W?#$D{Kw;3&6eA1*UVop zynK@8(wdda?vze?QSQrsmGk)xtAoe3n!jq%`FZ*JiV(iV^P4krbEoou|9*}A`)@8` zb-yX!pSsJ}ipZ_BvwpLo+4AEN;j?FE8Xx7Ax7!mfd#P*D7rr>LYiqaNIwezhMDS9D zylvH%o$vSkKJ#>XyxctBZ3?bCpUuiXle_({*t@CfZ=*cLG{x7SHM@NV?>fA#m;J0~ zD)-x5@|k6Fl1IiOVBh=B8$00Z@Zuu00uL$aZogNRy?oE=C+*cG-)^SQ&fRu%neMrM zyI&dY)z2oc^ZLFwO!405XJ>^QXH0n?dFkj+?R7hxq{NG#ojDn^Phn+DD3jvVUlAv_ z+=+^1Y)(j({km+v_+e#3w;OKPpE)kxl-HMg#4hTuMahA-@a^&EM7C{B{ARrDOKUEV zee&Two6B3G{hu0NkLDlQ z@AH^e^kU80ti-n`9NA@6-cIY*+ofR>R4cGzi)7-rH#aZ++x_{Rb*jDgzFf{*Gk3q; z7Jaj9?jnt+A7>q|deF#z>cfficE5FOCh_bIvDt7=v8lph_osw=b-&+UI#B-Z=kxje z=UEaQou>pm6y|%_D`RP7^O}88?{qfXs!WBT4O=F%J#u{NHlt!M2AdHe9(UHA^e$YxXw5KT~p9?nf9cZQWM5r`)gYMBU7c zeY?K9?7CF?>KmU`&8+!&{)ZOnk3Icl!R`4}t1BY6EHCq5r#f6uFTkKKWaQLO}Kq#*<*i4t38)Q zCAWFReERnNqe(L|ydoq#8@p{7=Ho2CvfCoiz(D zKAduHzO}e-vd9{{Wc$2gh%0%Y5BD_kFO;r|?se-M!m&uLo+ys&8DAcQY^rXLwez>!@|b@=9(;}oIVEo{V-+>;@%I?F#y9zgEpE-T-?6u6 z<~4;}p|*6(k`HzXCBfxwEXfzG|NVHpw72+#V*82TcgtD)!vtd7WlN`Q5ngn-bn)Tr zjZsM^2OjM>E>}InF!|Vw@c3MRo42yJTIM`1Z)W42w9@R|ormr6dixp8W5l1gz44oA zbaYP9Da~d3_U(IHUhw6j`{~k`CGnfneA_KPeqS~_PwR4}g*dx?x48bhPZJX6Z)0I= z*JnHXL|8}KGe#6G^wMZM4|3MBfH#$+$p_gw>-8zKGr)qSLjQW zVGf)4`_4mA9SyD%eCON6vgkM)Ptp(=f~(> zSFEpK+{rc1B>66A+3XoM=^TaQ2afY6o#mZ)Z2#*@SA(PN@~)S^emKlu?hziY{c$UA zX8OIXTLG)rE$OzAGs}tC5gz-B|McuT5x1x9y3MtHx7W5rsd*g=-BWL{MP*LRRkxK| z(%mM+d*RHD6ax|dZneXSpD*Xg@O@pyb!)f+b!>}~9`RZjiao(f?vGm$%D=ao@6zcvTUutRgt!&HTz|GZ*&1 zYTnWG!}`P8?e~nh#dHK#t2zl?{Pp8;|7q)K+gO_f?*!bx9#`%A=kxBl<@X|8tn9BK5n_-ZgWD}TeIUoK0bDxAjE#4pt0g;VULAkTQ$@CZ7u$@eFLYQN~u;}dce8+ zse#s?vO9(Dl?F52C61qczWd*=*N2tgCiF6C&Ga?9>7upgwBBx?Bh!;_ZOMGrYkqIS z&ej<2;~yTZkayp?L11Hu%+v?02fs?XdHYKi+ird^**-9^?jWnU$E>>_V!|DN|6qJp zA8@hw+}4()EqeQq?9UwF@uKV>#TU zXn)H-ca52sj7q-8?fQs|*`GyUb5uMnw)k>!4d@Qb4-RGXJ=<2U^jx#Jr0cPSCEK|K zhiX2 zdcAIPwt)Hf+4qiYKf6rBD|MAp#N!VWJ{cHYF+F=~YTDH$PBuTEOiqnZza8vv8`=`P zr@iOY^Gn|PQ#;?}ZB+e}u(fBhn3!$D#fQ3?AF2;oe!o-fo1DCOk3xlJ$)&V60NsWq35D9+#3 zVQbc5Em-uhP|7Ju@KsZ((mtV;CR={BTF<+%V6oz<3C_*#HR?-?$Rd#22s*2t6t;d&dpRg(Gf2r1L!%fRp>)3{>M{K+=T-tQL_~C5( zpt`&7h309m*?!^uqXjZaOPA)X5tY!oz4z7t8;gg z+<5-GZUL=~$>kI53^n6^_lqNRd#XlBh{xtj2Dt|(`&oI)JZ!rCO>N7~M=D!yiny-y z>E{T0*5(xc>~~^s58vhmMaveYM7WA*={?s!>uDbiywcjS@mi%LSbg?r$?3JsBRriY5wqHIv?-BgX^ZqFRwAdC6v6sSr77HgoV4be_ z!f#iV&^yDI{>6b3dVcM0K~cN8y7ks>{=&aqQZU;q<;UzDtZ&8oye?Gu9E#i`A@qt> zyKi11>)EiKA3iHA^;@(mt7Y}zgBbRp`JNwKE&j$DE2?Ndlp|+%kx)PSErVUO!NO1n_fJv zJT?DO8^62kwHrD+5)TTi8C+44;FH<9?$f>Wb-KqlK6o@u&-dls*Q|?v&b^ivEONA8 zPH>IR$FqkeMCTP=(y8RoZS!+kDAc95i{;}(Sx?Za%u{_!{Ywt?E{+Z&@xn8T80;hHRq(9s}=i|bP zBuR%8t;bVuD2TNf-OufbGZxk6kvMm9!#R~pQdfDl9u5+O&ME}f==)UXSQt5f*im=I zN=S8{-o_nYHy7@_Y}R-9Vb#%c>t)By=W8n_-gGP5x#3Ysx`*cX*tWE`!wK3Cjq451 z_UB6`=cS5hDu*>#t_n1(d6zlIWOaD-E~lD#b4>)!-TQv|p7BPOTlbG-?)E#h)W50bRh9QQo-+!Wm3aA+q@4GyfahC^B76m$wnPf7=diz5 zen$AM=bf4>>l#kT*i>wg5L=)^(=>b9Oq?tH(MrFe4W z1;xzNj1#l;!WN~ux<5NSVSl9Nb}s|%D6z5=C%G0J=vv)6H)7?bF0UrP zHGMWdH#1rGF1qsY9qng3c3!oMRGmCEB=YH`%9b@>G!Ng;snpTk+Us;XV6vzq`;Fw) zYv-@D46ZLInEUL%zJyL@k-Z}P{?+ZT8*#~H?&Mf@~ryL(=Ajr?pqv6YJabLU^Wf9T7U zlj%~kT6NdVU)%I!hU&K)A0EhA-dHaZ7X9n3;-5+L?s&Yup7(ij&aFdrS}q)qZ1yiK zJ+s>ROJV$*eY=-C%z7Ph@`~BEr*;Ll(*J+FTK)C^1b(q@&TxI-MQh~yjntklzjy5L zw*5t_udlx|cAxSmJNo5Tr<*U{*5r9l-|H5%%j)R!;A$N)dG@5wGn|IfrN(Ti3WZ{C$}GGB7qyLJ7mZy%03{w(q0zJ0e(N!;+Bs@pDDAzxBH zHDSTzc|R9Lysg--QmeU8YJ-se;`NWTzbHvn?%ybDkkG&pd@HYGi$<%{!J4Pq@q&(> z4l(tAzove@S7VnyO*dO#=Evf__-T9n zsZ9OlW0CoLzdpI~>FH_fmg7zVI(04)clf_ub}13E%kA{aHFFL8^Fe6M8P%taj~iK9 zc=^l!|NHy&vPPgpU)O$by`3soA9<>}xHf6@*l!5g7m^k7qGJuO`JI3^v0_{H)o+71 z0~;T;vvSB)zuCxFC-d}B*JKqPKD!?Yg)-?2RpK~0tn6Ba+r3r?MI4tY?g$>PMHKModotL}fjZuhpQikZ89y;^-+%=Ck2_|D&T@ArN`wK1wioORLM zEt_YA{E0JIf7CpdquNgR$fJlS#o-$25evUWo_uorX4}-k^MTylx6wz9szB^If$$$zL3POS^QtUP?I(8t0zvA8}6q zQLn+0gW20nUUfgbmG<>G>tU|DN(M#sJJ*$(JUaNaI4|e7X~EI!Z+FgkaHfCzxqGW0 z1%Hd_nxXal`0^f4=KFH$=TB&NcIyg%ioNtfSYzrbxBTbDOG|zpIeu`fK<<&lHF@8I zKg~SPF8Yn*)3Lj!HHu`TpA~vucl7y|H8+8E?vmLCr>=4ySSd(NbJ20Se*uLPEt^pr0xl4$+zXv&g3WuDM#huN%&-%n|;FWJCz>8D?mz-otC zKeq7BeHk(4Rotw1r#=>IxgPj|m>^&P3`RK`m z?;T*0^)AtIg8#a>YJT zyO;M9!mkE1o4z<)KHRN3Kk-2M zokI7DE!kJDUiDnBbuILH#KE2F)kl8I@NP4@Wtsj-x#+F4_taU=Iziv!Cw=|sefX8a z-=*J~PA}A96YVoP)4%2E_xxj9?t~j1O-_rk;*E(1y?hsd27g6d1FNJ(*K{qq!l&G`M&Pu1?xbfIG(p3^ zi!|c(P__kda-Rclw+C+$@H)G&DHOg(V5Mh?$A(Sk4n0r#(Ze7w@VvS_(mwledEL)v z()WG3>vsE|^t(LcvTxnl=w*8Yf(`Wbn`E>g5=97Ef1J~_*CRKbpcYAB~Q_tODLHSd< z#TO+#?lm`Smo1wSxhci7;#z<4Im_dF{(L%Jlq+6lqSJC}qPtwCn(wR>Ha;1R``@2P z&k~QTSUBgqqW8ZG%qGRpd^o$e+^KrKcJ8NjljPrByqP{fciYXh+1o7af4x{N`%W^% z%lgNUm8KfvpP!wbJl#2HU%1x!K#TgLqTw?F7rV{0EPj@v7r(TP*XQz!$v&s6s%Hh& zB_z!J^+@jWh9A=x_RnKI81?_=d+2)p{$m^M4bCd}8#Tw@wX>OY?iJ_tS%+qYSL@z= zEfo1_=WYd;@cgR9D}Vn!yzKw)`~3Z7ZAE*l9{xT|1~wIdXmojQ&9@1t2azr;U?|yv*_~d+~x0|vCX>f(_OjfN=n&D z(;4%hw9nXi^y#Mi;U6=dDwi#O)Wiz&@b?Y*1{e`#l_s7cRAuq9S?JeNQe{y2t z!$^CL2O>Aln%}pvxg_br6=rqG!A2mz#q&qx$|?3SlYc1aalT&MZ)XHL0pZByhmW7{ z`FzgUIPFYEw{hLivoDv=m;3d>dw2i*t&E~)mHTa!^cz+BtlwGOOp^3{)WFR5LF@d1 zzdL_Eo4s`5ru%ijTiyRUt})}keg8^r-S(IN4^|qr2xTifr|kFPsrW7;y1KUES)h%z z<>masPn#b*EHKmgDgLr0GcJGrhm71$m*@Yx!Xs}Nvy9c{wU3nJYo@99rfzjJz0JAT z(eyS$*QLoh^H-anpgf?S8lG^qG0Kx9?OwpZjoPiiDHr zdiK8i7f$>?9dC8M(8*$|u*`>Nv-7?DrxfqGAoHhhI>)oxHLKL;*KBH;nes?qT#VIn z`f1OI%!1rIU$4jOe^iM5a%@e+Mx`$g{cY;mcqA?;)(f=1-}`;u9Q8Fji=Ur5J*WNZ z!^iE@wJRS!>WN>_KI2OhXp{F9CFz_EjZ5sK(iYvR{eCy@=}kwubul|HdZNF|43G7#NVz=xm$X58=KbM`=a(cydYoV-_E2{pJm$`*^d9y$$KhR zZ<2lNztftSK&IC>d>iMV^gnUooW{FdLibv-pW6Jpk$-2y-KYRfoi>SLzSZ&;`**zf zko^4oPKDmNnR10QBDenBzy0^-LT{<8t`pB|Oh7}M&U0dBc}gd`AKc|(bCO?t{*pa? z@#?d#-~43x;riSfwly0vUv}L)U-YptX}|B%;G@TryeiL$hRak<-J@}ScQg4*A2y&ku)KW9q!=Eoln z^NZU*pI4nX{qbAxo#m-wT1&I1$CeqU&na}fZEN}O$K$h8G=mRq{uffk51M>^VqSRE zQq6CU#`ormbj6jt46&Ka8eUR(mb!02Ug{ow?5$x$MpWu;*|ev>JS&c{yIXK19T#m~ z_{{6|xikEEpFb!6HZLsw@uP4`_Ug}PLbEo9?w%K(#jk&GmI}*a1F`w@f>@Ru7M;W{ zSK+WN(Ck)*^36VJbH8n#7iKr9@``VjV_L*Ju`Ob@UF|NB4_o4w)S1jYNxktc%*sLY6f$}s`z-8W%@gpeNWBM*Sr6tFEcWq ze{PH`nWFZe3^*j z(KgP5+v<99CTl!rUwF;tlgI33+f{WG-t5?+du^?LU&Z78FUgwaEY2nUIh$uq=J!qB zcU?_r^V83|oIhnA%s%h9tIx|fTwwlZtz+z6f7RGNhuJFnoGJ=0bkciy6SfYW&nnnn zpeeG`{{3Ci{-=+4n%ViqwkI59TKT!tym#%qMR#@^aX-D(8gVz7KRQoJ>Pg*%<;ll- zG;hC@IsV;tM(Y-f;JANZmfN5F+019<(RX&1>1h*F)0y}G|NFja{?rWV2M^oj*F|=3 zxb)p&O_F46Kuofpo}PLCv{`37zey|-oHixu?4Ce^OUv3u34Lo+suvQ-ohy?tQcAv`dYp#NvO) zr1MYQaaz}!Tv2cZwD!eDf@7lBhoEP_B1$DE9r!a<=f@NOEei`FBcv0Q)n#h`9B>tn zRq0hd_c3VcMv?jQPbQb$|MQY}2YZRggBKeZ-mSF`>lOj8Bkzcg+)N-!@T+%y`uV#F+qchbTl)Fw9Ol)L38K-8b<)%RME$uMnVy{7UkhDU zKKI$tP3P~Q+qtFjbG!HA$|HeB>&qV;dfaSazvn>LYWY)kgKF2W}UutV!rg^L!>=zQ7>jlJB06;SnE8*zGyIT~wam`g3E;HlCZ#=cb=ee-~N` z8VI*K{ye<%W2E$-Z`=1z-Sz4qyF5?-mgWNs-DS(~mR_Ivd|tJmOHjGA((ZS=R(C8} zVo<9gp5ETA|L$QAei&Ynu#!f2!u}6DzfL<(UY@o+TG4>Asvo^z^*4p?@+k|U0 z#M7HuC8ZWt;4p!wZ4MvKp0OU!27kN9{pCV1QWSz?=4+rHbC`=6NO z48GlMe2`*)YC+7sKPq$NyZ5KREi;#Xu8~>yD7om=)~mY@neY49n|JlI*V{nRgHr5? zN4aNBcb+v>`)ro`!=xX)%WAbmvF;DJpx!_|v z!<+64^BwM#l9qmY@YJQvGU}T3ZG5t~G~azxbhoQxSXlL9Vf!iXsdBk-w`97H-Bf?O z@8@M{1O4t(JkO=fFYb(fZNxP6zM)kU%B|V30yHt3~OJ({iO9#wNqjAOTo#39eQ@51i6&S<<{#`-EwY|UXOvG)_t zJ4MY=j8j+s^6K^N_Di8kQP)*1-FKF!UaG#(zNO^jzS{qPzgu_MvLuEdnSXfs+y~A( zs~dNy_3b&m(-z;#WpM30BoL|7pLaut&(Z2a*Io^deXU_0=eE^ttU4wh^vIlh z?S;3$E{imLsJ#}auGJ=Z=3;Gyy4e?rjWz~quC;$GF5P++xiZXl{S`6rBJb+_n`KMW zn-7WVChTdbJZZ4SluO?mJcz1sfiKR+;(GE~vzJjd$3LGjK0jr;_Tde?PTBot>7D{w zi@%9gF68^Aw6BhFedm;3t(jw2*I~{pAs6!J!s4^1MZ3R%cJT8`&fik5S`QvcJ-*W| zsN`LkNL%;i1Fy0c>vBFzPFKDE#qUSByUU#j387caV&-;=myBLy8^r|2_=JRZE3C<` zR@HIWoUijGOHyY|Ud6iN2)){uXRBwiK3kF|`6&7nKi~gVdN%t(Q-hNBb$cTBRBYU_ z)O&hc`fj#T3GU(q#bgn${DTI!bX;EVQhENNHax6bXVLBUCEpYL4;VbidU%WB!d$b4 zMMn-E>Jl}6^($OQjbD5vq;4#mSRb zT#@kb4xh5+&%%R(u}7LVylCu;vIMQ!E<#$fEfV5rm-#;H-xa2g&+j4@6nkBEsoKAB(heCI0ijM3fkfZ`LGAJ1n<&wCxC z+Fdp!>)Gb~T{S1xSE-uLvAAP=$T8>k-UkWd9#K!)pNluPd<742ma)!@e^wsUZuqK& zZ|}Q^FABMdi!Am`_c+ZCI!Ixq+`jjxbe1gb_H%l5;79d#>mOT$Yc$IBXY4-}kZ|b1 z+-KGIPX(^zU7!(pu*mWhmzU16!1hpMuHx9`v#d6*OGT>b&^8Oe3H<#V2PdT>V0UuP-mlj# zZMH9ab7R||#1)czZ?;5hzTNk8vzfx()6?~*dv(Tbu{g8$Zsqg2s{1}xvqYXU-f&4p zTgNzF+xxKb{ZE~u6DF@|_yu0qygb+Tvdt@r%SI{Tksp@jxhfPyvFBw+vFGYDEhyc4 zH}&e(1E;2LUi4U5(_hy)a?8=Kh~G?^lAF49R)l0FOl&&fpQyT;>-?ri{c-;nwcNIO znMdGg8I-%gAB#(Z$PoY+eyal``XM+@HO#LS!cnIr9zQsstw z^OSxSt*xK_w`TEfeOJl2+eUvBf?t^*Y}=Y#Ap3gxhEC)XY12bb0EJ?Y<}Xtw35Ey)5tN<7MyOA3S7Wd3!>J zTwTW<^I(aL&uta?>F1xQ=KuM;^N;PfHS+2O(>hf5vaiiF~o!#z*$S_YP&TC5!or-PI|ZFF<+;Csm&wPdkdrcmY6Zzf9jXTtl=eOV|~TkX0~T)+EUR1_GiCl zuU+$E&GxVVEq?pW$oYTn_OAMswfu}?SKdEmKW%gEe|S{pw)5sQW-dEulQ+wiVP9C5 z;b-sF))ryh|Jp=1c-~L6y&jj{C%S3%8|{w!q%URG`vC4~Tl(1^OD~wRuW@=pY8{W# z$+eGMQ_uFkyT5-wXyx{k8O?FLe`1SHs;;!%yT?K!qGR5V!@tiMpZDlDY3-bTwoN)u z!?}Csyyj50%E}Lm4)JTV*nPW^Ec?geRI8}^${@=h51Nk_&S6y&@Yu5=qCkm{KNz~6 zc?M_{S8!3!UdVdpB+z>1Jrm6DmIOz}9DxqwrrL!HP1Oqd@bR*=fSG8(qzG0m0~hPe z^dIpy>&~=$YoGsquX?>q@r8(olO4~@R-L~&DB`pD@B0FmjdJ3(tpwH`c3qPcSp1~> z^qPhn5iL3EuIM;>3-(6*Dmv;G>A`vMwpGdb2U1^?T~u8DDtLfaK>t0P6Y;y-VI_C? zowMsJW6yo=J-cjUdRGRci^{`m_4oE=sU1@0$X*{2vH9&*^Gxp3KV2o1yaZ20^!aW) zd8J+ZhFM-o=bE_JV*eFm{vF#WASe>{|7usnZ?A1H3$7m!4YRxJSs=!qlj-`X%|!N(!u5XSZiY?Qr-m`o*`M*dsADV3TgXn%zBs2DjRkwGwg|Ns_Wh3-z=t`lYhoG zTYt@jJsL4TUgZ9kK5{>8S;9XN;rhPbx9FH6oHO>Yui z6~DD^8{gf}VW-?!wUfU4?mpjj6ns?6-89FqA0KO657S@n7&BWp+xGn52XnZt?Q=e7 z#&RWX-J0SA*7Sc&b2&^p^!EKonpyvZ+qP;A2WOsLLAgWBx{XYE7o>mBj=9|aFmUO$ z2i9vMEZFzd<`g{e?l`ml?u+!KnXY>_XKj{#cSgPBM+?8l@zd?H?Mpxhs>mj;&~^@8 zyyor1th=rGYoc_s&Sm{ovY61&KmT5f;^BAN!hBZ0OI{S*KXCfcDverR7cJZT_51(r z`uSwCe@a~TELStmHOdj6B-zdsYN|hi>}&m|rS<>&KdcDKF6b9n6PW#CIEp(;162x+eL>#?I|&J6!Z4=GK&D`9Ge6Om=lu!sc^zs^m9RY1cAu-Js>Af6(C6vcQ`7D>Htbue+WYu^~u$MJsry z_ta%^-8EkK-B?#I`l7UH!=;AIPoA^4gf@wSkBJd+8hLc|NlOH z{6oIxgJZ?E_ZQyJ+H;cEY}t>jiUnOy%r=Ui5DVKA@?%X--73*F?oWK{iq37hx4y|# zd*9C(U4f;g?oWCmXBr=ERek-&Q9D4#p>V zHEQ)`w{y2|`Uo9FHuksu8j>WXJ@vW9oc)iww9l*vTzqB@=v+;G<3n9r{}wIVur~L_ zchI_A(f;#Od`{2Zt9(ab&D#E3AM)1LRg|{hE;)N<=CR9N4=0wq+Mj;(Z1uA1m-gyS zRGq(@`_YbvZ}w>J%klZV{&Y+F>C(e5Ddslew=qsW*0asx@$~x%g+0M~^EtbZ1?=3AH4$y;E&QYvlS|)j=kNcp z!d@wC|1{&Z{*f0el21+%lM23JtqvLtHi^GmtrYvxsbF{@&a6^h*%xH>`RDWbvq7EznDUR6`+x5#2c6HfbomD7@GM&2B96$LH66i+nlrsm%VL-?s11omcrxvht+s!oW|T=l{Q>6Sc)- zo#@fn*Y9?}7xR{XUf5yHe$8>+$4lP&GmXz#I9FA18fTtJ-?`!2z3TkUZ@1m{ikMTJ zXb(Eft?vEK=W>NSnon<>nyP(vUiCZ8hkw7WuNSqS>Tz1W>e1q#pC8NrkLaua|NH** z4FdfgDpi+387|-6#Nh+RDqH#aHEutawSK+e%-^~%s~@!Wspike<3W0p6nvkaDwa8y zyf;5LH}`b1{D!cr^7G8s`bTdp+bp_TUMjd|UkX=nk=pj1q8s^HnfSCb5?9@=H@cDj zFX3?i+JX&7Z}Kvi9)GnjmGy(3<>dQ1>0R$5{;zKStZ;2({f4@I$*dpa9$q-BTfFQ` zo`H%*Rqvl?*Zv#quAg<+{=l*HDPKQN`m-b{BI3_0|Fg6I98uu;V<|3M_vX*e;)^D3 z2dg6NkN?{I@1y&jGhVa5-3SPFsrxG_3qE{BXt{8c$z-$3Nq=wtDKz%#gaotxZ_4(`cRlnKTE@_=M;6VT)#g5x2#OZd@|A9D6;qRC-v#4OHas4<*zGrnYHoV z-tYIS)0%nBCL}+2%)ap%_(T+4fk#V&j<==mtog>dT1@fnndsi-S1(R&lYJ=%S_Avs zz3#C?#+0OkqW5)Y-nrnA@GB@LXwt&HJ1#AnbkcwGqgO}z`t$!LR!)}A{(AUsn8N+k zzd4fgI5KOEw|vsumiT@9p|mK!KZ+|hGAZXQ2#Q#Hp6z;}Q#*WYIKnk&=D%z9iMMtr zTzIuF`7rmQ%WippKOFdwFxkb@epA8(-t}8FIJh}(FWt>sJzMgX*2|6mJnW|xwf{L5 zcRccJxBfJyg&F;h`QVB5W9&s?*@pR7#Sf{h-{ds6cWGahh+=0$%KtOQ=Y4APc5^@C zdUXC{bpGB{F`Wp7Fz*(XMPEN2m!DqMR3(3qBX7m~?Dc!q{{6gr&icKM690=UCC3~0 z&wVTtFkflL-QC9z+BHqFV3@nUBV6Wqhj7vk^$#og@9u7>zC8UNYu6%;Q@a|cC#+@B zx6x@2e7OB_pYrY4>y6&3YlS+JFD_>uX#$I z;`Kv@HWsgr2kPm#=;+_|y0GKXFCPA7(`Q<)iTrSK{)&h_51P1D&T^y_^h(RyAGo6_ z`nJMNUhiie&|kg3Yya`LhQIaA4L?ki3bS}L?{m!Z zEjl|uM;gVNUi*1?siMjhx8t)=d;QyO)5Uef6a$wd4S%0UD3~$dzSC}8Dkhb z@8e6|ifJpS3-(>{zT*GJ{q=FL^VV}~BY%RHmwrvUqx7Qfx|ykIXX%taTbaf22TH4g z!j;@+YIoIdi!j@MBZ=G6|G3I=!6gsc-aN8+Uh)6$_ouV`9Fj|2HQevJ{&@at$Ln>w z{nk0&xszWtc}qFt_k`R%+#;@lQ}pFQhrd=F{HTB7XNUgYFF}z4921}ZQ87)qbZF6c8!HnX-a3Da=ku!9W#;5eIVQ@xYTYE* znXjVSVJ-EnKYlPu3Af8tt>DPk+5P2Gv5wU9qMtqzEu!k0Ywk5EW`LHOmddhZ-rZ<< zxxgO_&7@5TS{MjRJh&hxa4PqE{xQ77Nenpy6k z)urP1ODtn{b{t>6{l}wj{Uz%b{yS>*((2Hnkg9vTvszt$gdXbEax}U0^SPLD(hvPb zU;lhQpI_YJaU@Ob&R4-zd8Y$jteB9tH1X9V#9Gm<-}C}RTm$#jfQIVwG)hwQxUZdf zA)qQ{W@02UNu+i9zR%O2Etk0%m)s(AE>iZUT5R|Gid~A?JWJnd{eH8#KWFi02f?H3 z1dnkY;YdjrEVX@aSJz>#^yOi@yqJANnc$6xCH@uFFY;##E0nSL?JV}QI!yU8j3l2HC23CEZm$p!F;p~TXpoOLj=7V?FZr`<&-`-8~u;-IsjusBY}6OU;+LT|Qg?(t8lQ#SD`9 zr-N66_Mh+8(du}#BvL~#ipRR^5Vy@P34tT+ZmK^ERa&1u+jDM)p)-pt-)fZ&sd+g< zQ(X$9b}d@+l)u;H`fI+HR#mes)^$&gcfR}aNXCR`qkx4(Sh(i=+DF0N3g=d5b{4z@ z4On))XKgYOpB*=Mz5vg|BZsEC=$YyI=;ZzF$=qu!Vb_%`mow$kDuXpA@_+uz3{%m0 zHhCJSw%@d!I|>E9R(?{KVT{-HQ9rWuqRc*C{`G1KB?}EbeI7>~!wn z-osTd+n!s6pIjt+^!dDFo~P6Am;7uf_$H^6^L}Q;iMRQ?n4CJQ#6Na@csw(8UWTdl ztVcV3u83uvXj$yqo^4NLH7oN{= zFWd9yQJuhq80n8DNk=#$EhoLc*!?!_vTM$x{)Z9HZ9Fd@uq%qG>gxR4_pnXc$|6iC ztwU;m;oeg^5ib4~`p@5PyY07T%APg(JD-Y0>KbwtSHF<-_nO}PXJPv+^uruZ+d&U= zNPNf_wgI#-q<8Q3v^O_4=KefvV4Z$dclvofeo)*Pn9n>KFe$99P3Ng(MCYvcAB?8= z{P}u}i!E%1t`C1$=~>=Q_3vkHb}~xu@{ev5+EyI7bE;@ke7=ppcQ$=Zu0P-8wKMRb;h!o0(*C|V@Nt2o=EHYKOyB-2n3~YylgRx#eYJvJ zTBohq9j@6Yvi7_Q=jtwDoyWYtGR#&k^Y5+Px=Fs#H~5J zITN0`FZ*`Z88f+M_l_lZ`Np06we|NILDm1a!VYb{{%psZ9k=i29sjWXuk77=v-~RS z`PxfY2iMqbJ)ZQjP~qIpu$@z9$d>-Toqg-~?W*~_>JOv#UGO|s6S$_#X1(X?z2VGZ zk5W^7YFgu5lx1sk*m)!r4o#^NoG7+5Kx4-Gny1=I8iF0K*YDrA=HBe{&(Cg3eY=wiA+7HM1j==~&bJ;+H&N247h&_)t9+!Ll;CP_k8VeHx z4)Kf6{>asQIQYoooRg=?GJ(}Kd}@69f4mZ9i_aK-TI7F9{*qsF!+DVjdQx^AFEb+S z>NecDdhBiJfdaYQO$Ph72H6RV^%}91MMZ4o=+$wtE=-%a=EvRyzlf;Hec$g@ zKb_Ic#|>Ukxx`kxW|@ll3A>CA&z9E{9M&WSW*@#c>Cdl)3Ylk5UI=38iug5a?T3mR zT%6h_5kK75CAzx4ZVF;|6RqxQdso`-i6C{_^EtYdx!&J&Rf7`-leXhkx0>yi(S3LjdE_>)i_lg}) z^IZ&+rWmYT-+eTxozII!{m`Uc?K?LeIeGBC;MzU?74?Z%m;O`pu@ZkN(A#&e@NoCs z3|0Qkpdq&MaQR=bp5Td_(-Xf&Np}S)C$Od;6oIdCd^blwLUu+pJ7_Q~VMf;7Idd{? z?sLum^8T}Sw&cFUU9Ov7K9_v9cwM%^{%=PT6g7|NuQuEL@La_DpDp|+zPILIy50`z zV{6!UbA_eOWjoCn#&slXr{&ec8Qo_tnCr+#)=g(SY$UyJKL481{r`lz7fQLaW?S>T zJeNDe_~`zrx1L3x{_}v*?OFQrvj@XKomSVCYFZJZOQ%mroKoF>ZKZqi;ijpx9H94k zdgX%F6;@3abPMZvI@v61VGvkL;FM1*62NE9gU6b(CdvKeLtFs5s6-sJgb`FogBD!+ zE^=;~S}GRy>u&k|+NY0yo8PNQes=!Whq>|lrl0uEwt8dG2C$Ff^)kBuyl}VY`pyKp zKINqO@2U45x7+_&xP^1^huD`vo~Z%x4!UbjS#dT^7H3JC%Ky9m-_Pd{CGGA0{Yd_K zH*I!q+PXcTynfo8+vW7SVdb*ajN@L>oE9&aOg;iyLFr{$`D*3zGqvCEs^>jC)cP=R z>Gq13OQ+xZ6yGaQ%dgL^eSf3PqYmXWS5^j}eQ|N|)3kWVslWFYqo4X~eu}hHe@zJ9 zn}Rv?^S0o;%+pUBF4e9N?fAZ@^7E6Y6Q;nnY#-{8GCg%>rm=eQ&sDBc$LA%_a+k|w z+i!LE-POADi>lcmtNYHoBCqZ%jnxe}TeZ)8rJL+Jjjl_flWtA3Jz8GOjO(_Qo13-? z&q2Ly1+l;{V8Wcmi4F^2dMQ8qb~}H1`|X|2=hxfW2sg0u20A4w%N&xnEJ~@k#?NKJ zT1L?sx=dP67rw83pSvPMuJVasC8tW^ox+iI{Ti=y28WUx7QuMALzv^%>p?T)^{-z z<9W+(8`mA`(mcQY=fjwA1^M&#|7$>lqAl*X9!fkfi1^cQ_ba1vVan4ro6i{?m#fZ+ zuH5(SR`yfLJK5{^I?3PE5Kli2+QwUV&i4Bqz2mp17nR21TsrsT+Go2ZM`!*~TW4|n zNT=}AI)V2(A)EQaMO;ygHAHBiAN=lh83)yXh_8=AyqUHjkQ>Wmq;o?m84uytFj^8TJ zkHx92S8H!Ax|*l1v9*1Ag0E0hphUO7zxAWz54lca4vY96oYdAG#m{)L&BJ*OXm>DI zMU=kR&J7Yw+#>J13TmsKRBBYL*c2gr=@a`$iL>%;R(qi5!#?b?aBZ-x`D5_^67Tz? z`!}z#1C7Et%hy11tcP`P*7R!@v7X|UHaRyo6r7iDNq@Yf*jLm3!j<}O*1`MV@GrCY zpq+Mh$NVW~cuA^}dLNU-bp^R>IcH#e^3)>1pQM{rwWWG40_D=QA6MTN1(^eP6x%(Z-lL zi60&ud;}V~IIr{ZS?bN;DXU*^3ebz~xN7_D#$gi^lM{ax{EzoYPQGE`p`2~|-Z^TH zrhfge%k!W70iCmOJnihP)LFA;oq1pXzxw8Ruku#~u7OkLF>UOk{zh_$)fo{px zcKda_<*qT^VV0U{al}j@y?jkrrd;8alcx1aY4&L%+G|tSJb!{J_xz_?SDm{K-dd`?FY8K z>;A2xv*P!o{!RVGb(=ojzW*=m)U9TII|;c=_D1J^4bTo(8^;&f562{;AQr12Ptw^(a zCUE`C(dCPqHy!@cwuXC!WBF@vJSL^BJJ@x_?X$GD-OTJ$-$E}Zm2Evf*Y)f9rD=jS z9ML+{f_>(SZd~nh#!6tzd$(`9PaO;kD7ss(qdR>^OlXW^)8xMz%|(@;Cl}P|{F}G+ z_T8L0i~AK@ z+Xo&zasTJB)@330_1qY_*ZhwC^1W{t&TlX-QcbNiZo(3(GKRto16q_e2 zCR6?6Vf)^y*vlT>#z*FBg3kgCSmEVzI%`t#`I?=()~!CD5Vfn~+Wq?ffBBO59dC$! zeEwnO^IDB^eSzyoChnT`JN8_@<*Hav6%@kVb|R^=HqRLM%mixyFQom+{^{x!4<~->sL?tB-?)Ri^x~+d}HQ&cGpa5s^%K5)%HF8 zf!SrpvFC@su|4@8=q{UjtY1D~Og~O1@8zYX4{xqJXng7r7msP0h$Cbb7$^{bTr0h* z)cxYyLhG+JVxR*%SA~MZN?2!2gyG`6y24QPk2{s7>$ore>>Vj9ar}|@mZlrvOMl*P ziCT63R+hSkt$#m9*tB_W4zAM4g_o+f3r6vj_c}cmN1WMN^w~7h7j|an^_eb9kEK9H zQh2YuP||2=WmSfbq?l%3J8^Ae)z>WJjDX`>8s2i>R0W>j-T3HeVda@UH`XL*=E~YQ zi@A6?|8db-_^0>kRo=U2nXcVzJG$S5`JU}jo*He5sTYh=_-`ItIqj=y=0y2V@`%-1 zpXUFZYqPjVYJu}IYcX4o=ouP(=UFm;K4|7Yq3a_;{Q>+>qzu}H%!zv(IHq|6AP#eA#NM4s*PGc8SuJ$kB3qjU%B z^bJ@3nb^DBG5on;@!Yr5@Lw;K`NhTB5p~7Zha}L5iXp~|EobkaDGa<}e)JQb3wjF7<3%dzff>co_D6Ab6d#=XJrE~lw&fFdj9C$+_C|3Oy?Moa62eO@n+UEA54u0-l-+8!-H;O++eEMb|H~L0@w4X# zBhPtV=k)IRL4VmCYuIwHAMDp(v!Ht^SK+U3xAWWGnIF1Jo_YRxQH#sbRC;e&$VEmTAyG zJ58`*{nV&4e=fb^>$}>tKan-(QhCJrt=DHIx4zN3E%#u1;H+6fp+kQV`$i7$V$L=SSyn}REO>8tGIu1b=Id!-;A96`fJsswas<4vSd79)B7nok?<)b0ljrrfyIuxBZgm(Xel*0v1wSe*?* z`^C>J|Kcm&8*3+dWKZNLOYPRH^`<++exG`|eWhqy{N&b&1+7#4w{wPVTY4ylem-s!5luzoG~^V(`}$a8rHL z;o^4C0-@PYB+h>Vo#eE?v;5msWp7ZgF(_Ay^V;?`8)A}LHN(!9$L)EBdpf* z+bi2@Zt=&N39YZf*Un$-{C&^VO@RlyZq#0Ww>io1d2{RHu$G+yC*L~hqI=Ow@rn+lw z`B(I!xB2XQ`1JSL0Px6*h-=`gcEV@undk5SyA52$Y_6SsNA26e`%39v@@?!>_KHXmJ#i=X)v9&YEq?k5PgW(vz5oi)+9Tc=t+>QG+t(XQ%C zM)CdH?=w&9@3*m;{U`7V+7QW0<hJ%v zDdu*oEN}Mmxn)M6yQpWHWKNQ_C|FQ)fAahE*oud(MfciuejaY`%{(9^mbvrmwP@?7 zv8wiW7H>XpcN?@mCbzKe%SCtVJI3w)a$(BztA!s~=ijpdT`i^hta|AunbyzYF@>#? zrdd;F8mIgDF26l}*Wu!22FB@9{`yjjA8T6`UT|bTxw-hFt9YyXDt@M^&pFf1&(kfv zFt_}k=I_Xly65dmyFsz2;kEXmY5Ug$%=}XxPMlx+EwVyIQisK4+8k$%iw3W}7Qgs* zJ74_a3)p3|O>G)CYro%Bg^z$7(QtO#xNJ$I$H7Z(QXTBP-iK8FtO+?Kzs|EpV0uey z>+>y_{U%pf)%=-Ley=j^SdV0G;W7RF2OmisRp(zl@%-Mig~w&pZ)>mL6SS?6&*A~Y z`cFJ}=FiUG7b*1F*Z{yeug z?-tXYl^)yDc|YqhJ}2PKn5f44Q%C&Tz3TV5pv8oo z-Nkxu)aO+!I{V)C`yJ&z#icUQJByayEIx0mx@Z5tuj?Nz7s~wEtg1fC-u~am{--~_ z@Bd%R$vv^p^VlT{BrB|J2my7ITiTx^wH+t!c+256p_RSJ$6*c31vWpLzc#PCYioj`JMz^H=^G z>iAB~Jfvrt9QEdB`Qgda+KN9d`|x$n$=o!bzxEOJ->h~99utY@vOU02J@;>}g!}E! z?CgmTeOB+i7M&=u{F!f^()>@`es8}0_X*qW?7dp;#*MD;e?0D=UQ~2KvAski`6`?D zlR$S_)$<7@JMu~@bi=%@LN-{u+jv|q?aU0rXIH~-3m-Ff-+KhI3PkeVTf(bAwp>G6 z1u~%sc@>Dw<<8dq;AJ3Rk8R)gbuHi5Hb^4XeUzHG_R55)9R&*`?g^On?frJkdmF#a zhlZTRIa@BeZIU(5o0Bv7K+*vLvzR+v4NDmqyA?n?1v#|aj;aRg>9lQks##Ge6eF*F zG%|f|s$Auhi9TVExBNNzv`F?-`?3AmF_Y8QRD(8}-b`rbeIoJWhvS-^3wQr;_hE3_ z6T)?@;TI%|Jgh&5#YR0Yag~^G{BRpD_xC@JYm`KM>sL$5>(0Ek+*?WF^}fshUWlf# zI+u8NGubN~E-UA0NtHJH@#n^zAC70l_ND#3ao_6Ix1#GC9}0xM=&I*lck@@y$t`y> zz8_9HasOPQ9`_Vhol+Zi4o?=9-|6!z)3WxJiysj^^8PU90*&83m97R&(yxBs%3j}D zU$_19IqU8d%vMY96h6p*ZozI-&5;&fZgInXf)M+HVD^t)k0lD7c)qjPJ-vBV`p)bv zRnFiI^-IN8o=VTnFi5oQ;o1)xeUNfJqulfOS!Z1R->(lRmfiUfBULdwXH%z%nf~rK zMuPEunU|N%G{0Bj{7kG!SLSfoV$g9JPDd_ftY+ygUU&XO!Wxk?iD#Sq|GBvSWEGE5 z$aGU^&Xxh4E_UzYiDz?!MLt#@v8$Y+I=@=#9iuF3=DILJ#-FW*zWX(1O^aS@v1et< zto7Qb_Ax)ytovvA;qsr^33-`yxBHq*(_Ql~?`NsI@krg=#3r+F+T~~WZxwK8mnI;@Z?Wq9F4Fn(nE89triOwgF@gJT^iO;5^}YMttTo^bZbh=&Id(nV>g(&9 zB)c@3?f47Ug1KQ=G&=&Hbv>G|JMFCQwi{08E^jB zc})_RXIZmS4dC=uk8y9Iw5Y_%)2m zd3%TCFBhEoMH72d#kKj5KPvoo^03a&JChAsCib){hZUIsM0Zc10xIt`q;cm-)5sm)#or&K(;sZBi=O`V+MFLFU7sgt;Yi z)dIEdu3s0GC|EbOKv_Vbx8&ZAqdgM;x-)G5|M`4dMoDPnOxE(+mQ}ZnM69%yL}t!- z{$$ByKOy%W54P>){OUGm2|Pu;6g+t9PQZ&(cQn>ayr`IG(v!KwXo)UYuN!!q*P<}? z7MXLA<*@Y+{XCKHAnPCGt6nJHWLk8?^igD^MB@vcMdykhKW?v3{w81dBk^N%s(54X z(l4NHyMjsWrwzM*#^`d){<3_h22ZkEMtE?@<`+TXC%?XzEj<4rUT6E-|A9tJO;v+u zOf=@bf4+O__E{ffE>t#th}m1Uwa4bbr<40$i<~UlUnOi_WASRkv9!i?Cha#ZoWd#f ze|~(FU9)$;FpBDRFnLQM5!zp|!|xp?uh9!c1iEVIcgRIXjRB&w=gpYgPi# zcTAF3-}ShAw}Q?F1J{qBNR813t!y}1R0-*fpAEj^#`4>j zJkzzC)2zLh{d_ekbB;8R^*R5^zUnGx@)`sl?R4Cu^yftJv(9fzzTQ3S{+6rb%)w@M z@8#ZCEW0yh)-1_1(3xS>C_is?`Tg8@#qOE|S*JO;%ylF#M)m#J9GM%l_u)I=pu1pC zb_I1;tQ9&lVSnW2w3(o*CGO~kc`yBV_>TT|iO%L@+VdS2P873$en9B(cHc~!qkk0? zcA6c^IMN}w>66Cw!(5M`s}O3ys}S0M76x8A^ys~G-j0RM$*PHM7N4ZkrzRX!($O?| zx9xVG^~Rt}H+Dar7M-&7ti~DM!<#07uaszQMq7oDxOAp{eO+|K)IS?OaBDBf+xzv} zv3t?RknK$~mTi6oEv;wFK9gkSsqO4M?Gw+M31`YHW_El0OH;MiO@9^f@OsyUKhrEH z&rkY)PeWb%JJ;X0%^811EcPulEWLl~bj{UkzrB}zNqo>M+O+WN$!`m{tod~Mk#}C{ z{awj#w;q1|_|>-!*Co@=-%bka`PO^GTXdh)vE(Ja!vEBtTKzi7AS-c3{AiYY-S;b6 zm)0CPeWL9L*WL~{tE?M*5(W+}nqS$KbiEb^7{vcP748zCByc^xzBY2N`TX;> zc6pt?$2wD7l4UlYT3+zzLc$u6Ylg>VlzESW`kN*iKhLnfT=cMKLSNg{$LyzddEdmA z-`%Rq?|J!Zc>LXM3wr$J(oQ~)YI3t$G}mIYN7$b@gXIQmZSGgU-&=IXv7L23XHucg zCrj;5nNqW#?%cR=#vF+qk6wR^(TKSDDRSnYuy1$L!hFr5606EjZuytK+v2O>n%4DC zQyJC8*$!>_QF3z2os0jZC!Bw|<<^G};>SAAzYy8HR&|<(f8D2!=WL0v!+;o%eC~{3 zeWwbVC|BcJEG1ApC39ZzpFp+^bKH;W)+=`jE^aybIZQ{)y2L=yLU7s?*=MJO)Y< z9MAqWLNMK3i!|=KLMOGsN+;v%dv)t+nzCzudAIxhzGdz*Bx&Oh_VQfi;{>2jl= z=Tdaneuyp)I5)-4XxUfE+9%NN+)?(e8?;PWn?AeS|C-ow6|~c#<{!2R>NUQ)6Xs|6 zB)*@s(%LA!=kIcu4^#K(tf@Kv3F+=xv+sAy=kEj`VAJTzGjZZE)9W$GdAr|kD_C!n zdHvVT^L1(eejK-Nt7g=T>zLeYe(#1%*^R`slao{*M%tgVd@l3((`o(FTVror+#q0s z+|`QQnl-iZ*URN+TgBrvp4Xn3WN-iHAwTFKb5)&7MX%RxFFUo{t8RyT`utkZc{aJX z9{qW-xPRN-&1=4|miqaI`C&};+pUxKXMX&AQeJ_-?qhfSiL!HZENzAEuenmMpYHWu z@^aDgs0c{!S>!0YKKJWJb~%m1I?z*Mc=o%Tv1$)+(oo^-^11`s5g;=cwjywe3_m$c3)P>8?qhgNmU9-F7U?Z>{>rpXL?AiXkl0TQf^XW9%OR=4Y`YbQMpS5SDwb_j%$yF&FoGWkH_b^6cM}`F`FD%mN`jmRqdqbFz2DiAL$Z6?A$2MM%t3LS+<3Qzz63Ks^ z-O(2W^f&}rPJelM*)>AZZZhaRDrcx0bOS+uQn`)hHkwY9_bLI~5sV_!F# z8R^WjoCcacT%oZawz&Gxt>gSLB^O=O4mPnC@g??_+F0qEYTCvBeHE^1cTu!*^OIt! zC$dcTXMaAL{VgjfD&_8>2k)=3doA0sVet!{$lSX{t3?-IGrY6?tG$<+`N4S|5s@Dc zuR1U_jP2Z4^JOpNyNYzT9jiLe%j*x?!veFH$XNs<=zo>YEV+m-}Ok1;CV#bQkpvAs{-Lg)6&)4kEnY^y`>&G+3=S3!-obYtr zrt@~cPkdY0E;mV6p=)kMDr*wUqRkyv+8g@iANAg-|M9T>DF3t&7PD!Wg#9cO12cr= z`_%8QfA`FIzv8`2J+8z3f+sv5Y!A9MIUz2u-{8=UHsduOceT`~h0p2m0}p9|M$X*& zIl}Dfn>#wV&Rb+&vuhD^wp*-_Bw!r)=#Uz;eLDGS$=!nRHIoiZvz+Z2B9xu^vTM>b z`;Oh3!OL2z9U~&_7Kc6Xs+;p7ZgX01j_m62^=8d%yj=S$=(`5cy5s3-zl94IKAeAOe{iD3%Qcak&pbIfIaP2m z=$9X#4lVjcqtt8?4 zCD2`U z*=fgr39>5naTO0+KV&exJoxGLCD(|@$NSBpr?r^gg|u2SVwY+2wWa;){3@p&8NFf(}jZdJ_rgRi!6c(grz z)E=6Em`Z3aorKc9`|@)6{H}<-zu)bCS|h?z3*9&!3EnuJY@B{>N{J{JtKg-J8*2U} zXx=Tk?0a_I?svDQwJ&-3YV~?E?eKLxwX8dSAGNvqV<#xM1kSWHKS(QFy<}6M=3Bla zTJv`&25SFFIq1G&n%%}-B^zoR-t7ESq8-)FdR%bh&W+#7&o!JCd zx9d3J_q?diyz}5-bE(YYAmfC+`+hv?Ub>jk@%;UZQ@gbfW-x>mvzhmYgL~$#fm8N% za)m8bJGiJn!*$KH+1#@2mPdHR>z+;xH~Q-bQ*+B1rTz5fgw{c5?XgIs>ryOA)2ovU zarewpwLp}MY2XfY4V*H|1;tE!Z8DVe4XJLKK=Qx(6A{1vHC|zDV_;xl@O1TaS?83{ F1OS6AJmCNU diff --git a/include/indentlog/function.hpp b/include/indentlog/function.hpp index e6e6600f..57c2c1da 100644 --- a/include/indentlog/function.hpp +++ b/include/indentlog/function.hpp @@ -37,16 +37,24 @@ namespace xo { std::string_view const & pretty() const { return pretty_; } /* e.g. - * std::vector xo::sometemplateclass::fib(int, char**) + * <------------------------------------- s2 -------------------------------------> + * <--------------------- s3 -----------------> + * <----- s4 -----> + * std::vector xo::sometemplateclass::fib(int, char**) const * ^ ^ * p q + * + * fib <- .print_aux() */ static void print_simple(std::ostream & os, std::string_view const & s) { - std::size_t p = exclude_return_type(s); - std::string_view s2 = s.substr(p); - std::size_t q = find_toplevel_sep(s2, true /*last_flag*/); + std::size_t p = exclude_const_suffix(s); + std::string_view s2 = s.substr(0, p); /* no const suffix */ + std::size_t q = exclude_return_type(s2); + std::string_view s3 = s2.substr(q); /* no return type */ + std::size_t r = find_toplevel_sep(s3, true /*last_flag*/); + std::string_view s4 = s3.substr(r); - print_aux(os, s2.substr(q)); + print_aux(os, s4); } /*print_simple*/ /* e.g. From 2a6b7057b9d11b94f4b5af487ff83adcab43bb2d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 18 Sep 2023 11:36:33 -0400 Subject: [PATCH 0052/2693] + LICENSE --- LICENSE | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..ed66b81e --- /dev/null +++ b/LICENSE @@ -0,0 +1,14 @@ +The MIT License (MIT) +Copyright © 2023 Roland Conybeare + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), +to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From c608229981339f2dd71b7df4618e3ef9e487fca5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 18 Sep 2023 11:48:24 -0400 Subject: [PATCH 0053/2693] + FILES (map out directory layout) --- FILES | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 FILES diff --git a/FILES b/FILES new file mode 100644 index 00000000..68a84654 --- /dev/null +++ b/FILES @@ -0,0 +1,23 @@ +directory layout + ++- README.md markdown README, for github ++- img image files, used in docs +| +- ex1.png +| +- ex2.png +| +- ex3.png +| \- ex4.png ++- LICENSE software license ++- CMakeLists.txt toplevel cmake config ++- cmake +| \- nestlog.cmake cmake support files ++- compile_commands.json symlink to record of compiler commands; for LSP support ++- include to install, copy contents of this directory to permanent location +| \- indentlog +| +- scope.hpp +| ... +\- example + +- CMakeLists.txt cmake config + +- ex1 + | +- CMakeLists.txt ex1 cmake config + | \- ex1.cpp example .cpp exercising indentlog + ... From e324b9d007839de2cc97134272bfe21a35d7a135 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 18 Sep 2023 12:25:05 -0400 Subject: [PATCH 0054/2693] indentlog: refactor: + color_spec, streamline color implementation --- include/indentlog/color.hpp | 138 +++++++++++++++++++++++++----------- 1 file changed, 95 insertions(+), 43 deletions(-) diff --git a/include/indentlog/color.hpp b/include/indentlog/color.hpp index 6f1d52f8..5caa1ace 100644 --- a/include/indentlog/color.hpp +++ b/include/indentlog/color.hpp @@ -13,6 +13,93 @@ namespace xo { CE_Xterm, }; + /* specify a color (consistent with ANSI escape sequences - the Select Graphics Rendition subset + * see [[https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences]] + * + * this provides three ways to specify foreground color: + * + * | enum | escape | example | description | foreground codes | + * +-------+-----------+---------------------+---------------+------------------+ + * | ansi | \033[31 | \033[31;42m | 4-bit colors | 30..37, 90..97 | + * | xterm | \033[38;5 | \033[38;5;143m | 8-bit colors | 0..255 | + * | rgb | \033[38;2 | \033[38;2;10;20;30m | 24-bit colors | 3x 0..255 | + * + */ + class color_spec { + public: + color_spec() = default; + color_spec(color_encoding encoding, std::uint32_t code) + : encoding_{encoding}, code_{code} {} + + static color_spec none() { return color_spec(); } + static color_spec ansi(std::uint32_t code) { return color_spec(CE_Ansi, code); } + static color_spec xterm(std::uint32_t code) { return color_spec(CE_Xterm, code); } + static color_spec rgb(std::uint8_t red, std::uint8_t green, std::uint8_t blue) { + return none(); + //return color_spec(CE_Rgb, (red << 16 | green << 8 | blue)); + } + + /* 4-bit foreground colors */ + static color_spec black () { return ansi(30); } + static color_spec red () { return ansi(31); } + static color_spec green () { return ansi(32); } + static color_spec yellow () { return ansi(33); } + static color_spec blue () { return ansi(34); } + static color_spec magenta () { return ansi(35); } + static color_spec cyan () { return ansi(36); } + static color_spec white () { return ansi(37); } + static color_spec bright_black () { return ansi(90); } + static color_spec bright_red () { return ansi(91); } + static color_spec bright_green () { return ansi(92); } + static color_spec bright_yellow () { return ansi(99); } + static color_spec bright_blue () { return ansi(94); } + static color_spec bright_magenta () { return ansi(95); } + static color_spec bright_cyan () { return ansi(96); } + static color_spec bright_white () { return ansi(97); } + + color_encoding encoding() const { return encoding_; } + std::uint32_t code() const { return code_; } + + void print_fg_color_on (std::ostream & os) const { + switch (encoding_) { + case CE_None: + break; + case CE_Ansi: + os << "\033[31;" << code_ << "m"; + break; + case CE_Xterm: + os << "\033[38;5;" << code_ << "m"; + break; + } + } /*print_fg_color_on*/ + + /* escape to reverse effect of .print_on() */ + void print_fg_color_off (std::ostream & os) const { + switch (encoding_) { + case CE_None: + break; + case CE_Ansi: + case CE_Xterm: + os << "\033[0m"; + break; + } + } /*print_fg_color_off*/ + + private: + /* none | ansi | xterm | rgb */ + color_encoding encoding_ = CE_None; + /* ansi : 30..37, 90..97 + * xterm : 0..255 + * see [[https://i.stack.imgur.com/KTSQa.png]] + * 0..7 standard colors (muted: grey, red, green, yellow, blue, pink, cyan, white) + * 8..15 high-intensity colors (grey, red, green, yellow, blue, pink, cyan, white) + * 16..51 chooses hue + * 16..51 + (0..5)x36 increases whiteness + * rgb : r={hi 8 bits}, g={mid 8 bits}, b={lo 8 bits} + */ + std::uint32_t code_ = 0; + }; /*color_spec*/ + enum color_flags { CF_None = 0x0, CF_ColorOn = 0x01, @@ -21,66 +108,31 @@ namespace xo { CF_All = 0x07 }; + /* stream-insertable color control */ template class color_impl { public: color_impl(color_flags flags, color_encoding encoding, std::uint32_t color, Contents && contents) - : flags_{flags}, encoding_{encoding}, color_{color}, contents_{std::forward(contents)} {} + : flags_{flags}, spec_(encoding, color), contents_{std::forward(contents)} {} - std::uint32_t color() const { return color_; } + std::uint32_t color() const { return spec_.code(); } Contents const & contents() const { return contents_; } void print(std::ostream & os) const { - if ((flags_ & CF_ColorOn) && (color_ > 0)) { - switch(encoding_) { - case CE_None: - break; - case CE_Ansi: - os << "\033[" << color_ << "m"; - break; - case CE_Xterm: - os << "\033[38;5;" << color_ << "m"; - break; - } - } + if (flags_ & CF_ColorOn) + spec_.print_fg_color_on(os); if (flags_ & CF_Contents) os << contents_; - if ((flags_ & CF_ColorOff) && (color_ > 0)) { - switch(encoding_) { - case CE_None: - break; - case CE_Ansi: - case CE_Xterm: - os << "\033[0m"; - break; - } - } + if (flags_ & CF_ColorOff) + spec_.print_fg_color_off(os); } /*print*/ private: color_flags flags_ = CF_None; - color_encoding encoding_ = CE_Ansi; - /* .encoding = CE_Ansi: - * 0 = no color - * 30 = black - * 31 = red - * 32 = green - * 33 = yellow - * 34 = blue - * 35 = magenta - * 36 = cyan - * - * .encoding = CE_Xterm: - * see [[https://i.stack.imgur.com/KTSQa.png]] - * 0..7 standard colors (muted: grey, red, green, yellow, blue, pink, cyan, white) - * 8..15 high-intensity colors (grey, red, green, yellow, blue, pink, cyan, white) - * 16..51 chooses hue - * 16..51 + (0..5)x36 increases whiteness - */ - std::uint32_t color_ = 0; + color_spec spec_; Contents contents_; }; /*color_impl*/ From 1be7001966c27e4d91d3b7592c3f2e17fd0e636a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 18 Sep 2023 12:31:14 -0400 Subject: [PATCH 0055/2693] indentlog: refactor: promote use of color_spec --- include/indentlog/color.hpp | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/include/indentlog/color.hpp b/include/indentlog/color.hpp index 5caa1ace..508507ed 100644 --- a/include/indentlog/color.hpp +++ b/include/indentlog/color.hpp @@ -112,9 +112,10 @@ namespace xo { template class color_impl { public: - color_impl(color_flags flags, color_encoding encoding, std::uint32_t color, Contents && contents) - : flags_{flags}, spec_(encoding, color), contents_{std::forward(contents)} {} + color_impl(color_flags flags, color_spec spec, Contents && contents) + : flags_{flags}, spec_{spec}, contents_{std::forward(contents)} {} + color_spec const & spec() const { return spec_; } std::uint32_t color() const { return spec_.code(); } Contents const & contents() const { return contents_; } @@ -130,6 +131,11 @@ namespace xo { } /*print*/ private: + /* controls independently what to print + * \033[38;5;117m hello, world! \033[0m + * <------------> <-----------> <-----> + * CF_ColorOn CF_Contents CF_ColorOff + */ color_flags flags_ = CF_None; color_spec spec_; @@ -139,38 +145,38 @@ namespace xo { template color_impl with_ansi_color(std::uint32_t color, Contents && contents) { - return color_impl(CF_All, CE_Ansi, color, std::forward(contents)); + return color_impl(CF_All, color_spec::ansi(color), std::forward(contents)); } /*with_ansi_color*/ template color_impl with_xterm_color(std::uint32_t color, Contents && contents) { - return color_impl(CF_All, CE_Xterm, color, std::forward(contents)); + return color_impl(CF_All, color_spec::xterm(color), std::forward(contents)); } /*with_ansi_color*/ template color_impl with_color(color_encoding encoding, std::uint32_t color, Contents && contents) { - return color_impl(CF_All, encoding, color, std::forward(contents)); + return color_impl(CF_All, color_spec(encoding, color), std::forward(contents)); } /*with_color*/ inline color_impl color_on_ansi(std::uint32_t color) { - return color_impl(CF_ColorOn, CE_Ansi, color, 0); + return color_impl(CF_ColorOn, color_spec::ansi(color), 0); } /*color_on_ansi*/ inline color_impl color_on_xterm(std::uint32_t color) { - return color_impl(CF_ColorOn, CE_Xterm, color, 0); + return color_impl(CF_ColorOn, color_spec::xterm(color), 0); } /*color_on_xterm*/ inline color_impl color_on(color_encoding encoding, std::uint32_t color) { - return color_impl(CF_ColorOn, encoding, color, 0); + return color_impl(CF_ColorOn, color_spec(encoding, color), 0); } /*color_on*/ inline color_impl color_off() { - /* any non-zero value works here for color */ - return color_impl(CF_ColorOff, CE_Ansi, 1 /*color*/, 0); + /* any spec other than color_spec::none() works here */ + return color_impl(CF_ColorOff, color_spec::white(), 0); } /*color_off*/ template From a9777a7ddf32810dfd9aca86b42cd2bab8b3f783 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 18 Sep 2023 12:34:17 -0400 Subject: [PATCH 0056/2693] indentlog: refactor: with_color() uses color_spec --- include/indentlog/code_location.hpp | 2 +- include/indentlog/color.hpp | 4 ++-- include/indentlog/function.hpp | 4 ++-- include/indentlog/log_state.hpp | 4 ++-- include/indentlog/tag.hpp | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/indentlog/code_location.hpp b/include/indentlog/code_location.hpp index e343fb58..e0460225 100644 --- a/include/indentlog/code_location.hpp +++ b/include/indentlog/code_location.hpp @@ -25,7 +25,7 @@ namespace xo { void print_code_location(std::ostream & os) const { os << "[" - << with_color(encoding_, color_, basename(file_)) + << with_color(color_spec(encoding_, color_), basename(file_)) << ":" << line_ << "]"; diff --git a/include/indentlog/color.hpp b/include/indentlog/color.hpp index 508507ed..37e0b072 100644 --- a/include/indentlog/color.hpp +++ b/include/indentlog/color.hpp @@ -154,8 +154,8 @@ namespace xo { } /*with_ansi_color*/ template - color_impl with_color(color_encoding encoding, std::uint32_t color, Contents && contents) { - return color_impl(CF_All, color_spec(encoding, color), std::forward(contents)); + color_impl with_color(color_spec spec, Contents && contents) { + return color_impl(CF_All, spec, std::forward(contents)); } /*with_color*/ inline color_impl diff --git a/include/indentlog/function.hpp b/include/indentlog/function.hpp index 57c2c1da..4b44c2a8 100644 --- a/include/indentlog/function.hpp +++ b/include/indentlog/function.hpp @@ -250,10 +250,10 @@ namespace xo { switch(fn.style()) { case FS_Literal: - os << with_color(fn.encoding(), fn.color(), fn.pretty()); + os << with_color(color_spec(fn.encoding(), fn.color()), fn.pretty()); break; case FS_Pretty: - os << "[" << with_color(fn.encoding(), fn.color(), fn.pretty()) << "]"; + os << "[" << with_color(color_spec(fn.encoding(), fn.color()), fn.pretty()) << "]"; break; case FS_Simple: os << color_on(fn.encoding(), fn.color()); diff --git a/include/indentlog/log_state.hpp b/include/indentlog/log_state.hpp index 01566380..814bf070 100644 --- a/include/indentlog/log_state.hpp +++ b/include/indentlog/log_state.hpp @@ -223,8 +223,8 @@ namespace xo { */ this->ss_ << "(" - << with_color(log_config::encoding, - log_config::nesting_level_color, + << with_color(color_spec(log_config::encoding, + log_config::nesting_level_color), this->nesting_level_) << ")"; } diff --git a/include/indentlog/tag.hpp b/include/indentlog/tag.hpp index 258b432f..a6825bee 100644 --- a/include/indentlog/tag.hpp +++ b/include/indentlog/tag.hpp @@ -96,7 +96,7 @@ namespace xo { if(PrefixSpace) s << " "; - s << with_color(tag_config::encoding, tag_config::tag_color, concat((char const *)":", tag.name())) + s << with_color(color_spec(tag_config::encoding, tag_config::tag_color), concat((char const *)":", tag.name())) << " " << unq(tag.value()); return s; From dfc84d5fdedcf10bf77762edf2e8497d9eaf5162 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 18 Sep 2023 12:35:29 -0400 Subject: [PATCH 0057/2693] indentlog: refactor: color_on() uses color_spec --- include/indentlog/color.hpp | 4 ++-- include/indentlog/function.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/indentlog/color.hpp b/include/indentlog/color.hpp index 37e0b072..7e600fa4 100644 --- a/include/indentlog/color.hpp +++ b/include/indentlog/color.hpp @@ -169,8 +169,8 @@ namespace xo { } /*color_on_xterm*/ inline color_impl - color_on(color_encoding encoding, std::uint32_t color) { - return color_impl(CF_ColorOn, color_spec(encoding, color), 0); + color_on(color_spec spec) { + return color_impl(CF_ColorOn, spec, 0); } /*color_on*/ inline color_impl diff --git a/include/indentlog/function.hpp b/include/indentlog/function.hpp index 4b44c2a8..85727eb9 100644 --- a/include/indentlog/function.hpp +++ b/include/indentlog/function.hpp @@ -256,13 +256,13 @@ namespace xo { os << "[" << with_color(color_spec(fn.encoding(), fn.color()), fn.pretty()) << "]"; break; case FS_Simple: - os << color_on(fn.encoding(), fn.color()); + os << color_on(color_spec(fn.encoding(), fn.color())); function_name::print_simple(os, fn.pretty()); os << color_off(); break; case FS_Streamlined: /* omit namespace qualifiers and template arguments */ - os << color_on(fn.encoding(), fn.color()); + os << color_on(color_spec(fn.encoding(), fn.color())); function_name::print_streamlined(os, fn.pretty()); os << color_off(); break; From 3fa1ed052fb15998912a939785b2afe69861a69e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 18 Sep 2023 12:37:36 -0400 Subject: [PATCH 0058/2693] indentlog: refactor: function_name_impl<> rep uses color_spec --- include/indentlog/function.hpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/include/indentlog/function.hpp b/include/indentlog/function.hpp index 85727eb9..65ee5ae5 100644 --- a/include/indentlog/function.hpp +++ b/include/indentlog/function.hpp @@ -29,11 +29,11 @@ namespace xo { color_encoding encoding, std::uint32_t color, std::string_view pretty) - : style_{style}, encoding_{encoding}, color_{color}, pretty_{pretty} {} + : style_{style}, color_spec_(encoding, color), pretty_{pretty} {} function_style style() const { return style_; } - color_encoding encoding() const { return encoding_; } - std::uint32_t color() const { return color_; } + color_encoding encoding() const { return color_spec_.encoding(); } + std::uint32_t color() const { return color_spec_.code(); } std::string_view const & pretty() const { return pretty_; } /* e.g. @@ -232,10 +232,8 @@ namespace xo { private: /* FS_Simple | FS_Pretty (= FS_Literal) | FS_Streamlined */ function_style style_; - /* CE_Ansi | CE_Xterm */ - color_encoding encoding_; - /* color, if non-zero */ - std::uint32_t color_; + /* terminal color (controls vt100 escape) */ + color_spec color_spec_; /* e.g. __PRETTY_FUNCTION__ */ std::string_view pretty_; }; /*function_name_impl*/ From 921da6aac62baf1944c8c6b3c758688080b32125 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 18 Sep 2023 12:39:55 -0400 Subject: [PATCH 0059/2693] indentlog: refactor: color_spec arg to function_name_impl<> --- include/indentlog/function.hpp | 6 +++--- include/indentlog/log_state.hpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/indentlog/function.hpp b/include/indentlog/function.hpp index 65ee5ae5..d79417e0 100644 --- a/include/indentlog/function.hpp +++ b/include/indentlog/function.hpp @@ -26,12 +26,12 @@ namespace xo { * 31 = red */ function_name_impl(function_style style, - color_encoding encoding, - std::uint32_t color, + color_spec spec, std::string_view pretty) - : style_{style}, color_spec_(encoding, color), pretty_{pretty} {} + : style_{style}, color_spec_{spec}, pretty_{pretty} {} function_style style() const { return style_; } + color_spec const & spec() const { return color_spec_; } color_encoding encoding() const { return color_spec_.encoding(); } std::uint32_t color() const { return color_spec_.code(); } std::string_view const & pretty() const { return pretty_; } diff --git a/include/indentlog/log_state.hpp b/include/indentlog/log_state.hpp index 814bf070..ad92c08e 100644 --- a/include/indentlog/log_state.hpp +++ b/include/indentlog/log_state.hpp @@ -233,7 +233,7 @@ namespace xo { this->ss_ << ' '; /* scope name - note no trailing newline; expect .preamble()/.postamble() caller to supply */ - this->ss_ << function_name(style, encoding, fn_color, name1) << name2; + this->ss_ << function_name(style, color_spec(encoding, fn_color), name1) << name2; } /*entryexit_aux*/ template From 92c74b90a00d6a5b1d5ac53ded7d8ec03d1933c3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 18 Sep 2023 12:42:18 -0400 Subject: [PATCH 0060/2693] indentlog: refactor: more function_name_impl<> use of color_spec --- include/indentlog/function.hpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/include/indentlog/function.hpp b/include/indentlog/function.hpp index d79417e0..1de2301f 100644 --- a/include/indentlog/function.hpp +++ b/include/indentlog/function.hpp @@ -26,14 +26,14 @@ namespace xo { * 31 = red */ function_name_impl(function_style style, - color_spec spec, + color_spec const & spec, std::string_view pretty) : style_{style}, color_spec_{spec}, pretty_{pretty} {} function_style style() const { return style_; } - color_spec const & spec() const { return color_spec_; } - color_encoding encoding() const { return color_spec_.encoding(); } - std::uint32_t color() const { return color_spec_.code(); } + color_spec const & colorspec() const { return color_spec_; } + //color_encoding encoding() const { return color_spec_.encoding(); } + //std::uint32_t color() const { return color_spec_.code(); } std::string_view const & pretty() const { return pretty_; } /* e.g. @@ -248,19 +248,19 @@ namespace xo { switch(fn.style()) { case FS_Literal: - os << with_color(color_spec(fn.encoding(), fn.color()), fn.pretty()); + os << with_color(fn.colorspec(), fn.pretty()); break; case FS_Pretty: - os << "[" << with_color(color_spec(fn.encoding(), fn.color()), fn.pretty()) << "]"; + os << "[" << with_color(fn.colorspec(), fn.pretty()) << "]"; break; case FS_Simple: - os << color_on(color_spec(fn.encoding(), fn.color())); + os << color_on(fn.colorspec()); function_name::print_simple(os, fn.pretty()); os << color_off(); break; case FS_Streamlined: /* omit namespace qualifiers and template arguments */ - os << color_on(color_spec(fn.encoding(), fn.color())); + os << color_on(fn.colorspec()); function_name::print_streamlined(os, fn.pretty()); os << color_off(); break; From a51355417a69dca172bec7e9a07136dd1c790294 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 18 Sep 2023 12:45:57 -0400 Subject: [PATCH 0061/2693] indentlog: refactor: use color_spec in code_location_impl<> inserter --- include/indentlog/code_location.hpp | 13 +++++-------- include/indentlog/function.hpp | 2 -- include/indentlog/log_state.hpp | 2 +- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/include/indentlog/code_location.hpp b/include/indentlog/code_location.hpp index e0460225..4e4bab21 100644 --- a/include/indentlog/code_location.hpp +++ b/include/indentlog/code_location.hpp @@ -19,13 +19,12 @@ namespace xo { public: code_location_impl(std::string_view file, std::uint32_t line, - color_encoding encoding = CE_Ansi, - std::uint32_t color = 31 /*red*/) - : file_{file}, line_{line}, encoding_{encoding}, color_{color} {} + color_spec colorspec) + : file_{file}, line_{line}, color_spec_{colorspec} {} void print_code_location(std::ostream & os) const { os << "[" - << with_color(color_spec(encoding_, color_), basename(file_)) + << with_color(color_spec_, basename(file_)) << ":" << line_ << "]"; @@ -36,10 +35,8 @@ namespace xo { std::string_view file_; /* __LINE__ */ std::uint32_t line_ = 0; - /* color encoding for file,line */ - color_encoding encoding_ = CE_Ansi; - /* color for file,line */ - std::uint32_t color_ = 0; + /* color encoding for [file:line] */ + color_spec color_spec_; }; /*code_location_impl*/ using code_location = code_location_impl; diff --git a/include/indentlog/function.hpp b/include/indentlog/function.hpp index 1de2301f..bfb107e8 100644 --- a/include/indentlog/function.hpp +++ b/include/indentlog/function.hpp @@ -32,8 +32,6 @@ namespace xo { function_style style() const { return style_; } color_spec const & colorspec() const { return color_spec_; } - //color_encoding encoding() const { return color_spec_.encoding(); } - //std::uint32_t color() const { return color_spec_.code(); } std::string_view const & pretty() const { return pretty_; } /* e.g. diff --git a/include/indentlog/log_state.hpp b/include/indentlog/log_state.hpp index ad92c08e..e99ef44a 100644 --- a/include/indentlog/log_state.hpp +++ b/include/indentlog/log_state.hpp @@ -348,7 +348,7 @@ namespace xo { std::stringstream ss; ss << code_location(this->file_, this->line_, - log_config::encoding, log_config::code_location_color); + color_spec(log_config::encoding, log_config::code_location_color)); std::string ss_str = std::move(ss.str()); /*c++20*/ sbuf2->sputn(ss_str.c_str(), ss_str.size()); From 62d2ac27663552fce615445bb3e7648dd6c3db83 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 18 Sep 2023 12:49:45 -0400 Subject: [PATCH 0062/2693] indentlog: refactor: log_config.code_location_color uses color_spec --- example/ex3/ex3.cpp | 2 +- include/indentlog/log_config.hpp | 6 +++--- include/indentlog/log_state.hpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/example/ex3/ex3.cpp b/example/ex3/ex3.cpp index 281a6e33..cc77ca6e 100644 --- a/example/ex3/ex3.cpp +++ b/example/ex3/ex3.cpp @@ -34,7 +34,7 @@ main(int argc, char ** argv) { log_config::encoding = CE_Xterm; log_config::function_entry_color = 69; log_config::function_exit_color = 70; - log_config::code_location_color = 166; + log_config::code_location_color = color_spec::xterm(166); int n = 3; diff --git a/include/indentlog/log_config.hpp b/include/indentlog/log_config.hpp index 76ecf63f..4f48b0f6 100644 --- a/include/indentlog/log_config.hpp +++ b/include/indentlog/log_config.hpp @@ -41,7 +41,7 @@ namespace xo { /* when .location_enabled, write [file:line] starting this many chars from left margin */ static std::uint32_t location_tab; /* color to use for code location */ - static std::uint32_t code_location_color; + static color_spec code_location_color; }; /*log_config_impl*/ template @@ -101,8 +101,8 @@ namespace xo { log_config_impl::location_tab = 80; template - std::uint32_t - log_config_impl::code_location_color = 31; + color_spec + log_config_impl::code_location_color = color_spec::red(); using log_config = log_config_impl; } /*namespace xo*/ diff --git a/include/indentlog/log_state.hpp b/include/indentlog/log_state.hpp index e99ef44a..834aa119 100644 --- a/include/indentlog/log_state.hpp +++ b/include/indentlog/log_state.hpp @@ -348,7 +348,7 @@ namespace xo { std::stringstream ss; ss << code_location(this->file_, this->line_, - color_spec(log_config::encoding, log_config::code_location_color)); + log_config::code_location_color); std::string ss_str = std::move(ss.str()); /*c++20*/ sbuf2->sputn(ss_str.c_str(), ss_str.size()); From 9a12eba62f79d8636deb94e1facac8b86f5fb2b5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 18 Sep 2023 12:51:36 -0400 Subject: [PATCH 0063/2693] indentlog: refactor: log_config.nesting_level_color color_spec --- include/indentlog/log_config.hpp | 6 +++--- include/indentlog/log_state.hpp | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/include/indentlog/log_config.hpp b/include/indentlog/log_config.hpp index 4f48b0f6..d3e8c91d 100644 --- a/include/indentlog/log_config.hpp +++ b/include/indentlog/log_config.hpp @@ -26,7 +26,7 @@ namespace xo { /* if true enable explicit nesting level display [nnn] */ static bool nesting_level_enabled; /* color to use for explicit nesting level */ - static std::uint32_t nesting_level_color; + static color_spec nesting_level_color; /* display style for function names. FS_Simple|FS_Pretty|FS_Streamlined */ static function_style style; /* color encoding */ @@ -73,8 +73,8 @@ namespace xo { log_config_impl::nesting_level_enabled = true; template - std::uint32_t - log_config_impl::nesting_level_color = 195; + color_spec + log_config_impl::nesting_level_color = color_spec::xterm(195); template function_style diff --git a/include/indentlog/log_state.hpp b/include/indentlog/log_state.hpp index 834aa119..6895fb25 100644 --- a/include/indentlog/log_state.hpp +++ b/include/indentlog/log_state.hpp @@ -223,8 +223,7 @@ namespace xo { */ this->ss_ << "(" - << with_color(color_spec(log_config::encoding, - log_config::nesting_level_color), + << with_color(log_config::nesting_level_color, this->nesting_level_) << ")"; } From 0c9eb6e2dad7a7ff8b9fc8b295569e2cf07d2634 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 18 Sep 2023 12:56:21 -0400 Subject: [PATCH 0064/2693] indentlog: refactor: log_config function_name uses color_spec --- example/ex3/ex3.cpp | 4 ++-- include/indentlog/color.hpp | 20 -------------------- include/indentlog/log_config.hpp | 12 ++++++------ include/indentlog/log_state.hpp | 6 ++---- 4 files changed, 10 insertions(+), 32 deletions(-) diff --git a/example/ex3/ex3.cpp b/example/ex3/ex3.cpp index cc77ca6e..69ad095d 100644 --- a/example/ex3/ex3.cpp +++ b/example/ex3/ex3.cpp @@ -32,8 +32,8 @@ main(int argc, char ** argv) { log_config::max_indent_width = 30; log_config::location_tab = 80; log_config::encoding = CE_Xterm; - log_config::function_entry_color = 69; - log_config::function_exit_color = 70; + log_config::function_entry_color = color_spec::xterm(69); + log_config::function_exit_color = color_spec::xterm(70); log_config::code_location_color = color_spec::xterm(166); int n = 3; diff --git a/include/indentlog/color.hpp b/include/indentlog/color.hpp index 7e600fa4..6d0ee749 100644 --- a/include/indentlog/color.hpp +++ b/include/indentlog/color.hpp @@ -143,31 +143,11 @@ namespace xo { Contents contents_; }; /*color_impl*/ - template - color_impl with_ansi_color(std::uint32_t color, Contents && contents) { - return color_impl(CF_All, color_spec::ansi(color), std::forward(contents)); - } /*with_ansi_color*/ - - template - color_impl with_xterm_color(std::uint32_t color, Contents && contents) { - return color_impl(CF_All, color_spec::xterm(color), std::forward(contents)); - } /*with_ansi_color*/ - template color_impl with_color(color_spec spec, Contents && contents) { return color_impl(CF_All, spec, std::forward(contents)); } /*with_color*/ - inline color_impl - color_on_ansi(std::uint32_t color) { - return color_impl(CF_ColorOn, color_spec::ansi(color), 0); - } /*color_on_ansi*/ - - inline color_impl - color_on_xterm(std::uint32_t color) { - return color_impl(CF_ColorOn, color_spec::xterm(color), 0); - } /*color_on_xterm*/ - inline color_impl color_on(color_spec spec) { return color_impl(CF_ColorOn, spec, 0); diff --git a/include/indentlog/log_config.hpp b/include/indentlog/log_config.hpp index d3e8c91d..b239aa60 100644 --- a/include/indentlog/log_config.hpp +++ b/include/indentlog/log_config.hpp @@ -34,8 +34,8 @@ namespace xo { /* color to use for function name, on entry/exit (xo::scope creation/destruction) * (ansi color codes, see Select Graphics Rendition subset) */ - static std::uint32_t function_entry_color; - static std::uint32_t function_exit_color; + static color_spec function_entry_color; + static color_spec function_exit_color; /* if true, append [file:line] to output */ static bool location_enabled; /* when .location_enabled, write [file:line] starting this many chars from left margin */ @@ -85,12 +85,12 @@ namespace xo { log_config_impl::encoding = CE_Ansi; template - std::uint32_t - log_config_impl::function_entry_color = 34; + color_spec + log_config_impl::function_entry_color = color_spec::ansi(34); template - std::uint32_t - log_config_impl::function_exit_color = 32; + color_spec + log_config_impl::function_exit_color = color_spec::ansi(32); template bool diff --git a/include/indentlog/log_state.hpp b/include/indentlog/log_state.hpp index 6895fb25..c1450779 100644 --- a/include/indentlog/log_state.hpp +++ b/include/indentlog/log_state.hpp @@ -194,9 +194,7 @@ namespace xo { this->indent(' '); char ee_label = '\0'; - std::uint32_t fn_color = 0; - - color_encoding encoding = log_config::encoding; + color_spec fn_color; /* mnemonic for scope entry/exit */ switch(entryexit) { @@ -232,7 +230,7 @@ namespace xo { this->ss_ << ' '; /* scope name - note no trailing newline; expect .preamble()/.postamble() caller to supply */ - this->ss_ << function_name(style, color_spec(encoding, fn_color), name1) << name2; + this->ss_ << function_name(style, fn_color, name1) << name2; } /*entryexit_aux*/ template From e674fe65b13f7f907e15beed8e432a6ce54aca91 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 18 Sep 2023 12:57:10 -0400 Subject: [PATCH 0065/2693] indentlog: refactor: retire log_config.encoding --- example/ex3/ex3.cpp | 1 - include/indentlog/log_config.hpp | 6 ------ 2 files changed, 7 deletions(-) diff --git a/example/ex3/ex3.cpp b/example/ex3/ex3.cpp index 69ad095d..ab343dd4 100644 --- a/example/ex3/ex3.cpp +++ b/example/ex3/ex3.cpp @@ -31,7 +31,6 @@ main(int argc, char ** argv) { log_config::indent_width = 4; log_config::max_indent_width = 30; log_config::location_tab = 80; - log_config::encoding = CE_Xterm; log_config::function_entry_color = color_spec::xterm(69); log_config::function_exit_color = color_spec::xterm(70); log_config::code_location_color = color_spec::xterm(166); diff --git a/include/indentlog/log_config.hpp b/include/indentlog/log_config.hpp index b239aa60..ff63ade1 100644 --- a/include/indentlog/log_config.hpp +++ b/include/indentlog/log_config.hpp @@ -29,8 +29,6 @@ namespace xo { static color_spec nesting_level_color; /* display style for function names. FS_Simple|FS_Pretty|FS_Streamlined */ static function_style style; - /* color encoding */ - static color_encoding encoding; /* color to use for function name, on entry/exit (xo::scope creation/destruction) * (ansi color codes, see Select Graphics Rendition subset) */ @@ -80,10 +78,6 @@ namespace xo { function_style log_config_impl::style = FS_Streamlined; - template - color_encoding - log_config_impl::encoding = CE_Ansi; - template color_spec log_config_impl::function_entry_color = color_spec::ansi(34); From 30753c76af479e3a5aaeb7719756e5fb194c880c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 18 Sep 2023 13:01:44 -0400 Subject: [PATCH 0066/2693] indentlog: refactor: color_encoding -> strongly-typed --- include/indentlog/color.hpp | 26 +++++++++++++------------- include/indentlog/log_level.hpp | 2 +- include/indentlog/tag_config.hpp | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/include/indentlog/color.hpp b/include/indentlog/color.hpp index 6d0ee749..32661d9b 100644 --- a/include/indentlog/color.hpp +++ b/include/indentlog/color.hpp @@ -7,10 +7,10 @@ #include namespace xo { - enum color_encoding { - CE_None, - CE_Ansi, - CE_Xterm, + enum class color_encoding : std::uint8_t { + none, + ansi, + xterm, }; /* specify a color (consistent with ANSI escape sequences - the Select Graphics Rendition subset @@ -32,8 +32,8 @@ namespace xo { : encoding_{encoding}, code_{code} {} static color_spec none() { return color_spec(); } - static color_spec ansi(std::uint32_t code) { return color_spec(CE_Ansi, code); } - static color_spec xterm(std::uint32_t code) { return color_spec(CE_Xterm, code); } + static color_spec ansi(std::uint32_t code) { return color_spec(color_encoding::ansi, code); } + static color_spec xterm(std::uint32_t code) { return color_spec(color_encoding::xterm, code); } static color_spec rgb(std::uint8_t red, std::uint8_t green, std::uint8_t blue) { return none(); //return color_spec(CE_Rgb, (red << 16 | green << 8 | blue)); @@ -62,12 +62,12 @@ namespace xo { void print_fg_color_on (std::ostream & os) const { switch (encoding_) { - case CE_None: + case color_encoding::none: break; - case CE_Ansi: + case color_encoding::ansi: os << "\033[31;" << code_ << "m"; break; - case CE_Xterm: + case color_encoding::xterm: os << "\033[38;5;" << code_ << "m"; break; } @@ -76,10 +76,10 @@ namespace xo { /* escape to reverse effect of .print_on() */ void print_fg_color_off (std::ostream & os) const { switch (encoding_) { - case CE_None: + case color_encoding::none: break; - case CE_Ansi: - case CE_Xterm: + case color_encoding::ansi: + case color_encoding::xterm: os << "\033[0m"; break; } @@ -87,7 +87,7 @@ namespace xo { private: /* none | ansi | xterm | rgb */ - color_encoding encoding_ = CE_None; + color_encoding encoding_ = color_encoding::none; /* ansi : 30..37, 90..97 * xterm : 0..255 * see [[https://i.stack.imgur.com/KTSQa.png]] diff --git a/include/indentlog/log_level.hpp b/include/indentlog/log_level.hpp index 55c4f2a4..8b470891 100644 --- a/include/indentlog/log_level.hpp +++ b/include/indentlog/log_level.hpp @@ -3,7 +3,7 @@ #include namespace xo { - enum class log_level : std::uint32_t { + enum class log_level : std::uint8_t { /* control log message severity * silent > always > severe > error > warning > info > chatty > never * diff --git a/include/indentlog/tag_config.hpp b/include/indentlog/tag_config.hpp index d4a67cb7..8b9643e6 100644 --- a/include/indentlog/tag_config.hpp +++ b/include/indentlog/tag_config.hpp @@ -22,7 +22,7 @@ namespace xo { template color_encoding - tag_config_impl::encoding = CE_Xterm; + tag_config_impl::encoding = color_encoding::xterm; template std::uint32_t From 346eef69a42cdbc17cf9c991a230ef64f1db9d8a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 18 Sep 2023 13:06:28 -0400 Subject: [PATCH 0067/2693] indentlog: refactor: color_spec -> color_spec_type --- example/ex3/ex3.cpp | 6 +-- include/indentlog/code_location.hpp | 4 +- include/indentlog/color.hpp | 62 ++++++++++++++--------------- include/indentlog/function.hpp | 6 +-- include/indentlog/log_config.hpp | 24 +++++------ include/indentlog/log_state.hpp | 2 +- include/indentlog/tag.hpp | 2 +- 7 files changed, 53 insertions(+), 53 deletions(-) diff --git a/example/ex3/ex3.cpp b/example/ex3/ex3.cpp index ab343dd4..cc592a5b 100644 --- a/example/ex3/ex3.cpp +++ b/example/ex3/ex3.cpp @@ -31,9 +31,9 @@ main(int argc, char ** argv) { log_config::indent_width = 4; log_config::max_indent_width = 30; log_config::location_tab = 80; - log_config::function_entry_color = color_spec::xterm(69); - log_config::function_exit_color = color_spec::xterm(70); - log_config::code_location_color = color_spec::xterm(166); + log_config::function_entry_color = color_spec_type::xterm(69); + log_config::function_exit_color = color_spec_type::xterm(70); + log_config::code_location_color = color_spec_type::xterm(166); int n = 3; diff --git a/include/indentlog/code_location.hpp b/include/indentlog/code_location.hpp index 4e4bab21..cd6c9308 100644 --- a/include/indentlog/code_location.hpp +++ b/include/indentlog/code_location.hpp @@ -19,7 +19,7 @@ namespace xo { public: code_location_impl(std::string_view file, std::uint32_t line, - color_spec colorspec) + color_spec_type colorspec) : file_{file}, line_{line}, color_spec_{colorspec} {} void print_code_location(std::ostream & os) const { @@ -36,7 +36,7 @@ namespace xo { /* __LINE__ */ std::uint32_t line_ = 0; /* color encoding for [file:line] */ - color_spec color_spec_; + color_spec_type color_spec_; }; /*code_location_impl*/ using code_location = code_location_impl; diff --git a/include/indentlog/color.hpp b/include/indentlog/color.hpp index 32661d9b..52548b5a 100644 --- a/include/indentlog/color.hpp +++ b/include/indentlog/color.hpp @@ -25,37 +25,37 @@ namespace xo { * | rgb | \033[38;2 | \033[38;2;10;20;30m | 24-bit colors | 3x 0..255 | * */ - class color_spec { + class color_spec_type { public: - color_spec() = default; - color_spec(color_encoding encoding, std::uint32_t code) + color_spec_type() = default; + color_spec_type(color_encoding encoding, std::uint32_t code) : encoding_{encoding}, code_{code} {} - static color_spec none() { return color_spec(); } - static color_spec ansi(std::uint32_t code) { return color_spec(color_encoding::ansi, code); } - static color_spec xterm(std::uint32_t code) { return color_spec(color_encoding::xterm, code); } - static color_spec rgb(std::uint8_t red, std::uint8_t green, std::uint8_t blue) { + static color_spec_type none() { return color_spec_type(); } + static color_spec_type ansi(std::uint32_t code) { return color_spec_type(color_encoding::ansi, code); } + static color_spec_type xterm(std::uint32_t code) { return color_spec_type(color_encoding::xterm, code); } + static color_spec_type rgb(std::uint8_t red, std::uint8_t green, std::uint8_t blue) { return none(); //return color_spec(CE_Rgb, (red << 16 | green << 8 | blue)); } /* 4-bit foreground colors */ - static color_spec black () { return ansi(30); } - static color_spec red () { return ansi(31); } - static color_spec green () { return ansi(32); } - static color_spec yellow () { return ansi(33); } - static color_spec blue () { return ansi(34); } - static color_spec magenta () { return ansi(35); } - static color_spec cyan () { return ansi(36); } - static color_spec white () { return ansi(37); } - static color_spec bright_black () { return ansi(90); } - static color_spec bright_red () { return ansi(91); } - static color_spec bright_green () { return ansi(92); } - static color_spec bright_yellow () { return ansi(99); } - static color_spec bright_blue () { return ansi(94); } - static color_spec bright_magenta () { return ansi(95); } - static color_spec bright_cyan () { return ansi(96); } - static color_spec bright_white () { return ansi(97); } + static color_spec_type black () { return ansi(30); } + static color_spec_type red () { return ansi(31); } + static color_spec_type green () { return ansi(32); } + static color_spec_type yellow () { return ansi(33); } + static color_spec_type blue () { return ansi(34); } + static color_spec_type magenta () { return ansi(35); } + static color_spec_type cyan () { return ansi(36); } + static color_spec_type white () { return ansi(37); } + static color_spec_type bright_black () { return ansi(90); } + static color_spec_type bright_red () { return ansi(91); } + static color_spec_type bright_green () { return ansi(92); } + static color_spec_type bright_yellow () { return ansi(99); } + static color_spec_type bright_blue () { return ansi(94); } + static color_spec_type bright_magenta () { return ansi(95); } + static color_spec_type bright_cyan () { return ansi(96); } + static color_spec_type bright_white () { return ansi(97); } color_encoding encoding() const { return encoding_; } std::uint32_t code() const { return code_; } @@ -98,7 +98,7 @@ namespace xo { * rgb : r={hi 8 bits}, g={mid 8 bits}, b={lo 8 bits} */ std::uint32_t code_ = 0; - }; /*color_spec*/ + }; /*color_spec_type*/ enum color_flags { CF_None = 0x0, @@ -112,10 +112,10 @@ namespace xo { template class color_impl { public: - color_impl(color_flags flags, color_spec spec, Contents && contents) + color_impl(color_flags flags, color_spec_type spec, Contents && contents) : flags_{flags}, spec_{spec}, contents_{std::forward(contents)} {} - color_spec const & spec() const { return spec_; } + color_spec_type const & spec() const { return spec_; } std::uint32_t color() const { return spec_.code(); } Contents const & contents() const { return contents_; } @@ -138,25 +138,25 @@ namespace xo { */ color_flags flags_ = CF_None; - color_spec spec_; + color_spec_type spec_; Contents contents_; }; /*color_impl*/ template - color_impl with_color(color_spec spec, Contents && contents) { + color_impl with_color(color_spec_type spec, Contents && contents) { return color_impl(CF_All, spec, std::forward(contents)); } /*with_color*/ inline color_impl - color_on(color_spec spec) { + color_on(color_spec_type spec) { return color_impl(CF_ColorOn, spec, 0); } /*color_on*/ inline color_impl color_off() { - /* any spec other than color_spec::none() works here */ - return color_impl(CF_ColorOff, color_spec::white(), 0); + /* any spec other than color_spec_type::none() works here */ + return color_impl(CF_ColorOff, color_spec_type::white(), 0); } /*color_off*/ template diff --git a/include/indentlog/function.hpp b/include/indentlog/function.hpp index bfb107e8..d68c75b9 100644 --- a/include/indentlog/function.hpp +++ b/include/indentlog/function.hpp @@ -26,12 +26,12 @@ namespace xo { * 31 = red */ function_name_impl(function_style style, - color_spec const & spec, + color_spec_type const & spec, std::string_view pretty) : style_{style}, color_spec_{spec}, pretty_{pretty} {} function_style style() const { return style_; } - color_spec const & colorspec() const { return color_spec_; } + color_spec_type const & colorspec() const { return color_spec_; } std::string_view const & pretty() const { return pretty_; } /* e.g. @@ -231,7 +231,7 @@ namespace xo { /* FS_Simple | FS_Pretty (= FS_Literal) | FS_Streamlined */ function_style style_; /* terminal color (controls vt100 escape) */ - color_spec color_spec_; + color_spec_type color_spec_; /* e.g. __PRETTY_FUNCTION__ */ std::string_view pretty_; }; /*function_name_impl*/ diff --git a/include/indentlog/log_config.hpp b/include/indentlog/log_config.hpp index ff63ade1..22371bb7 100644 --- a/include/indentlog/log_config.hpp +++ b/include/indentlog/log_config.hpp @@ -26,20 +26,20 @@ namespace xo { /* if true enable explicit nesting level display [nnn] */ static bool nesting_level_enabled; /* color to use for explicit nesting level */ - static color_spec nesting_level_color; + static color_spec_type nesting_level_color; /* display style for function names. FS_Simple|FS_Pretty|FS_Streamlined */ static function_style style; /* color to use for function name, on entry/exit (xo::scope creation/destruction) * (ansi color codes, see Select Graphics Rendition subset) */ - static color_spec function_entry_color; - static color_spec function_exit_color; + static color_spec_type function_entry_color; + static color_spec_type function_exit_color; /* if true, append [file:line] to output */ static bool location_enabled; /* when .location_enabled, write [file:line] starting this many chars from left margin */ static std::uint32_t location_tab; /* color to use for code location */ - static color_spec code_location_color; + static color_spec_type code_location_color; }; /*log_config_impl*/ template @@ -71,20 +71,20 @@ namespace xo { log_config_impl::nesting_level_enabled = true; template - color_spec - log_config_impl::nesting_level_color = color_spec::xterm(195); + color_spec_type + log_config_impl::nesting_level_color = color_spec_type::xterm(195); template function_style log_config_impl::style = FS_Streamlined; template - color_spec - log_config_impl::function_entry_color = color_spec::ansi(34); + color_spec_type + log_config_impl::function_entry_color = color_spec_type::ansi(34); template - color_spec - log_config_impl::function_exit_color = color_spec::ansi(32); + color_spec_type + log_config_impl::function_exit_color = color_spec_type::ansi(32); template bool @@ -95,8 +95,8 @@ namespace xo { log_config_impl::location_tab = 80; template - color_spec - log_config_impl::code_location_color = color_spec::red(); + color_spec_type + log_config_impl::code_location_color = color_spec_type::red(); using log_config = log_config_impl; } /*namespace xo*/ diff --git a/include/indentlog/log_state.hpp b/include/indentlog/log_state.hpp index c1450779..66f1e15d 100644 --- a/include/indentlog/log_state.hpp +++ b/include/indentlog/log_state.hpp @@ -194,7 +194,7 @@ namespace xo { this->indent(' '); char ee_label = '\0'; - color_spec fn_color; + color_spec_type fn_color; /* mnemonic for scope entry/exit */ switch(entryexit) { diff --git a/include/indentlog/tag.hpp b/include/indentlog/tag.hpp index a6825bee..7b22e67b 100644 --- a/include/indentlog/tag.hpp +++ b/include/indentlog/tag.hpp @@ -96,7 +96,7 @@ namespace xo { if(PrefixSpace) s << " "; - s << with_color(color_spec(tag_config::encoding, tag_config::tag_color), concat((char const *)":", tag.name())) + s << with_color(color_spec_type(tag_config::encoding, tag_config::tag_color), concat((char const *)":", tag.name())) << " " << unq(tag.value()); return s; From 6a06c769c8324a5f106ef1147d51bd470ab0454a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 18 Sep 2023 13:09:15 -0400 Subject: [PATCH 0068/2693] indentlog: refactor: tag_config.tag_color uses color_spec --- include/indentlog/tag.hpp | 2 +- include/indentlog/tag_config.hpp | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/include/indentlog/tag.hpp b/include/indentlog/tag.hpp index 7b22e67b..6c7eafbb 100644 --- a/include/indentlog/tag.hpp +++ b/include/indentlog/tag.hpp @@ -96,7 +96,7 @@ namespace xo { if(PrefixSpace) s << " "; - s << with_color(color_spec_type(tag_config::encoding, tag_config::tag_color), concat((char const *)":", tag.name())) + s << with_color(tag_config::tag_color, concat((char const *)":", tag.name())) << " " << unq(tag.value()); return s; diff --git a/include/indentlog/tag_config.hpp b/include/indentlog/tag_config.hpp index 8b9643e6..f86b2c8f 100644 --- a/include/indentlog/tag_config.hpp +++ b/include/indentlog/tag_config.hpp @@ -9,24 +9,18 @@ namespace xo { /* Tag here b/c we want header-only library */ template struct tag_config_impl { - /* color encoding */ - static color_encoding encoding; /* color to use for tags * os << tag("foo", foovalue) * to produces output like * :foo foovalue * with :foo using .tag_color */ - static std::uint32_t tag_color; + static color_spec_type tag_color; }; /*tag_config_impl*/ template - color_encoding - tag_config_impl::encoding = color_encoding::xterm; - - template - std::uint32_t - tag_config_impl::tag_color = 245; + color_spec_type + tag_config_impl::tag_color = color_spec_type::xterm(245); using tag_config = tag_config_impl; } /*namespace xo*/ From fbe0de6cba9f71d73f2e8a57dd01ac9ee9c7cad3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 18 Sep 2023 13:12:19 -0400 Subject: [PATCH 0069/2693] indentlog: + support rgb color --- include/indentlog/color.hpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/include/indentlog/color.hpp b/include/indentlog/color.hpp index 52548b5a..688a152a 100644 --- a/include/indentlog/color.hpp +++ b/include/indentlog/color.hpp @@ -11,6 +11,7 @@ namespace xo { none, ansi, xterm, + rgb }; /* specify a color (consistent with ANSI escape sequences - the Select Graphics Rendition subset @@ -35,8 +36,7 @@ namespace xo { static color_spec_type ansi(std::uint32_t code) { return color_spec_type(color_encoding::ansi, code); } static color_spec_type xterm(std::uint32_t code) { return color_spec_type(color_encoding::xterm, code); } static color_spec_type rgb(std::uint8_t red, std::uint8_t green, std::uint8_t blue) { - return none(); - //return color_spec(CE_Rgb, (red << 16 | green << 8 | blue)); + return color_spec_type(color_encoding::rgb, (red << 16 | green << 8 | blue)); } /* 4-bit foreground colors */ @@ -70,6 +70,11 @@ namespace xo { case color_encoding::xterm: os << "\033[38;5;" << code_ << "m"; break; + case color_encoding::rgb: + os << "\033[38;2;" + << (0xff & (code_ >> 16)) << ";" + << (0xff & (code_ >> 8)) << ";" + << (0xff & (code_ >> 0)) << "m"; } } /*print_fg_color_on*/ @@ -80,6 +85,7 @@ namespace xo { break; case color_encoding::ansi: case color_encoding::xterm: + case color_encoding::rgb: os << "\033[0m"; break; } From 21c1a8e1aec94011c46f5c80257723c53f29546c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 18 Sep 2023 13:18:07 -0400 Subject: [PATCH 0070/2693] indentlog: refactor: strongly-typed coloring_control_flags --- include/indentlog/color.hpp | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/include/indentlog/color.hpp b/include/indentlog/color.hpp index 688a152a..49eb0bcf 100644 --- a/include/indentlog/color.hpp +++ b/include/indentlog/color.hpp @@ -106,19 +106,26 @@ namespace xo { std::uint32_t code_ = 0; }; /*color_spec_type*/ - enum color_flags { - CF_None = 0x0, - CF_ColorOn = 0x01, - CF_Contents = 0x02, - CF_ColorOff = 0x04, - CF_All = 0x07 + enum class coloring_control_flags : std::uint8_t { + none = 0x0, + color_on = 0x01, + contents = 0x02, + color_off = 0x04, + all = 0x07 }; + inline std::uint8_t operator& (coloring_control_flags x, coloring_control_flags y) { + return static_cast(x) & static_cast(y); + } + inline std::uint8_t operator| (coloring_control_flags x, coloring_control_flags y) { + return static_cast(x) | static_cast(y); + } + /* stream-insertable color control */ template class color_impl { public: - color_impl(color_flags flags, color_spec_type spec, Contents && contents) + color_impl(coloring_control_flags flags, color_spec_type spec, Contents && contents) : flags_{flags}, spec_{spec}, contents_{std::forward(contents)} {} color_spec_type const & spec() const { return spec_; } @@ -126,13 +133,13 @@ namespace xo { Contents const & contents() const { return contents_; } void print(std::ostream & os) const { - if (flags_ & CF_ColorOn) + if (flags_ & coloring_control_flags::color_on) spec_.print_fg_color_on(os); - if (flags_ & CF_Contents) + if (flags_ & coloring_control_flags::contents) os << contents_; - if (flags_ & CF_ColorOff) + if (flags_ & coloring_control_flags::color_off) spec_.print_fg_color_off(os); } /*print*/ @@ -140,9 +147,9 @@ namespace xo { /* controls independently what to print * \033[38;5;117m hello, world! \033[0m * <------------> <-----------> <-----> - * CF_ColorOn CF_Contents CF_ColorOff + * color_on contents color_off */ - color_flags flags_ = CF_None; + coloring_control_flags flags_ = coloring_control_flags::none; color_spec_type spec_; @@ -151,18 +158,18 @@ namespace xo { template color_impl with_color(color_spec_type spec, Contents && contents) { - return color_impl(CF_All, spec, std::forward(contents)); + return color_impl(coloring_control_flags::all, spec, std::forward(contents)); } /*with_color*/ inline color_impl color_on(color_spec_type spec) { - return color_impl(CF_ColorOn, spec, 0); + return color_impl(coloring_control_flags::color_on, spec, 0); } /*color_on*/ inline color_impl color_off() { /* any spec other than color_spec_type::none() works here */ - return color_impl(CF_ColorOff, color_spec_type::white(), 0); + return color_impl(coloring_control_flags::color_off, color_spec_type::white(), 0); } /*color_off*/ template From 5c60277610beda7d818df2761d597ead1b914789 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 18 Sep 2023 13:24:41 -0400 Subject: [PATCH 0071/2693] indentlog: strongly-typed fucntion_style enum --- example/ex3/ex3.cpp | 2 +- example/ex4/ex4.cpp | 9 ++++--- include/indentlog/function.hpp | 41 +++++++++++++++++++------------- include/indentlog/log_config.hpp | 2 +- include/indentlog/scope.hpp | 4 ++-- 5 files changed, 32 insertions(+), 26 deletions(-) diff --git a/example/ex3/ex3.cpp b/example/ex3/ex3.cpp index cc592a5b..2a09d2d7 100644 --- a/example/ex3/ex3.cpp +++ b/example/ex3/ex3.cpp @@ -27,7 +27,7 @@ main(int argc, char ** argv) { log_config::min_log_level = log_level::info; log_config::time_enabled = true; log_config::time_local_flag = true; - log_config::style = FS_Streamlined; + log_config::style = function_style::streamlined; log_config::indent_width = 4; log_config::max_indent_width = 30; log_config::location_tab = 80; diff --git a/example/ex4/ex4.cpp b/example/ex4/ex4.cpp index 05ca39e0..d59e83c1 100644 --- a/example/ex4/ex4.cpp +++ b/example/ex4/ex4.cpp @@ -26,8 +26,7 @@ private: int main(int argc, char ** argv) { - //log_config::style = FS_Pretty; - log_config::style = FS_Streamlined; + log_config::style = function_style::streamlined; log_config::min_log_level = log_level::info; scope log(XO_ENTER0(info)); @@ -37,15 +36,15 @@ main(int argc, char ** argv) { double x = 3.0; double r = 0.0; - log_config::style = FS_Pretty; + log_config::style = function_style::pretty; r = quadratic(x); - log_config::style = FS_Streamlined; + log_config::style = function_style::streamlined; r = quadratic(x); - log_config::style = FS_Simple; + log_config::style = function_style::simple; r = quadratic(x); } diff --git a/include/indentlog/function.hpp b/include/indentlog/function.hpp index d68c75b9..5bbfbd05 100644 --- a/include/indentlog/function.hpp +++ b/include/indentlog/function.hpp @@ -6,15 +6,22 @@ #include namespace xo { - enum function_style { - /* literal: print given name, no alterations */ - FS_Literal, - /* pretty: print name, surrounded by [] */ - FS_Pretty, - /* streamlined: remove extraneous detail, try to print something like class::method */ - FS_Streamlined, - /* simple: remove everything except function/method name */ - FS_Simple + enum class function_style : std::uint8_t { + /* literal: print supplied text, no alterations */ + literal, + /* pretty: print name, surrounded by [] + * [double Quadratic::operator()(double) const] + */ + pretty, + /* streamlined: remove extraneous detail, + * try to print something like class::method + * Quadratic::operator() + */ + streamlined, + /* simple: remove everything except function/method name + * operator() + */ + simple }; /* Tag to drive header-only expression */ @@ -245,23 +252,23 @@ namespace xo { /* set text color */ switch(fn.style()) { - case FS_Literal: + case function_style::literal: os << with_color(fn.colorspec(), fn.pretty()); break; - case FS_Pretty: + case function_style::pretty: os << "[" << with_color(fn.colorspec(), fn.pretty()) << "]"; break; - case FS_Simple: - os << color_on(fn.colorspec()); - function_name::print_simple(os, fn.pretty()); - os << color_off(); - break; - case FS_Streamlined: + case function_style::streamlined: /* omit namespace qualifiers and template arguments */ os << color_on(fn.colorspec()); function_name::print_streamlined(os, fn.pretty()); os << color_off(); break; + case function_style::simple: + os << color_on(fn.colorspec()); + function_name::print_simple(os, fn.pretty()); + os << color_off(); + break; } return os; diff --git a/include/indentlog/log_config.hpp b/include/indentlog/log_config.hpp index 22371bb7..1630b5c2 100644 --- a/include/indentlog/log_config.hpp +++ b/include/indentlog/log_config.hpp @@ -76,7 +76,7 @@ namespace xo { template function_style - log_config_impl::style = FS_Streamlined; + log_config_impl::style = function_style::streamlined; template color_spec_type diff --git a/include/indentlog/scope.hpp b/include/indentlog/scope.hpp index fac759c2..fd0da297 100644 --- a/include/indentlog/scope.hpp +++ b/include/indentlog/scope.hpp @@ -56,7 +56,7 @@ namespace xo { /* threshold level for logging -- write messages with severity >= this level */ log_level log_level_ = log_level::error; /* FS_Pretty | FS_Streamlined | FS_Simple */ - function_style style_ = FS_Pretty; + function_style style_ = function_style::pretty; std::string_view name1_ = "<.name1>"; std::string_view name2_ = "<.name2>"; /* __FILE__ */ @@ -168,7 +168,7 @@ namespace xo { /* send indented output to this streambuf (e.g. std::clog.rdbuf()) */ std::streambuf * dest_sbuf_ = std::clog.rdbuf(); /* style for displaying .name1 */ - function_style style_ = FS_Pretty; + function_style style_ = function_style::pretty; /* name of this scope (part 1) */ std::string_view name1_ = ""; /* name of this scope (part 2) */ From efe207a4deb0038b6791dc843bbc705c9b8cb6e0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 18 Sep 2023 13:29:04 -0400 Subject: [PATCH 0072/2693] indentlog: refactor: move stream-inserters to print/ subdir --- include/indentlog/log_config.hpp | 4 ++-- include/indentlog/log_state.hpp | 8 ++++---- include/indentlog/{ => print}/array.hpp | 0 include/indentlog/{ => print}/code_location.hpp | 0 include/indentlog/{ => print}/color.hpp | 0 include/indentlog/{ => print}/concat.hpp | 0 include/indentlog/{ => print}/filename.hpp | 0 include/indentlog/{ => print}/fixed.hpp | 0 include/indentlog/{ => print}/function.hpp | 0 include/indentlog/{ => print}/pad.hpp | 0 include/indentlog/{ => print}/printer.hpp | 0 include/indentlog/{ => print}/quoted.hpp | 0 include/indentlog/{ => print}/quoted_char.hpp | 0 include/indentlog/{ => print}/tag.hpp | 0 include/indentlog/{ => print}/tag_config.hpp | 0 include/indentlog/{ => print}/time.hpp | 0 include/indentlog/{ => print}/tostr.hpp | 0 include/indentlog/{ => print}/vector.hpp | 0 include/indentlog/scope.hpp | 6 +++--- 19 files changed, 9 insertions(+), 9 deletions(-) rename include/indentlog/{ => print}/array.hpp (100%) rename include/indentlog/{ => print}/code_location.hpp (100%) rename include/indentlog/{ => print}/color.hpp (100%) rename include/indentlog/{ => print}/concat.hpp (100%) rename include/indentlog/{ => print}/filename.hpp (100%) rename include/indentlog/{ => print}/fixed.hpp (100%) rename include/indentlog/{ => print}/function.hpp (100%) rename include/indentlog/{ => print}/pad.hpp (100%) rename include/indentlog/{ => print}/printer.hpp (100%) rename include/indentlog/{ => print}/quoted.hpp (100%) rename include/indentlog/{ => print}/quoted_char.hpp (100%) rename include/indentlog/{ => print}/tag.hpp (100%) rename include/indentlog/{ => print}/tag_config.hpp (100%) rename include/indentlog/{ => print}/time.hpp (100%) rename include/indentlog/{ => print}/tostr.hpp (100%) rename include/indentlog/{ => print}/vector.hpp (100%) diff --git a/include/indentlog/log_config.hpp b/include/indentlog/log_config.hpp index 1630b5c2..9e2099cf 100644 --- a/include/indentlog/log_config.hpp +++ b/include/indentlog/log_config.hpp @@ -3,8 +3,8 @@ #pragma once #include "log_level.hpp" -#include "function.hpp" -#include "color.hpp" +#include "print/function.hpp" +#include "print/color.hpp" #include namespace xo { diff --git a/include/indentlog/log_state.hpp b/include/indentlog/log_state.hpp index 66f1e15d..42e23d1f 100644 --- a/include/indentlog/log_state.hpp +++ b/include/indentlog/log_state.hpp @@ -4,10 +4,10 @@ #include "log_config.hpp" #include "log_streambuf.hpp" -#include "pad.hpp" -#include "filename.hpp" -#include "code_location.hpp" -#include "time.hpp" +#include "print/pad.hpp" +#include "print/filename.hpp" +#include "print/code_location.hpp" +#include "print/time.hpp" #include #include #include // for std::unique_ptr diff --git a/include/indentlog/array.hpp b/include/indentlog/print/array.hpp similarity index 100% rename from include/indentlog/array.hpp rename to include/indentlog/print/array.hpp diff --git a/include/indentlog/code_location.hpp b/include/indentlog/print/code_location.hpp similarity index 100% rename from include/indentlog/code_location.hpp rename to include/indentlog/print/code_location.hpp diff --git a/include/indentlog/color.hpp b/include/indentlog/print/color.hpp similarity index 100% rename from include/indentlog/color.hpp rename to include/indentlog/print/color.hpp diff --git a/include/indentlog/concat.hpp b/include/indentlog/print/concat.hpp similarity index 100% rename from include/indentlog/concat.hpp rename to include/indentlog/print/concat.hpp diff --git a/include/indentlog/filename.hpp b/include/indentlog/print/filename.hpp similarity index 100% rename from include/indentlog/filename.hpp rename to include/indentlog/print/filename.hpp diff --git a/include/indentlog/fixed.hpp b/include/indentlog/print/fixed.hpp similarity index 100% rename from include/indentlog/fixed.hpp rename to include/indentlog/print/fixed.hpp diff --git a/include/indentlog/function.hpp b/include/indentlog/print/function.hpp similarity index 100% rename from include/indentlog/function.hpp rename to include/indentlog/print/function.hpp diff --git a/include/indentlog/pad.hpp b/include/indentlog/print/pad.hpp similarity index 100% rename from include/indentlog/pad.hpp rename to include/indentlog/print/pad.hpp diff --git a/include/indentlog/printer.hpp b/include/indentlog/print/printer.hpp similarity index 100% rename from include/indentlog/printer.hpp rename to include/indentlog/print/printer.hpp diff --git a/include/indentlog/quoted.hpp b/include/indentlog/print/quoted.hpp similarity index 100% rename from include/indentlog/quoted.hpp rename to include/indentlog/print/quoted.hpp diff --git a/include/indentlog/quoted_char.hpp b/include/indentlog/print/quoted_char.hpp similarity index 100% rename from include/indentlog/quoted_char.hpp rename to include/indentlog/print/quoted_char.hpp diff --git a/include/indentlog/tag.hpp b/include/indentlog/print/tag.hpp similarity index 100% rename from include/indentlog/tag.hpp rename to include/indentlog/print/tag.hpp diff --git a/include/indentlog/tag_config.hpp b/include/indentlog/print/tag_config.hpp similarity index 100% rename from include/indentlog/tag_config.hpp rename to include/indentlog/print/tag_config.hpp diff --git a/include/indentlog/time.hpp b/include/indentlog/print/time.hpp similarity index 100% rename from include/indentlog/time.hpp rename to include/indentlog/print/time.hpp diff --git a/include/indentlog/tostr.hpp b/include/indentlog/print/tostr.hpp similarity index 100% rename from include/indentlog/tostr.hpp rename to include/indentlog/print/tostr.hpp diff --git a/include/indentlog/vector.hpp b/include/indentlog/print/vector.hpp similarity index 100% rename from include/indentlog/vector.hpp rename to include/indentlog/print/vector.hpp diff --git a/include/indentlog/scope.hpp b/include/indentlog/scope.hpp index fd0da297..b86923c1 100644 --- a/include/indentlog/scope.hpp +++ b/include/indentlog/scope.hpp @@ -3,9 +3,9 @@ #pragma once #include "log_state.hpp" -#include "filename.hpp" -#include "tostr.hpp" -#include "tag.hpp" +#include "print/filename.hpp" +#include "print/tostr.hpp" +#include "print/tag.hpp" #include #include From 38101cce90ed38a7562af913689d1f9dd725b618 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 18 Sep 2023 13:33:07 -0400 Subject: [PATCH 0073/2693] indentlog: ++ FILES --- FILES | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/FILES b/FILES index 68a84654..7838f779 100644 --- a/FILES +++ b/FILES @@ -1,23 +1,29 @@ directory layout -+- README.md markdown README, for github -+- img image files, used in docs ++- README.md markdown README, for github ++- img image files, used in docs | +- ex1.png | +- ex2.png | +- ex3.png | \- ex4.png -+- LICENSE software license -+- CMakeLists.txt toplevel cmake config ++- LICENSE software license ++- CMakeLists.txt toplevel cmake config +- cmake -| \- nestlog.cmake cmake support files -+- compile_commands.json symlink to record of compiler commands; for LSP support -+- include to install, copy contents of this directory to permanent location +| \- nestlog.cmake cmake support files ++- compile_commands.json symlink to record of compiler commands; for LSP support ++- include to install, copy contents of this directory to permanent location | \- indentlog -| +- scope.hpp -| ... +| +- scope.hpp logger api -- appl code will #include this +| +- log_config.hpp logger api -- control logger format, verbosity, colors etc. +| +- log_level.hpp encode logger verbosity +| +- log_state.hpp per-thread state tracking (e.g. recognize nesting) +| +- log_streambuf.hpp custom streambuf +| \- print +| +- tag.hpp stream inserters +| ... \- example - +- CMakeLists.txt cmake config + +- CMakeLists.txt cmake config +- ex1 - | +- CMakeLists.txt ex1 cmake config - | \- ex1.cpp example .cpp exercising indentlog + | +- CMakeLists.txt ex1 cmake config + | \- ex1.cpp example .cpp exercising indentlog ... From eaa5f3ada3e31f5e01bade9e66f1d277792f6196 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 18 Sep 2023 16:15:41 -0400 Subject: [PATCH 0074/2693] refactor: + indentlog/timeutil/, for clarity --- include/indentlog/log_state.hpp | 4 +- include/indentlog/print/time.hpp | 300 ++---------------------- include/indentlog/scope.hpp | 2 +- include/indentlog/timeutil/timeutil.hpp | 287 +++++++++++++++++++++++ 4 files changed, 304 insertions(+), 289 deletions(-) create mode 100644 include/indentlog/timeutil/timeutil.hpp diff --git a/include/indentlog/log_state.hpp b/include/indentlog/log_state.hpp index 42e23d1f..d7bec9ae 100644 --- a/include/indentlog/log_state.hpp +++ b/include/indentlog/log_state.hpp @@ -37,7 +37,7 @@ namespace xo { std::ostream & ss() { return ss_; } void check_print_time(utc_nanos now_tm) { - using xo::time::time; + using xo::time::timeutil; using xo::time::utc_nanos; using xo::time::hms_msec; using xo::time::hms_usec; @@ -190,7 +190,7 @@ namespace xo { sbuf->reset_stream(); - this->check_print_time(xo::time::time::now()); + this->check_print_time(xo::time::timeutil::now()); this->indent(' '); char ee_label = '\0'; diff --git a/include/indentlog/print/time.hpp b/include/indentlog/print/time.hpp index 1d93dd26..275f43da 100644 --- a/include/indentlog/print/time.hpp +++ b/include/indentlog/print/time.hpp @@ -2,284 +2,11 @@ #pragma once -#include -#include -#ifdef NOT_YET -# include -#endif -#include -#include -#include +#include "indentlog/timeutil/timeutil.hpp" namespace xo { namespace time { - - using utc_nanos = std::chrono::time_point; - - using nanos = std::chrono::nanoseconds; - using microseconds = std::chrono::microseconds; - using milliseconds = std::chrono::milliseconds; - using seconds = std::chrono::seconds; - using hours = std::chrono::hours; - using days = std::chrono::days; - - struct time { - static utc_nanos now() { - return utc_nanos(std::chrono::system_clock::now()); - } - - static utc_nanos epoch() { - return utc_nanos(std::chrono::system_clock::from_time_t(0)); - } /*epoch*/ - - static utc_nanos ymd_hms(uint32_t ymd, uint32_t hms) { - /* e.g. ymd=20220610 -> n_yr=2022, n_mon=06, n_dy=10 */ - - uint32_t n_yr = ymd / 10000; - uint32_t n_mon = (ymd % 10000) / 100; - uint32_t n_dy = ymd % 100; - - uint32_t n_hr = hms / 10000; - uint32_t n_min = (hms % 10000) / 100; - uint32_t n_sec = hms % 100; - - struct tm t; - - t.tm_year = n_yr - 1900; /* 0 means 1900 */ - t.tm_mon = n_mon - 1; /* 0 means january */ - t.tm_mday = n_dy; - - t.tm_hour = n_hr; /* 24 hour clock */ - t.tm_min = n_min; - t.tm_sec = n_sec; - - /* time since epoch */ - time_t epoch_time = timegm(&t); - - return std::chrono::system_clock::from_time_t(epoch_time); - } /*ymd_hms*/ - - /* midnight UTC on date ymd. - * e.g. ymd_midnight(20220707) -> midnight UTC on 7jul22 - */ - static utc_nanos ymd_midnight(uint32_t ymd) { - return ymd_hms(ymd, 0); - } /*ymd_midnight*/ - - static utc_nanos ymd_hms_usec(uint32_t ymd, uint32_t hms, uint32_t usec) { - utc_nanos s = ymd_hms(ymd, hms); - - return s + microseconds(usec); - } /*ymd_hms_usec*/ - - /* .first: UTC midnight on same calendar day as t0 - * .second: elapsed time from .first to t0 (i.e. UTC time-of-day for t0) - */ - static std::pair utc_split_vs_midnight(utc_nanos t0) { - using xo::time::microseconds; - using xo::time::utc_nanos; - - /* use yyyymmdd.hh:mm:ss.nnnnnn */ - - time_t t0_time_t = (std::chrono::system_clock::to_time_t - (std::chrono::time_point_cast(t0))); - - /* convert to std::tm, - * only provides 1-second precision - */ - std::tm t0_tm; - ::gmtime_r(&t0_time_t, &t0_tm); - - /* midnight on the same calendar day as t0_tm */ - std::tm midnight_tm = t0_tm; - { - midnight_tm.tm_hour = 0; - midnight_tm.tm_min = 0; - midnight_tm.tm_sec = 0; - } - - /* convert to UTC epoch seconds */ - time_t midnight_time_t = ::timegm(&midnight_tm); - - utc_nanos t0_midnight = - (std::chrono::time_point_cast( - std::chrono::system_clock::from_time_t(midnight_time_t))); - - nanos t0_tdy = t0 - t0_midnight; - - return std::pair(t0_midnight, t0_tdy); - } /*utc_split_vs_midnight*/ - - /* .first: LOCAL midnight on same calendar day as t0 (but in UTC coords) - * .second: elapsed time from .first to t0 (i.e. LOCAL time-of-day for t0) - */ - static std::pair local_split_vs_midnight(utc_nanos t0) { - using xo::time::microseconds; - using xo::time::utc_nanos; - - /* use yyyymmdd.hh:mm:ss.nnnnnn */ - - time_t t0_time_t = (std::chrono::system_clock::to_time_t - (std::chrono::time_point_cast(t0))); - - /* convert to std::tm, - * only provides 1-second precision - */ - std::tm t0_tm; - ::localtime_r(&t0_time_t, &t0_tm); - - /* midnight on the same calendar day as t0_tm */ - std::tm midnight_tm = t0_tm; - { - midnight_tm.tm_hour = 0; - midnight_tm.tm_min = 0; - midnight_tm.tm_sec = 0; - } - - /* convert local midnight to UTC epoch seconds */ - time_t midnight_time_t = ::timelocal(&midnight_tm); - - utc_nanos t0_midnight = - (std::chrono::time_point_cast( - std::chrono::system_clock::from_time_t(midnight_time_t))); - - nanos t0_tdy = t0 - t0_midnight; - - return std::pair(t0_midnight, t0_tdy); - } /*local_split_vs_midnight*/ - - /* split utc_nanos into - * std::tm - * .tm_year - * .tm_mon (1-12) - * .tm_mday (1-31) - * .tm_hour (0-23) - * .tm_min (0-59) - * .tm_sec (0-59) - * .tm_wday (0=sunday .. 6=saturday) - * .tm_yday (0=1jan .. 365) - * .tm_isdst (daylight savings time flag) - * usec (0-999999) - */ - static std::pair split_tm(utc_nanos t0) { - using xo::time::microseconds; - using xo::time::utc_nanos; - - /* use yyyymmdd.hh:mm:ss.nnnnnn */ - - time_t t0_time_t = (std::chrono::system_clock::to_time_t - (std::chrono::time_point_cast(t0))); - - /* convert to std::tm, un UTC coords, - * only provides 1-second precision - */ - std::tm t0_tm; - ::gmtime_r(&t0_time_t, &t0_tm); - - /* midnight on the same calendar day as t0_tm */ - std::tm midnight_tm = t0_tm; - - midnight_tm.tm_isdst = 0; - midnight_tm.tm_hour = 0; - midnight_tm.tm_min = 0; - midnight_tm.tm_sec = 0; - - /* convert back to epoch seconds */ - time_t midnight_time_t = ::mktime(&midnight_tm); - - utc_nanos t0_midnight = - (std::chrono::time_point_cast( - std::chrono::system_clock::from_time_t(midnight_time_t))); - - uint32_t usec = - (std::chrono::duration_cast( - std::chrono::hh_mm_ss(t0 - t0_midnight).subseconds())) - .count(); - - return std::make_pair(t0_tm, usec); - } /*split_tm*/ - - static void print_hms_msec(nanos dt, std::ostream & os) { - /* use hhmmss.nnn */ - using std::int32_t; - - auto hms = std::chrono::hh_mm_ss(dt); - int32_t h = hms.hours().count(); - int32_t m = hms.minutes().count(); - int32_t s = hms.seconds().count(); - int32_t msec = std::chrono::duration_cast(hms.subseconds()).count(); - - char buf[32]; - snprintf(buf, sizeof(buf), "%02d:%02d:%02d.%03d", h, m, s, msec); - - os << buf; - } /*print_hms_msec*/ - - static void print_utc_hms_msec(utc_nanos t0, std::ostream & os) { - print_hms_msec(utc_split_vs_midnight(t0).second, os); - } /*print_utc_hms_usec*/ - - static void print_hms_usec(nanos dt, std::ostream & os) { - /* use hhmmss.uuuuuu */ - using std::int32_t; - - auto hms = std::chrono::hh_mm_ss(dt); - int32_t h = hms.hours().count(); - int32_t m = hms.minutes().count(); - int32_t s = hms.seconds().count(); - int32_t usec = std::chrono::duration_cast(hms.subseconds()).count(); - - char buf[32]; - snprintf(buf, sizeof(buf), "%02d:%02d:%02d.%06d", h, m, s, usec); - - os << buf; - } /*print_hms_usec*/ - - static void print_utc_ymd_hms_usec(utc_nanos t0, std::ostream & os) { - using xo::time::microseconds; - using xo::time::utc_nanos; - - /* use yyyymmdd.hh:mm:ss.nnnnnn */ - - //std::tm t0_tm; - //uint32_t t0_usec; - - /* (structured binding ftw!) */ - auto [t0_tm, t0_usec] = split_tm(t0); - - /* no std::format in clang11 afaict */ - char usec_buf[7]; - snprintf(usec_buf, sizeof(usec_buf), "%06d", t0_usec); - - - /* control string | example - * ----------------------------+-------------------------- - * %c - locale-specific string | Fri Jun 10 16:29:05 2022 - * %Y - year | 2022 - * %m - month | 06 - * %d - day of month | 10 - * %H - hour | 16 - * %M - minute | 29 - * %S - second | 05 - * %Z - timezone | UTC - */ - os << std::put_time(&t0_tm, "%Y%m%d:%H:%M:%S.") << usec_buf; - } /*print_utc_ymd_hms_usec*/ - - /* print datetime in format compatible with ISO 8601. - * copying the format javascript uses, e.g: - * 2012-04-23T18:25:43.511Z - */ - static void print_iso8601(utc_nanos t0, std::ostream & os) { - auto [t0_tm, t0_usec] = split_tm(t0); - - char msec_buf[8]; - snprintf(msec_buf, sizeof(msec_buf), "%03d", t0_usec / 1000); - - os << std::put_time(&t0_tm, "%Y-%m-%dT%H:%M:%S.") << msec_buf << "Z"; - } /*print_iso8601*/ - }; /*time*/ + // ----- iso8601 ----- /* stream inserter that displays time in ISO 8601 format: * 2012-04-23T18:25:43.511Z @@ -294,18 +21,20 @@ namespace xo { operator<<(std::ostream & os, iso8601 x) { - time::print_iso8601(x.t0_, os); + timeutil::print_iso8601(x.t0_, os); return os; } /*operator<<*/ + // ----- hms_msec ----- + /* stream inserter that display time like: * hh:mm:ss.nnn */ struct hms_msec { hms_msec(nanos dt) : dt_{dt} {} - static hms_msec utc(utc_nanos t0) { return hms_msec(time::utc_split_vs_midnight(t0).second); } - static hms_msec local(utc_nanos t0) { return hms_msec(time::local_split_vs_midnight(t0).second); } + static hms_msec utc(utc_nanos t0) { return hms_msec(timeutil::utc_split_vs_midnight(t0).second); } + static hms_msec local(utc_nanos t0) { return hms_msec(timeutil::local_split_vs_midnight(t0).second); } nanos dt_; }; /*hms_msec*/ @@ -313,10 +42,11 @@ namespace xo { inline std::ostream & operator<<(std::ostream & os, hms_msec x) { - time::print_hms_msec(x.dt_, os); + timeutil::print_hms_msec(x.dt_, os); return os; } /*operator<<*/ + // ----- hms_usec ----- /* stream inserter that display time like: * hh:mm:ss.nnnnnn @@ -324,8 +54,8 @@ namespace xo { struct hms_usec { hms_usec(nanos dt) : dt_{dt} {} - static hms_usec utc(utc_nanos t0) { return hms_usec(time::utc_split_vs_midnight(t0).second); } - static hms_usec local(utc_nanos t0) { return hms_usec(time::local_split_vs_midnight(t0).second); } + static hms_usec utc(utc_nanos t0) { return hms_usec(timeutil::utc_split_vs_midnight(t0).second); } + static hms_usec local(utc_nanos t0) { return hms_usec(timeutil::local_split_vs_midnight(t0).second); } nanos dt_; }; /*hms_msec*/ @@ -333,11 +63,9 @@ namespace xo { inline std::ostream & operator<<(std::ostream & os, hms_usec x) { - time::print_hms_usec(x.dt_, os); + timeutil::print_hms_usec(x.dt_, os); return os; } /*operator<<*/ - - } /*namespace time*/ } /*namespace xo*/ @@ -346,14 +74,14 @@ namespace std { inline std::ostream & operator<<(std::ostream & os, xo::time::utc_nanos t0) { - xo::time::time::print_utc_ymd_hms_usec(t0, os); + xo::time::timeutil::print_utc_ymd_hms_usec(t0, os); return os; } /*operator<<*/ inline std::ostream & operator<<(std::ostream & os, xo::time::nanos dt) { - xo::time::time::print_hms_usec(dt, os); + xo::time::timeutil::print_hms_usec(dt, os); return os; } /*operator<<*/ } /*namespace chrono*/ diff --git a/include/indentlog/scope.hpp b/include/indentlog/scope.hpp index b86923c1..551589e9 100644 --- a/include/indentlog/scope.hpp +++ b/include/indentlog/scope.hpp @@ -23,7 +23,7 @@ namespace xo { # define XO_DEBUG(debug_flag) XO_ENTER1(always, debug_flag) # define XO_DEBUG2(debug_flag, name1) XO_ENTER2(always, debug_flag, name1) -# define XO_LITERAL(lvl, name1, name2) xo::scope_setup(xo::log_level::lvl, FS_Literal, name1, name2, __FILE__, __LINE__) +# define XO_LITERAL(lvl, name1, name2) xo::scope_setup(xo::log_level::lvl, function_style::literal, name1, name2, __FILE__, __LINE__) //# define XO_SSETUP0() xo::scope_setup(__FUNCTION__) //# define XO_SSETUP0(lvl) xo::scope_setup(xo::log_level::lvl, xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__) diff --git a/include/indentlog/timeutil/timeutil.hpp b/include/indentlog/timeutil/timeutil.hpp new file mode 100644 index 00000000..6e24d3ce --- /dev/null +++ b/include/indentlog/timeutil/timeutil.hpp @@ -0,0 +1,287 @@ +/* @file time.hpp */ + +#pragma once + +#include +#include +#ifdef NOT_YET +# include +#endif +#include +#include +#include + +namespace xo { + namespace time { + + using utc_nanos = std::chrono::time_point; + + using nanos = std::chrono::nanoseconds; + using microseconds = std::chrono::microseconds; + using milliseconds = std::chrono::milliseconds; + using seconds = std::chrono::seconds; + using hours = std::chrono::hours; + using days = std::chrono::days; + + struct timeutil { + static utc_nanos now() { + return utc_nanos(std::chrono::system_clock::now()); + } + + static utc_nanos epoch() { + return utc_nanos(std::chrono::system_clock::from_time_t(0)); + } /*epoch*/ + + static utc_nanos ymd_hms(uint32_t ymd, uint32_t hms) { + /* e.g. ymd=20220610 -> n_yr=2022, n_mon=06, n_dy=10 */ + + uint32_t n_yr = ymd / 10000; + uint32_t n_mon = (ymd % 10000) / 100; + uint32_t n_dy = ymd % 100; + + uint32_t n_hr = hms / 10000; + uint32_t n_min = (hms % 10000) / 100; + uint32_t n_sec = hms % 100; + + struct tm t; + + t.tm_year = n_yr - 1900; /* 0 means 1900 */ + t.tm_mon = n_mon - 1; /* 0 means january */ + t.tm_mday = n_dy; + + t.tm_hour = n_hr; /* 24 hour clock */ + t.tm_min = n_min; + t.tm_sec = n_sec; + + /* time since epoch */ + time_t epoch_time = timegm(&t); + + return std::chrono::system_clock::from_time_t(epoch_time); + } /*ymd_hms*/ + + /* midnight UTC on date ymd. + * e.g. ymd_midnight(20220707) -> midnight UTC on 7jul22 + */ + static utc_nanos ymd_midnight(uint32_t ymd) { + return ymd_hms(ymd, 0); + } /*ymd_midnight*/ + + static utc_nanos ymd_hms_usec(uint32_t ymd, uint32_t hms, uint32_t usec) { + utc_nanos s = ymd_hms(ymd, hms); + + return s + microseconds(usec); + } /*ymd_hms_usec*/ + + /* .first: UTC midnight on same calendar day as t0 + * .second: elapsed time from .first to t0 (i.e. UTC time-of-day for t0) + */ + static std::pair utc_split_vs_midnight(utc_nanos t0) { + //using xo::timeutil::microseconds; + //using xo::timeutil::utc_nanos; + + /* use yyyymmdd.hh:mm:ss.nnnnnn */ + + time_t t0_time_t = (std::chrono::system_clock::to_time_t + (std::chrono::time_point_cast(t0))); + + /* convert to std::tm, + * only provides 1-second precision + */ + std::tm t0_tm; + ::gmtime_r(&t0_time_t, &t0_tm); + + /* midnight on the same calendar day as t0_tm */ + std::tm midnight_tm = t0_tm; + { + midnight_tm.tm_hour = 0; + midnight_tm.tm_min = 0; + midnight_tm.tm_sec = 0; + } + + /* convert to UTC epoch seconds */ + time_t midnight_time_t = ::timegm(&midnight_tm); + + utc_nanos t0_midnight = + (std::chrono::time_point_cast( + std::chrono::system_clock::from_time_t(midnight_time_t))); + + nanos t0_tdy = t0 - t0_midnight; + + return std::pair(t0_midnight, t0_tdy); + } /*utc_split_vs_midnight*/ + + /* .first: LOCAL midnight on same calendar day as t0 (but in UTC coords) + * .second: elapsed time from .first to t0 (i.e. LOCAL time-of-day for t0) + */ + static std::pair local_split_vs_midnight(utc_nanos t0) { + using xo::time::microseconds; + using xo::time::utc_nanos; + + /* use yyyymmdd.hh:mm:ss.nnnnnn */ + + time_t t0_time_t = (std::chrono::system_clock::to_time_t + (std::chrono::time_point_cast(t0))); + + /* convert to std::tm, + * only provides 1-second precision + */ + std::tm t0_tm; + ::localtime_r(&t0_time_t, &t0_tm); + + /* midnight on the same calendar day as t0_tm */ + std::tm midnight_tm = t0_tm; + { + midnight_tm.tm_hour = 0; + midnight_tm.tm_min = 0; + midnight_tm.tm_sec = 0; + } + + /* convert local midnight to UTC epoch seconds */ + time_t midnight_time_t = ::timelocal(&midnight_tm); + + utc_nanos t0_midnight = + (std::chrono::time_point_cast( + std::chrono::system_clock::from_time_t(midnight_time_t))); + + nanos t0_tdy = t0 - t0_midnight; + + return std::pair(t0_midnight, t0_tdy); + } /*local_split_vs_midnight*/ + + /* split utc_nanos into + * std::tm + * .tm_year + * .tm_mon (1-12) + * .tm_mday (1-31) + * .tm_hour (0-23) + * .tm_min (0-59) + * .tm_sec (0-59) + * .tm_wday (0=sunday .. 6=saturday) + * .tm_yday (0=1jan .. 365) + * .tm_isdst (daylight savings time flag) + * usec (0-999999) + */ + static std::pair split_tm(utc_nanos t0) { + using xo::time::microseconds; + using xo::time::utc_nanos; + + /* use yyyymmdd.hh:mm:ss.nnnnnn */ + + time_t t0_time_t = (std::chrono::system_clock::to_time_t + (std::chrono::time_point_cast(t0))); + + /* convert to std::tm, un UTC coords, + * only provides 1-second precision + */ + std::tm t0_tm; + ::gmtime_r(&t0_time_t, &t0_tm); + + /* midnight on the same calendar day as t0_tm */ + std::tm midnight_tm = t0_tm; + + midnight_tm.tm_isdst = 0; + midnight_tm.tm_hour = 0; + midnight_tm.tm_min = 0; + midnight_tm.tm_sec = 0; + + /* convert back to epoch seconds */ + time_t midnight_time_t = ::mktime(&midnight_tm); + + utc_nanos t0_midnight = + (std::chrono::time_point_cast( + std::chrono::system_clock::from_time_t(midnight_time_t))); + + uint32_t usec = + (std::chrono::duration_cast( + std::chrono::hh_mm_ss(t0 - t0_midnight).subseconds())) + .count(); + + return std::make_pair(t0_tm, usec); + } /*split_tm*/ + + static void print_hms_msec(nanos dt, std::ostream & os) { + /* use hhmmss.nnn */ + using std::int32_t; + + auto hms = std::chrono::hh_mm_ss(dt); + int32_t h = hms.hours().count(); + int32_t m = hms.minutes().count(); + int32_t s = hms.seconds().count(); + int32_t msec = std::chrono::duration_cast(hms.subseconds()).count(); + + char buf[32]; + snprintf(buf, sizeof(buf), "%02d:%02d:%02d.%03d", h, m, s, msec); + + os << buf; + } /*print_hms_msec*/ + + static void print_utc_hms_msec(utc_nanos t0, std::ostream & os) { + print_hms_msec(utc_split_vs_midnight(t0).second, os); + } /*print_utc_hms_usec*/ + + static void print_hms_usec(nanos dt, std::ostream & os) { + /* use hhmmss.uuuuuu */ + using std::int32_t; + + auto hms = std::chrono::hh_mm_ss(dt); + int32_t h = hms.hours().count(); + int32_t m = hms.minutes().count(); + int32_t s = hms.seconds().count(); + int32_t usec = std::chrono::duration_cast(hms.subseconds()).count(); + + char buf[32]; + snprintf(buf, sizeof(buf), "%02d:%02d:%02d.%06d", h, m, s, usec); + + os << buf; + } /*print_hms_usec*/ + + static void print_utc_ymd_hms_usec(utc_nanos t0, std::ostream & os) { + using xo::time::microseconds; + using xo::time::utc_nanos; + + /* use yyyymmdd.hh:mm:ss.nnnnnn */ + + //std::tm t0_tm; + //uint32_t t0_usec; + + /* (structured binding ftw!) */ + auto [t0_tm, t0_usec] = split_tm(t0); + + /* no std::format in clang11 afaict */ + char usec_buf[7]; + snprintf(usec_buf, sizeof(usec_buf), "%06d", t0_usec); + + + /* control string | example + * ----------------------------+-------------------------- + * %c - locale-specific string | Fri Jun 10 16:29:05 2022 + * %Y - year | 2022 + * %m - month | 06 + * %d - day of month | 10 + * %H - hour | 16 + * %M - minute | 29 + * %S - second | 05 + * %Z - timezone | UTC + */ + os << std::put_time(&t0_tm, "%Y%m%d:%H:%M:%S.") << usec_buf; + } /*print_utc_ymd_hms_usec*/ + + /* print datetime in format compatible with ISO 8601. + * copying the format javascript uses, e.g: + * 2012-04-23T18:25:43.511Z + */ + static void print_iso8601(utc_nanos t0, std::ostream & os) { + auto [t0_tm, t0_usec] = split_tm(t0); + + char msec_buf[8]; + snprintf(msec_buf, sizeof(msec_buf), "%03d", t0_usec / 1000); + + os << std::put_time(&t0_tm, "%Y-%m-%dT%H:%M:%S.") << msec_buf << "Z"; + } /*print_iso8601*/ + }; /*timeutil*/ + + } /*namespace time*/ +} /*namespace xo*/ + +/* end timeutil.hpp */ From 6f74ce1cf42f7d23a5450a9f94808ef13b82d43d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 18 Sep 2023 17:51:28 -0400 Subject: [PATCH 0075/2693] build: cmake install targets --- CMakeLists.txt | 7 +++++++ include/CMakeLists.txt | 6 ++++++ 2 files changed, 13 insertions(+) create mode 100644 include/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 38939895..aeeda3ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,4 +6,11 @@ enable_testing() include(cmake/nestlog.cmake) +add_subdirectory(include) add_subdirectory(example) + +# this doesn't work in include/CMakeLists.txt +install(TARGETS indentlog DESTINATION include) + +set(CMAKE_INSTALL_PREFIX /home/roland/local) +set(CMAKE_INSTALL_RPATH /home/roland/local/lib) diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt new file mode 100644 index 00000000..72fcb50d --- /dev/null +++ b/include/CMakeLists.txt @@ -0,0 +1,6 @@ +# header-only library +add_library(indentlog INTERFACE) +target_include_directories(indentlog INTERFACE + $ + $ + ) From 468c525470ccf66cd841ddb810c7835d638fec50 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 19 Sep 2023 14:34:41 -0400 Subject: [PATCH 0076/2693] indentlog: + hello example + cmake install attempt --- CMakeLists.txt | 24 +++++++++++++++++------- cmake/nestlog.cmake | 3 +-- example/CMakeLists.txt | 1 + example/hello/CMakeLists.txt | 2 ++ example/hello/hello.cpp | 5 +++++ include/CMakeLists.txt | 6 ------ 6 files changed, 26 insertions(+), 15 deletions(-) create mode 100644 example/hello/CMakeLists.txt create mode 100644 example/hello/hello.cpp delete mode 100644 include/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index aeeda3ed..d819b716 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,13 +4,23 @@ project(nestlog VERSION 0.1) enable_language(CXX) enable_testing() -include(cmake/nestlog.cmake) - -add_subdirectory(include) -add_subdirectory(example) - -# this doesn't work in include/CMakeLists.txt -install(TARGETS indentlog DESTINATION include) +# always write compile_commands.json +set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") set(CMAKE_INSTALL_PREFIX /home/roland/local) set(CMAKE_INSTALL_RPATH /home/roland/local/lib) + +include(cmake/nestlog.cmake) +add_subdirectory(example) + +# header-only library +#add_library(indentlog INTERFACE) +#target_include_directories(indentlog INTERFACE +# $ +# $ +# ) +# +#install(TARGETS indentlog +# PUBLIC_HEADER DESTINATION include) # COMPONENT Development + +install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) diff --git a/cmake/nestlog.cmake b/cmake/nestlog.cmake index 30736007..4a1a26ae 100644 --- a/cmake/nestlog.cmake +++ b/cmake/nestlog.cmake @@ -25,7 +25,6 @@ macro(xo_include_options target) # (2) clangd (run from emacs lsp-mode) can find them # if(CMAKE_EXPORT_COMPILE_COMMANDS) - set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES - ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) endif() endmacro() diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 7f8eb99d..0f629f7a 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -7,6 +7,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") #include(cmake/FindSphinx.cmake) +add_subdirectory(hello) add_subdirectory(ex1) add_subdirectory(ex2) add_subdirectory(ex3) diff --git a/example/hello/CMakeLists.txt b/example/hello/CMakeLists.txt new file mode 100644 index 00000000..d6f694e6 --- /dev/null +++ b/example/hello/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(hello hello.cpp) +xo_include_options(hello) diff --git a/example/hello/hello.cpp b/example/hello/hello.cpp new file mode 100644 index 00000000..5147511e --- /dev/null +++ b/example/hello/hello.cpp @@ -0,0 +1,5 @@ +#include + +int main(int argc, char ** argv) { + std::cout << "Hello, world!" << std::endl; +} diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt deleted file mode 100644 index 72fcb50d..00000000 --- a/include/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -# header-only library -add_library(indentlog INTERFACE) -target_include_directories(indentlog INTERFACE - $ - $ - ) From 3aa23ef8940f2d7cbe9675cf7e966c34fc3aff62 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 19 Sep 2023 14:52:11 -0400 Subject: [PATCH 0077/2693] indentlog: build: install fixes --- CMakeLists.txt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d819b716..bb0649be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,8 +7,12 @@ enable_testing() # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") -set(CMAKE_INSTALL_PREFIX /home/roland/local) -set(CMAKE_INSTALL_RPATH /home/roland/local/lib) +if(NOT CMAKE_INSTALL_PREFIX) + set(CMAKE_INSTALL_PREFIX /home/roland/local CACHE STRING "install directory") +endif() +if(NOT CMAKE_INSTALL_RPATH) + set(CMAKE_INSTALL_RPATH /home/roland/local/lib CACHE STRING "runpath in installed libraries/executables") +endif() include(cmake/nestlog.cmake) add_subdirectory(example) @@ -24,3 +28,9 @@ add_subdirectory(example) # PUBLIC_HEADER DESTINATION include) # COMPONENT Development install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) + +install(TARGETS hello DESTINATION bin/indentlog/example) +install(TARGETS ex1 DESTINATION bin/indentlog/example) +install(TARGETS ex2 DESTINATION bin/indentlog/example) +install(TARGETS ex3 DESTINATION bin/indentlog/example) +install(TARGETS ex4 DESTINATION bin/indentlog/example) From 0977ff960601b501e36d60d42a7b916c6bebe0c8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 19 Sep 2023 14:59:09 -0400 Subject: [PATCH 0078/2693] + FAQ --- FAQ | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 FAQ diff --git a/FAQ b/FAQ new file mode 100644 index 00000000..6e85b042 --- /dev/null +++ b/FAQ @@ -0,0 +1,25 @@ +development environment that works + +1. + 1. nix stdenv = gcc12Stdenv (see mkderivation.nix) + 2. baseInputs has gcc (but probably doesn't need it) + 3. devInputs has llvmPackages_16.clang-unwrapped + + This leads to env with + CC=gcc + CXX=g++ + NIX_CC=/nix/store/$hash-gcc-wrapper-12.3.0 + +2. + + 1. nix stdenv = clang16Stdenv (see mkderivation.nix) + 2. baseInputs has gcc + 3. devInputs has llvmPackages_16.clang-unwrapped + + This leads to env with: + CC=clang + CXX=clang++ + NIX_CC=/nix/store/$hash-clang-wrapper-16.0.1 + + To build, need to tell cmake to use gcc: + cmake -DCMAKE_CXX_COMPILER=$(which g++) -DCMAKE_C_COMPILER=$(which gcc) path/to/src From 414630a09a3d2be8a6483649a8746a6bc712cc08 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 19 Sep 2023 15:22:20 -0400 Subject: [PATCH 0079/2693] doc: expand build instructions --- CMakeLists.txt | 8 ++++++-- README.md | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bb0649be..b5a8caaa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,11 +7,15 @@ enable_testing() # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") +if(NOT USER) + set(USER $ENV{USER}) +endif() + if(NOT CMAKE_INSTALL_PREFIX) - set(CMAKE_INSTALL_PREFIX /home/roland/local CACHE STRING "install directory") + set(CMAKE_INSTALL_PREFIX /home/${USER}/local CACHE STRING "install directory") endif() if(NOT CMAKE_INSTALL_RPATH) - set(CMAKE_INSTALL_RPATH /home/roland/local/lib CACHE STRING "runpath in installed libraries/executables") + set(CMAKE_INSTALL_RPATH /home/${USER}/local/lib CACHE STRING "runpath in installed libraries/executables") endif() include(cmake/nestlog.cmake) diff --git a/README.md b/README.md index 1db16b19..22e8bf63 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,27 @@ Indentlog is a lightweight header-only library for console logging. - logger is 'truthy' -> only pay for formatting when entry points is enabled. - also provides family of convenience stream-inserters +## Getting Started + +### copy repository locally + +``` +$ git clone git@github.com:rconybea/indentlog.git +$ ls -d indentlog +indentlog +``` + +### build & install + +``` +$ cd indentlog +$ mkdir build +$ cd build +$ cmake -DCMAKE_INSTALL_PREFIX=/usr/local .. +$ make +$ make install +``` + ## Examples ### 1 From 6775e8243ae9c6f56788421c71c20eb9a00a6a7a Mon Sep 17 00:00:00 2001 From: Roland Date: Tue, 19 Sep 2023 15:36:01 -0400 Subject: [PATCH 0080/2693] Create cmake-single-platform.yml --- .github/workflows/cmake-single-platform.yml | 39 +++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/cmake-single-platform.yml diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml new file mode 100644 index 00000000..28c6f783 --- /dev/null +++ b/.github/workflows/cmake-single-platform.yml @@ -0,0 +1,39 @@ +# This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml +name: CMake on a single platform + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Test + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} + From 6364779191fdff7f89457399a671e1bdd430d3f5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 19 Sep 2023 15:56:22 -0400 Subject: [PATCH 0081/2693] doc: + nix instructions --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 22e8bf63..fb8b2deb 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,15 @@ $ make $ make install ``` +alternatively, if you're a nix user: +``` +$ git clone git@github.com:rconybea/indentlog-nix.git +$ ls -d indentlog-nix +indentlog-nix +$ cd indentlog-nix +$ nix-build +``` + ## Examples ### 1 From 7676b7a6761be8f77b4706aa8670706c13fd468b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 19 Sep 2023 17:12:13 -0400 Subject: [PATCH 0082/2693] + unit test for xo::fixed --- CMakeLists.txt | 1 + utest/CMakeLists.txt | 25 ++++++++++ utest/fixed.test.cpp | 84 ++++++++++++++++++++++++++++++++++ utest/indentlog_utest_main.cpp | 6 +++ 4 files changed, 116 insertions(+) create mode 100644 utest/CMakeLists.txt create mode 100644 utest/fixed.test.cpp create mode 100644 utest/indentlog_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b5a8caaa..9318caa2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ endif() include(cmake/nestlog.cmake) add_subdirectory(example) +add_subdirectory(utest) # header-only library #add_library(indentlog INTERFACE) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..187a92c3 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,25 @@ +# indentlog unit test + +set(SELF_EXECUTABLE_NAME utest.indentlog) +set(SELF_SOURCE_FILES fixed.test.cpp indentlog_utest_main.cpp) + +add_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) +xo_include_options(${SELF_EXECUTABLE_NAME}) + +add_test(NAME ${SELF_EXECUTABLE_NAME} COMMAND ${SELF_EXECUTABLE_NAME}) + +# ---------------------------------------------------------------- +# 3rd party dependency: catch2 + +find_package(Catch2 2 REQUIRED) + +# ---------------------------------------------------------------- +# make standard directories for std:: includes explicit +# so that +# (1) they appear in compile_commands.json. +# (2) clangd (run from emacs lsp-mode) can find them +# +if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES + ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +endif() diff --git a/utest/fixed.test.cpp b/utest/fixed.test.cpp new file mode 100644 index 00000000..d882e542 --- /dev/null +++ b/utest/fixed.test.cpp @@ -0,0 +1,84 @@ +/* @file fixed.test.cpp */ + +#include "indentlog/print/fixed.hpp" +#include "indentlog/print/tag.hpp" +#include +#include + +using namespace xo; + +namespace ut { + struct fixed_tcase { + fixed_tcase() = default; + fixed_tcase(double x, std::uint32_t prec, std::string s) + : x_{x}, prec_{prec}, s_{std::move(s)} {} + + /* floating-point value to format */ + double x_ = 0.0; + /* precision */ + std::uint32_t prec_ = 0; + /* expected result */ + std::string s_; + }; /*fixed_tcase*/ + + std::vector s_fixed_tcase_v( + {fixed_tcase(0.0, 0, "0"), + fixed_tcase(0.0, 1, "0.0"), + fixed_tcase(0.0, 2, "0.00"), + + //fixed_tcase(0.5, 0, "1"), // failing --> 0 + fixed_tcase(0.5, 1, "0.5"), + + fixed_tcase(0.049, 0, "0"), + fixed_tcase(0.049, 1, "0.0"), + fixed_tcase(0.049, 2, "0.05"), + + fixed_tcase(0.05, 0, "0"), + fixed_tcase(0.05, 1, "0.1"), + fixed_tcase(0.05, 2, "0.05"), + + fixed_tcase(1e-6, 0, "0"), + fixed_tcase(1e-6, 1, "0.0"), + fixed_tcase(1e-6, 2, "0.00"), + fixed_tcase(1e-6, 3, "0.000"), + fixed_tcase(1e-6, 4, "0.0000"), + fixed_tcase(1e-6, 5, "0.00000"), + fixed_tcase(1e-6, 6, "0.000001"), + + fixed_tcase(-1e-6, 0, "-0"), + fixed_tcase(-1e-6, 1, "-0.0"), + fixed_tcase(-1e-6, 2, "-0.00"), + fixed_tcase(-1e-6, 3, "-0.000"), + fixed_tcase(-1e-6, 4, "-0.0000"), + fixed_tcase(-1e-6, 5, "-0.00000"), + fixed_tcase(-1e-6, 6, "-0.000001"), + + fixed_tcase(666.66, 1, "666.7"), + fixed_tcase(666.66, 2, "666.66"), + + fixed_tcase(-666.66, 1, "-666.7"), + fixed_tcase(-666.66, 2, "-666.66"), + + }); + + TEST_CASE("fixed", "[fixed]") { + tag_config::tag_color = color_spec_type::none(); + + for (std::uint32_t i_tc = 0, z_tc = s_fixed_tcase_v.size(); i_tc < z_tc; ++i_tc) { + fixed_tcase const & tc = s_fixed_tcase_v[i_tc]; + + INFO(tostr(xtag("i_tc", i_tc), xtag("x", tc.x_), xtag("prec", tc.prec_))); + + std::stringstream ss; + ss << fixed(tc.x_, tc.prec_); + + INFO(xtag("ss.str", ss.str())); + + REQUIRE(ss.str() == tc.s_); + } + + REQUIRE(s_fixed_tcase_v.size() > 1); + } +} /*namespace ut*/ + +/* end fixed.test.cpp */ diff --git a/utest/indentlog_utest_main.cpp b/utest/indentlog_utest_main.cpp new file mode 100644 index 00000000..6337b638 --- /dev/null +++ b/utest/indentlog_utest_main.cpp @@ -0,0 +1,6 @@ +/* @file indentlog_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end indentlog_utest_main.cpp */ From 9678b6fdea5098916b4352fa5e27b937a17ac2e3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 19 Sep 2023 17:33:02 -0400 Subject: [PATCH 0083/2693] github: + catch2 install for build --- .github/workflows/cmake-single-platform.yml | 5 ++++- FAQ | 17 ++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 28c6f783..059cf017 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -22,6 +22,10 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type @@ -36,4 +40,3 @@ jobs: # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest -C ${{env.BUILD_TYPE}} - diff --git a/FAQ b/FAQ index 6e85b042..27d04f3b 100644 --- a/FAQ +++ b/FAQ @@ -1,4 +1,4 @@ -development environment that works +Q1. how to get a nix development environment that works 1. 1. nix stdenv = gcc12Stdenv (see mkderivation.nix) @@ -23,3 +23,18 @@ development environment that works To build, need to tell cmake to use gcc: cmake -DCMAKE_CXX_COMPILER=$(which g++) -DCMAKE_C_COMPILER=$(which gcc) path/to/src + +Q2. how to add a dependency to github workflow + + comments. + 1. workflow configured in ./.github/cmake-single-platform.yml + 2. workflow runs on ubuntu vm. see + runs-on: ubuntu-latest + in cmake-single-platform.yml + 3. find a desired dependency + $ apt-cache search ${keyword} + e.g. + $ apt-cache search catch2 + 4. add/edit install step to ./.github/cmake-single-platform.yml + - name: Install catch2 + run: sudo apt-get install -y catch2 From dd75bcfedff534eb67908126ba65b2293e603815 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 20 Sep 2023 16:59:46 -0400 Subject: [PATCH 0084/2693] + test code coverage + writeup --- CMakeLists.txt | 8 +- README.md | 2 + cmake/code-coverage.cmake | 678 ++++++++++++++++++++++++++++++++++++++ img/lcov1.png | Bin 0 -> 125426 bytes utest/CMakeLists.txt | 1 + 5 files changed, 688 insertions(+), 1 deletion(-) create mode 100644 cmake/code-coverage.cmake create mode 100755 img/lcov1.png diff --git a/CMakeLists.txt b/CMakeLists.txt index 9318caa2..7656fc0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,14 @@ cmake_minimum_required(VERSION 3.10) project(nestlog VERSION 0.1) enable_language(CXX) + +include(cmake/nestlog.cmake) +include(cmake/code-coverage.cmake) + enable_testing() +# activate code coverage for all executables + libraries (when -DCODE_COVERAGE=ON ??) +add_code_coverage() +add_code_coverage_all_targets() # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") @@ -18,7 +25,6 @@ if(NOT CMAKE_INSTALL_RPATH) set(CMAKE_INSTALL_RPATH /home/${USER}/local/lib CACHE STRING "runpath in installed libraries/executables") endif() -include(cmake/nestlog.cmake) add_subdirectory(example) add_subdirectory(utest) diff --git a/README.md b/README.md index fb8b2deb..bd376bd9 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,8 @@ $ cd indentlog-nix $ nix-build ``` +For some more detail see [BUILD.md](BUILD.md) + ## Examples ### 1 diff --git a/cmake/code-coverage.cmake b/cmake/code-coverage.cmake new file mode 100644 index 00000000..b6b36064 --- /dev/null +++ b/cmake/code-coverage.cmake @@ -0,0 +1,678 @@ +# +# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +# USAGE: To enable any code coverage instrumentation/targets, the single CMake +# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or +# on the command line. +# +# From this point, there are two primary methods for adding instrumentation to +# targets: +# +# 1 - A blanket instrumentation by calling `add_code_coverage()`, where +# all targets in that directory and all subdirectories are automatically +# instrumented. +# +# 2 - Per-target instrumentation by calling +# `target_code_coverage()`, where the target is given and thus only +# that target is instrumented. This applies to both libraries and executables. +# +# To add coverage targets, such as calling `make ccov` to generate the actual +# coverage information for perusal or consumption, call +# `target_code_coverage()` on an *executable* target. +# +# Example 1: All targets instrumented +# +# In this case, the coverage information reported will will be that of the +# `theLib` library target and `theExe` executable. +# +# 1a: Via global command +# +# ~~~ +# add_code_coverage() # Adds instrumentation to all targets +# +# add_library(theLib lib.cpp) +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target +# # (instrumentation already added via global anyways) +# # for generating code coverage reports. +# ~~~ +# +# 1b: Via target commands +# +# ~~~ +# add_library(theLib lib.cpp) +# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. +# ~~~ +# +# Example 2: Target instrumented, but with regex pattern of files to be excluded +# from report +# +# ~~~ +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ +# +# Example 3: Target added to the 'ccov' and 'ccov-all' targets +# +# ~~~ +# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. +# +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ + +# Options +option( + CODE_COVERAGE + "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" + OFF) + +# Programs +find_program(LLVM_COV_PATH llvm-cov) +find_program(LLVM_PROFDATA_PATH llvm-profdata) +find_program(LCOV_PATH lcov) +find_program(GENHTML_PATH genhtml) +# Hide behind the 'advanced' mode flag for GUI/ccmake +mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) + +# Variables +set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) +set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) + +# Common initialization/checks +if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) + set(CODE_COVERAGE_ADDED ON) + + # Common Targets + add_custom_target( + ccov-preprocessing + COMMAND ${CMAKE_COMMAND} -E make_directory + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} + DEPENDS ccov-clean) + + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + # Messages + message(STATUS "Building with llvm Code Coverage Tools") + + if(NOT LLVM_COV_PATH) + message(FATAL_ERROR "llvm-cov not found! Aborting.") + else() + # Version number checking for 'EXCLUDE' compatibility + execute_process(COMMAND ${LLVM_COV_PATH} --version + OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) + string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION + ${LLVM_COV_VERSION_CALL_OUTPUT}) + + if(LLVM_COV_VERSION VERSION_LESS "7.0.0") + message( + WARNING + "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" + ) + endif() + endif() + + # Targets + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + else() + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + endif() + + # Used to get the shared object file list before doing the main all- + # processing + add_custom_target( + ccov-libs + COMMAND ; + COMMENT "libs ready for coverage report.") + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + # Messages + message(STATUS "Building with lcov Code Coverage Tools") + + if(CMAKE_BUILD_TYPE) + string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) + if(NOT ${upper_build_type} STREQUAL "DEBUG") + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + else() + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() + + # Targets + add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory + ${CMAKE_BINARY_DIR} --zerocounters) + + else() + message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") + endif() +endif() + +# Adds code coverage instrumentation to a library, or instrumentation/targets +# for an executable target. +# ~~~ +# EXECUTABLE ADDED TARGETS: +# GCOV/LCOV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# +# LLVM-COV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report. +# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. +# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. +# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. +# ccov-all-export : Exports the coverage report to a JSON file. +# +# Required: +# TARGET_NAME - Name of the target to generate code coverage for. +# Optional: +# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. +# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. +# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) +# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. +# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. +# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory +# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** +# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output +# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call +# ~~~ +function(target_code_coverage TARGET_NAME) + # Argument parsing + set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) + set(single_value_keywords COVERAGE_TARGET_NAME) + set(multi_value_keywords EXCLUDE OBJECTS ARGS) + cmake_parse_arguments( + target_code_coverage "${options}" "${single_value_keywords}" + "${multi_value_keywords}" ${ARGN}) + + # Set the visibility of target functions to PUBLIC, INTERFACE or default to + # PRIVATE. + if(target_code_coverage_PUBLIC) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY PUBLIC) + elseif(target_code_coverage_INTERFACE) + set(TARGET_VISIBILITY INTERFACE) + set(TARGET_LINK_VISIBILITY INTERFACE) + elseif(target_code_coverage_PLAIN) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY) + else() + set(TARGET_VISIBILITY PRIVATE) + set(TARGET_LINK_VISIBILITY PRIVATE) + endif() + + if(NOT target_code_coverage_COVERAGE_TARGET_NAME) + # If a specific name was given, use that instead. + set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) + endif() + + if(CODE_COVERAGE) + + # Add code coverage instrumentation to the target's linker command + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs + -ftest-coverage) + target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) + endif() + + # Targets + get_target_property(target_type ${TARGET_NAME} TYPE) + + # Add shared library to processing for 'all' targets + if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" >> + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + if(NOT TARGET ccov-libs) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-libs + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # For executables add targets to run and produce output + if(target_type STREQUAL "EXECUTABLE") + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # If there are shared objects to also work with, generate the string to + # add them here + foreach(SO_TARGET ${target_code_coverage_OBJECTS}) + # Check to see if the target is a shared object + if(TARGET ${SO_TARGET}) + get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) + if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") + set(SO_OBJECTS ${SO_OBJECTS} -object=$) + endif() + endif() + endforeach() + + # Run the executable, generating raw profile data Make the run data + # available for further processing. Separated to allow Windows to run + # this target serially. + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E env + LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw + $ ${target_code_coverage_ARGS} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" + ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND + ${CMAKE_COMMAND} -E echo + "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" + >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list + JOB_POOL ccov_serial_pool + DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) + + # Merge the generated profile data so llvm-cov can process it + add_custom_target( + ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_PROFDATA_PATH} merge -sparse + ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o + ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Ignore regex only works on LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print out details of the coverage information to the command line + add_custom_target( + ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Print out a summary of the coverage information to the command line + add_custom_target( + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} report $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} export $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO + "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" + ) + + # Run the executable, generating coverage information + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND $ ${target_code_coverage_ARGS} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + # Generate exclusion string for use + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + if(NOT ${target_code_coverage_EXTERNAL}) + set(EXTERNAL_OPTION --no-external) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + else() + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + endif() + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${GENHTML_PATH} -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + ${COVERAGE_INFO} + DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + + add_custom_command( + TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." + ) + + # AUTO + if(target_code_coverage_AUTO) + if(NOT TARGET ccov) + add_custom_target(ccov) + endif() + add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) + + if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID + MATCHES "GNU") + if(NOT TARGET ccov-report) + add_custom_target(ccov-report) + endif() + add_dependencies( + ccov-report + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # ALL + if(target_code_coverage_ALL) + if(NOT TARGET ccov-all-processing) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-all-processing + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + endif() +endfunction() + +# Adds code coverage instrumentation to all targets in the current directory and +# any subdirectories. To add coverage instrumentation to only specific targets, +# use `target_code_coverage`. +function(add_code_coverage) + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_compile_options(-fprofile-instr-generate -fcoverage-mapping) + add_link_options(-fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + add_compile_options(-fprofile-arcs -ftest-coverage) + link_libraries(gcov) + endif() + endif() +endfunction() + +# Adds the 'ccov-all' type targets that calls all targets added via +# `target_code_coverage` with the `ALL` parameter, but merges all the coverage +# data from them into a single large report instead of the numerous smaller +# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for +# use with coverage dashboards (e.g. codecov.io, coveralls). +# ~~~ +# Optional: +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! +# ~~~ +function(add_code_coverage_all_targets) + # Argument parsing + set(multi_value_keywords EXCLUDE) + cmake_parse_arguments(add_code_coverage_all_targets "" "" + "${multi_value_keywords}" ${ARGN}) + + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # Merge the profile data for all of the run executables + if(WIN32) + add_custom_target( + ccov-all-processing + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe + merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -sparse $$FILELIST) + else() + add_custom_target( + ccov-all-processing + COMMAND + ${LLVM_PROFDATA_PATH} merge -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) + endif() + + # Regex exclude only available for LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print summary of the code coverage information to the command line + if(WIN32) + add_custom_target( + ccov-all-report + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe + report $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all-report + COMMAND + ${LLVM_COV_PATH} report `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-all-export + COMMAND + ${LLVM_COV_PATH} export `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json + DEPENDS ccov-all-processing) + + # Generate HTML output of all added targets for perusal + if(WIN32) + add_custom_target( + ccov-all + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show + $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all + COMMAND + ${LLVM_COV_PATH} show `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") + + # Nothing required for gcov + add_custom_target(ccov-all-processing COMMAND ;) + + # Exclusion regex string creation + set(EXCLUDE_REGEX) + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + else() + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + endif() + + # Generates HTML output of all targets for perusal + add_custom_target( + ccov-all + COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} + DEPENDS ccov-all-capture) + + endif() + + add_custom_command( + TARGET ccov-all + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." + ) + endif() +endfunction() diff --git a/img/lcov1.png b/img/lcov1.png new file mode 100755 index 0000000000000000000000000000000000000000..c8e66e119fda63dfdb8c6f453584fb017be09ab3 GIT binary patch literal 125426 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 z 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 literal 0 HcmV?d00001 diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 187a92c3..707b7fd1 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -7,6 +7,7 @@ add_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) xo_include_options(${SELF_EXECUTABLE_NAME}) add_test(NAME ${SELF_EXECUTABLE_NAME} COMMAND ${SELF_EXECUTABLE_NAME}) +target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) # ---------------------------------------------------------------- # 3rd party dependency: catch2 From d259cee0097785a0e679f669abcb5ad854e97fb9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 20 Sep 2023 17:00:04 -0400 Subject: [PATCH 0085/2693] BUILD.mk: missed in prior commit --- BUILD.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 BUILD.md diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 00000000..29023957 --- /dev/null +++ b/BUILD.md @@ -0,0 +1,43 @@ +# indentlog build details + +## Test Coverage + +### enable coverage build +``` +$ cd indentlog +$ mkdir ccov +$ cd ccov +$ cmake -DCODE_COVERAGE=ON .. # prepares coverage build +``` + +### build + generate test coverage +``` +$ make ccov # builds + runs unit tests +$ make ccov-all # generates .html report +``` + +### view coverage report +``` +$ firefox +[navigate to coverage report; path something like file://home/roland/proj/indentlog/ccov/ccov/all-merged/index.html] +``` + +![lcov_output](img/lcov1.png) + +## Implementation Notes + +- coverage builds creates `.gcno` files alongside `.o` object files +- running coverage-enabled executables creates/appends to `.gcda` files +- e.g. see `ccov/utest/CMakeFiles/utest.indentlog.dir` +- coverage feature enabled globally in top-level `CMakeLists.txt` by: +``` +include(cmake/code-coverage.cmake) +add_code_coverage() +add_code_coverage_all_targets() +``` +- looks like these need to appear before executable targets are introduced +- also need to opt-in individual executables, e.g. in `utest/CMakeLists.txt`: +``` +target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) +``` +- here `AUTO` opts in to the `ccov` target; `ALL` opts in to the `ccov-all` target From 78eceaab4243b6db694740205e8b75701d1d0b29 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 20 Sep 2023 17:13:02 -0400 Subject: [PATCH 0086/2693] coverage: exclude system files (gcc, catch, ..) --- CMakeLists.txt | 8 +++++++- img/lcov1.png | Bin 125426 -> 59982 bytes 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7656fc0b..cbfe6953 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,13 @@ include(cmake/code-coverage.cmake) enable_testing() # activate code coverage for all executables + libraries (when -DCODE_COVERAGE=ON ??) add_code_coverage() -add_code_coverage_all_targets() + +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") diff --git a/img/lcov1.png b/img/lcov1.png index c8e66e119fda63dfdb8c6f453584fb017be09ab3..4fb14106bb882a87b76ef1b28f1ad1705e596603 100755 GIT binary patch literal 59982 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 literal 125426 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 z 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 From 896a997a5309dc344c9eafde9dfd80e067ed1c10 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 21 Sep 2023 10:20:12 -0400 Subject: [PATCH 0087/2693] utest: ++ fixed --- utest/fixed.test.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/utest/fixed.test.cpp b/utest/fixed.test.cpp index d882e542..c2b7146b 100644 --- a/utest/fixed.test.cpp +++ b/utest/fixed.test.cpp @@ -37,6 +37,10 @@ namespace ut { fixed_tcase(0.05, 1, "0.1"), fixed_tcase(0.05, 2, "0.05"), + fixed_tcase(-0.05, 0, "-0"), + fixed_tcase(-0.05, 1, "-0.1"), + fixed_tcase(-0.05, 2, "-0.05"), + fixed_tcase(1e-6, 0, "0"), fixed_tcase(1e-6, 1, "0.0"), fixed_tcase(1e-6, 2, "0.00"), From d656a3970dd166d317eca921b6bbb07c4e9af30d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 21 Sep 2023 11:10:22 -0400 Subject: [PATCH 0088/2693] cosmetic: code layout in quoted.hpp --- include/indentlog/print/quoted.hpp | 240 ++++++++++++++--------------- 1 file changed, 120 insertions(+), 120 deletions(-) diff --git a/include/indentlog/print/quoted.hpp b/include/indentlog/print/quoted.hpp index 15d7a397..3691d874 100644 --- a/include/indentlog/print/quoted.hpp +++ b/include/indentlog/print/quoted.hpp @@ -12,136 +12,136 @@ #include namespace xo { - namespace print { - /* use this to avoid template conversion hassles - * since literal strings get treated as arrays - */ - template - char const * ccs(T x) { return x; } + namespace print { + /* use this to avoid template conversion hassles + * since literal strings get treated as arrays + */ + template + char const * ccs(T x) { return x; } - /* Printing cases: - * 1. T&&: - * move into quoted_impl. T must be moveable! - * 2. T&: - * copy reference into quoted_impl. - * similarly for T const &, copy reference into quoted_impl - */ + /* Printing cases: + * 1. T&&: + * move into quoted_impl. T must be moveable! + * 2. T&: + * copy reference into quoted_impl. + * similarly for T const &, copy reference into quoted_impl + */ - template - class quoted_impl { - public: - quoted_impl(bool unq_flag, T const & x) : unq_flag_{unq_flag}, value_{x} {} - quoted_impl(bool unq_flag, T && x) : unq_flag_{unq_flag}, value_{std::move(x)} {} + template + class quoted_impl { + public: + quoted_impl(bool unq_flag, T const & x) : unq_flag_{unq_flag}, value_{x} {} + quoted_impl(bool unq_flag, T && x) : unq_flag_{unq_flag}, value_{std::move(x)} {} - bool unq_flag() const { return unq_flag_; } - T const & value() const { return value_; } + bool unq_flag() const { return unq_flag_; } + T const & value() const { return value_; } - void print(std::ostream & os) const { - std::string xs = xo::tostr(value_); + void print(std::ostream & os) const { + std::string xs = xo::tostr(value_); - if (xs.empty()) { - /* print empty string as "" */ - os << "\"\""; - } else if ((xs.at(0) == '<') && (xs.at(xs.size() - 1) == '>')) { - /* assume string represents output of a well-formed object printer, - * and already self-escapes - */ - os << xs; - } else if (xs.find_first_of(" \"\n\r\\") == std::string::npos) { - /* no escapes needed, just print xs */ - if (unq_flag_) - os << xs; - else - os << "\"" << xs << "\""; - } else { - /* printed value contains a space - * and/or a must-be-escaped character. - * in any case, need quotes - */ + if (xs.empty()) { + /* always print empty string as "" */ + os << "\"\""; + } else if ((xs.at(0) == '<') && (xs.at(xs.size() - 1) == '>')) { + /* assume string represents output of a well-formed object printer, + * and already self-escapes + */ + os << xs; + } else if (xs.find_first_of(" \"\n\r\\") == std::string::npos) { + /* no escapes needed, just print xs */ + if (unq_flag_) + os << xs; + else + os << "\"" << xs << "\""; + } else { + /* printed value contains a space + * and/or a must-be-escaped character. + * in any case, need quotes + */ - os << "\""; + os << "\""; - /* print contents of ss, with escapes: - * \ => \\ - * " => \" - * newline => \n - * cr => \r - */ - for (char ch : xs) { - switch (ch) { - case '"': - /* " => \" */ - os << "\\\""; - break; - case '\n': - /* newline -> \n */ - os << "\\\n"; - break; - case '\r': - /* cr -> \r */ - os << "\\\r"; - break; - case '\\': - /* \ => \\ (mind c++ requires we escape \) */ - os << "\\\\"; - break; - default: - os << ch; - break; + /* print contents of ss, with escapes: + * \ => \\ + * " => \" + * newline => \n + * cr => \r + */ + for (char ch : xs) { + switch (ch) { + case '"': + /* " => \" */ + os << "\\\""; + break; + case '\n': + /* newline -> \n */ + os << "\\\n"; + break; + case '\r': + /* cr -> \r */ + os << "\\\r"; + break; + case '\\': + /* \ => \\ (mind c++ requires we escape \) */ + os << "\\\\"; + break; + default: + os << ch; + break; + } + } + + os << "\""; + } + } /*print*/ + + private: + /* .unq_flag: if true, omit surrounding " chars + * if printed value satisfies both: + * - no escaped chars + * - no spaces + */ + bool unq_flag_ = false; + /* .value: value to be printed */ + T value_; + }; /*quoted_impl*/ + + template + std::ostream & + operator<<(std::ostream & os, quoted_impl const & x) { + x.print(os); + return os; + } /*operator*/ + + /* 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] + * (in particular: _not_ std::string &, std::string const &, std::string &&) + * - rvalue std::string passed to quoted_impl ctor + * + * 2a. call quoted(x) with std::string & x, then: + * - T deduced to [std::string &] + * - std::string & passed to quoted_impl ctor + * + * 2b. call quoted(x) with std::string const & x, then: + * - T deduced to [std::string const &] + * - std::string const & passed to quoted_impl ctor + */ + template + auto quoted(T && x) { + return quoted_impl(false /*unq_flag*/, std::forward(x)); } - } - os << "\""; - } - } /*print*/ + inline auto qcstr(char const * x) { + return quoted(x); + } /*qcstr*/ - private: - /* .unq_flag: if true, omit surrounding " chars - * if printed value satisfies both: - * - no escaped chars - * - no spaces - */ - bool unq_flag_ = false; - /* .value: value to be printed */ - T value_; - }; /*quoted_impl*/ - - template - std::ostream & - operator<<(std::ostream & os, quoted_impl const & x) { - x.print(os); - return os; - } /*operator*/ - - /* 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] - * (in particular: _not_ std::string &, std::string const &, std::string &&) - * - rvalue std::string passed to quoted_impl ctor - * - * 2a. call quoted(x) with std::string & x, then: - * - T deduced to [std::string &] - * - std::string & passed to quoted_impl ctor - * - * 2b. call quoted(x) with std::string const & x, then: - * - T deduced to [std::string const &] - * - std::string const & passed to quoted_impl ctor - */ - template - auto quoted(T && x) { - return quoted_impl(false /*unq_flag*/, std::forward(x)); - } - - inline auto qcstr(char const * x) { - return quoted(x); - } /*qcstr*/ - - template - auto unq(T && x) { - return quoted_impl(true /*unq_flag*/, std::forward(x)); - } - } /*namespace print*/ + template + auto unq(T && x) { + return quoted_impl(true /*unq_flag*/, std::forward(x)); + } + } /*namespace print*/ } /*namespace xo*/ /* end quoted.hpp */ From bc4474c72fdebad50ef5fecb809e954462a7375e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 21 Sep 2023 11:10:50 -0400 Subject: [PATCH 0089/2693] print: utest for print::quoted, print::unq --- utest/CMakeLists.txt | 2 +- utest/quoted.test.cpp | 76 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 utest/quoted.test.cpp diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 707b7fd1..c4ecf78e 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -1,7 +1,7 @@ # indentlog unit test set(SELF_EXECUTABLE_NAME utest.indentlog) -set(SELF_SOURCE_FILES fixed.test.cpp indentlog_utest_main.cpp) +set(SELF_SOURCE_FILES fixed.test.cpp quoted.test.cpp indentlog_utest_main.cpp) add_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) xo_include_options(${SELF_EXECUTABLE_NAME}) diff --git a/utest/quoted.test.cpp b/utest/quoted.test.cpp new file mode 100644 index 00000000..35583d4d --- /dev/null +++ b/utest/quoted.test.cpp @@ -0,0 +1,76 @@ +/* @file fixed.test.cpp */ + +#include "indentlog/print/quoted.hpp" +#include "indentlog/print/tag.hpp" +#include +#include + +using namespace xo; +using namespace xo::print; + +namespace ut { + struct quoted_tcase { + quoted_tcase() = default; + quoted_tcase(std::string x, bool unq_flag, std::string s) + : x_{x}, unq_flag_{unq_flag}, s_{std::move(s)} {} + + /* string to be printed-in-machine-readable-form */ + std::string x_; + /* if true: omit surrounding " chars when unambiguous + * (printed string does not contain spaces or escaped chars) + * if false: always require surrounding " chars + */ + bool unq_flag_ = true; + /* expected result */ + std::string s_; + }; /*quoted_tcase*/ + + std::vector s_quoted_tcase_v( + { + quoted_tcase("", true, "\"\""), + quoted_tcase("", false, "\"\""), + + quoted_tcase("foo", true, "foo"), + quoted_tcase("foo", false, "\"foo\""), + + quoted_tcase("foo\n", true, "\"foo\\\n\""), + quoted_tcase("foo\n", false, "\"foo\\\n\""), + + quoted_tcase("two words", true, "\"two words\""), + quoted_tcase("two words", false, "\"two words\""), + + quoted_tcase("1st\n2nd", true, "\"1st\\\n2nd\""), + quoted_tcase("1st\n2nd", true, "\"1st\\\n2nd\""), + + quoted_tcase("misakte\rfix", true, "\"misakte\\\rfix\""), + quoted_tcase("misakte\rfix", true, "\"misakte\\\rfix\""), + + quoted_tcase("\"oh!\", she said", true, "\"\\\"oh!\\\", she said\""), + quoted_tcase("\"oh!\", she said", false, "\"\\\"oh!\\\", she said\""), + + quoted_tcase("", true, ""), + quoted_tcase("", false, ""), + }); + + TEST_CASE("quoted", "[quoted]") { + for (std::uint32_t i_tc = 0, z_tc = s_quoted_tcase_v.size(); i_tc < z_tc; ++i_tc) { + quoted_tcase const & tc = s_quoted_tcase_v[i_tc]; + + INFO(tostr(xtag("i_tc", i_tc), xtag("x", tc.x_), xtag("unq_flag", tc.unq_flag_))); + + std::stringstream ss; + if (tc.unq_flag_) + ss << unq(tc.x_); + else + ss << quoted(tc.x_); + + INFO(xtag("ss.str", ss.str())); + + REQUIRE(ss.str() == tc.s_); + } + + REQUIRE(s_quoted_tcase_v.size() > 1); + } +} /*namespace ut*/ + +/* end quoted.test.cpp */ From cc82199a8e403f6bbde06ab452448d3e2498ff1d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 21 Sep 2023 11:20:56 -0400 Subject: [PATCH 0090/2693] utest: + array,vector printers --- utest/CMakeLists.txt | 2 +- utest/array.test.cpp | 40 ++++++++++++++++++++++++++++++++++ utest/vector.test.cpp | 50 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 utest/array.test.cpp create mode 100644 utest/vector.test.cpp diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index c4ecf78e..95a6e5d3 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -1,7 +1,7 @@ # indentlog unit test set(SELF_EXECUTABLE_NAME utest.indentlog) -set(SELF_SOURCE_FILES fixed.test.cpp quoted.test.cpp indentlog_utest_main.cpp) +set(SELF_SOURCE_FILES fixed.test.cpp quoted.test.cpp vector.test.cpp array.test.cpp indentlog_utest_main.cpp) add_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) xo_include_options(${SELF_EXECUTABLE_NAME}) diff --git a/utest/array.test.cpp b/utest/array.test.cpp new file mode 100644 index 00000000..4ba8b83a --- /dev/null +++ b/utest/array.test.cpp @@ -0,0 +1,40 @@ +/* @file array.test.cpp */ + +#include "indentlog/print/array.hpp" /* overload operator<< for std::array */ +#include "indentlog/print/tag.hpp" +#include +#include + +using namespace xo; + +namespace ut { + TEST_CASE("array", "[array]") { + tag_config::tag_color = color_spec_type::none(); + + { + std::array x = {}; + std::stringstream ss; + ss << x; + + REQUIRE(ss.str() == "[]"); + } + + { + std::array x = {1}; + std::stringstream ss; + ss << x; + + REQUIRE(ss.str() == "[1]"); + } + + { + std::array x = {1, 2}; + std::stringstream ss; + ss << x; + + REQUIRE(ss.str() == "[1 2]"); + } + } +} /*namespace ut*/ + +/* end array.test.cpp */ diff --git a/utest/vector.test.cpp b/utest/vector.test.cpp new file mode 100644 index 00000000..8b4f1f4c --- /dev/null +++ b/utest/vector.test.cpp @@ -0,0 +1,50 @@ +/* @file vector.test.cpp */ + +#include "indentlog/print/vector.hpp" /* overload operator<< for std::vector */ +#include "indentlog/print/tag.hpp" +#include +#include + +using namespace xo; + +namespace ut { + struct vector_tcase { + vector_tcase() = default; + vector_tcase(std::vector const & x, std::string s) + : x_{x}, s_{std::move(s)} {} + + /* vector to print */ + std::vector x_; + /* expected result */ + std::string s_; + }; /*vector_tcase*/ + + std::vector s_vector_tcase_v( + {vector_tcase({}, "[]"), + vector_tcase({1}, "[1]"), + vector_tcase({1, 2}, "[1 2]"), + vector_tcase({10, 20, 30}, "[10 20 30]"), + + }); + + TEST_CASE("vector", "[vector]") { + tag_config::tag_color = color_spec_type::none(); + + for (std::uint32_t i_tc = 0, z_tc = s_vector_tcase_v.size(); i_tc < z_tc; ++i_tc) { + vector_tcase const & tc = s_vector_tcase_v[i_tc]; + + INFO(tostr(xtag("i_tc", i_tc), xtag("x", tc.x_))); + + std::stringstream ss; + ss << tc.x_; + + INFO(xtag("ss.str", ss.str())); + + REQUIRE(ss.str() == tc.s_); + } + + REQUIRE(s_vector_tcase_v.size() > 1); + } +} /*namespace ut*/ + +/* end vector.test.cpp */ From b07b3e09b5b5b580a2d528066a98f1ba32ece240 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 21 Sep 2023 17:44:00 -0400 Subject: [PATCH 0091/2693] timeutil: bugfix: needs timegm() instead of mktime() ! --- include/indentlog/timeutil/timeutil.hpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/include/indentlog/timeutil/timeutil.hpp b/include/indentlog/timeutil/timeutil.hpp index 6e24d3ce..740283f3 100644 --- a/include/indentlog/timeutil/timeutil.hpp +++ b/include/indentlog/timeutil/timeutil.hpp @@ -162,16 +162,16 @@ namespace xo { * .tm_isdst (daylight savings time flag) * usec (0-999999) */ - static std::pair split_tm(utc_nanos t0) { + static std::pair utc_split_tm(utc_nanos t0) { using xo::time::microseconds; using xo::time::utc_nanos; /* use yyyymmdd.hh:mm:ss.nnnnnn */ - time_t t0_time_t = (std::chrono::system_clock::to_time_t - (std::chrono::time_point_cast(t0))); + time_t t0_time_t = (std::chrono::system_clock::to_time_t(t0)); + //time_t t0_time_t = (std::chrono::system_clock::to_time_t(std::chrono::time_point_cast(t0))); - /* convert to std::tm, un UTC coords, + /* convert to std::tm, in UTC coords, * only provides 1-second precision */ std::tm t0_tm; @@ -186,7 +186,7 @@ namespace xo { midnight_tm.tm_sec = 0; /* convert back to epoch seconds */ - time_t midnight_time_t = ::mktime(&midnight_tm); + time_t midnight_time_t = ::timegm(&midnight_tm); utc_nanos t0_midnight = (std::chrono::time_point_cast( @@ -198,7 +198,7 @@ namespace xo { .count(); return std::make_pair(t0_tm, usec); - } /*split_tm*/ + } /*utc_split_tm*/ static void print_hms_msec(nanos dt, std::ostream & os) { /* use hhmmss.nnn */ @@ -236,6 +236,12 @@ namespace xo { os << buf; } /*print_hms_usec*/ + /* print t0 like: + * yyyymmdd:hh:mm:ss.uuuuuu + * e.g. + * 19700101:00:00:00.000000 // epoch + * 20230921:16:29:35.123456 // 21sep2023 4:29:35 pm + 12345 us + */ static void print_utc_ymd_hms_usec(utc_nanos t0, std::ostream & os) { using xo::time::microseconds; using xo::time::utc_nanos; @@ -246,7 +252,7 @@ namespace xo { //uint32_t t0_usec; /* (structured binding ftw!) */ - auto [t0_tm, t0_usec] = split_tm(t0); + auto [t0_tm, t0_usec] = utc_split_tm(t0); /* no std::format in clang11 afaict */ char usec_buf[7]; @@ -272,7 +278,7 @@ namespace xo { * 2012-04-23T18:25:43.511Z */ static void print_iso8601(utc_nanos t0, std::ostream & os) { - auto [t0_tm, t0_usec] = split_tm(t0); + auto [t0_tm, t0_usec] = utc_split_tm(t0); char msec_buf[8]; snprintf(msec_buf, sizeof(msec_buf), "%03d", t0_usec / 1000); From 9cfa6db5db26af9d475a713e4961e0af10064007 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 21 Sep 2023 17:45:01 -0400 Subject: [PATCH 0092/2693] utest: + timeutil tests --- CMakeLists.txt | 3 + utest/CMakeLists.txt | 2 +- utest/timeutil.test.cpp | 168 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 utest/timeutil.test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index cbfe6953..b8365f9c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,9 @@ add_code_coverage() # add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED True) + # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 95a6e5d3..6c4c07a0 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -1,7 +1,7 @@ # indentlog unit test set(SELF_EXECUTABLE_NAME utest.indentlog) -set(SELF_SOURCE_FILES fixed.test.cpp quoted.test.cpp vector.test.cpp array.test.cpp indentlog_utest_main.cpp) +set(SELF_SOURCE_FILES fixed.test.cpp quoted.test.cpp vector.test.cpp array.test.cpp timeutil.test.cpp indentlog_utest_main.cpp) add_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) xo_include_options(${SELF_EXECUTABLE_NAME}) diff --git a/utest/timeutil.test.cpp b/utest/timeutil.test.cpp new file mode 100644 index 00000000..816c92da --- /dev/null +++ b/utest/timeutil.test.cpp @@ -0,0 +1,168 @@ +/* @file timeutil.test.cpp */ + +#include "indentlog/timeutil/timeutil.hpp" +#include "indentlog/print/tag.hpp" +#include +#include + +using namespace xo; +using namespace xo::time; +using namespace std::chrono; + +namespace ut { + TEST_CASE("epoch", "[timeutil]") { + //tag_config::tag_color = color_spec_type::none(); + + utc_nanos t0 = timeutil::epoch(); + + REQUIRE(std::chrono::system_clock::to_time_t(t0) == std::time_t(0)); + } /*TEST_CASE(epoch)*/ + + TEST_CASE("ymd_hms", "[timeutil]") { + { + utc_nanos t0 = timeutil::ymd_hms(19700101 /*ymd*/, 0 /*hms*/); + REQUIRE(std::chrono::system_clock::to_time_t(t0) == std::time_t(0)); + } + + { + utc_nanos t0 = timeutil::ymd_hms(19700101 /*ymd*/, 1 /*hms*/); + REQUIRE(std::chrono::system_clock::to_time_t(t0) == std::time_t(1)); + } + + { + utc_nanos t0 = timeutil::ymd_hms(19700101 /*ymd*/, 100 /*hms*/); + REQUIRE(std::chrono::system_clock::to_time_t(t0) == std::time_t(60)); + } + + { + utc_nanos t0 = timeutil::ymd_hms(19700101 /*ymd*/, 10000 /*hms*/); + REQUIRE(std::chrono::system_clock::to_time_t(t0) == std::time_t(3600)); + } + + { + utc_nanos t0 = timeutil::ymd_hms(19700101 /*ymd*/, 235959 /*hms*/); + REQUIRE(std::chrono::system_clock::to_time_t(t0) == std::time_t(86399)); + } + + { + utc_nanos t0 = timeutil::ymd_hms(19700102 /*ymd*/, 235959 /*hms*/); + REQUIRE(std::chrono::system_clock::to_time_t(t0) == std::time_t(86400 + 86399)); + } + + { + utc_nanos t0 = timeutil::ymd_hms(19700131 /*ymd*/, 235959 /*hms*/); + REQUIRE(std::chrono::system_clock::to_time_t(t0) == std::time_t(30 * 86400 + 86399)); + } + + { + utc_nanos t0 = timeutil::ymd_hms(19700201 /*ymd*/, 235959 /*hms*/); + REQUIRE(std::chrono::system_clock::to_time_t(t0) == std::time_t(31 * 86400 + 86399)); + } + } /*TEST_CASE(ymd_hms)*/ + + TEST_CASE("ymd_midnight", "[timeutil]") { + { + utc_nanos t0 = timeutil::ymd_midnight(19700101 /*ymd*/); + REQUIRE(std::chrono::system_clock::to_time_t(t0) == std::time_t(0)); + } + + { + utc_nanos t0 = timeutil::ymd_midnight(19700102 /*ymd*/); + REQUIRE(std::chrono::system_clock::to_time_t(t0) == std::time_t(86400)); + } + + { + utc_nanos t0 = timeutil::ymd_midnight(19700131 /*ymd*/); + REQUIRE(std::chrono::system_clock::to_time_t(t0) == std::time_t(30 * 86400)); + } + + { + utc_nanos t0 = timeutil::ymd_midnight(19700201 /*ymd*/); + REQUIRE(system_clock::to_time_t(t0) == std::time_t(31 * 86400)); + } + } /*TEST_CASE(ymd_midnight)*/ + + struct timeutil_tcase { + timeutil_tcase() = default; + timeutil_tcase(uint32_t ymd, uint32_t hms, uint32_t usec, + std::time_t epoch_sec, std::time_t midnight_sec, std::uint32_t fractional_sec, std::uint32_t fractional_usec, + std::string const & utc_ymd_hms_usec_str, + std::string const & iso8601_str) + : ymd_{ymd}, hms_{hms}, usec_{usec}, + epoch_sec_{epoch_sec}, + midnight_sec_{midnight_sec}, + fractional_sec_{fractional_sec}, + fractional_usec_{fractional_usec}, + utc_ymd_hms_usec_str_{utc_ymd_hms_usec_str}, + iso8601_str_{iso8601_str} {} + + /* target time value to test */ + std::uint32_t ymd_ = 19700101; + std::uint32_t hms_ = 0; + std::uint32_t usec_ = 0; + + std::time_t epoch_sec_ = 0; + std::time_t midnight_sec_ = 0; + std::uint32_t fractional_sec_ = 0; + std::uint32_t fractional_usec_ = 0; + + std::string utc_ymd_hms_usec_str_; + std::string iso8601_str_; + }; /*timeutil_tcase*/ + + std::vector s_timeutil_tcase_v( + /* -------- inputs ------- ------------------------------------------------ outputs --------------------------------------- + * fractional_usec + * fractional_sec | + * ymd hms usec epoch_sec midnight_sec v v utc_ymd_hms_usec_str iso8601_str + */ + { + timeutil_tcase(19700101, 0, 0, 0, 0, 0, 0, "19700101:00:00:00.000000", "1970-01-01T00:00:00.000Z"), + timeutil_tcase(19700101, 0, 1, 0, 0, 0, 1, "19700101:00:00:00.000001", "1970-01-01T00:00:00.000Z"), + timeutil_tcase(19700101, 0, 123456, 0, 0, 0, 123456, "19700101:00:00:00.123456", "1970-01-01T00:00:00.123Z"), + timeutil_tcase(19700101, 0, 500000, 0, 0, 0, 500000, "19700101:00:00:00.500000", "1970-01-01T00:00:00.500Z"), + timeutil_tcase(19700101, 0, 987654, 0, 0, 0, 987654, "19700101:00:00:00.987654", "1970-01-01T00:00:00.987Z"), + + timeutil_tcase(19700101, 0, 999999, 0, 0, 0, 999999, "19700101:00:00:00.999999", "1970-01-01T00:00:00.999Z"), + + timeutil_tcase(19700101, 1, 999999, 1, 0, 1, 999999, "19700101:00:00:01.999999", "1970-01-01T00:00:01.999Z"), + + timeutil_tcase(19700101, 100, 999999, 60, 0, 60, 999999, "19700101:00:01:00.999999", "1970-01-01T00:01:00.999Z"), + timeutil_tcase(19700101, 10000, 999999, 3600, 0, 3600, 999999, "19700101:01:00:00.999999", "1970-01-01T01:00:00.999Z"), + timeutil_tcase(19700101, 235959, 999999, 24*3600-1, 0, 86399, 999999, "19700101:23:59:59.999999", "1970-01-01T23:59:59.999Z"), + + timeutil_tcase(19700102, 100, 999999, 86400+60, 86400, 60, 999999, "19700102:00:01:00.999999", "1970-01-02T00:01:00.999Z"), + + }); + + TEST_CASE("ymd_hms_usec", "[timeutil]") { + for (std::uint32_t i_tc = 0, z_tc = s_timeutil_tcase_v.size(); i_tc < z_tc; ++i_tc) { + timeutil_tcase const & tc = s_timeutil_tcase_v[i_tc]; + + INFO(tostr(xtag("i_tc", i_tc), xtag("ymd", tc.ymd_))); + INFO(xtag("tc.epoch_sec", tc.epoch_sec_)); + INFO(xtag("tc.utc_ymd_hms_usec_str", tc.utc_ymd_hms_usec_str_)); + + utc_nanos const t0 = timeutil::ymd_hms_usec(tc.ymd_, tc.hms_, tc.usec_); + REQUIRE(system_clock::to_time_t(t0) == std::time_t(tc.epoch_sec_)); + + auto x = timeutil::utc_split_vs_midnight(t0); + REQUIRE(system_clock::to_time_t(x.first) == tc.midnight_sec_); + REQUIRE(x.second == seconds(tc.fractional_sec_) + microseconds(tc.fractional_usec_)); + + { + std::stringstream ss; + timeutil::print_utc_ymd_hms_usec(t0, ss); + REQUIRE(ss.str() == tc.utc_ymd_hms_usec_str_); + } + + { + std::stringstream ss; + timeutil::print_iso8601(t0, ss); + REQUIRE(ss.str() == tc.iso8601_str_); + } + } + } /*TEST_CASE(ymd_hms_usec)*/ +} /*namespace ut*/ + +/* end timeutil.test.cpp */ From e7317b122c709df629d2563b954ad446e6bce21f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 22 Sep 2023 12:15:09 -0400 Subject: [PATCH 0093/2693] print: simplify quoted_impl --- include/indentlog/print/quoted.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/indentlog/print/quoted.hpp b/include/indentlog/print/quoted.hpp index 3691d874..8629ac39 100644 --- a/include/indentlog/print/quoted.hpp +++ b/include/indentlog/print/quoted.hpp @@ -30,8 +30,7 @@ namespace xo { template class quoted_impl { public: - quoted_impl(bool unq_flag, T const & x) : unq_flag_{unq_flag}, value_{x} {} - quoted_impl(bool unq_flag, T && x) : unq_flag_{unq_flag}, value_{std::move(x)} {} + quoted_impl(bool unq_flag, T x) : unq_flag_{unq_flag}, value_{std::move(x)} {} bool unq_flag() const { return unq_flag_; } T const & value() const { return value_; } From 20b19d9cfe2fbb8cd9acabd4f51d91372a59aac7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 22 Sep 2023 12:15:43 -0400 Subject: [PATCH 0094/2693] tidy: forward instead of move in tag_impl --- include/indentlog/print/tag.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/include/indentlog/print/tag.hpp b/include/indentlog/print/tag.hpp index 6c7eafbb..7d4a0abc 100644 --- a/include/indentlog/print/tag.hpp +++ b/include/indentlog/print/tag.hpp @@ -29,7 +29,7 @@ namespace xo { tag_impl(Name const & n, Value const & v) : name_{n}, value_{v} {} tag_impl(Name && n, Value && v) - : name_{std::move(n)}, value_{std::move(v)} {} + : name_{std::forward(n)}, value_{std::forward(v)} {} Name const & name() const { return name_; } Value const & value() const { return value_; } @@ -72,6 +72,8 @@ namespace xo { return tag_impl(n, ""); } /*xtag_pre*/ + // ----- tag ----- + template tag_impl tag(Name && n, Value && v) @@ -86,6 +88,8 @@ namespace xo { return tag_impl(n, v); } /*tag*/ + // ----- operator<< on tag_impl ----- + template inline std::ostream & operator<<(std::ostream &s, From 2b7d7f9d784401fd06b1f2a491d023b61612a084 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 22 Sep 2023 12:16:06 -0400 Subject: [PATCH 0095/2693] cosmetic: consistent comment in color.hpp --- include/indentlog/print/color.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/indentlog/print/color.hpp b/include/indentlog/print/color.hpp index 49eb0bcf..47167b66 100644 --- a/include/indentlog/print/color.hpp +++ b/include/indentlog/print/color.hpp @@ -21,7 +21,7 @@ namespace xo { * * | enum | escape | example | description | foreground codes | * +-------+-----------+---------------------+---------------+------------------+ - * | ansi | \033[31 | \033[31;42m | 4-bit colors | 30..37, 90..97 | + * | ansi | \033[31 | \033[31;34m | 4-bit colors | 30..37, 90..97 | * | xterm | \033[38;5 | \033[38;5;143m | 8-bit colors | 0..255 | * | rgb | \033[38;2 | \033[38;2;10;20;30m | 24-bit colors | 3x 0..255 | * From ca64cdd91194bf95dbf4240529818f922f9755e2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 22 Sep 2023 12:16:41 -0400 Subject: [PATCH 0096/2693] utest: + tag/xtag + basename tests --- utest/CMakeLists.txt | 5 ++- utest/filename.test.cpp | 41 +++++++++++++++++++++++++ utest/tag.test.cpp | 67 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 utest/filename.test.cpp create mode 100644 utest/tag.test.cpp diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 6c4c07a0..6e6352b5 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -1,7 +1,10 @@ # indentlog unit test 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 indentlog_utest_main.cpp) +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 + indentlog_utest_main.cpp) add_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) xo_include_options(${SELF_EXECUTABLE_NAME}) diff --git a/utest/filename.test.cpp b/utest/filename.test.cpp new file mode 100644 index 00000000..ee5fdfaf --- /dev/null +++ b/utest/filename.test.cpp @@ -0,0 +1,41 @@ +/* @file filename.test.cpp */ + +#include "indentlog/print/filename.hpp" +#include "indentlog/print/tag.hpp" +#include +#include + +using namespace xo; + +namespace ut { + struct filename_tcase { + filename_tcase() = default; + filename_tcase(std::string_view path, std::string_view basename) + : path_{path}, basename_{basename} {} + + /* target time value to test */ + std::string_view path_; + std::string_view basename_; + }; /*filename_tcase*/ + + std::vector s_filename_tcase_v( + { + filename_tcase("foo", "foo"), + }); + + TEST_CASE("filename", "[filename]") { + for (std::uint32_t i_tc = 0, z_tc = s_filename_tcase_v.size(); i_tc < z_tc; ++i_tc) { + filename_tcase const & tc = s_filename_tcase_v[i_tc]; + + INFO(tostr(xtag("i_tc", i_tc), xtag("path", tc.path_))); + INFO(xtag("tc.basename", tc.basename_)); + + std::stringstream ss; + ss << basename(tc.path_); + + REQUIRE(ss.str() == tc.basename_); + } + } /*TEST_CASE(filename)*/ +} /*namespace ut*/ + +/* end filename.test.cpp */ diff --git a/utest/tag.test.cpp b/utest/tag.test.cpp new file mode 100644 index 00000000..8ecabb8d --- /dev/null +++ b/utest/tag.test.cpp @@ -0,0 +1,67 @@ +/* @file tag.test.cpp */ + +#include "indentlog/print/tag.hpp" +#include "indentlog/print/vector.hpp" +#include "indentlog/print/concat.hpp" +#include +#include + +using namespace xo; + +namespace ut { + TEST_CASE("tag", "[tag]") { + tag_config::tag_color = color_spec_type::none(); + + { + std::stringstream ss; + ss << tag("foo", "hello,world!"); + + REQUIRE(ss.str() == ":foo hello,world!"); + } + + { + std::stringstream ss; + ss << tag("foo", "hello, world!"); + + REQUIRE(ss.str() == ":foo \"hello, world!\""); + } + + { + std::stringstream ss; + std::vector v = {1, 2, 3}; + ss << tag("foo", v); + + REQUIRE(ss.str() == ":foo \"[1 2 3]\""); + } + + { + std::stringstream ss; + ss << tag("foo", concat("farenheit", 451)); + + REQUIRE(ss.str() == ":foo farenheit451"); + } + + { + std::stringstream ss; + ss << tag("foo", "hello") << xtag("bar", "there"); + + REQUIRE(ss.str() == ":foo hello :bar there"); + } + + tag_config::tag_color = color_spec_type::blue(); + + { + std::stringstream ss; + ss << tag("foo", "hello,world!"); + + /* color on color off + * <---------> <-----> + * + * see [indentlog/print/color.hpp] for escape sequences + */ + REQUIRE(ss.str() == "\033[31;34m:foo\033[0m hello,world!"); + } + } /*TEST_CASE(tag)*/ +} /*namespace ut*/ + +/* end tag.test.cpp */ From b2d939363e5f6e2d0623d6705e5d7a30284a5fe5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 22 Sep 2023 14:55:38 -0400 Subject: [PATCH 0097/2693] utest: + code_location test --- include/indentlog/print/color.hpp | 18 ++++++++++++ include/indentlog/print/concat.hpp | 5 ++++ utest/CMakeLists.txt | 2 +- utest/code_location.test.cpp | 47 ++++++++++++++++++++++++++++++ utest/filename.test.cpp | 2 ++ 5 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 utest/code_location.test.cpp diff --git a/include/indentlog/print/color.hpp b/include/indentlog/print/color.hpp index 47167b66..f8b64985 100644 --- a/include/indentlog/print/color.hpp +++ b/include/indentlog/print/color.hpp @@ -14,6 +14,18 @@ namespace xo { rgb }; + 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::xterm: os << "xterm"; break; + case color_encoding::rgb: os << "rgb"; break; + default: os << "???"; break; + } + return os; + } /*operator<<*/ + /* specify a color (consistent with ANSI escape sequences - the Select Graphics Rendition subset * see [[https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences]] * @@ -106,6 +118,12 @@ namespace xo { std::uint32_t code_ = 0; }; /*color_spec_type*/ + inline std::ostream & + operator<< (std::ostream & os, color_spec_type const & x) { + os << ""; + return os; + } /*operator<<*/ + enum class coloring_control_flags : std::uint8_t { none = 0x0, color_on = 0x01, diff --git a/include/indentlog/print/concat.hpp b/include/indentlog/print/concat.hpp index 31bd6b43..223900ec 100644 --- a/include/indentlog/print/concat.hpp +++ b/include/indentlog/print/concat.hpp @@ -20,6 +20,11 @@ namespace xo { T2 x2_; }; /*concat_impl*/ + template + T1 concat(T1 && x1) { + return x1; + } /*concat*/ + template concat_impl concat(T1 && x1, T2 && x2) { return concat_impl(std::move(x1), std::move(x2)); diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 6e6352b5..1513f8bc 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -3,7 +3,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 + filename.test.cpp code_location.test.cpp indentlog_utest_main.cpp) add_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) diff --git a/utest/code_location.test.cpp b/utest/code_location.test.cpp new file mode 100644 index 00000000..64f1a0c4 --- /dev/null +++ b/utest/code_location.test.cpp @@ -0,0 +1,47 @@ +/* @file code_location.test.cpp */ + +#include "indentlog/print/code_location.hpp" +#include "indentlog/print/color.hpp" +#include "indentlog/print/tag.hpp" +#include +#include + +using namespace xo; + +namespace ut { + struct code_location_tcase { + code_location_tcase() = default; + code_location_tcase(std::string_view file, std::uint32_t line, color_spec_type color, std::string_view output) + : file_{file}, line_{line}, color_{color}, output_{output} {} + + /* target time value to test */ + std::string_view file_; + std::uint32_t line_; + color_spec_type color_; + std::string_view output_; + }; /*code_location_tcase*/ + + std::vector s_code_location_tcase_v( + { + code_location_tcase("/foo/bar", 123, color_spec_type::none(), "[bar:123]"), + code_location_tcase("/foo/bar", 123, color_spec_type::blue(), "[\033[31;34mbar\033[0m:123]"), + code_location_tcase("/foo/bar", 123, color_spec_type::xterm(196), "[\033[38;5;196mbar\033[0m:123]"), + code_location_tcase("/foo/bar", 123, color_spec_type::rgb(255, 127, 63), "[\033[38;2;255;127;63mbar\033[0m:123]"), + }); + + TEST_CASE("code_location", "[code_location]") { + for (std::uint32_t i_tc = 0, z_tc = s_code_location_tcase_v.size(); i_tc < z_tc; ++i_tc) { + code_location_tcase const & tc = s_code_location_tcase_v[i_tc]; + + INFO(tostr(xtag("i_tc", i_tc), xtag("file", tc.file_), xtag("line", tc.line_), xtag("color", tc.color_))); + INFO(xtag("tc.output", tc.output_)); + + std::stringstream ss; + ss << code_location(tc.file_, tc.line_, tc.color_); + + REQUIRE(ss.str() == tc.output_); + } + } /*TEST_CASE(code_location)*/ +} /*namespace ut*/ + +/* end code_location.test.cpp */ diff --git a/utest/filename.test.cpp b/utest/filename.test.cpp index ee5fdfaf..ebff42b4 100644 --- a/utest/filename.test.cpp +++ b/utest/filename.test.cpp @@ -21,6 +21,8 @@ namespace ut { std::vector s_filename_tcase_v( { filename_tcase("foo", "foo"), + filename_tcase("/foo", "foo"), + filename_tcase("/foo/bar", "bar"), }); TEST_CASE("filename", "[filename]") { From 3983361e78f56d84c6137b3b9861e2830cab08a4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 22 Sep 2023 15:14:50 -0400 Subject: [PATCH 0098/2693] color: bugfix: color_off() needs to know color spec --- include/indentlog/print/color.hpp | 8 ++++---- include/indentlog/print/function.hpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/indentlog/print/color.hpp b/include/indentlog/print/color.hpp index f8b64985..bcd3e634 100644 --- a/include/indentlog/print/color.hpp +++ b/include/indentlog/print/color.hpp @@ -175,19 +175,19 @@ namespace xo { }; /*color_impl*/ template - color_impl with_color(color_spec_type spec, Contents && contents) { + 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 spec) { + 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_off(color_spec_type const & spec) { /* any spec other than color_spec_type::none() works here */ - return color_impl(coloring_control_flags::color_off, color_spec_type::white(), 0); + return color_impl(coloring_control_flags::color_off, spec, 0); } /*color_off*/ template diff --git a/include/indentlog/print/function.hpp b/include/indentlog/print/function.hpp index 5bbfbd05..0e5584f8 100644 --- a/include/indentlog/print/function.hpp +++ b/include/indentlog/print/function.hpp @@ -262,12 +262,12 @@ namespace xo { /* omit namespace qualifiers and template arguments */ os << color_on(fn.colorspec()); function_name::print_streamlined(os, fn.pretty()); - os << color_off(); + os << color_off(fn.colorspec()); break; case function_style::simple: os << color_on(fn.colorspec()); function_name::print_simple(os, fn.pretty()); - os << color_off(); + os << color_off(fn.colorspec()); break; } From 7aa534567ff171cfcd43bb3342b7a9ae3b8808db Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 22 Sep 2023 15:15:11 -0400 Subject: [PATCH 0099/2693] utest: + function_name test --- include/indentlog/print/function.hpp | 12 ++++++ utest/CMakeLists.txt | 2 +- utest/function.test.cpp | 59 ++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 utest/function.test.cpp diff --git a/include/indentlog/print/function.hpp b/include/indentlog/print/function.hpp index 0e5584f8..2689ef1a 100644 --- a/include/indentlog/print/function.hpp +++ b/include/indentlog/print/function.hpp @@ -24,6 +24,18 @@ namespace xo { simple }; + inline std::ostream & + operator<< (std::ostream & os, function_style x) { + switch(x) { + case function_style::literal: os << "literal"; break; + case function_style::pretty: os << "pretty"; break; + case function_style::streamlined: os << "streamlined"; break; + case function_style::simple: os << "simple"; break; + default: os << "???"; break; + } + return os; + } /*operator<<*/ + /* Tag to drive header-only expression */ template class function_name_impl { diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 1513f8bc..c6e6e596 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -3,7 +3,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 + filename.test.cpp code_location.test.cpp function.test.cpp indentlog_utest_main.cpp) add_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) diff --git a/utest/function.test.cpp b/utest/function.test.cpp new file mode 100644 index 00000000..323a3f30 --- /dev/null +++ b/utest/function.test.cpp @@ -0,0 +1,59 @@ +/* @file function.test.cpp */ + +#include "indentlog/print/function.hpp" +#include "indentlog/print/tag.hpp" +#include +#include + +using namespace xo; + +namespace ut { + struct function_tcase { + function_tcase() = default; + function_tcase(function_style style, color_spec_type spec, std::string_view pretty, std::string_view output) + : style_{style}, spec_{spec}, pretty_{pretty}, output_{output} {} + + /* function style: literal|pretty|streamlined|simple*/ + function_style style_; + /* color spec for output */ + color_spec_type spec_; + /* function signature (as per __PRETTY_FUNCTION__) */ + std::string_view pretty_; + /* output text */ + std::string_view output_; + }; /*function_tcase*/ + + std::vector s_function_tcase_v( + { + function_tcase(function_style::pretty, color_spec_type::none(), "void foo() const", "[void foo() const]"), + function_tcase(function_style::streamlined, color_spec_type::none(), "void foo() const", "foo"), + function_tcase(function_style::simple, color_spec_type::none(), "void foo() const", "foo"), + + function_tcase(function_style::pretty, color_spec_type::none(), "void xo::class::foo() const", "[void xo::class::foo() const]"), + function_tcase(function_style::streamlined, color_spec_type::none(), "void xo::class::foo() const", "class::foo"), + function_tcase(function_style::simple, color_spec_type::none(), "void xo::class::foo() const", "foo"), + + function_tcase(function_style::pretty, color_spec_type::blue(), "void xo::class::foo() const", "[\033[31;34mvoid xo::class::foo() const\033[0m]"), + }); + + TEST_CASE("function", "[function]") { + tag_config::tag_color = color_spec_type::none(); + + for (std::uint32_t i_tc = 0, z_tc = s_function_tcase_v.size(); i_tc < z_tc; ++i_tc) { + function_tcase const & tc = s_function_tcase_v[i_tc]; + + INFO(tostr(xtag("i_tc", i_tc), xtag("style", tc.style_), xtag("spec", tc.spec_), xtag("pretty", tc.pretty_))); + + std::stringstream ss; + ss << function_name(tc.style_, tc.spec_, tc.pretty_); + + INFO(xtag("ss.str", ss.str())); + + REQUIRE(ss.str() == tc.output_); + } + + REQUIRE(s_function_tcase_v.size() > 1); + } +} /*namespace ut*/ + +/* end function.test.cpp */ From 4f6f045423289cae4d1422594ead8a5b82120b63 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 22 Sep 2023 15:19:06 -0400 Subject: [PATCH 0100/2693] utest: ++ function test --- utest/function.test.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/utest/function.test.cpp b/utest/function.test.cpp index 323a3f30..bd71bd35 100644 --- a/utest/function.test.cpp +++ b/utest/function.test.cpp @@ -25,6 +25,8 @@ namespace ut { std::vector s_function_tcase_v( { + function_tcase(function_style::literal, color_spec_type::none(), "anything goes here", "anything goes here"), + function_tcase(function_style::pretty, color_spec_type::none(), "void foo() const", "[void foo() const]"), function_tcase(function_style::streamlined, color_spec_type::none(), "void foo() const", "foo"), function_tcase(function_style::simple, color_spec_type::none(), "void foo() const", "foo"), From e2533d7ae61e55727b443b8493a6938dd2ba2598 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 22 Sep 2023 16:25:37 -0400 Subject: [PATCH 0101/2693] initial commit --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..70653ec5 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# random number generators From 5d6bd7cd405bec3663efbf475c891c3db8be509a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 22 Sep 2023 19:46:26 -0400 Subject: [PATCH 0102/2693] build: + cmake boilerplate to make indentlog cmake-findable --- CMakeLists.txt | 51 +++++++++++++++++++++++++++------- cmake/indentlogConfig.cmake.in | 4 +++ 2 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 cmake/indentlogConfig.cmake.in diff --git a/CMakeLists.txt b/CMakeLists.txt index b8365f9c..472cd5cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,13 +1,13 @@ cmake_minimum_required(VERSION 3.10) -project(nestlog VERSION 0.1) +project(indentlog VERSION 0.1) enable_language(CXX) include(cmake/nestlog.cmake) include(cmake/code-coverage.cmake) enable_testing() -# activate code coverage for all executables + libraries (when -DCODE_COVERAGE=ON ??) +# activate code coverage for all executables + libraries (when -DCODE_COVERAGE=ON) add_code_coverage() # 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. @@ -27,6 +27,9 @@ if(NOT USER) set(USER $ENV{USER}) endif() +# hmm. this works if explicitly given with cmake: +# cmake -DCMAKE_INSTALL_PREFIX=/home/roland/local path/to/source +# but not as default if(NOT CMAKE_INSTALL_PREFIX) set(CMAKE_INSTALL_PREFIX /home/${USER}/local CACHE STRING "install directory") endif() @@ -37,16 +40,44 @@ endif() add_subdirectory(example) add_subdirectory(utest) -# header-only library -#add_library(indentlog INTERFACE) -#target_include_directories(indentlog INTERFACE -# $ -# $ -# ) +# header-only library. +# see [[https://stackoverflow.com/questions/47718485/install-and-export-interface-only-library-cmake]] # -#install(TARGETS indentlog -# PUBLIC_HEADER DESTINATION include) # COMPONENT Development +add_library(indentlog INTERFACE) +target_include_directories(indentlog INTERFACE + $ + $ + ) +include(CMakePackageConfigHelpers) +write_basic_package_version_file("${PROJECT_BINARY_DIR}/indentlogConfigVersion.cmake" + VERSION 0.1 + COMPATIBILITY AnyNewerVersion +) + +install( + TARGETS indentlog + EXPORT indentlogTargets + LIBRARY DESTINATION lib COMPONENT Runtime + ARCHIVE DESTINATION lib COMPONENT Development + RUNTIME DESTINATION bin COMPONENT Runtime + PUBLIC_HEADER DESTINATION include COMPONENT Development + BUNDLE DESTINATION bin COMPONENT Runtime + ) + +include(CMakePackageConfigHelpers) +configure_package_config_file( + "${PROJECT_SOURCE_DIR}/cmake/indentlogConfig.cmake.in" + "${PROJECT_BINARY_DIR}/indentlogConfig.cmake" + INSTALL_DESTINATION lib/cmake/indentlog + ) + +install(EXPORT indentlogTargets DESTINATION lib/cmake/indentlog) +install( + FILES + "${PROJECT_BINARY_DIR}/indentlogConfigVersion.cmake" + "${PROJECT_BINARY_DIR}/indentlogConfig.cmake" + DESTINATION lib/cmake/indentlog) install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) install(TARGETS hello DESTINATION bin/indentlog/example) diff --git a/cmake/indentlogConfig.cmake.in b/cmake/indentlogConfig.cmake.in new file mode 100644 index 00000000..cc57615e --- /dev/null +++ b/cmake/indentlogConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/indentlogTargets.cmake") +check_required_components("@PROJECT_NAME@") From fadbcd7b54bd689d4bd31026a51c956ea781044c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 23 Sep 2023 13:07:45 -0400 Subject: [PATCH 0103/2693] + xoshiro256ss (copied from kalman project) --- CMakeLists.txt | 73 +++ README.md | 15 + cmake/code-coverage.cmake | 678 +++++++++++++++++++++++++++ cmake/cxx.cmake | 40 ++ example/CMakeLists.txt | 2 + example/ex1/CMakeLists.txt | 2 + example/ex1/ex1.cpp | 26 + example/ex2/CMakeLists.txt | 3 + example/ex2/ex2.cpp | 16 + include/randomgen/engine_concept.hpp | 36 ++ include/randomgen/random_seed.hpp | 70 +++ include/randomgen/xoshiro256.hpp | 169 +++++++ 12 files changed, 1130 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/code-coverage.cmake create mode 100644 cmake/cxx.cmake create mode 100644 example/CMakeLists.txt create mode 100644 example/ex1/CMakeLists.txt create mode 100644 example/ex1/ex1.cpp create mode 100644 example/ex2/CMakeLists.txt create mode 100644 example/ex2/ex2.cpp create mode 100644 include/randomgen/engine_concept.hpp create mode 100644 include/randomgen/random_seed.hpp create mode 100644 include/randomgen/xoshiro256.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..533fe8e5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,73 @@ +# using indentlog/CMakeLists.txt as model + +cmake_minimum_required(VERSION 3.10) + +project(randomgen VERSION 0.1) +enable_language(CXX) + +include(cmake/cxx.cmake) +include(cmake/code-coverage.cmake) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() + + +# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) +add_code_coverage() +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) + +# ---------------------------------------------------------------- +# c++ settings + +set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") + +add_definitions(${PROJECT_CXX_FLAGS}) + +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 20) +endif() + +set(CMAKE_CXX_STANDARD_REQUIRED True) + +# always write compile_commands.json +set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") + +# ---------------------------------------------------------------- +# default install + +if(NOT USER) + set(USER $ENV{USER}) +endif() + +if(NOT CMAKE_INSTALL_PREFIX) + set(CMAKE_INSTALL_PREFIX /home/${USER}/local CACHE STRING "install directory") +endif() +if(NOT CMAKE_INSTALL_RPATH) + set(CMAKE_INSTALL_RPATH /home/${USER}/local/lib CACHE STRING "runpath in installed libraries/executables") +endif() + +# ---------------------------------------------------------------- +# external dependencies +# +# set CMAKE_INSTALL_PREFIX to analog of /usr +# to use .cmake assistants from /usr/lib/cmake/indentlog +# +find_package(indentlog REQUIRED) + +# ---------------------------------------------------------------- + +add_subdirectory(example) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- + +install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) + +install(TARGETS ex1 DESTINATION bin/randomgen/example) diff --git a/README.md b/README.md index 70653ec5..18300542 100644 --- a/README.md +++ b/README.md @@ -1 +1,16 @@ # random number generators + +# to build + install locally + +``` +$ cd randomgen +$ mkdir build +$ cd build +$ cmake -DCMAKE_PREFIX_PATH=$(HOME)/local .. +$ make +$ make install +``` + +# to build + install to /usr/local (deprecated) + +same as above, but set `CMAKE_PREFIX_PATH` to `/usr/local` diff --git a/cmake/code-coverage.cmake b/cmake/code-coverage.cmake new file mode 100644 index 00000000..b6b36064 --- /dev/null +++ b/cmake/code-coverage.cmake @@ -0,0 +1,678 @@ +# +# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +# USAGE: To enable any code coverage instrumentation/targets, the single CMake +# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or +# on the command line. +# +# From this point, there are two primary methods for adding instrumentation to +# targets: +# +# 1 - A blanket instrumentation by calling `add_code_coverage()`, where +# all targets in that directory and all subdirectories are automatically +# instrumented. +# +# 2 - Per-target instrumentation by calling +# `target_code_coverage()`, where the target is given and thus only +# that target is instrumented. This applies to both libraries and executables. +# +# To add coverage targets, such as calling `make ccov` to generate the actual +# coverage information for perusal or consumption, call +# `target_code_coverage()` on an *executable* target. +# +# Example 1: All targets instrumented +# +# In this case, the coverage information reported will will be that of the +# `theLib` library target and `theExe` executable. +# +# 1a: Via global command +# +# ~~~ +# add_code_coverage() # Adds instrumentation to all targets +# +# add_library(theLib lib.cpp) +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target +# # (instrumentation already added via global anyways) +# # for generating code coverage reports. +# ~~~ +# +# 1b: Via target commands +# +# ~~~ +# add_library(theLib lib.cpp) +# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. +# ~~~ +# +# Example 2: Target instrumented, but with regex pattern of files to be excluded +# from report +# +# ~~~ +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ +# +# Example 3: Target added to the 'ccov' and 'ccov-all' targets +# +# ~~~ +# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. +# +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ + +# Options +option( + CODE_COVERAGE + "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" + OFF) + +# Programs +find_program(LLVM_COV_PATH llvm-cov) +find_program(LLVM_PROFDATA_PATH llvm-profdata) +find_program(LCOV_PATH lcov) +find_program(GENHTML_PATH genhtml) +# Hide behind the 'advanced' mode flag for GUI/ccmake +mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) + +# Variables +set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) +set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) + +# Common initialization/checks +if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) + set(CODE_COVERAGE_ADDED ON) + + # Common Targets + add_custom_target( + ccov-preprocessing + COMMAND ${CMAKE_COMMAND} -E make_directory + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} + DEPENDS ccov-clean) + + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + # Messages + message(STATUS "Building with llvm Code Coverage Tools") + + if(NOT LLVM_COV_PATH) + message(FATAL_ERROR "llvm-cov not found! Aborting.") + else() + # Version number checking for 'EXCLUDE' compatibility + execute_process(COMMAND ${LLVM_COV_PATH} --version + OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) + string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION + ${LLVM_COV_VERSION_CALL_OUTPUT}) + + if(LLVM_COV_VERSION VERSION_LESS "7.0.0") + message( + WARNING + "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" + ) + endif() + endif() + + # Targets + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + else() + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + endif() + + # Used to get the shared object file list before doing the main all- + # processing + add_custom_target( + ccov-libs + COMMAND ; + COMMENT "libs ready for coverage report.") + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + # Messages + message(STATUS "Building with lcov Code Coverage Tools") + + if(CMAKE_BUILD_TYPE) + string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) + if(NOT ${upper_build_type} STREQUAL "DEBUG") + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + else() + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() + + # Targets + add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory + ${CMAKE_BINARY_DIR} --zerocounters) + + else() + message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") + endif() +endif() + +# Adds code coverage instrumentation to a library, or instrumentation/targets +# for an executable target. +# ~~~ +# EXECUTABLE ADDED TARGETS: +# GCOV/LCOV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# +# LLVM-COV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report. +# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. +# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. +# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. +# ccov-all-export : Exports the coverage report to a JSON file. +# +# Required: +# TARGET_NAME - Name of the target to generate code coverage for. +# Optional: +# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. +# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. +# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) +# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. +# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. +# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory +# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** +# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output +# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call +# ~~~ +function(target_code_coverage TARGET_NAME) + # Argument parsing + set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) + set(single_value_keywords COVERAGE_TARGET_NAME) + set(multi_value_keywords EXCLUDE OBJECTS ARGS) + cmake_parse_arguments( + target_code_coverage "${options}" "${single_value_keywords}" + "${multi_value_keywords}" ${ARGN}) + + # Set the visibility of target functions to PUBLIC, INTERFACE or default to + # PRIVATE. + if(target_code_coverage_PUBLIC) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY PUBLIC) + elseif(target_code_coverage_INTERFACE) + set(TARGET_VISIBILITY INTERFACE) + set(TARGET_LINK_VISIBILITY INTERFACE) + elseif(target_code_coverage_PLAIN) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY) + else() + set(TARGET_VISIBILITY PRIVATE) + set(TARGET_LINK_VISIBILITY PRIVATE) + endif() + + if(NOT target_code_coverage_COVERAGE_TARGET_NAME) + # If a specific name was given, use that instead. + set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) + endif() + + if(CODE_COVERAGE) + + # Add code coverage instrumentation to the target's linker command + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs + -ftest-coverage) + target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) + endif() + + # Targets + get_target_property(target_type ${TARGET_NAME} TYPE) + + # Add shared library to processing for 'all' targets + if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" >> + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + if(NOT TARGET ccov-libs) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-libs + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # For executables add targets to run and produce output + if(target_type STREQUAL "EXECUTABLE") + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # If there are shared objects to also work with, generate the string to + # add them here + foreach(SO_TARGET ${target_code_coverage_OBJECTS}) + # Check to see if the target is a shared object + if(TARGET ${SO_TARGET}) + get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) + if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") + set(SO_OBJECTS ${SO_OBJECTS} -object=$) + endif() + endif() + endforeach() + + # Run the executable, generating raw profile data Make the run data + # available for further processing. Separated to allow Windows to run + # this target serially. + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E env + LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw + $ ${target_code_coverage_ARGS} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" + ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND + ${CMAKE_COMMAND} -E echo + "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" + >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list + JOB_POOL ccov_serial_pool + DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) + + # Merge the generated profile data so llvm-cov can process it + add_custom_target( + ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_PROFDATA_PATH} merge -sparse + ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o + ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Ignore regex only works on LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print out details of the coverage information to the command line + add_custom_target( + ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Print out a summary of the coverage information to the command line + add_custom_target( + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} report $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} export $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO + "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" + ) + + # Run the executable, generating coverage information + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND $ ${target_code_coverage_ARGS} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + # Generate exclusion string for use + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + if(NOT ${target_code_coverage_EXTERNAL}) + set(EXTERNAL_OPTION --no-external) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + else() + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + endif() + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${GENHTML_PATH} -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + ${COVERAGE_INFO} + DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + + add_custom_command( + TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." + ) + + # AUTO + if(target_code_coverage_AUTO) + if(NOT TARGET ccov) + add_custom_target(ccov) + endif() + add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) + + if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID + MATCHES "GNU") + if(NOT TARGET ccov-report) + add_custom_target(ccov-report) + endif() + add_dependencies( + ccov-report + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # ALL + if(target_code_coverage_ALL) + if(NOT TARGET ccov-all-processing) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-all-processing + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + endif() +endfunction() + +# Adds code coverage instrumentation to all targets in the current directory and +# any subdirectories. To add coverage instrumentation to only specific targets, +# use `target_code_coverage`. +function(add_code_coverage) + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_compile_options(-fprofile-instr-generate -fcoverage-mapping) + add_link_options(-fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + add_compile_options(-fprofile-arcs -ftest-coverage) + link_libraries(gcov) + endif() + endif() +endfunction() + +# Adds the 'ccov-all' type targets that calls all targets added via +# `target_code_coverage` with the `ALL` parameter, but merges all the coverage +# data from them into a single large report instead of the numerous smaller +# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for +# use with coverage dashboards (e.g. codecov.io, coveralls). +# ~~~ +# Optional: +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! +# ~~~ +function(add_code_coverage_all_targets) + # Argument parsing + set(multi_value_keywords EXCLUDE) + cmake_parse_arguments(add_code_coverage_all_targets "" "" + "${multi_value_keywords}" ${ARGN}) + + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # Merge the profile data for all of the run executables + if(WIN32) + add_custom_target( + ccov-all-processing + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe + merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -sparse $$FILELIST) + else() + add_custom_target( + ccov-all-processing + COMMAND + ${LLVM_PROFDATA_PATH} merge -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) + endif() + + # Regex exclude only available for LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print summary of the code coverage information to the command line + if(WIN32) + add_custom_target( + ccov-all-report + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe + report $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all-report + COMMAND + ${LLVM_COV_PATH} report `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-all-export + COMMAND + ${LLVM_COV_PATH} export `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json + DEPENDS ccov-all-processing) + + # Generate HTML output of all added targets for perusal + if(WIN32) + add_custom_target( + ccov-all + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show + $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all + COMMAND + ${LLVM_COV_PATH} show `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") + + # Nothing required for gcov + add_custom_target(ccov-all-processing COMMAND ;) + + # Exclusion regex string creation + set(EXCLUDE_REGEX) + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + else() + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + endif() + + # Generates HTML output of all targets for perusal + add_custom_target( + ccov-all + COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} + DEPENDS ccov-all-capture) + + endif() + + add_custom_command( + TARGET ccov-all + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." + ) + endif() +endfunction() diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake new file mode 100644 index 00000000..e01e6801 --- /dev/null +++ b/cmake/cxx.cmake @@ -0,0 +1,40 @@ +# ---------------------------------------------------------------- +# use this in subdirs that compile c++ code +# +macro(xo_include_options target) + # ---------------------------------------------------------------- + # PROJECT_SOURCE_DIR: + # so we can for example write + # #include "ordinaltree/foo.hpp" + # from anywhere in the project + # PROJECT_BINARY_DIR: + # since generated version file will be in build directory, + # need that build directory to also appear in + # compiler's include path + # + target_include_directories( + ${target} PUBLIC + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_BINARY_DIR} + ) + + # ---------------------------------------------------------------- + # make standard directories for std:: includes explicit + # so that + # (1) they appear in compile_commands.json. + # (2) clangd (run from emacs lsp-mode) can find them + # + if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) + endif() +endmacro() + +# ---------------------------------------------------------------- +# use this when relying on indentlog [[https://github.com/rconybea/indentlog]] headers +# +macro(xo_indentlog_dependency target) + find_package(indentlog REQUIRED) + #add_dependencies(${target} indentlog) + target_link_libraries(${target} PUBLIC indentlog) + #target_include_directories(${target} PUBLIC ${indentlog_DIR}/../../../include) +endmacro() diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 00000000..ac5b07f6 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(ex1) +add_subdirectory(ex2) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt new file mode 100644 index 00000000..6af29d79 --- /dev/null +++ b/example/ex1/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ex1 ex1.cpp) +xo_include_options(ex1) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp new file mode 100644 index 00000000..8c908fa5 --- /dev/null +++ b/example/ex1/ex1.cpp @@ -0,0 +1,26 @@ +/* @file ex1.cpp */ + +#include "randomgen/xoshiro256.hpp" +#include +#include +//#include +//#include + +using namespace xo; +using namespace xo::rng; + +int +main(int argc, char ** argv) { + xoshiro256ss rng{123456789}; + + std::array v; + + std::generate(v.begin(), v.end(), rng); + + for (std::uint64_t i=0; i seed; + + xoshiro256ss eng(seed); +} /*main*/ + +/* end ex2.cpp */ diff --git a/include/randomgen/engine_concept.hpp b/include/randomgen/engine_concept.hpp new file mode 100644 index 00000000..42557b0e --- /dev/null +++ b/include/randomgen/engine_concept.hpp @@ -0,0 +1,36 @@ +/* @file engine_concept.hpp */ + +#pragma once + +#include +#include + +namespace xo { + namespace rng { + /* an engine generates psuedo-random bits. + * given + * RngEngine eng = ...; + * + * RngEngine::result_type x = eng(); + * + * puts random bits into x. + */ + template + concept engine_concept = requires(RngEngine engine, typename RngEngine::result_type r) { + /* note: the first 4 requirements characterize UniformRandomBitGenerator */ + typename RngEngine::result_type; + { RngEngine(r) }; + { engine.min() } -> std::same_as; + { engine.max() } -> std::same_as; + /* must return value in closed interval [.min(), .max()] */ + { engine() } -> std::same_as; + + { engine.seed() }; + { engine.seed(r) }; + { engine == engine }; + { engine != engine }; + } && std::copyable && std::uniform_random_bit_generator; + } /*namespace rng*/ +} /*namespace xo*/ + +/* end engine_concept.hpp */ diff --git a/include/randomgen/random_seed.hpp b/include/randomgen/random_seed.hpp new file mode 100644 index 00000000..5273ddeb --- /dev/null +++ b/include/randomgen/random_seed.hpp @@ -0,0 +1,70 @@ +/* @file random_seed.hpp */ + +#include "indentlog/print/array.hpp" +#include +#include +#include + +namespace xo { + namespace rng { + /* generate a 64-bit random seed using /dev/urandom or similar source. + * This is relatively expensive; at least cost of a system call + * + may block if host has rebooted recently + * + * Require: + * - T is null-constructible. + * + * return value will contain a T-instance in which representation + * has been populated with random bits. Expecting T to be something + * like int32_t, or std::array + */ + template + void random_seed(T * p_seed) { + /* NOTE: arc4random_buf() works on darwin/nix; + * probably need to do something else on intel linux + */ + arc4random_buf(p_seed, sizeof(*p_seed)); + } /*random_seed*/ + + template + T random_seed() { + T retval; + random_seed(&retval); + + return retval; + } /*random_seed*/ + + /* RAII-style random-number seed + * + * Usage: + * + * Seed seed; + * + * auto eng = xoshiro256ss(seed); + * or + * auto rng = UnitIntervalGen::make(seed); + */ + template + struct Seed { + using seed_type = typename Engine::seed_type; + + Seed() { random_seed(&seed_); } + + operator seed_type const & () const { return seed_; } + + seed_type seed_; + }; /*Seed*/ + + template + inline std::ostream & + operator<<(std::ostream & os, + Seed const & x) + { + os << x.seed_; + return os; + } /*operator<<*/ + + } /*namespace rng*/ +} /*namespace xo*/ + +/* end random_seed.hpp */ diff --git a/include/randomgen/xoshiro256.hpp b/include/randomgen/xoshiro256.hpp new file mode 100644 index 00000000..b202650a --- /dev/null +++ b/include/randomgen/xoshiro256.hpp @@ -0,0 +1,169 @@ +/* @file xoshiro256.hpp */ + +#pragma once + +#include "engine_concept.hpp" +#include +#include +#include +#include + +namespace xo { + namespace rng { + + /* engine for producing 64-bit random numbers + * + * see https:/en.wikipedia.org/wiki/Xorshift#xoshiro256** + * + * - satisfies c++ UniformRandomBitGenerator + * - satisfies c++ + * + * Note: zero seed --> constant output sequence {0, 0, 0, ...} + */ + class xoshiro256ss { + public: + using result_type = std::uint64_t; + using seed_type = std::array; + + public: + /* null state -- generates constant stream of 0 bits */ + xoshiro256ss() : xoshiro256ss(0) {} + /* copy ctor */ + xoshiro256ss(xoshiro256ss const & x) = default; + xoshiro256ss(seed_type const & seed) : s_(seed) {} + + /* fallback version -- deprecated */ + xoshiro256ss(std::uint64_t seed) + { + this->s_[0] = 0; + this->s_[1] = seed; + this->s_[2] = 0; + this->s_[3] = 0; + + generate(); + } + + static constexpr std::uint64_t min() { return 0; } + static constexpr std::uint64_t max() { return std::numeric_limits::max(); } + + static std::uint64_t rol64(std::uint64_t x, std::int64_t k) + { + return (x << k) | (x >> (64 - k)); + } + + static bool equal(xoshiro256ss const & x, xoshiro256ss const & y) { + return ((x.s_[0] == y.s_[0]) + && (x.s_[1] == y.s_[1]) + && (x.s_[2] == y.s_[2]) + && (x.s_[3] == y.s_[3])); + } + + /* puts generator into null state */ + void seed() { *this = xoshiro256ss(); } + void seed(std::uint64_t s) { *this = xoshiro256ss{s}; } + /* e.g. used with std::seed_seq<> */ + template + void seed(SeedSeq & sseq) { + sseq.generate(s_.begin(), s_.end()); + } + + std::uint64_t generate() { + std::array & s = (this->s_); + std::uint64_t const result = rol64(s[1] * 5, 7) * 9; + std::uint64_t const t = s[1] << 17; + + s[2] ^= s[0]; + s[3] ^= s[1]; + s[1] ^= s[2]; + s[0] ^= s[3]; + + s[2] ^= t; + s[3] = rol64(s[3], 45); + + return result; + } /*generate*/ + + /* advance to same state as obtained from z calls to .generate(). O(z) ! + * usually better to use jump(). + * + * providing .discard() to satisfy c++ named requirement _RandomNumberEngine_ + */ + void discard(std::uint64_t z) { + for (std::uint64_t i=0; igenerate(); + } + + /* equivalent to .discard(2^128), but uses O(1) time + * + * (may use in multithreaded program to get determinsitic non-overlapping random sequences) + */ + void jump() { + std::array const s_jump_v + = {{0x180ec6d33cfd0aba, + 0xd5a61266f0c9392c, + 0xa9582618e03fc9aa, + 0x39abdc4529b1661c}}; + + std::array & s = (this->s_); + + std::uint64_t s0 = 0; + std::uint64_t s1 = 0; + std::uint64_t s2 = 0; + std::uint64_t s3 = 0; + for (std::uint32_t i = 0; i < s_jump_v.size(); ++i) { + for (std::uint32_t bit = 0; bit < 64; ++bit) { + if (s_jump_v[i] & 1UL << bit) { + s0 ^= s[0]; + s1 ^= s[1]; + s2 ^= s[2]; + s3 ^= s[3]; + } + this->generate(); + } + } + + s[0] = s0; + s[1] = s1; + s[2] = s2; + s[3] = s3; + } /*jump*/ + + /* inverse of .load() */ + void print(std::ostream & os) const { + os << ""; + } + + /* inverse of .print() */ + void load(std::istream & is) { + std::string header, trailer; + std::array sv; + + is >> header >> sv[0] >> sv[1] >> sv[2] >> sv[3] >> trailer; + + if ((header != ""); + + this->s_ = sv; + } /*load*/ + + std::uint64_t operator()() { return generate(); } + + private: + /* state */ + std::array s_; + }; /*xoshiro256ss*/ + + inline bool operator==(xoshiro256ss const & x, xoshiro256ss const & y) { + return xoshiro256ss::equal(x, y); + } + + inline bool operator!=(xoshiro256ss const & x, xoshiro256ss const & y) { + return !xoshiro256ss::equal(x, y); + } + + static_assert(engine_concept); + + } /*namespace rng*/ +} /*namespace xo*/ + +/* end xoshiro256.hpp */ From 1db6cd4d4a267320502f64f6e941a5990d221ede Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 23 Sep 2023 13:14:50 -0400 Subject: [PATCH 0104/2693] build: fix CMAKE_INSTALL_RPATH --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 533fe8e5..f53843ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,7 +50,7 @@ if(NOT CMAKE_INSTALL_PREFIX) set(CMAKE_INSTALL_PREFIX /home/${USER}/local CACHE STRING "install directory") endif() if(NOT CMAKE_INSTALL_RPATH) - set(CMAKE_INSTALL_RPATH /home/${USER}/local/lib CACHE STRING "runpath in installed libraries/executables") + set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib CACHE STRING "runpath in installed libraries/executables") endif() # ---------------------------------------------------------------- From 0e18026fba8eab1b646488770c02001fad7b08b7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 23 Sep 2023 15:47:31 -0400 Subject: [PATCH 0105/2693] initial commit --- README.md | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..0835e7b9 --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +# intrusive reference counting + +Refcnt is a small shared library supplying intrusive reference counting. + +## Features + +- base class `ref::Refcounted`. + Application classes opt-in to reference counting by inheriting this class. +- common base simplifies connecting to common-base-object applications such as python, java etc. + +## Getting Started + +### build + install `indentlog` dependency + +see [github/rconybea/indentlog](https://github.com/Rconybea/indentlog) + +### copy `refcnt` repository locally +``` +$ git clone git@github.com:rconybea/refcnt.git +$ ls -d refcnt +refcnt +``` + +### build + install +``` +$ cd refcnt +$ mkdir build +$ cd build +$ cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` + +alternatively, if you're a nix user: +``` +$ git clone git@github.com:rconybea/xo-nix.git +$ ls -d xo-nix +xo-nix +$ cd xo-nix +$ nix-build -A refcnt +``` + +## Examples + +### 1 +``` +#include "refcnt/Refcounted.hpp" + +using xo::ref::Refcounted; + +struct MyObject : public Refcounted { + static rp make() { return new MyObject(); } + +private: + // intrusively-reference-counted objects should only be heap-allocated + MyObject() { ... } +}; + +int main() { + // create reference-counted instance + auto x = MyObject::make(); + auto y = x; + // x,y refer to the same instance. + x = nullptr; + // y holds last reference + y = nullptr; + // MyObject has been deleted +} +``` + +### 2 + +To log reference-counting activity + +``` +xo::ref::intrusive_ptr_set_debug(true); +``` From 87b4bfa7953f254e716611f8a30e4679a39bfd54 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 23 Sep 2023 15:48:36 -0400 Subject: [PATCH 0106/2693] + implementation --- CMakeLists.txt | 52 +++ cmake/code-coverage.cmake | 678 +++++++++++++++++++++++++++++++++ cmake/cxx.cmake | 86 +++++ include/cxxutil/demangle.hpp | 92 +++++ include/refcnt/Displayable.hpp | 29 ++ include/refcnt/Refcounted.hpp | 294 ++++++++++++++ include/refcnt/Unowned.hpp | 28 ++ src/CMakeLists.txt | 14 + src/Displayable.cpp | 16 + src/Refcounted.cpp | 157 ++++++++ 10 files changed, 1446 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/code-coverage.cmake create mode 100644 cmake/cxx.cmake create mode 100644 include/cxxutil/demangle.hpp create mode 100644 include/refcnt/Displayable.hpp create mode 100644 include/refcnt/Refcounted.hpp create mode 100644 include/refcnt/Unowned.hpp create mode 100644 src/CMakeLists.txt create mode 100644 src/Displayable.cpp create mode 100644 src/Refcounted.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..1e8053f9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,52 @@ +# refcnt/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo-refcnt VERSION 0.1) +enable_language(CXX) + +include(cmake/cxx.cmake) +include(cmake/code-coverage.cmake) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() + + +# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) +add_code_coverage() +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) + +# ---------------------------------------------------------------- +# c++ settings + +set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") + +add_definitions(${PROJECT_CXX_FLAGS}) + +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 20) +endif() + +set(CMAKE_CXX_STANDARD_REQUIRED True) + +# always write compile_commands.json +set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") + +# ---------------------------------------------------------------- +# sources + +add_subdirectory(src) + +# ---------------------------------------------------------------- +# install .hpp + +install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) + +# end CMakeLists.txt diff --git a/cmake/code-coverage.cmake b/cmake/code-coverage.cmake new file mode 100644 index 00000000..b6b36064 --- /dev/null +++ b/cmake/code-coverage.cmake @@ -0,0 +1,678 @@ +# +# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +# USAGE: To enable any code coverage instrumentation/targets, the single CMake +# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or +# on the command line. +# +# From this point, there are two primary methods for adding instrumentation to +# targets: +# +# 1 - A blanket instrumentation by calling `add_code_coverage()`, where +# all targets in that directory and all subdirectories are automatically +# instrumented. +# +# 2 - Per-target instrumentation by calling +# `target_code_coverage()`, where the target is given and thus only +# that target is instrumented. This applies to both libraries and executables. +# +# To add coverage targets, such as calling `make ccov` to generate the actual +# coverage information for perusal or consumption, call +# `target_code_coverage()` on an *executable* target. +# +# Example 1: All targets instrumented +# +# In this case, the coverage information reported will will be that of the +# `theLib` library target and `theExe` executable. +# +# 1a: Via global command +# +# ~~~ +# add_code_coverage() # Adds instrumentation to all targets +# +# add_library(theLib lib.cpp) +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target +# # (instrumentation already added via global anyways) +# # for generating code coverage reports. +# ~~~ +# +# 1b: Via target commands +# +# ~~~ +# add_library(theLib lib.cpp) +# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. +# ~~~ +# +# Example 2: Target instrumented, but with regex pattern of files to be excluded +# from report +# +# ~~~ +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ +# +# Example 3: Target added to the 'ccov' and 'ccov-all' targets +# +# ~~~ +# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. +# +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ + +# Options +option( + CODE_COVERAGE + "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" + OFF) + +# Programs +find_program(LLVM_COV_PATH llvm-cov) +find_program(LLVM_PROFDATA_PATH llvm-profdata) +find_program(LCOV_PATH lcov) +find_program(GENHTML_PATH genhtml) +# Hide behind the 'advanced' mode flag for GUI/ccmake +mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) + +# Variables +set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) +set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) + +# Common initialization/checks +if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) + set(CODE_COVERAGE_ADDED ON) + + # Common Targets + add_custom_target( + ccov-preprocessing + COMMAND ${CMAKE_COMMAND} -E make_directory + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} + DEPENDS ccov-clean) + + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + # Messages + message(STATUS "Building with llvm Code Coverage Tools") + + if(NOT LLVM_COV_PATH) + message(FATAL_ERROR "llvm-cov not found! Aborting.") + else() + # Version number checking for 'EXCLUDE' compatibility + execute_process(COMMAND ${LLVM_COV_PATH} --version + OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) + string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION + ${LLVM_COV_VERSION_CALL_OUTPUT}) + + if(LLVM_COV_VERSION VERSION_LESS "7.0.0") + message( + WARNING + "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" + ) + endif() + endif() + + # Targets + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + else() + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + endif() + + # Used to get the shared object file list before doing the main all- + # processing + add_custom_target( + ccov-libs + COMMAND ; + COMMENT "libs ready for coverage report.") + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + # Messages + message(STATUS "Building with lcov Code Coverage Tools") + + if(CMAKE_BUILD_TYPE) + string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) + if(NOT ${upper_build_type} STREQUAL "DEBUG") + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + else() + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() + + # Targets + add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory + ${CMAKE_BINARY_DIR} --zerocounters) + + else() + message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") + endif() +endif() + +# Adds code coverage instrumentation to a library, or instrumentation/targets +# for an executable target. +# ~~~ +# EXECUTABLE ADDED TARGETS: +# GCOV/LCOV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# +# LLVM-COV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report. +# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. +# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. +# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. +# ccov-all-export : Exports the coverage report to a JSON file. +# +# Required: +# TARGET_NAME - Name of the target to generate code coverage for. +# Optional: +# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. +# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. +# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) +# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. +# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. +# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory +# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** +# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output +# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call +# ~~~ +function(target_code_coverage TARGET_NAME) + # Argument parsing + set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) + set(single_value_keywords COVERAGE_TARGET_NAME) + set(multi_value_keywords EXCLUDE OBJECTS ARGS) + cmake_parse_arguments( + target_code_coverage "${options}" "${single_value_keywords}" + "${multi_value_keywords}" ${ARGN}) + + # Set the visibility of target functions to PUBLIC, INTERFACE or default to + # PRIVATE. + if(target_code_coverage_PUBLIC) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY PUBLIC) + elseif(target_code_coverage_INTERFACE) + set(TARGET_VISIBILITY INTERFACE) + set(TARGET_LINK_VISIBILITY INTERFACE) + elseif(target_code_coverage_PLAIN) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY) + else() + set(TARGET_VISIBILITY PRIVATE) + set(TARGET_LINK_VISIBILITY PRIVATE) + endif() + + if(NOT target_code_coverage_COVERAGE_TARGET_NAME) + # If a specific name was given, use that instead. + set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) + endif() + + if(CODE_COVERAGE) + + # Add code coverage instrumentation to the target's linker command + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs + -ftest-coverage) + target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) + endif() + + # Targets + get_target_property(target_type ${TARGET_NAME} TYPE) + + # Add shared library to processing for 'all' targets + if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" >> + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + if(NOT TARGET ccov-libs) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-libs + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # For executables add targets to run and produce output + if(target_type STREQUAL "EXECUTABLE") + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # If there are shared objects to also work with, generate the string to + # add them here + foreach(SO_TARGET ${target_code_coverage_OBJECTS}) + # Check to see if the target is a shared object + if(TARGET ${SO_TARGET}) + get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) + if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") + set(SO_OBJECTS ${SO_OBJECTS} -object=$) + endif() + endif() + endforeach() + + # Run the executable, generating raw profile data Make the run data + # available for further processing. Separated to allow Windows to run + # this target serially. + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E env + LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw + $ ${target_code_coverage_ARGS} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" + ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND + ${CMAKE_COMMAND} -E echo + "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" + >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list + JOB_POOL ccov_serial_pool + DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) + + # Merge the generated profile data so llvm-cov can process it + add_custom_target( + ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_PROFDATA_PATH} merge -sparse + ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o + ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Ignore regex only works on LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print out details of the coverage information to the command line + add_custom_target( + ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Print out a summary of the coverage information to the command line + add_custom_target( + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} report $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} export $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO + "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" + ) + + # Run the executable, generating coverage information + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND $ ${target_code_coverage_ARGS} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + # Generate exclusion string for use + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + if(NOT ${target_code_coverage_EXTERNAL}) + set(EXTERNAL_OPTION --no-external) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + else() + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + endif() + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${GENHTML_PATH} -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + ${COVERAGE_INFO} + DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + + add_custom_command( + TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." + ) + + # AUTO + if(target_code_coverage_AUTO) + if(NOT TARGET ccov) + add_custom_target(ccov) + endif() + add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) + + if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID + MATCHES "GNU") + if(NOT TARGET ccov-report) + add_custom_target(ccov-report) + endif() + add_dependencies( + ccov-report + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # ALL + if(target_code_coverage_ALL) + if(NOT TARGET ccov-all-processing) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-all-processing + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + endif() +endfunction() + +# Adds code coverage instrumentation to all targets in the current directory and +# any subdirectories. To add coverage instrumentation to only specific targets, +# use `target_code_coverage`. +function(add_code_coverage) + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_compile_options(-fprofile-instr-generate -fcoverage-mapping) + add_link_options(-fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + add_compile_options(-fprofile-arcs -ftest-coverage) + link_libraries(gcov) + endif() + endif() +endfunction() + +# Adds the 'ccov-all' type targets that calls all targets added via +# `target_code_coverage` with the `ALL` parameter, but merges all the coverage +# data from them into a single large report instead of the numerous smaller +# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for +# use with coverage dashboards (e.g. codecov.io, coveralls). +# ~~~ +# Optional: +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! +# ~~~ +function(add_code_coverage_all_targets) + # Argument parsing + set(multi_value_keywords EXCLUDE) + cmake_parse_arguments(add_code_coverage_all_targets "" "" + "${multi_value_keywords}" ${ARGN}) + + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # Merge the profile data for all of the run executables + if(WIN32) + add_custom_target( + ccov-all-processing + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe + merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -sparse $$FILELIST) + else() + add_custom_target( + ccov-all-processing + COMMAND + ${LLVM_PROFDATA_PATH} merge -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) + endif() + + # Regex exclude only available for LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print summary of the code coverage information to the command line + if(WIN32) + add_custom_target( + ccov-all-report + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe + report $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all-report + COMMAND + ${LLVM_COV_PATH} report `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-all-export + COMMAND + ${LLVM_COV_PATH} export `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json + DEPENDS ccov-all-processing) + + # Generate HTML output of all added targets for perusal + if(WIN32) + add_custom_target( + ccov-all + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show + $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all + COMMAND + ${LLVM_COV_PATH} show `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") + + # Nothing required for gcov + add_custom_target(ccov-all-processing COMMAND ;) + + # Exclusion regex string creation + set(EXCLUDE_REGEX) + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + else() + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + endif() + + # Generates HTML output of all targets for perusal + add_custom_target( + ccov-all + COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} + DEPENDS ccov-all-capture) + + endif() + + add_custom_command( + TARGET ccov-all + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." + ) + endif() +endfunction() diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake new file mode 100644 index 00000000..9fc84e60 --- /dev/null +++ b/cmake/cxx.cmake @@ -0,0 +1,86 @@ +# ---------------------------------------------------------------- +# use this in subdirs that compile c++ code +# +macro(xo_include_options target) + # ---------------------------------------------------------------- + # PROJECT_SOURCE_DIR: + # so we can for example write + # #include "ordinaltree/foo.hpp" + # from anywhere in the project + # PROJECT_BINARY_DIR: + # since generated version file will be in build directory, + # need that build directory to also appear in + # compiler's include path + # + target_include_directories( + ${target} PUBLIC + ${PROJECT_SOURCE_DIR}/include # e.g. for #include "indentlog/scope.hpp" + ${PROJECT_SOURCE_DIR}/include/${target} # e.g. for #include "Refcounted.hpp" in refcnt/src + ${PROJECT_BINARY_DIR} # e.g. for generated config.hpp file + ) + + # ---------------------------------------------------------------- + # make standard directories for std:: includes explicit + # so that + # (1) they appear in compile_commands.json. + # (2) clangd (run from emacs lsp-mode) can find them + # + if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) + endif() +endmacro() + +# ---------------------------------------------------------------- +# variable +# XO_ADDRESS_SANITIZE +# determines whether to enable address sanitizer for the XO project +# (see toplevel CMakeLists.txt) +# ---------------------------------------------------------------- +if(XO_ADDRESS_SANITIZE) + add_compile_options(-fsanitize=address) + add_link_options(-fsanitize=address) +endif() + +# XO_STANDARD_COMPILE_OPTIONS: use these when XO_ADDRESS_SANITIZE=OFF +set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) + +# XO_ADDRESS_SANITIZE_COMPILE_OPTIONS: use when XO_ADDRESS_SANITIZE=ON +# +# address sanitizer build complains about _FORTIFY_SOURCE redefines +# In file included from :460: +# :1:9: error: '_FORTIFY_SOURCE' macro redefined [-Werror,-Wmacro-redefined] +# #define _FORTIFY_SOURCE 2 +# +set(XO_ADDRESS_SANITIZE_COMPILE_OPTIONS -Werror -Wall -Wextra -Wno-macro-redefined) + +# XO_COMPILE_OPTIONS: use these with xo_compile_options() macro +if(XO_ADDRESS_SANITIZE) + set(XO_COMPILE_OPTIONS ${XO_ADDRESS_SANITIZE_COMPILE_OPTIONS}) +else() + set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) +endif() + +# ---------------------------------------------------------------- +# generally want all the errors+warnings! +# however: address sanitizer generates error on _FORTIFY_SOURCE +# +macro(xo_compile_options target) + target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) +endmacro() + +# ---------------------------------------------------------------- +# use this for a subdir that builds a library +# +macro(xo_install_library target) + install(TARGETS ${target} DESTINATION lib) +endmacro() + +# ---------------------------------------------------------------- +# use this when relying on indentlog [[https://github.com/rconybea/indentlog]] headers +# +macro(xo_indentlog_dependency target) + find_package(indentlog REQUIRED) + #add_dependencies(${target} indentlog) + target_link_libraries(${target} PUBLIC indentlog) + #target_include_directories(${target} PUBLIC ${indentlog_DIR}/../../../include) +endmacro() diff --git a/include/cxxutil/demangle.hpp b/include/cxxutil/demangle.hpp new file mode 100644 index 00000000..8b8b8ba3 --- /dev/null +++ b/include/cxxutil/demangle.hpp @@ -0,0 +1,92 @@ +/* @file demangle.hpp */ + +#pragma once + +#include +#include +#include // std::array +#include // std::index_sequence + +namespace xo { + namespace reflect { + + template + constexpr auto + substring_as_array(std::string_view str, + std::index_sequence indexes) + { + //return std::array{str[Idxs]..., '\n'}; + return std::array{str[Idxs]...}; + } /*substring_as_array*/ + + template constexpr auto type_name_array() { +#if defined(__clang__) + constexpr auto prefix = std::string_view{"[T = "}; + constexpr auto suffix = std::string_view{"]"}; + constexpr auto function = std::string_view{__PRETTY_FUNCTION__}; +#elif defined(__GNUC__) + constexpr auto prefix = std::string_view{"with T = "}; + constexpr auto suffix = std::string_view{"]"}; + constexpr auto function = std::string_view{__PRETTY_FUNCTION__}; +#elif defined(_MSC_VER) + constexpr auto prefix = std::string_view{"type_name_array<"}; + constexpr auto suffix = std::string_view{">(void)"}; + constexpr auto function = std::string_view{__FUNCSIG__}; +#else +# error type_name_array: Unsupported compiler +#endif + + constexpr auto start = function.find(prefix) + prefix.size(); + constexpr auto end = function.rfind(suffix); + + //static_assert(start < end); + + constexpr auto name = function.substr(start, (end - start)); + + constexpr auto ixseq = std::make_index_sequence{}; + + return substring_as_array(name, ixseq); + } /*type_name_array*/ + + template + struct type_name_holder { + static inline constexpr auto value = type_name_array(); + }; + + template + constexpr auto type_name() -> std::string_view + { + constexpr auto& value = type_name_holder::value; + return std::string_view{value.data(), value.size()}; + } + +#ifdef NOT_IN_USE + template + struct join + { + // Join all strings into a single std::array of chars + static constexpr auto impl() noexcept + { + constexpr std::size_t len = (Strs.size() + ... + 0); + std::array arr{}; + auto append = [i = 0, &arr](auto const& s) mutable { + for (auto c : s) arr[i++] = c; + }; + (append(Strs), ...); + arr[len] = 0; + return arr; + } + // Give the joined string static storage + static constexpr auto arr = impl(); + // View as a std::string_view + static constexpr std::string_view value {arr.data(), arr.size() - 1}; + }; + + // Helper to get the value out + template + static constexpr auto join_v = join::value; +#endif + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end demangle.hpp */ diff --git a/include/refcnt/Displayable.hpp b/include/refcnt/Displayable.hpp new file mode 100644 index 00000000..74708573 --- /dev/null +++ b/include/refcnt/Displayable.hpp @@ -0,0 +1,29 @@ +/* @file Displayable.hpp */ + +#pragma once + +#include "refcnt/Refcounted.hpp" + +namespace xo { + namespace ref { + class Displayable : public Refcount { + public: + /* write some kind of human-readable representation on stream */ + virtual void display(std::ostream & os) const = 0; + std::string display_string() const; + }; /*Displayable*/ + + /* see also + * operator<<(std::ostream &, intrusive_ptr const &) + * in [Refcounted.hpp] + */ + inline std::ostream & + operator<<(std::ostream &os, Displayable const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace ref*/ +} /*namespace xo*/ + +/* end Displayable.hpp */ diff --git a/include/refcnt/Refcounted.hpp b/include/refcnt/Refcounted.hpp new file mode 100644 index 00000000..17d778c7 --- /dev/null +++ b/include/refcnt/Refcounted.hpp @@ -0,0 +1,294 @@ +/* @file Refcounted.hpp */ + +#pragma once + +#include "indentlog/scope.hpp" +#include "cxxutil/demangle.hpp" + +//#include +#include +#include + +namespace xo { + namespace ref { + class Refcount; + + template + class Borrow; + + /* originally used boost::instrusive_ptr<>. + * ran into a bug. probably mine, but implemented + * refcounting inline for debugging + */ + template + class intrusive_ptr { + public: + using element_type = T; + + public: + intrusive_ptr() : ptr_(nullptr) {} + intrusive_ptr(T * x) : ptr_(x) { + intrusive_ptr_log_ctor(sc_self_type, this, x); + intrusive_ptr_add_ref(ptr_); + } /*ctor*/ + + /* NOTE: need exactly this form for copy-constructor + * clang11 will not recognize template form below as + * supplying copy ctor, and default version is broken for + * instrusive_ptr. + */ + intrusive_ptr(intrusive_ptr const & x) : ptr_(x.get()) { + intrusive_ptr_log_cctor(sc_self_type, this, x.get()); + intrusive_ptr_add_ref(ptr_); + } /*cctor*/ + + /* create from instrusive pointer to some related type S */ + template + intrusive_ptr(intrusive_ptr const & x) : ptr_(x.get()) { + intrusive_ptr_log_cctor(sc_self_type, this, x.get()); + intrusive_ptr_add_ref(ptr_); + } /*cctor*/ + + /* move ctor -- in this case don't need to update refcount */ + intrusive_ptr(intrusive_ptr && x) : ptr_{std::move(x.ptr_)} { + intrusive_ptr_log_mctor(sc_self_type, this, ptr_); + /* since we're moving from x, need to make sure x dtor + * doesn't decrement refcount + */ + x.ptr_ = nullptr; + } + + /* aliasing ctor. see ctor (8) here: + * [[https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr]] + * and this dicsussion: + * [[https://stackoverflow.com/questions/49178231/pybind11-multiple-inheritance-with-custom-holder-type-fails-to-cast-to-base-type/73131206#73131206]] + */ + template + intrusive_ptr(intrusive_ptr const & /*x*/, element_type * y) : ptr_{y} { + if (std::is_same()) { + intrusive_ptr_log_actor(sc_self_type, this, y); + intrusive_ptr_add_ref(ptr_); + ; /* trivial aliasing, proceed */ + } else { + using xo::xtag; + throw std::runtime_error(tostr("attempt to use aliasing ctor with", + xtag("Y", reflect::type_name()), + xtag("T", reflect::type_name()))); + } + } /*ctor*/ + + ~intrusive_ptr() { + T * x = this->ptr_; + + intrusive_ptr_log_dtor(sc_self_type, this, x); + + this->ptr_ = nullptr; + + intrusive_ptr_release(x); + } /*dtor*/ + + static bool compare(intrusive_ptr const & x, + intrusive_ptr const & y) { + return ptrdiff_t(x.get() - y.get()); + } + + Borrow borrow() const; + + T * get() const { return ptr_; } + + T * operator->() const { return ptr_; } + + operator bool() const { return ptr_ != nullptr; } + + intrusive_ptr & operator=(intrusive_ptr const & rhs) { + T * x = rhs.get(); + + intrusive_ptr_log_assign(sc_self_type, this, x); + + T * old = this->ptr_; + this->ptr_ = x; + + intrusive_ptr_add_ref(x); + intrusive_ptr_release(old); + + return *this; + } /*operator=*/ + + intrusive_ptr & operator=(intrusive_ptr && rhs) { + intrusive_ptr_log_massign(sc_self_type, this, rhs.get()); + + std::swap(this->ptr_, rhs.ptr_); + + /* dtor on rhs will decrement refcount on old value of this->ptr_ + * don't increment for new value, since refcount just transfers from rhs to *this + */ + + return *this; + } /*operator=*/ + + private: + static constexpr std::string_view sc_self_type = xo::reflect::type_name>(); + + private: + T * ptr_ = nullptr; + }; /*intrusive_ptr*/ + + template + inline bool operator==(intrusive_ptr const & x, intrusive_ptr const & y) { return intrusive_ptr::compare(x, y) == 0; } + + template + using rp = intrusive_ptr; + + class Refcount { + public: + Refcount() : reference_counter_(0) {} + /* WARNING: virtual dtor here is essential, + * since it's what allows us to invoke delete on a Refcount*, + * for an object of some derived class type T. Otherwise clang + * will use different addresses for Refcount-part and T-part of + * such instance, which means pointer given to delete will not be + * the same as pointer returned from new + */ + virtual ~Refcount() = default; + + uint32_t reference_counter() const { return reference_counter_.load(); } + + private: + friend uint32_t intrusive_ptr_refcount(Refcount *); + friend void intrusive_ptr_add_ref(Refcount *); + friend void intrusive_ptr_release(Refcount *); + + private: + std::atomic reference_counter_; + }; /*Refcount*/ + + inline uint32_t + intrusive_ptr_refcount(Refcount * x) { + /* reporting accurately for diagnostics */ + if (x) + return x->reference_counter_.load(); + else + return 0; + } /*intrusive_ptr_refcount*/ + + void intrusive_ptr_set_debug(bool x); + void intrusive_ptr_log_ctor(std::string_view const & self_type, + void * this_ptr, + Refcount * x); + /* here actor short for 'aliasing ctor' */ + void intrusive_ptr_log_actor(std::string_view const & self_type, + void * this_ptr, + Refcount * x); + void intrusive_ptr_log_cctor(std::string_view const & self_type, + void * this_ptr, + Refcount * x); + void intrusive_ptr_log_mctor(std::string_view const & self_type, + void *this_ptr, + Refcount * x); + void intrusive_ptr_log_dtor(std::string_view const & self_type, + void * this_ptr, + Refcount * x); + void intrusive_ptr_log_assign(std::string_view const & self_type, + void * this_ptr, + Refcount * x); + void intrusive_ptr_log_massign(std::string_view const & self_type, + void *this_ptr, + Refcount * x); + void intrusive_ptr_add_ref(Refcount * x); + void intrusive_ptr_release(Refcount * x); + + template + inline std::ostream & + operator<<(std::ostream & os, intrusive_ptr const & x) { + if(x.get()) { + os << *(x.get()); + } else { + os << "() << ">"; + } + return os; + } /*operator<<*/ + + /* borrow a reference-counted pointer to pass down the stack + * 1. borrowed pointer intended to replace: + * a. code like + * foo(rp x), + * passing rp by value requires increment/decrement pair, + * which is superfluous given that caller holds reference throughout + * b. code like + * foo(rp const & x) + * passing rp by reference requires double-indirection in called + * code + * 2. borrowed pointer does not check/maintain reference count. + * it should never be stored in a struct; intended strictly + * to be passed down stack + * 3. just the same, want to be able to copy the borrowed pointer, + * to avoid double-indirection + * 4. also can promote borrowed pointer to full reference-counted + * whenever desired + */ + template + class Borrow { + public: + template + Borrow(rp const & x) : ptr_(x.get()) {} + + Borrow(Borrow const & x) = default; + + /* convert from another borrow, if it has compatible pointer type */ + template + Borrow(Borrow const & x) : ptr_(x.get()) {} + + /* dynamic cast from a pointer to an object of some convertible type */ + template + static Borrow from(Borrow x) { + return Borrow(dynamic_cast(x.get())); + } /*from*/ + + /* promote from native pointer */ + static Borrow from_native(T * x) { + return Borrow(x); + } /*from_native*/ + + T * get() const { return ptr_; } + + rp promote() const { return rp(ptr_); } + + T & operator*() const { return *ptr_; } + T * operator->() const { return ptr_; } + + operator bool() const { return ptr_ != nullptr; } + + static int32_t compare(Borrow const & x, Borrow const & y) { + return ptrdiff_t(x.get() - y.get()); + } /*compare*/ + + static int32_t compare(rp const & x, Borrow const & y) { + return ptrdiff_t(x.get() - y.get()); + } /*compare*/ + + private: + Borrow(T * x) : ptr_(x) {} + + private: + T * ptr_ = nullptr; + }; /*Borrow*/ + + template + inline bool operator==(Borrow x, Borrow y) { return Borrow::compare(x, y) == 0; } + + template + inline bool operator==(rp const & x, Borrow y) { return Borrow::compare(x, y) == 0; } + + template + using brw = Borrow; + + template + Borrow + intrusive_ptr::borrow() const { + return Borrow(*this); + } /*borrow*/ + + } /*namespace ref*/ +} /*namespace xo*/ + +/* end Refcounted.hpp */ diff --git a/include/refcnt/Unowned.hpp b/include/refcnt/Unowned.hpp new file mode 100644 index 00000000..3f78f0d2 --- /dev/null +++ b/include/refcnt/Unowned.hpp @@ -0,0 +1,28 @@ +/* @file Unowned.hpp */ + +namespace xo { + namespace ref { + /* use this is a holder type for pointers that pybind11 should treat + * as "not-my-problem". in particular that pybind11 should never delete. + */ + template + class unowned_ptr { + public: + unowned_ptr(T * x) : ptr_{x} {} + unowned_ptr(unowned_ptr const & x) = default; + ~unowned_ptr() = default; + + T * get() const { return ptr_; } + T * operator->() const { return ptr_; } + + operator bool() const { return ptr_ != nullptr; } + + unowned_ptr & operator=(unowned_ptr const & rhs) = default; + + private: + T * ptr_ = nullptr; + }; /*unowned_ptr*/ + } /*namespace ref*/ +} /*namespace xo*/ + +/* end Unowned.hpp */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 00000000..8083cee0 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,14 @@ +set(SELF_LIBRARY_NAME refcnt) +set(SELF_SOURCE_FILES Refcounted.cpp Displayable.cpp) +add_library(${SELF_LIBRARY_NAME} SHARED ${SELF_SOURCE_FILES}) + +set_target_properties(${SELF_LIBRARY_NAME} + PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION 1) + +xo_indentlog_dependency(${SELF_LIBRARY_NAME}) + +xo_include_options(${SELF_LIBRARY_NAME}) +xo_compile_options(${SELF_LIBRARY_NAME}) +xo_install_library(${SELF_LIBRARY_NAME}) diff --git a/src/Displayable.cpp b/src/Displayable.cpp new file mode 100644 index 00000000..b8793ad3 --- /dev/null +++ b/src/Displayable.cpp @@ -0,0 +1,16 @@ +/* @file Displayable.cpp */ + +#include "refcnt/Displayable.hpp" + +namespace xo { + using xo::tostr; + + namespace ref { + std::string + Displayable::display_string() const { + return tostr(*this); + } /*display_string*/ + } /*namespace ref*/ +} /*namespace xo*/ + +/* end Displayable.cpp */ diff --git a/src/Refcounted.cpp b/src/Refcounted.cpp new file mode 100644 index 00000000..9aeb579d --- /dev/null +++ b/src/Refcounted.cpp @@ -0,0 +1,157 @@ +/* @file Refcounted.cpp */ + +#include "Refcounted.hpp" + +namespace xo { + namespace ref { + namespace { + /* verbose logging for intrusive_ptr */ + static bool s_logging_enabled = false; + + void + intrusive_ptr_log_aux(std::string_view const & self_type, + std::string_view const & method_name, + void * this_ptr, + Refcount * x) + { + scope lscope(XO_LITERAL(verbose, self_type, method_name), + "enter", + xtag("this", this_ptr), + xtag("x", x), + xtag("n", intrusive_ptr_refcount(x))); + } /*intrusive_ptr_log_aux*/ + } /*namespace*/ + + bool + intrusive_ptr_set_debug(bool debug_flag) { + s_logging_enabled = debug_flag; + } /*intrusive_ptr_set_debug*/ + + void + intrusive_ptr_log_ctor(std::string_view const & self_type, + void * this_ptr, + Refcount * x) + { + if (s_logging_enabled) + intrusive_ptr_log_aux(self_type, "::ctor", this_ptr, x); + } /*intrusive_ptr_log_ctor*/ + + void + intrusive_ptr_log_actor(std::string_view const & self_type, + void * this_ptr, + Refcount * x) + { + if (s_logging_enabled) + intrusive_ptr_log_aux(self_type, "::actor", this_ptr, x); + } /*intrusive_ptr_log_actor*/ + + void + intrusive_ptr_log_cctor(std::string_view const & self_type, + void * this_ptr, + Refcount * x) + { + if (s_logging_enabled) + intrusive_ptr_log_aux(self_type, "::cctor", this_ptr, x); + } /*intrusive_ptr_log_cctor*/ + + void + intrusive_ptr_log_mctor(std::string_view const & self_type, + void * this_ptr, + Refcount * x) + { + if (s_logging_enabled) + intrusive_ptr_log_aux(self_type, "::mctor", this_ptr, x); + } /*intrusive_ptr_log_mctor*/ + + void + intrusive_ptr_log_dtor(std::string_view const & self_type, + void * this_ptr, + Refcount * x) + { + if (s_logging_enabled) + intrusive_ptr_log_aux(self_type, "::dtor", this_ptr, x); + } /*intrusive_ptr_log_dtor*/ + + void + intrusive_ptr_log_assign(std::string_view const & self_type, + void * this_ptr, + Refcount * x) + { + if (s_logging_enabled) + intrusive_ptr_log_aux(self_type, "::=", this_ptr, x); + } /*intrusive_ptr_log_assign*/ + + void + intrusive_ptr_log_massign(std::string_view const & self_type, + void * this_ptr, + Refcount * x) + { + if (s_logging_enabled) + intrusive_ptr_log_aux(self_type, "::m=", this_ptr, x); + } /*intrusive_ptr_log_massign*/ + + void + intrusive_ptr_add_ref(Refcount * x) + { + /* for adding reference -- can use relaxed order, + * since any reordering of a set of intrusive_ptr_add_ref() + * calls is ok, provided no intervening intrusive_ptr_release() + * calls. + */ + bool success = (x == nullptr); + + while(!success) { + uint32_t n = x->reference_counter_.load(std::memory_order_relaxed); + + success = x->reference_counter_.compare_exchange_strong(n, n+1, + std::memory_order_relaxed); + } + } /*intrusive_ptr_add_ref*/ + + void + intrusive_ptr_release(Refcount * x) + { + using xo::scope; + using xo::xtag; + + scope log(XO_ENTER0(verbose), + "enter", + xtag("x", x), + xtag("n", x ? x->reference_counter_.load() : 0)); + + /* for decrement, need acq_rel ordering */ + bool success = (x == nullptr); + uint32_t n = 0; + + while(!success) { + n = x->reference_counter_.load(std::memory_order_acquire); + + if(n == static_cast(-1)) { + log && log("detected double-delete attempt", + xtag("x", x), + xtag("n", n)); + assert(false); + } + + success = x->reference_counter_.compare_exchange_strong(n, n-1, + std::memory_order_acq_rel); + } + + if(n == 1) { + /* just deleted the last reference, so recover the object */ + + log && log("delete object with 0 refs"); + + /* for good measure: replace refcount with -1, + * in hope of detecting a double-delete attempt + */ + x->reference_counter_.store(static_cast(-1)); + + delete x; + } + } /*intrusive_ptr_release*/ + + } /*namespace ref*/ +} /*namespace xo*/ + +/* end Refcounted.cpp */ From b6723b921bbd925c4c267ea46705c42281f92018 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 12:27:19 -0400 Subject: [PATCH 0107/2693] refcnt: build + install fixes --- CMakeLists.txt | 40 ++++++++++++++++++++++++++++-- cmake/cxx.cmake | 18 +++++++++++--- cmake/refcntConfig.cmake.in | 4 +++ include/refcnt/Refcounted.hpp | 46 +++++++++++++++++------------------ src/Refcounted.cpp | 2 +- 5 files changed, 80 insertions(+), 30 deletions(-) create mode 100644 cmake/refcntConfig.cmake.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e8053f9..ed3c4486 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10) -project(xo-refcnt VERSION 0.1) +project(refcnt VERSION 0.1) enable_language(CXX) include(cmake/cxx.cmake) @@ -13,7 +13,6 @@ include(cmake/code-coverage.cmake) enable_testing() - # activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) add_code_coverage() # 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. @@ -26,6 +25,7 @@ add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) # ---------------------------------------------------------------- # c++ settings +set(XO_PROJECT_NAME refcnt) set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") add_definitions(${PROJECT_CXX_FLAGS}) @@ -43,6 +43,42 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") # sources add_subdirectory(src) +add_subdirectory(utest) + +# ---------------------------------------------------------------- +# cmake export + +set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") +set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") + +include(CMakePackageConfigHelpers) +write_basic_package_version_file("${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" + VERSION 0.1 + COMPATIBILITY AnyNewerVersion +) + +#install( +# TARGETS ${XO_PROJECT_NAME} +# EXPORT ${XO_PROJECT_NAME}Targets +# LIBRARY DESTINATION lib COMPONENT Runtime +# ARCHIVE DESTINATION lib COMPONENT Development +# RUNTIME DESTINATION bin COMPONENT Runtime +# PUBLIC_HEADER DESTINATION include COMPONENT Development +# BUNDLE DESTINATION bin COMPONENT Runtime +# ) + +configure_package_config_file( + "${PROJECT_SOURCE_DIR}/cmake/${XO_PROJECT_NAME}Config.cmake.in" + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" + INSTALL_DESTINATION lib/cmake/${XO_PROJECT_NAME} + ) + +install(EXPORT ${XO_PROJECT_NAME}Targets DESTINATION lib/cmake/${XO_PROJECT_NAME}) +install( + FILES + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" + DESTINATION lib/cmake/${XO_PROJECT_NAME}) # ---------------------------------------------------------------- # install .hpp diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake index 9fc84e60..4f2dd2fa 100644 --- a/cmake/cxx.cmake +++ b/cmake/cxx.cmake @@ -14,9 +14,11 @@ macro(xo_include_options target) # target_include_directories( ${target} PUBLIC - ${PROJECT_SOURCE_DIR}/include # e.g. for #include "indentlog/scope.hpp" - ${PROJECT_SOURCE_DIR}/include/${target} # e.g. for #include "Refcounted.hpp" in refcnt/src - ${PROJECT_BINARY_DIR} # e.g. for generated config.hpp file + $ # e.g. for #include "indentlog/scope.hpp" + $ + $ # e.g. for #include "Refcounted.hpp" in refcnt/src + $ + $ # e.g. for generated config.hpp file ) # ---------------------------------------------------------------- @@ -72,7 +74,15 @@ endmacro() # use this for a subdir that builds a library # macro(xo_install_library target) - install(TARGETS ${target} DESTINATION lib) + install( + TARGETS ${target} + EXPORT ${target}Targets + LIBRARY DESTINATION lib COMPONENT Runtime + ARCHIVE DESTINATION lib COMPONENT Development + RUNTIME DESTINATION bin COMPONENT Runtime + PUBLIC_HEADER DESTINATION include COMPONENT Development + BUNDLE DESTINATION bin COMPONENT Runtime + ) endmacro() # ---------------------------------------------------------------- diff --git a/cmake/refcntConfig.cmake.in b/cmake/refcntConfig.cmake.in new file mode 100644 index 00000000..e13a2c54 --- /dev/null +++ b/cmake/refcntConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@XO_PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/refcnt/Refcounted.hpp b/include/refcnt/Refcounted.hpp index 17d778c7..dc069cf5 100644 --- a/include/refcnt/Refcounted.hpp +++ b/include/refcnt/Refcounted.hpp @@ -171,31 +171,31 @@ namespace xo { return 0; } /*intrusive_ptr_refcount*/ - void intrusive_ptr_set_debug(bool x); - void intrusive_ptr_log_ctor(std::string_view const & self_type, - void * this_ptr, - Refcount * x); + extern void intrusive_ptr_set_debug(bool x); + extern void intrusive_ptr_log_ctor(std::string_view const & self_type, + void * this_ptr, + Refcount * x); /* here actor short for 'aliasing ctor' */ - void intrusive_ptr_log_actor(std::string_view const & self_type, - void * this_ptr, - Refcount * x); - void intrusive_ptr_log_cctor(std::string_view const & self_type, - void * this_ptr, - Refcount * x); - void intrusive_ptr_log_mctor(std::string_view const & self_type, - void *this_ptr, - Refcount * x); - void intrusive_ptr_log_dtor(std::string_view const & self_type, - void * this_ptr, - Refcount * x); - void intrusive_ptr_log_assign(std::string_view const & self_type, - void * this_ptr, - Refcount * x); - void intrusive_ptr_log_massign(std::string_view const & self_type, - void *this_ptr, + extern void intrusive_ptr_log_actor(std::string_view const & self_type, + void * this_ptr, + Refcount * x); + extern void intrusive_ptr_log_cctor(std::string_view const & self_type, + void * this_ptr, + Refcount * x); + extern void intrusive_ptr_log_mctor(std::string_view const & self_type, + void *this_ptr, + Refcount * x); + extern void intrusive_ptr_log_dtor(std::string_view const & self_type, + void * this_ptr, + Refcount * x); + extern void intrusive_ptr_log_assign(std::string_view const & self_type, + void * this_ptr, + Refcount * x); + extern void intrusive_ptr_log_massign(std::string_view const & self_type, + void *this_ptr, Refcount * x); - void intrusive_ptr_add_ref(Refcount * x); - void intrusive_ptr_release(Refcount * x); + extern void intrusive_ptr_add_ref(Refcount * x); + extern void intrusive_ptr_release(Refcount * x); template inline std::ostream & diff --git a/src/Refcounted.cpp b/src/Refcounted.cpp index 9aeb579d..11c8cb62 100644 --- a/src/Refcounted.cpp +++ b/src/Refcounted.cpp @@ -22,7 +22,7 @@ namespace xo { } /*intrusive_ptr_log_aux*/ } /*namespace*/ - bool + void intrusive_ptr_set_debug(bool debug_flag) { s_logging_enabled = debug_flag; } /*intrusive_ptr_set_debug*/ From bf92724aa5d123b6acb9b00318e579828fb9ff38 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 12:49:03 -0400 Subject: [PATCH 0108/2693] bugfix: default CMAKE_INSTALL_RPATH --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 472cd5cf..eb5f2cf2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,7 @@ if(NOT CMAKE_INSTALL_PREFIX) set(CMAKE_INSTALL_PREFIX /home/${USER}/local CACHE STRING "install directory") endif() if(NOT CMAKE_INSTALL_RPATH) - set(CMAKE_INSTALL_RPATH /home/${USER}/local/lib CACHE STRING "runpath in installed libraries/executables") + set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib CACHE STRING "runpath in installed libraries/executables") endif() add_subdirectory(example) From f8ca4dbe096b50c9628ba7e64ce46fde5d304d04 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 12:56:22 -0400 Subject: [PATCH 0109/2693] refcnt: + unit test --- utest/CMakeLists.txt | 60 +++++++ utest/README | 7 + utest/intrusive_ptr.test.cpp | 301 +++++++++++++++++++++++++++++++++++ utest/refcnt_utest_main.cpp | 6 + 4 files changed, 374 insertions(+) create mode 100644 utest/CMakeLists.txt create mode 100644 utest/README create mode 100644 utest/intrusive_ptr.test.cpp create mode 100644 utest/refcnt_utest_main.cpp diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..74706592 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,60 @@ +# build unittest 'refcnt/utest/utest.refcnt + +set(SELF_EXECUTABLE_NAME utest.refcnt) + +# These tests can use the Catch2-provided main +set(SELF_SOURCE_FILES intrusive_ptr.test.cpp refcnt_utest_main.cpp) +add_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) +xo_include_options(${SELF_EXECUTABLE_NAME}) + +add_test(NAME ${SELF_EXECUTABLE_NAME} COMMAND ${SELF_EXECUTABLE_NAME}) +target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) + +#target_link_libraries(${SELF_EXECUTABLE_NAME} PRIVATE Catch2::Catch2WithMain) + +# ---------------------------------------------------------------- +# generic project dependency + +# PROJECT_SOURCE_DIR: +# so we can for example write +# #include "indentlog/scope.hpp" +# from anywhere in the project +# PROJECT_BINARY_DIR: +# since version file will be in build directory, need that directory +# to also be included in compiler's include path +# +target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC + ${PROJECT_SOURCE_DIR} + ${PROJECT_BINARY_DIR}) + +# ---------------------------------------------------------------- +# internal dependencies: refcnt, ... + +target_link_libraries(${SELF_EXECUTABLE_NAME} PUBLIC refcnt) + +# ---------------------------------------------------------------- +# 3rd part dependency: catch2: + +find_package(Catch2 2 REQUIRED) + +# need this so that catch2/include appears in compile_commands.json, +# on which lsp integration relies. +# +# See also /nix/store/*-catch2-*/lib/cmake/Catch2/ParseAndAddCatchTests.cmake; +# commands here derived from ^ .cmake file +# +#find_path(CATCH_INCLUDE_DIR "catch2/catch.hpp") +#target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC ${CATCH_INCLUDE_DIR}) + +# ---------------------------------------------------------------- +# make standard directories for std:: includes explicit +# so that +# (1) they appear in compile_commands.json. +# (2) clangd (run from emacs lsp-mode) can find them +# +if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES + ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +endif() + +# end CMakeLists.txt diff --git a/utest/README b/utest/README new file mode 100644 index 00000000..85cc27c2 --- /dev/null +++ b/utest/README @@ -0,0 +1,7 @@ +* to run unit tests for this directoyr + + $ cd path/to/kalman/build + $ ./refcnt/utest/utest.refcnt + + + \ No newline at end of file diff --git a/utest/intrusive_ptr.test.cpp b/utest/intrusive_ptr.test.cpp new file mode 100644 index 00000000..d8d756e2 --- /dev/null +++ b/utest/intrusive_ptr.test.cpp @@ -0,0 +1,301 @@ +/* @file intrusive_ptr.test.cpp */ + +#include "refcnt/Refcounted.hpp" +#include "indentlog/scope.hpp" +#include "catch2/catch.hpp" +#include +#include + +namespace xo { + using xo::ref::Refcount; + using xo::ref::Borrow; + using xo::ref::rp; + using xo::ref::brw; + using xo::ref::intrusive_ptr_refcount; + using xo::ref::intrusive_ptr_add_ref; + using xo::ref::intrusive_ptr_release; + + namespace ut { + namespace { + static uint32_t ctor_count = 0; + static uint32_t dtor_count = 0; + + /* empty object, except for refcount */ + class JustRefcount : public ref::Refcount { + public: + JustRefcount() { ++ctor_count; } + ~JustRefcount() { ++dtor_count; } + }; /*JustRefcount*/ + + inline std::ostream & operator<<(std::ostream & os, JustRefcount & x) { + os << "JustRefcount"; + return os; + } /*operator<<*/ + } /*namespace*/ + + TEST_CASE("refcount", "[refcnt][trivial]") { + REQUIRE(std::is_default_constructible() == true); + REQUIRE(std::has_virtual_destructor() == true); + + /* refcount object self-initializes to 0 */ + Refcount x; + REQUIRE(x.reference_counter() == 0); + } /*TEST_CASE(refcount)*/ + + TEST_CASE("null-intrusive-ptr", "[refcnt][trivial]") { + //constexpr std::string_view c_self = "TEST_CASE:null-intrusive-ptr"; + + REQUIRE(std::has_virtual_destructor() == true); + + rp p1; + rp p2; + + REQUIRE(sizeof(p1) == sizeof(JustRefcount*)); + + REQUIRE(p1.get() == nullptr); + REQUIRE(p1.operator->() == nullptr); + + REQUIRE(p2.get() == nullptr); + REQUIRE(p2.operator->() == nullptr); + + /* can assign a nullptr */ + rp p3; + + REQUIRE(p3.get() == nullptr); + p3 = p1; + REQUIRE(p3.get() == nullptr); + + /* can use aux functions on null pointers */ + REQUIRE(intrusive_ptr_refcount(p1.get()) == 0); + + intrusive_ptr_add_ref(nullptr); + intrusive_ptr_release(nullptr); + + /* can borrow a null intrusive_ptr */ + brw p1_brw = p1.borrow(); + brw p2_brw = p2.borrow(); + + REQUIRE(p1_brw.get() == nullptr); + REQUIRE(p1_brw.operator->() == nullptr); + /* null borrow is false-y */ + REQUIRE(p1_brw == false); + + /* can promote a borrowed pointer */ + rp pp = p1_brw.promote(); + + REQUIRE(p1.get() == pp.get()); + + /* comparisons */ + REQUIRE(Borrow::compare(p1_brw, p2_brw) == 0); + REQUIRE(p1_brw == p2_brw); + REQUIRE((p1_brw != p2_brw) == false); + REQUIRE(p1 == p1_brw); + REQUIRE((p1 != p1_brw) == false); + REQUIRE(p1_brw == p1); + REQUIRE((p1_brw != p1) == false); + } /*TEST_CASE(null-intrusive_ptr)*/ + + TEST_CASE("intrusive-ptr-identity", "[refcnt][identity]") + { + uint32_t cc = ctor_count; + uint32_t dc = dtor_count; + + rp p1(new JustRefcount()); + + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get() != nullptr); + REQUIRE(p1.get() == p1.operator->()); + REQUIRE(intrusive_ptr_refcount(p1.get()) == 1); + REQUIRE(p1->reference_counter() == 1); + + intrusive_ptr_add_ref(p1.get()); + + REQUIRE(intrusive_ptr_refcount(p1.get()) == 2); + + intrusive_ptr_release(p1.get()); + + REQUIRE(intrusive_ptr_refcount(p1.get()) == 1); + + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + + rp p2(new JustRefcount()); + + REQUIRE(ctor_count == cc + 2); + REQUIRE(dtor_count == dc); + + REQUIRE(p2.get() != nullptr); + REQUIRE(p2.get() != p1.get()); + REQUIRE(p2.get() == p2.operator->()); + REQUIRE(p2->reference_counter() == 1); + + /* can borrow a non-null intrusive-ptr */ + brw p1_brw = p1.borrow(); + + REQUIRE(p1_brw.get() == p1.get()); + + /* borrowing does not change refcount, borrow not tracked */ + REQUIRE(ctor_count == cc + 2); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get()->reference_counter() == 1); + + /* copying borrowed pointer does not touch refcount */ + brw p1_brw2 = p1_brw; + + REQUIRE(ctor_count == cc + 2); + REQUIRE(dtor_count == dc); + REQUIRE(p1_brw2.get() == p1.get()); + + REQUIRE(p1.get()->reference_counter() == 1); + } /*TEST_CASE(identity-intrusive-ptr)*/ + + TEST_CASE("intrusive-ptr-release", "[refcnt][release]") + { + uint32_t cc = ctor_count; + uint32_t dc = dtor_count; + + rp p1(new JustRefcount()); + + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get() != nullptr); + REQUIRE(p1->reference_counter() == 1); + + /* reference count going to 0 -> delete object */ + p1 = nullptr; + + REQUIRE(p1.get() == nullptr); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc + 1); + } /*TEST_CASE(intrusive-ptr-release)*/ + + TEST_CASE("intrusive-ptr-copy", "[refcnt][copy]") + { + uint32_t cc = ctor_count; + uint32_t dc = dtor_count; + + rp p1(new JustRefcount()); + JustRefcount * p1_native = p1.get(); + + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get() != nullptr); + REQUIRE(p1->reference_counter() == 1); + + /* copy ctor ran to make copy of p1, did not allocate */ + rp p2(p1); + + REQUIRE(p1->reference_counter() == 2); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + + } /*TEST_CASE(intrusive-ptr-copy)*/ + + TEST_CASE("intrusive-ptr-move", "[refcnt][move]") + { + uint32_t cc = ctor_count; + uint32_t dc = dtor_count; + + rp p1(new JustRefcount()); + JustRefcount * p1_native = p1.get(); + + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get() != nullptr); + REQUIRE(p1->reference_counter() == 1); + + rp p2{std::move(p1)}; + + REQUIRE(p2->reference_counter() == 1); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + + p2 = nullptr; + + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc + 1); + } /*TEST_CASE(intrusive-ptr-move)*/ + + TEST_CASE("instrusive-ptr-assign", "[refcnt][assign]") + { + uint32_t cc = ctor_count; + uint32_t dc = dtor_count; + + rp p1(new JustRefcount()); + JustRefcount * p1_native = p1.get(); + + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get() != nullptr); + REQUIRE(p1->reference_counter() == 1); + + rp p2; + + REQUIRE(p2.get() == nullptr); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + + p2 = p1; + + REQUIRE(p2.get() == p1.get()); + REQUIRE(p2->reference_counter() == 2); + + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + + p1 = nullptr; + + REQUIRE(p2->reference_counter() == 1); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + + p2 = nullptr; + + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc + 1); + } /*TEST_CASE(intrusive-ptr-assign)*/ + + TEST_CASE("intrusive-ptr-move-assign", "[refcnt][move-assign]") + { + uint32_t cc = ctor_count; + uint32_t dc = dtor_count; + + rp p1(new JustRefcount()); + JustRefcount * p1_native = p1.get(); + + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get() != nullptr); + REQUIRE(p1->reference_counter() == 1); + + rp p2; + + REQUIRE(p2.get() == nullptr); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + + p2 = std::move(p1); + + REQUIRE(p1.get() == nullptr); + REQUIRE(p2.get() == p1_native); + REQUIRE(p2->reference_counter() == 1); + + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + + p1 = nullptr; /*no-op*/ + + REQUIRE(p2->reference_counter() == 1); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + + p2 = nullptr; + + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc + 1); + } /*TEST_CASE(intrusive-ptr-move-assign)*/ + } /*namespace ut*/ +} /*namespace xo*/ + +/* end intrusive_ptr.test.cpp */ diff --git a/utest/refcnt_utest_main.cpp b/utest/refcnt_utest_main.cpp new file mode 100644 index 00000000..327713b7 --- /dev/null +++ b/utest/refcnt_utest_main.cpp @@ -0,0 +1,6 @@ +/* @file refcnt_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end refcnt_utest_main.cpp */ From 1bdb8bb459f6aecfd6c7eccd731ac3aeda294021 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:06:25 -0400 Subject: [PATCH 0110/2693] github actions attempt --- .github/cmake-single-platform.yml | 42 +++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/cmake-single-platform.yml diff --git a/.github/cmake-single-platform.yml b/.github/cmake-single-platform.yml new file mode 100644 index 00000000..542d220d --- /dev/null +++ b/.github/cmake-single-platform.yml @@ -0,0 +1,42 @@ +# This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml +name: CMake on a single platform + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + +# - name: Configure CMake +# # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. +# # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type +# run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + +# - name: Build +# # Build your program with the given configuration +# run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + +# - name: Test +# working-directory: ${{github.workspace}}/build +# # Execute tests defined by the CMake configuration. +# # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail +# run: ctest -C ${{env.BUILD_TYPE}} From 9161210d5237659680d7d5f8fd86debcd366329d Mon Sep 17 00:00:00 2001 From: Roland Date: Sun, 24 Sep 2023 13:09:31 -0400 Subject: [PATCH 0111/2693] Create main.yml --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1 @@ + From 572572c7bb1fb3f7c5b689519ae175fd456aeae1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:10:35 -0400 Subject: [PATCH 0112/2693] github actions, take 2 --- .github/{ => workflows}/cmake-single-platform.yml | 0 .github/workflows/main.yml | 1 - 2 files changed, 1 deletion(-) rename .github/{ => workflows}/cmake-single-platform.yml (100%) delete mode 100644 .github/workflows/main.yml diff --git a/.github/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml similarity index 100% rename from .github/cmake-single-platform.yml rename to .github/workflows/cmake-single-platform.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 8b137891..00000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1 +0,0 @@ - From 8340c0c6159f8cfda4779d7a33837789c3c08f99 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:13:55 -0400 Subject: [PATCH 0113/2693] github actions, take 3 --- .github/workflows/cmake-single-platform.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 542d220d..749f36c1 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -26,6 +26,10 @@ jobs: # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] run: sudo apt-get install -y catch2 + - name: Fetch indentlog + # fetch source tree for indentlog dependency + run: git clone git@github.com:rconybea/indentlog.git + # - name: Configure CMake # # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 464449df065ea3857ad25b122f38c14c7c3c0036 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:23:09 -0400 Subject: [PATCH 0114/2693] github actions, take 4 --- .github/workflows/cmake-single-platform.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 749f36c1..fb4ec30d 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -28,7 +28,7 @@ jobs: - name: Fetch indentlog # fetch source tree for indentlog dependency - run: git clone git@github.com:rconybea/indentlog.git + run: git clone https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com:rconybea/indentlog.git # - name: Configure CMake # # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. From 8f596d8f3d973287eaab31b390d4e8a77aa50ab5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:28:31 -0400 Subject: [PATCH 0115/2693] github actions, take 5 --- .github/workflows/cmake-single-platform.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index fb4ec30d..505cdc1c 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -26,7 +26,11 @@ jobs: # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] run: sudo apt-get install -y catch2 - - name: Fetch indentlog + - name: Clone indentlog + with: + repository: Rconybea/indentlog + path: repo/indentlog + # fetch source tree for indentlog dependency run: git clone https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com:rconybea/indentlog.git From 264da8e122ff49104aa6aa7deda3fac062e7bd4f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:29:34 -0400 Subject: [PATCH 0116/2693] bugfix: typo in .tm --- .github/workflows/cmake-single-platform.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 505cdc1c..92e5ac73 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -31,8 +31,8 @@ jobs: repository: Rconybea/indentlog path: repo/indentlog - # fetch source tree for indentlog dependency - run: git clone https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com:rconybea/indentlog.git +# # fetch source tree for indentlog dependency +# run: git clone https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com:rconybea/indentlog.git # - name: Configure CMake # # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. From a605654fb9aa64dfbf57b915fb4136fe0ff9cb25 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:30:41 -0400 Subject: [PATCH 0117/2693] bugfix: missing 'uses: ..' in .yml --- .github/workflows/cmake-single-platform.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 92e5ac73..cd9aca1a 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -27,6 +27,7 @@ jobs: run: sudo apt-get install -y catch2 - name: Clone indentlog + uses: actions/checkout@v3 with: repository: Rconybea/indentlog path: repo/indentlog From 2fc39afdcc8979e7288ee450a317d04467117b05 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:36:16 -0400 Subject: [PATCH 0118/2693] github actions, take 6 --- .github/workflows/cmake-single-platform.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index cd9aca1a..26c36193 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -32,6 +32,10 @@ jobs: repository: Rconybea/indentlog path: repo/indentlog + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + # # fetch source tree for indentlog dependency # run: git clone https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com:rconybea/indentlog.git From be3880779f1a078e879b68e8b4bbce143445642c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:37:59 -0400 Subject: [PATCH 0119/2693] github: + indentlog build action --- .github/workflows/cmake-single-platform.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 26c36193..d943af45 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -36,6 +36,9 @@ jobs: # configure cmake for indentlog in dedicated build directory. run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + # # fetch source tree for indentlog dependency # run: git clone https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com:rconybea/indentlog.git From 7cc51fc13de69c09e07358aef3072e36d421aec1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:40:07 -0400 Subject: [PATCH 0120/2693] github: install indentlog --- .github/workflows/cmake-single-platform.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index d943af45..9fceb907 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -39,6 +39,10 @@ jobs: - name: Build indentlog run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --build ${{github.workspace}}/build_indentlog install + # # fetch source tree for indentlog dependency # run: git clone https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com:rconybea/indentlog.git From 9a3b104ec01084424e35b30c9b2b6c2ed02e01d9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:42:04 -0400 Subject: [PATCH 0121/2693] bugfix: cmake --install syntax --- .github/workflows/cmake-single-platform.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 9fceb907..4f435149 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -41,7 +41,7 @@ jobs: - name: Install indentlog # install into ${{github.workspace}}/local - run: cmake --build ${{github.workspace}}/build_indentlog install + run: cmake --install ${{github.workspace}}/build_indentlog # # fetch source tree for indentlog dependency # run: git clone https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com:rconybea/indentlog.git From f305581cdf43a456257e794f909bc39aa2de477a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:47:08 -0400 Subject: [PATCH 0122/2693] github: try building refcnt, now with indentlog dep --- .github/workflows/cmake-single-platform.yml | 27 +++++++++------------ 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 4f435149..ed2bd910 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -43,20 +43,17 @@ jobs: # install into ${{github.workspace}}/local run: cmake --install ${{github.workspace}}/build_indentlog -# # fetch source tree for indentlog dependency -# run: git clone https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com:rconybea/indentlog.git + - name: Configure refcnt + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -# - name: Configure CMake -# # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. -# # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type -# run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + - name: Build refcnt + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} -# - name: Build -# # Build your program with the given configuration -# run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - -# - name: Test -# working-directory: ${{github.workspace}}/build -# # Execute tests defined by the CMake configuration. -# # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail -# run: ctest -C ${{env.BUILD_TYPE}} + - name: Test refcnt + working-directory: ${{github.workspace}}/build_recnt + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From df4420c6a5e7b8f6cd18fc4174e14c27a1cd4fb5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:49:09 -0400 Subject: [PATCH 0123/2693] github: bugfix: typo in .yml --- .github/workflows/cmake-single-platform.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index ed2bd910..5d74447d 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -53,7 +53,7 @@ jobs: run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} - name: Test refcnt - working-directory: ${{github.workspace}}/build_recnt + working-directory: ${{github.workspace}}/build_refcnt # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest -C ${{env.BUILD_TYPE}} From 34e0a8acea5ea2cfa35a35f6630d68ce1c4c4ace Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:53:38 -0400 Subject: [PATCH 0124/2693] github: streamline attempt in .yml --- .github/workflows/cmake-single-platform.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 5d74447d..29cb9534 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -34,7 +34,7 @@ jobs: - name: Configure indentlog # configure cmake for indentlog in dedicated build directory. - run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{env.LOCAL_INSTALL_PREFIX}} repo/indentlog - name: Build indentlog run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} From 4b3c854fc7872ba404ce4e0af25ecfdc47822ae1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:56:34 -0400 Subject: [PATCH 0125/2693] github: abandon streamline experiment, not sucessful --- .github/workflows/cmake-single-platform.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 29cb9534..5d74447d 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -34,7 +34,7 @@ jobs: - name: Configure indentlog # configure cmake for indentlog in dedicated build directory. - run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{env.LOCAL_INSTALL_PREFIX}} repo/indentlog + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog - name: Build indentlog run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} From 8560fc63fdda5fa208e42e3389bec454eaa8870f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 14:42:38 -0400 Subject: [PATCH 0126/2693] initial commit --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..b43d50c1 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# subsys repo From 1b363f94a08f7a0a8f24085a131fe63ec728dbf1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 15:33:23 -0400 Subject: [PATCH 0127/2693] subsys: build+install (header-only library) --- CMakeLists.txt | 93 +++++ cmake/code-coverage.cmake | 678 +++++++++++++++++++++++++++++++++++ cmake/cxx.cmake | 96 +++++ cmake/subsysConfig.cmake.in | 4 + include/subsys/Subsystem.hpp | 303 ++++++++++++++++ 5 files changed, 1174 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/code-coverage.cmake create mode 100644 cmake/cxx.cmake create mode 100644 cmake/subsysConfig.cmake.in create mode 100644 include/subsys/Subsystem.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..c63d6740 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,93 @@ +cmake_minimum_required(VERSION 3.10) + +project(subsys VERSION 0.1) +enable_language(CXX) + +include(cmake/cxx.cmake) +include(cmake/code-coverage.cmake) + +enable_testing() +# activate code coverage for all executables + libraries (when -DCODE_COVERAGE=ON) +add_code_coverage() + +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) + +set(XO_PROJECT_NAME subsys) + +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 20) +endif() + +set(CMAKE_CXX_STANDARD_REQUIRED True) + +# always write compile_commands.json +set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") + +# ---------------------------------------------------------------- +# - author's convenience: default install prefix to /home/$USER/local +# - otherwise use -DCMAKE_INSTALL_PREFIX=/path/to/somewhere + +if(NOT USER) + set(USER $ENV{USER}) +endif() + +# hmm. this works if explicitly given with cmake: +# cmake -DCMAKE_INSTALL_PREFIX=/home/roland/local path/to/source +# but not as default +if(NOT CMAKE_INSTALL_PREFIX) + set(CMAKE_INSTALL_PREFIX /home/${USER}/local CACHE STRING "install directory") +endif() +if(NOT CMAKE_INSTALL_RPATH) + set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib CACHE STRING "runpath in installed libraries/executables") +endif() + +#add_subdirectory(example) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# installing header-only library + +add_library(subsys INTERFACE) +target_include_directories(subsys INTERFACE + $ + $ +) +xo_install_library(subsys) + +# ---------------------------------------------------------------- +# cmake export +# (so this library works with cmake's find_package()) + +set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") +set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") + +include(CMakePackageConfigHelpers) +write_basic_package_version_file("${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" + VERSION 0.1 + COMPATIBILITY AnyNewerVersion +) + +configure_package_config_file( + "${PROJECT_SOURCE_DIR}/cmake/${XO_PROJECT_NAME}Config.cmake.in" + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" + INSTALL_DESTINATION lib/cmake/${XO_PROJECT_NAME} + ) + +install(EXPORT ${XO_PROJECT_NAME}Targets DESTINATION lib/cmake/${XO_PROJECT_NAME}) +install( + FILES + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" + DESTINATION lib/cmake/${XO_PROJECT_NAME}) + +# ---------------------------------------------------------------- +# install .hpp + +install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) + +# end CMakeLists.txt diff --git a/cmake/code-coverage.cmake b/cmake/code-coverage.cmake new file mode 100644 index 00000000..b6b36064 --- /dev/null +++ b/cmake/code-coverage.cmake @@ -0,0 +1,678 @@ +# +# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +# USAGE: To enable any code coverage instrumentation/targets, the single CMake +# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or +# on the command line. +# +# From this point, there are two primary methods for adding instrumentation to +# targets: +# +# 1 - A blanket instrumentation by calling `add_code_coverage()`, where +# all targets in that directory and all subdirectories are automatically +# instrumented. +# +# 2 - Per-target instrumentation by calling +# `target_code_coverage()`, where the target is given and thus only +# that target is instrumented. This applies to both libraries and executables. +# +# To add coverage targets, such as calling `make ccov` to generate the actual +# coverage information for perusal or consumption, call +# `target_code_coverage()` on an *executable* target. +# +# Example 1: All targets instrumented +# +# In this case, the coverage information reported will will be that of the +# `theLib` library target and `theExe` executable. +# +# 1a: Via global command +# +# ~~~ +# add_code_coverage() # Adds instrumentation to all targets +# +# add_library(theLib lib.cpp) +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target +# # (instrumentation already added via global anyways) +# # for generating code coverage reports. +# ~~~ +# +# 1b: Via target commands +# +# ~~~ +# add_library(theLib lib.cpp) +# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. +# ~~~ +# +# Example 2: Target instrumented, but with regex pattern of files to be excluded +# from report +# +# ~~~ +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ +# +# Example 3: Target added to the 'ccov' and 'ccov-all' targets +# +# ~~~ +# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. +# +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ + +# Options +option( + CODE_COVERAGE + "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" + OFF) + +# Programs +find_program(LLVM_COV_PATH llvm-cov) +find_program(LLVM_PROFDATA_PATH llvm-profdata) +find_program(LCOV_PATH lcov) +find_program(GENHTML_PATH genhtml) +# Hide behind the 'advanced' mode flag for GUI/ccmake +mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) + +# Variables +set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) +set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) + +# Common initialization/checks +if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) + set(CODE_COVERAGE_ADDED ON) + + # Common Targets + add_custom_target( + ccov-preprocessing + COMMAND ${CMAKE_COMMAND} -E make_directory + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} + DEPENDS ccov-clean) + + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + # Messages + message(STATUS "Building with llvm Code Coverage Tools") + + if(NOT LLVM_COV_PATH) + message(FATAL_ERROR "llvm-cov not found! Aborting.") + else() + # Version number checking for 'EXCLUDE' compatibility + execute_process(COMMAND ${LLVM_COV_PATH} --version + OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) + string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION + ${LLVM_COV_VERSION_CALL_OUTPUT}) + + if(LLVM_COV_VERSION VERSION_LESS "7.0.0") + message( + WARNING + "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" + ) + endif() + endif() + + # Targets + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + else() + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + endif() + + # Used to get the shared object file list before doing the main all- + # processing + add_custom_target( + ccov-libs + COMMAND ; + COMMENT "libs ready for coverage report.") + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + # Messages + message(STATUS "Building with lcov Code Coverage Tools") + + if(CMAKE_BUILD_TYPE) + string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) + if(NOT ${upper_build_type} STREQUAL "DEBUG") + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + else() + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() + + # Targets + add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory + ${CMAKE_BINARY_DIR} --zerocounters) + + else() + message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") + endif() +endif() + +# Adds code coverage instrumentation to a library, or instrumentation/targets +# for an executable target. +# ~~~ +# EXECUTABLE ADDED TARGETS: +# GCOV/LCOV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# +# LLVM-COV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report. +# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. +# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. +# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. +# ccov-all-export : Exports the coverage report to a JSON file. +# +# Required: +# TARGET_NAME - Name of the target to generate code coverage for. +# Optional: +# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. +# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. +# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) +# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. +# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. +# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory +# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** +# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output +# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call +# ~~~ +function(target_code_coverage TARGET_NAME) + # Argument parsing + set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) + set(single_value_keywords COVERAGE_TARGET_NAME) + set(multi_value_keywords EXCLUDE OBJECTS ARGS) + cmake_parse_arguments( + target_code_coverage "${options}" "${single_value_keywords}" + "${multi_value_keywords}" ${ARGN}) + + # Set the visibility of target functions to PUBLIC, INTERFACE or default to + # PRIVATE. + if(target_code_coverage_PUBLIC) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY PUBLIC) + elseif(target_code_coverage_INTERFACE) + set(TARGET_VISIBILITY INTERFACE) + set(TARGET_LINK_VISIBILITY INTERFACE) + elseif(target_code_coverage_PLAIN) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY) + else() + set(TARGET_VISIBILITY PRIVATE) + set(TARGET_LINK_VISIBILITY PRIVATE) + endif() + + if(NOT target_code_coverage_COVERAGE_TARGET_NAME) + # If a specific name was given, use that instead. + set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) + endif() + + if(CODE_COVERAGE) + + # Add code coverage instrumentation to the target's linker command + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs + -ftest-coverage) + target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) + endif() + + # Targets + get_target_property(target_type ${TARGET_NAME} TYPE) + + # Add shared library to processing for 'all' targets + if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" >> + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + if(NOT TARGET ccov-libs) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-libs + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # For executables add targets to run and produce output + if(target_type STREQUAL "EXECUTABLE") + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # If there are shared objects to also work with, generate the string to + # add them here + foreach(SO_TARGET ${target_code_coverage_OBJECTS}) + # Check to see if the target is a shared object + if(TARGET ${SO_TARGET}) + get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) + if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") + set(SO_OBJECTS ${SO_OBJECTS} -object=$) + endif() + endif() + endforeach() + + # Run the executable, generating raw profile data Make the run data + # available for further processing. Separated to allow Windows to run + # this target serially. + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E env + LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw + $ ${target_code_coverage_ARGS} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" + ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND + ${CMAKE_COMMAND} -E echo + "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" + >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list + JOB_POOL ccov_serial_pool + DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) + + # Merge the generated profile data so llvm-cov can process it + add_custom_target( + ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_PROFDATA_PATH} merge -sparse + ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o + ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Ignore regex only works on LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print out details of the coverage information to the command line + add_custom_target( + ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Print out a summary of the coverage information to the command line + add_custom_target( + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} report $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} export $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO + "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" + ) + + # Run the executable, generating coverage information + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND $ ${target_code_coverage_ARGS} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + # Generate exclusion string for use + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + if(NOT ${target_code_coverage_EXTERNAL}) + set(EXTERNAL_OPTION --no-external) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + else() + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + endif() + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${GENHTML_PATH} -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + ${COVERAGE_INFO} + DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + + add_custom_command( + TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." + ) + + # AUTO + if(target_code_coverage_AUTO) + if(NOT TARGET ccov) + add_custom_target(ccov) + endif() + add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) + + if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID + MATCHES "GNU") + if(NOT TARGET ccov-report) + add_custom_target(ccov-report) + endif() + add_dependencies( + ccov-report + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # ALL + if(target_code_coverage_ALL) + if(NOT TARGET ccov-all-processing) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-all-processing + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + endif() +endfunction() + +# Adds code coverage instrumentation to all targets in the current directory and +# any subdirectories. To add coverage instrumentation to only specific targets, +# use `target_code_coverage`. +function(add_code_coverage) + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_compile_options(-fprofile-instr-generate -fcoverage-mapping) + add_link_options(-fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + add_compile_options(-fprofile-arcs -ftest-coverage) + link_libraries(gcov) + endif() + endif() +endfunction() + +# Adds the 'ccov-all' type targets that calls all targets added via +# `target_code_coverage` with the `ALL` parameter, but merges all the coverage +# data from them into a single large report instead of the numerous smaller +# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for +# use with coverage dashboards (e.g. codecov.io, coveralls). +# ~~~ +# Optional: +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! +# ~~~ +function(add_code_coverage_all_targets) + # Argument parsing + set(multi_value_keywords EXCLUDE) + cmake_parse_arguments(add_code_coverage_all_targets "" "" + "${multi_value_keywords}" ${ARGN}) + + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # Merge the profile data for all of the run executables + if(WIN32) + add_custom_target( + ccov-all-processing + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe + merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -sparse $$FILELIST) + else() + add_custom_target( + ccov-all-processing + COMMAND + ${LLVM_PROFDATA_PATH} merge -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) + endif() + + # Regex exclude only available for LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print summary of the code coverage information to the command line + if(WIN32) + add_custom_target( + ccov-all-report + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe + report $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all-report + COMMAND + ${LLVM_COV_PATH} report `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-all-export + COMMAND + ${LLVM_COV_PATH} export `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json + DEPENDS ccov-all-processing) + + # Generate HTML output of all added targets for perusal + if(WIN32) + add_custom_target( + ccov-all + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show + $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all + COMMAND + ${LLVM_COV_PATH} show `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") + + # Nothing required for gcov + add_custom_target(ccov-all-processing COMMAND ;) + + # Exclusion regex string creation + set(EXCLUDE_REGEX) + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + else() + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + endif() + + # Generates HTML output of all targets for perusal + add_custom_target( + ccov-all + COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} + DEPENDS ccov-all-capture) + + endif() + + add_custom_command( + TARGET ccov-all + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." + ) + endif() +endfunction() diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake new file mode 100644 index 00000000..7fa32372 --- /dev/null +++ b/cmake/cxx.cmake @@ -0,0 +1,96 @@ +# ---------------------------------------------------------------- +# use this in subdirs that compile c++ code +# +macro(xo_include_options target) + # ---------------------------------------------------------------- + # PROJECT_SOURCE_DIR: + # so we can for example write + # #include "ordinaltree/foo.hpp" + # from anywhere in the project + # PROJECT_BINARY_DIR: + # since generated version file will be in build directory, + # need that build directory to also appear in + # compiler's include path + # + target_include_directories( + ${target} PUBLIC + $ # e.g. for #include "indentlog/scope.hpp" + $ + $ # e.g. for #include "Refcounted.hpp" in refcnt/src + $ + $ # e.g. for generated config.hpp file + ) + + # ---------------------------------------------------------------- + # make standard directories for std:: includes explicit + # so that + # (1) they appear in compile_commands.json. + # (2) clangd (run from emacs lsp-mode) can find them + # + if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) + endif() +endmacro() + +# ---------------------------------------------------------------- +# variable +# XO_ADDRESS_SANITIZE +# determines whether to enable address sanitizer for the XO project +# (see toplevel CMakeLists.txt) +# ---------------------------------------------------------------- +if(XO_ADDRESS_SANITIZE) + add_compile_options(-fsanitize=address) + add_link_options(-fsanitize=address) +endif() + +# XO_STANDARD_COMPILE_OPTIONS: use these when XO_ADDRESS_SANITIZE=OFF +set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) + +# XO_ADDRESS_SANITIZE_COMPILE_OPTIONS: use when XO_ADDRESS_SANITIZE=ON +# +# address sanitizer build complains about _FORTIFY_SOURCE redefines +# In file included from :460: +# :1:9: error: '_FORTIFY_SOURCE' macro redefined [-Werror,-Wmacro-redefined] +# #define _FORTIFY_SOURCE 2 +# +set(XO_ADDRESS_SANITIZE_COMPILE_OPTIONS -Werror -Wall -Wextra -Wno-macro-redefined) + +# XO_COMPILE_OPTIONS: use these with xo_compile_options() macro +if(XO_ADDRESS_SANITIZE) + set(XO_COMPILE_OPTIONS ${XO_ADDRESS_SANITIZE_COMPILE_OPTIONS}) +else() + set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) +endif() + +# ---------------------------------------------------------------- +# generally want all the errors+warnings! +# however: address sanitizer generates error on _FORTIFY_SOURCE +# +macro(xo_compile_options target) + target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) +endmacro() + +# ---------------------------------------------------------------- +# use this for a subdir that builds a library +# +macro(xo_install_library target) + install( + TARGETS ${target} + EXPORT ${target}Targets + LIBRARY DESTINATION lib COMPONENT Runtime + ARCHIVE DESTINATION lib COMPONENT Development + RUNTIME DESTINATION bin COMPONENT Runtime + PUBLIC_HEADER DESTINATION include COMPONENT Development + BUNDLE DESTINATION bin COMPONENT Runtime + ) +endmacro() + +# ---------------------------------------------------------------- +# use this when relying on indentlog [[https://github.com/rconybea/indentlog]] headers +# +macro(xo_indentlogg_dependency target) + find_package(indentlog REQUIRED) + #add_dependencies(${target} indentlog) + target_link_libraries(${target} PUBLIC indentlog) + #target_include_directories(${target} PUBLIC ${indentlog_DIR}/../../../include) +endmacro() diff --git a/cmake/subsysConfig.cmake.in b/cmake/subsysConfig.cmake.in new file mode 100644 index 00000000..e13a2c54 --- /dev/null +++ b/cmake/subsysConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@XO_PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/subsys/Subsystem.hpp b/include/subsys/Subsystem.hpp new file mode 100644 index 00000000..b9a41e18 --- /dev/null +++ b/include/subsys/Subsystem.hpp @@ -0,0 +1,303 @@ +/* file Subsystem.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "indentlog/scope.hpp" +#include +#include +#include +#include + +/* e.g. XO_SUBSYSTEM_TAG(simulator) => xo::S_simulator_tag */ +#define XO_SUBSYSTEM_TAG(subsys_name) xo::S_ ## subsys_name ## _tag + +/* e.g. XO_SUBSYSTEM_REQUIRE(simulator) => + * xo::InitSubsys::require() + */ +#define XO_SUBSYSTEM_REQUIRE(subsys_name) xo::InitSubsys::require(); + +/* e.g. XO_SUBSYSTEM_PROVIDE(simulator, &init) => + * xo::Subsystem::provide("simulator", &init) + */ +#define XO_SUBSYSTEM_PROVIDE(subsys_name, init_addr) xo::Subsystem::provide(STRINGIFY(subsys_name), init_addr) + +//#define VERIFY_SUBSYSTEM(tag) Subsystem::verify_present(STRINGIFY(tag)) + +namespace xo { + using xo::tostr; + + /* evidence that one or more subsystems have been initialized. + * Used to prevent static linker stripping must-run initialization code + */ + class InitEvidence { + public: + InitEvidence() = default; + InitEvidence(std::uint64_t x) : evidence_{x} {} + + std::uint64_t evidence() const { return evidence_; } + + InitEvidence operator^=(InitEvidence x) { + this->evidence_ ^= x.evidence_; + + return *this; + } /*operator^=*/ + + InitEvidence operator^(InitEvidence x) { + return InitEvidence(this->evidence_ ^ x.evidence_); + } + + private: + /* we don't care about the specific value computed here, + * purpose is to be sufficiently impenentrable to compiler such + * that static linker can't optimize it away + */ + std::uint64_t evidence_ = 0; + }; /*InitEvidence*/ + + /* Goals: + * 1. provide for code that must run once (and only once) + * to initialize subsystems + * 2. in executable, want to run such code after main() starts + * instead of relying on static initializers; + * that way init behavior can be parameterized based on + * program arguments + * + * Use + * // subsystem foo + * + * enum Foo_tag {}; + * + * // guarantees that if anything gets initialized, then + * // foo_init() is included + * // + * template<> + * struct InitSubsys { + * static void foo_init() { ... } + * + * static InitEvidence require() { + * return Subsystem::require("foo", &foo_init); + * } + * }; + * + * .. register other subsystems .. + * + * Subsystem::initialize_all(); // foo_init() has been called once + * + * If subsystem bar depends on supporting subsystem {foo, quux}, then write: + * + * enum Bar_tag {}; + * + * template<> + * struct InitSubsys { + * static void bar_init() { ... } + * + * static InitEvidence require() { + * InitEvidence retval; + * + * retval ^= InitSubsys::require(); + * retval ^= InitSubsys::require(); + * + * retval ^= Subsystem::require("bar", &bar_init); + * } + * }; + * + * If using subsystems from a shared library (so no access to cmdline args etc): + * e.g. in pyfoo.cpp: + * + * InitEvidence s_pyfoo_init = InitSubsys::require(); + * or + * InitEvidence s_pyfoo_init = (InitSubsys::require() + * ^ InitSubsys::require()); + * + * Note: Tag argument here no relation of BuildTag in SubsystemImpl below + */ + template + struct InitSubsys {}; + + /* BuildTag: placeholder; insisting on header-only library */ + template + class SubsystemImpl { + public: + SubsystemImpl() = default; + SubsystemImpl(bool require_flag, + std::string_view subsys_name, + std::function init_fn) + : require_flag_{require_flag}, + subsys_name_{subsys_name}, + init_fn_{init_fn} {} + + /* establish an empty Subsystem record for subsys_name. + * record is _not_ linked into s_subsys_l! + * idempotent. + */ + template + static SubsystemImpl * establish() { + static SubsystemImpl s_subsys; + + return &s_subsys; + } /*establish*/ + + template + static bool verify_present(std::string subsys_tag) { + SubsystemImpl * subsys = establish(); + + if (!subsys->require_flag()) { + throw std::runtime_error(tostr("subsystem not present." + "(missing InitSubsys<", subsys_tag, ">::require() ?)")); + return false; + } + + return true; + } /*verify_present*/ + + /* provide (once only) initialization code for a subsystem with tag SubsystemTag. + * ideally this would be called just once for a particular tag; + * if called multiple times, calls after the first are no-ops. + */ + template + static InitEvidence provide(std::string_view subsys_name, + std::function init_fn) { + SubsystemImpl * subsys = establish(); + + provide_aux(subsys_name, init_fn, subsys); + + return InitEvidence(reinterpret_cast(subsys)); + } /*provide*/ + + /* throw exception if there's anything left for .initialize_all() to do, + * or subsystems have been added since last call to .initialize_all() + * Can use this to remind application author to call SubsystemImpl::initialize_all() + */ + static bool verify_all_initialized(); + + /* 1. initialize all subsystems: promise that for every preceding call + * to .require(), the corresponding initialization function has been + * run exactly once. + * 2. harmless to call this multiple times -- will not call any init_fn more than once + * 3. can interleave .initialize_all() with .require() as desired + */ + static InitEvidence initialize_all(); + + bool require_flag() const { return require_flag_; } + bool init_flag() const { return init_flag_; } + std::string_view subsys_name() const { return subsys_name_; } + + InitEvidence initialize(); + + private: + /* helper for .provide() */ + static void provide_aux(std::string_view subsys_name, + std::function init_fn, + SubsystemImpl * p_subsys); + + private: + /* set to true iff .s_subsys_l has been extended since last call to .initialize_all() */ + static bool s_dirty_flag; + /* one member for each unique call to .require() */ + static std::list s_subsys_l; + + private: + /* set to true on 1st call to .require() */ + bool require_flag_ = false; + /* set to true when .init_fn() invoked */ + bool init_flag_ = false; + /* unique subsystem name */ + std::string_view subsys_name_; + /* call this function once (at most) to initialize this subsystem */ + std::function init_fn_; + }; /*SubsystemImpl*/ + + template + bool + SubsystemImpl::s_dirty_flag = false; + + template + std::list *> + SubsystemImpl::s_subsys_l; + + template + void + SubsystemImpl::provide_aux(std::string_view subsys_name, + std::function init_fn, + SubsystemImpl * p_subsys) + { + if (!p_subsys->require_flag()) { + /* 1st call to .provide() for this SubsystemTag */ + + using xo::scope; + using xo::xtag; + + scope log(XO_ENTER0(chatty), + xtag("subsys", subsys_name), + xtag("address", p_subsys)); + + *p_subsys = SubsystemImpl(true /*require_flag*/, + subsys_name, + init_fn); + + s_dirty_flag = true; + s_subsys_l.push_back(p_subsys); + } + } /*provide_aux*/ + + template + bool + SubsystemImpl::verify_all_initialized() + { + if (s_dirty_flag) { + scope log(XO_ENTER0(error), "required subsystems NOT initialized!?"); + + for (SubsystemImpl * subsys : s_subsys_l) { + if (!subsys->init_flag()) { + log && log("missing InitSubsyssubsys_name(), "_tag>::require()"); + } + } + + throw std::runtime_error("Subsystem::verify_initialized:" + " Subsystem::initialize_all() never called, or out-of-date"); + return false; + } + + return true; + } /*verify_all_initialized*/ + + template + InitEvidence + SubsystemImpl::initialize_all() { + scope log(XO_ENTER0(chatty)); + + InitEvidence retval; + + if (s_dirty_flag) { + for (SubsystemImpl * subsys : s_subsys_l) { + log && log("init", xtag("subsys", subsys->subsys_name())); + + retval ^= subsys->initialize(); + } + } + + s_dirty_flag = false; + + return retval; + } /*initialize_all*/ + + template + InitEvidence + SubsystemImpl::initialize() + { + if (!init_flag_) { + init_flag_ = true; + + init_fn_(); + } + + return InitEvidence(reinterpret_cast(this)); + } /*initialize*/ + + using Subsystem = SubsystemImpl; +} /*namespace xo*/ + +/* end Subsystem.hpp */ From 539917e24c2c3985b89594947bc033b3df30848b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 15:34:08 -0400 Subject: [PATCH 0128/2693] subsys: README --- README.md | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b43d50c1..b7701781 100644 --- a/README.md +++ b/README.md @@ -1 +1,101 @@ -# subsys repo +# plugin initialization support + +subsys is a small header-only library providing support for plugin initialization + +## Features + +- provide application control of initialization order across c++ libraries +- circumvents the 'static order initialization fiasco' +- ensure initialization code runs exactly once if subsystem is linked +- enforce initialization order constraints +- defend against static linker stripping essential initialization code +- designed to work cleanly for libraries integrating into existing executable like python, java runtime, .. +- initialization state browseable at runtime + +## Getting Started + +### build + install `indentlog` dependency + +see [github/rconybea/indentlog](https://github.com/Rconybea/indentlog) + +### copy `subsys` repository locally +``` +$ git clone git@github.com:rconybea/subsys.git +$ ls -d subsys +subsys +``` + +### build + install +``` +$ cd subsys +$ mkdir build +$ cd build +$ cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` + +`CMAKE_PREFIX_PATH` should point to prefix where `indentlog` is installed + +alternatively, if you're a nix user: +``` +$ git clone git@github.com:rconybea/xo-nix.git +$ ls -d xo-nix +xo-nix +$ cd xo-nix +$ nix-build -A subsys +``` + +### build for unit test coverage +``` +$ cd subsys +$ mkdir ccov +$ cd ccov +$ cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + +## Examples + +### 1 +``` +// initialization code in .hpp for a subsystem foo, that relies on related subsystem bar + +#include "subsys/Subsystem.hpp" + +enum S_foo_tag {}; /* tag to represent initialization of subsystem foo */ + +template<> +struct InitSubsys { + static void init() { + // plugin initialization for subsystem foo + } + + static InitEvidence require() { + InitEvidence retval; + + // demand initialization of dependent subsystem bar, + // before initialization subsystem foo + // + retval ^= InitSubsys::require(); + + // initialization of this subsystem foo + retval ^= Subsystem::provide("foo", &init); + + return retval; + } +}; +``` + +``` +// in application code that relies on foo (perhaps along with other subsystems), +// for example in a pybind11 module: +// +PYBIND11_MODULE(pyfoo, m) { + // include foo in initialization set + InitSubsys::require(); + // ensure foo + dependencies are initialized + Subsystem::initialize_all(); + + ... +} +``` From 1c384e1ff0a01885bdf0713be858085814b6ece6 Mon Sep 17 00:00:00 2001 From: Roland Date: Sun, 24 Sep 2023 15:36:06 -0400 Subject: [PATCH 0129/2693] Create cmake-single-platform.yml --- .github/workflows/cmake-single-platform.yml | 39 +++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/cmake-single-platform.yml diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml new file mode 100644 index 00000000..28c6f783 --- /dev/null +++ b/.github/workflows/cmake-single-platform.yml @@ -0,0 +1,39 @@ +# This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml +name: CMake on a single platform + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Test + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} + From a29faaa1ae1bdebded9b594f7d91cf88ce147aad Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 17:31:32 -0400 Subject: [PATCH 0130/2693] initial commit --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..27fe0373 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# reflection library From 064659e17945a63ac3753f4020dcc82c184144bb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 19:41:40 -0400 Subject: [PATCH 0131/2693] + submodule indentlog --- .gitmodules | 6 ++++++ repo/indentlog | 1 + 2 files changed, 7 insertions(+) create mode 100644 .gitmodules create mode 160000 repo/indentlog diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..698a44d5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "repo/indentlog"] + path = repo/indentlog + url = git@github.com:Rconybea/indentlog.git +[submodule "repo/refcnt/refcnt"] + path = repo/refcnt/refcnt + url = git@github.com:Rconybea/refcnt.git diff --git a/repo/indentlog b/repo/indentlog new file mode 160000 index 00000000..bf92724a --- /dev/null +++ b/repo/indentlog @@ -0,0 +1 @@ +Subproject commit bf92724aa5d123b6acb9b00318e579828fb9ff38 From 2e63293139be22e487ef82a6f20fefcf1bdd6608 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 25 Sep 2023 17:47:13 -0400 Subject: [PATCH 0132/2693] + git submodules {subsys, refcnt} --- .gitmodules | 11 +++++++---- repo/refcnt | 1 + repo/subsys | 1 + 3 files changed, 9 insertions(+), 4 deletions(-) create mode 160000 repo/refcnt create mode 160000 repo/subsys diff --git a/.gitmodules b/.gitmodules index 698a44d5..ebcf3a27 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,9 @@ [submodule "repo/indentlog"] - path = repo/indentlog - url = git@github.com:Rconybea/indentlog.git -[submodule "repo/refcnt/refcnt"] - path = repo/refcnt/refcnt + path = repo/indentlog + url = git@github.com:Rconybea/indentlog.git +[submodule "repo/refcnt"] + path = repo/refcnt url = git@github.com:Rconybea/refcnt.git +[submodule "repo/subsys"] + path = repo/subsys + url = git@github.com:Rconybea/subsys.git diff --git a/repo/refcnt b/repo/refcnt new file mode 160000 index 00000000..4b3c854f --- /dev/null +++ b/repo/refcnt @@ -0,0 +1 @@ +Subproject commit 4b3c854fc7872ba404ce4e0af25ecfdc47822ae1 diff --git a/repo/subsys b/repo/subsys new file mode 160000 index 00000000..1c384e1f --- /dev/null +++ b/repo/subsys @@ -0,0 +1 @@ +Subproject commit 1c384e1ff0a01885bdf0713be858085814b6ece6 From fdb4ca37f44105d8062f31a3d1dcffeaaf3c0efc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 25 Sep 2023 17:49:42 -0400 Subject: [PATCH 0133/2693] reflect: initial implementation --- CMakeLists.txt | 184 +++++++ FILESYSTEM | 1 + cmake/code-coverage.cmake | 678 ++++++++++++++++++++++++ cmake/cxx.cmake | 98 ++++ cmake/reflectConfig.cmake.in | 4 + cmake/run-external-ctest | 8 + include/reflect/CMakeLists.txt | 32 ++ include/reflect/EstablishTypeDescr.hpp | 62 +++ include/reflect/Metatype.hpp | 38 ++ include/reflect/Reflect.hpp | 235 ++++++++ include/reflect/SelfTagging.hpp | 31 ++ include/reflect/StructReflector.hpp | 161 ++++++ include/reflect/TaggedPtr.hpp | 125 +++++ include/reflect/TaggedRcptr.hpp | 88 +++ include/reflect/TypeDescr.hpp | 302 +++++++++++ include/reflect/TypeDescrExtra.hpp | 65 +++ include/reflect/TypeDrivenMap.hpp | 47 ++ include/reflect/atomic/AtomicTdx.hpp | 37 ++ include/reflect/init_reflect.hpp | 22 + include/reflect/pointer/PointerTdx.hpp | 76 +++ include/reflect/struct/StructMember.hpp | 236 +++++++++ include/reflect/struct/StructTdx.hpp | 94 ++++ include/reflect/vector/VectorTdx.hpp | 100 ++++ repo/README.md | 5 + src/reflect/CMakeLists.txt | 52 ++ src/reflect/TaggedRcptr.cpp | 30 ++ src/reflect/TypeDescr.cpp | 194 +++++++ src/reflect/TypeDescrExtra.cpp | 37 ++ src/reflect/atomic/AtomicTdx.cpp | 24 + src/reflect/init_reflect.cpp | 23 + src/reflect/pointer/PointerTdx.cpp | 17 + src/reflect/struct/StructMember.cpp | 36 ++ src/reflect/struct/StructTdx.cpp | 55 ++ src/reflect/vector/VectorTdx.cpp | 20 + utest/CMakeLists.txt | 57 ++ utest/StructReflector.test.cpp | 142 +++++ utest/StructTdx.test.cpp | 59 +++ utest/VectorTdx.test.cpp | 181 +++++++ utest/reflect_utest_main.cpp | 6 + 39 files changed, 3662 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 FILESYSTEM create mode 100644 cmake/code-coverage.cmake create mode 100644 cmake/cxx.cmake create mode 100644 cmake/reflectConfig.cmake.in create mode 100755 cmake/run-external-ctest create mode 100644 include/reflect/CMakeLists.txt create mode 100644 include/reflect/EstablishTypeDescr.hpp create mode 100644 include/reflect/Metatype.hpp create mode 100644 include/reflect/Reflect.hpp create mode 100644 include/reflect/SelfTagging.hpp create mode 100644 include/reflect/StructReflector.hpp create mode 100644 include/reflect/TaggedPtr.hpp create mode 100644 include/reflect/TaggedRcptr.hpp create mode 100644 include/reflect/TypeDescr.hpp create mode 100644 include/reflect/TypeDescrExtra.hpp create mode 100644 include/reflect/TypeDrivenMap.hpp create mode 100644 include/reflect/atomic/AtomicTdx.hpp create mode 100644 include/reflect/init_reflect.hpp create mode 100644 include/reflect/pointer/PointerTdx.hpp create mode 100644 include/reflect/struct/StructMember.hpp create mode 100644 include/reflect/struct/StructTdx.hpp create mode 100644 include/reflect/vector/VectorTdx.hpp create mode 100644 repo/README.md create mode 100644 src/reflect/CMakeLists.txt create mode 100644 src/reflect/TaggedRcptr.cpp create mode 100644 src/reflect/TypeDescr.cpp create mode 100644 src/reflect/TypeDescrExtra.cpp create mode 100644 src/reflect/atomic/AtomicTdx.cpp create mode 100644 src/reflect/init_reflect.cpp create mode 100644 src/reflect/pointer/PointerTdx.cpp create mode 100644 src/reflect/struct/StructMember.cpp create mode 100644 src/reflect/struct/StructTdx.cpp create mode 100644 src/reflect/vector/VectorTdx.cpp create mode 100644 utest/CMakeLists.txt create mode 100644 utest/StructReflector.test.cpp create mode 100644 utest/StructTdx.test.cpp create mode 100644 utest/VectorTdx.test.cpp create mode 100644 utest/reflect_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..4f777c59 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,184 @@ +# reflect/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(reflect VERSION 0.1) +enable_language(CXX) + +include(cmake/cxx.cmake) +include(cmake/code-coverage.cmake) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() +# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) +add_code_coverage() +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) + +# ---------------------------------------------------------------- +# c++ settings + +set(XO_PROJECT_NAME reflect) +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") + +add_definitions(${PROJECT_CXX_FLAGS}) + +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 20) +endif() + +set(CMAKE_CXX_STANDARD_REQUIRED True) + +# always write compile_commands.json +set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") + +# ---------------------------------------------------------------- +# external projects (need these to exist before add_subdirectory() below) +# +# we are expecting these projects to coexist peacefully in build/local +# (i.e. can run their `make install` steps independently with prefix build/local, +# without any collisions) +# + +include(ExternalProject) + +## ----- indentlog ------ + +# NOTE: we could have cmake handle git interaction, +# but we want source for certain dependencies to live in a location +# that's suitable for accepting changes + coordinated commits. +# In particular, not in the build directory! +# +externalproject_add( + project_indentlog + SOURCE_DIR ${PROJECT_SOURCE_DIR}/repo/indentlog + BINARY_DIR ${PROJECT_BINARY_DIR}/ext/indentlog + INSTALL_DIR ${PROJECT_BINARY_DIR}/local + CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCODE_COVERAGE=${CODE_COVERAGE} -DCMAKE_PREFIX_PATH= -DCMAKE_INSTALL_PREFIX= + BUILD_COMMAND make + INSTALL_COMMAND make install + TEST_BEFORE_INSTALL True +) + +add_library(indentlog INTERFACE IMPORTED) +#set_property(TARGET indentlog PROPERTY IMPORTED_LOCATION ${PROJECT_BINARY_DIR}/local/lib/libindentlog.so) +add_dependencies(indentlog project_indentlog) + +# runs ctest in indentlog build dir +add_test(NAME indentlog COMMAND ${PROJECT_SOURCE_DIR}/cmake/run-external-ctest ${PROJECT_BINARY_DIR}/ext/indentlog) +#target_code_coverage(indentlog EXTERNAL AUTO ALL) + +# ----- subsys ----- + +externalproject_add( + project_subsys + SOURCE_DIR ${PROJECT_SOURCE_DIR}/repo/subsys + BINARY_DIR ${PROJECT_BINARY_DIR}/ext/subsys + INSTALL_DIR ${PROJECT_BINARY_DIR}/local + CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCODE_COVERAGE=${CODE_COVERAGE} -DCMAKE_PREFIX_PATH= -DCMAKE_INSTALL_PREFIX= + BUILD_COMMAND make + INSTALL_COMMAND make install + TEST_BEFORE_INSTALL True +) + +add_library(subsys INTERFACE IMPORTED) +add_dependencies(subsys project_subsys) + +# runs ctest in subsys build dir +add_test(NAME subsys COMMAND ${PROJECT_SOURCE_DIR}/cmake/run-external-ctest ${PROJECT_BINARY_DIR}/ext/subsys) + +# ----- refcnt ----- + +# CMAKE_ARGS +# CMAKE_BUILD_TYPE propagate Debug/Release build type +# CODE_COVERAGE propagate code coverage setting +# CMAKE_PREFIX_PATH path for support cmake files of dependencies (needed for find_package() to work) +# CMAKE_INSTALL_PREFIX install subproject here +# SOURCE_DIR -- where to find already established source code +# BINARY_DIR -- run build for external project here +# INSTALL_DIR -- (temporarily) install external project here +# +externalproject_add( + project_refcnt + SOURCE_DIR ${PROJECT_SOURCE_DIR}/repo/refcnt + BINARY_DIR ${PROJECT_BINARY_DIR}/ext/refcnt + INSTALL_DIR ${PROJECT_BINARY_DIR}/local + CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCODE_COVERAGE=${CODE_COVERAGE} -DCMAKE_PREFIX_PATH= -DCMAKE_INSTALL_PREFIX= + BUILD_COMMAND make + INSTALL_COMMAND make install + TEST_BEFORE_INSTALL True +) + +add_library(refcnt SHARED IMPORTED) +set_property(TARGET refcnt PROPERTY IMPORTED_LOCATION ${PROJECT_BINARY_DIR}/local/lib/librefcnt.so) +add_dependencies(refcnt project_refcnt) +add_dependencies(refcnt project_indentlog) + +# runs ctest in refcnt build dir +add_test(NAME refcnt COMMAND ${PROJECT_SOURCE_DIR}/cmake/run-external-ctest ${PROJECT_BINARY_DIR}/ext/refcnt) + +# ---------------------------------------------------------------- +# sources + +add_subdirectory(src/reflect) +add_subdirectory(utest) + +# ---------------------------------------------------------------- +# cmake export: +# +# populate .cmake files in $CMAKE_INSTALL_LIBDIR/cmake/reflect. +# cmake projects that include this directory in $CMAKE_PREFIX_PATH +# can use +# find_package(reflect REQUIRED) +# and +# target_link_libraries(${sometarget} PUBLIC reflect) +# to use the reflect library + +set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") +set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") + +include(CMakePackageConfigHelpers) + +# generates build/reflectConfigVersion.cmake +write_basic_package_version_file( + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" + VERSION 0.1 + COMPATIBILITY AnyNewerVersion +) + +# generates build/reflectConfig.cmake +configure_package_config_file( + "${PROJECT_SOURCE_DIR}/cmake/${XO_PROJECT_NAME}Config.cmake.in" + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" + INSTALL_DESTINATION lib/cmake/${XO_PROJECT_NAME} +) + +# creates {reflectTargets.cmake, reflectTargets-noconfig.cmake} in $CMAKE_INSTALL_LIBDIR/cmake/reflect/ +# requires +# install(.. EXPORT reflectTargets ..) +# +install( + EXPORT ${XO_PROJECT_NAME}Targets + DESTINATION lib/cmake/${XO_PROJECT_NAME} +) + +# creates {reflectConfigVersion.cmake, reflectConfig.cmake} in $CMAKE_INSTALL_LIBDIR/cmake/reflect/ +install( + FILES + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" + DESTINATION lib/cmake/${XO_PROJECT_NAME}) + +# ---------------------------------------------------------------- +# install .hpp files + +install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/reflect/ DESTINATION include/reflect) diff --git a/FILESYSTEM b/FILESYSTEM new file mode 100644 index 00000000..9af86a7a --- /dev/null +++ b/FILESYSTEM @@ -0,0 +1 @@ +repo -- git submoduules here diff --git a/cmake/code-coverage.cmake b/cmake/code-coverage.cmake new file mode 100644 index 00000000..b6b36064 --- /dev/null +++ b/cmake/code-coverage.cmake @@ -0,0 +1,678 @@ +# +# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +# USAGE: To enable any code coverage instrumentation/targets, the single CMake +# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or +# on the command line. +# +# From this point, there are two primary methods for adding instrumentation to +# targets: +# +# 1 - A blanket instrumentation by calling `add_code_coverage()`, where +# all targets in that directory and all subdirectories are automatically +# instrumented. +# +# 2 - Per-target instrumentation by calling +# `target_code_coverage()`, where the target is given and thus only +# that target is instrumented. This applies to both libraries and executables. +# +# To add coverage targets, such as calling `make ccov` to generate the actual +# coverage information for perusal or consumption, call +# `target_code_coverage()` on an *executable* target. +# +# Example 1: All targets instrumented +# +# In this case, the coverage information reported will will be that of the +# `theLib` library target and `theExe` executable. +# +# 1a: Via global command +# +# ~~~ +# add_code_coverage() # Adds instrumentation to all targets +# +# add_library(theLib lib.cpp) +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target +# # (instrumentation already added via global anyways) +# # for generating code coverage reports. +# ~~~ +# +# 1b: Via target commands +# +# ~~~ +# add_library(theLib lib.cpp) +# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. +# ~~~ +# +# Example 2: Target instrumented, but with regex pattern of files to be excluded +# from report +# +# ~~~ +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ +# +# Example 3: Target added to the 'ccov' and 'ccov-all' targets +# +# ~~~ +# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. +# +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ + +# Options +option( + CODE_COVERAGE + "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" + OFF) + +# Programs +find_program(LLVM_COV_PATH llvm-cov) +find_program(LLVM_PROFDATA_PATH llvm-profdata) +find_program(LCOV_PATH lcov) +find_program(GENHTML_PATH genhtml) +# Hide behind the 'advanced' mode flag for GUI/ccmake +mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) + +# Variables +set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) +set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) + +# Common initialization/checks +if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) + set(CODE_COVERAGE_ADDED ON) + + # Common Targets + add_custom_target( + ccov-preprocessing + COMMAND ${CMAKE_COMMAND} -E make_directory + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} + DEPENDS ccov-clean) + + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + # Messages + message(STATUS "Building with llvm Code Coverage Tools") + + if(NOT LLVM_COV_PATH) + message(FATAL_ERROR "llvm-cov not found! Aborting.") + else() + # Version number checking for 'EXCLUDE' compatibility + execute_process(COMMAND ${LLVM_COV_PATH} --version + OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) + string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION + ${LLVM_COV_VERSION_CALL_OUTPUT}) + + if(LLVM_COV_VERSION VERSION_LESS "7.0.0") + message( + WARNING + "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" + ) + endif() + endif() + + # Targets + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + else() + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + endif() + + # Used to get the shared object file list before doing the main all- + # processing + add_custom_target( + ccov-libs + COMMAND ; + COMMENT "libs ready for coverage report.") + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + # Messages + message(STATUS "Building with lcov Code Coverage Tools") + + if(CMAKE_BUILD_TYPE) + string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) + if(NOT ${upper_build_type} STREQUAL "DEBUG") + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + else() + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() + + # Targets + add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory + ${CMAKE_BINARY_DIR} --zerocounters) + + else() + message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") + endif() +endif() + +# Adds code coverage instrumentation to a library, or instrumentation/targets +# for an executable target. +# ~~~ +# EXECUTABLE ADDED TARGETS: +# GCOV/LCOV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# +# LLVM-COV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report. +# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. +# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. +# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. +# ccov-all-export : Exports the coverage report to a JSON file. +# +# Required: +# TARGET_NAME - Name of the target to generate code coverage for. +# Optional: +# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. +# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. +# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) +# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. +# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. +# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory +# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** +# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output +# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call +# ~~~ +function(target_code_coverage TARGET_NAME) + # Argument parsing + set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) + set(single_value_keywords COVERAGE_TARGET_NAME) + set(multi_value_keywords EXCLUDE OBJECTS ARGS) + cmake_parse_arguments( + target_code_coverage "${options}" "${single_value_keywords}" + "${multi_value_keywords}" ${ARGN}) + + # Set the visibility of target functions to PUBLIC, INTERFACE or default to + # PRIVATE. + if(target_code_coverage_PUBLIC) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY PUBLIC) + elseif(target_code_coverage_INTERFACE) + set(TARGET_VISIBILITY INTERFACE) + set(TARGET_LINK_VISIBILITY INTERFACE) + elseif(target_code_coverage_PLAIN) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY) + else() + set(TARGET_VISIBILITY PRIVATE) + set(TARGET_LINK_VISIBILITY PRIVATE) + endif() + + if(NOT target_code_coverage_COVERAGE_TARGET_NAME) + # If a specific name was given, use that instead. + set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) + endif() + + if(CODE_COVERAGE) + + # Add code coverage instrumentation to the target's linker command + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs + -ftest-coverage) + target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) + endif() + + # Targets + get_target_property(target_type ${TARGET_NAME} TYPE) + + # Add shared library to processing for 'all' targets + if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" >> + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + if(NOT TARGET ccov-libs) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-libs + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # For executables add targets to run and produce output + if(target_type STREQUAL "EXECUTABLE") + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # If there are shared objects to also work with, generate the string to + # add them here + foreach(SO_TARGET ${target_code_coverage_OBJECTS}) + # Check to see if the target is a shared object + if(TARGET ${SO_TARGET}) + get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) + if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") + set(SO_OBJECTS ${SO_OBJECTS} -object=$) + endif() + endif() + endforeach() + + # Run the executable, generating raw profile data Make the run data + # available for further processing. Separated to allow Windows to run + # this target serially. + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E env + LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw + $ ${target_code_coverage_ARGS} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" + ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND + ${CMAKE_COMMAND} -E echo + "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" + >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list + JOB_POOL ccov_serial_pool + DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) + + # Merge the generated profile data so llvm-cov can process it + add_custom_target( + ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_PROFDATA_PATH} merge -sparse + ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o + ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Ignore regex only works on LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print out details of the coverage information to the command line + add_custom_target( + ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Print out a summary of the coverage information to the command line + add_custom_target( + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} report $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} export $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO + "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" + ) + + # Run the executable, generating coverage information + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND $ ${target_code_coverage_ARGS} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + # Generate exclusion string for use + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + if(NOT ${target_code_coverage_EXTERNAL}) + set(EXTERNAL_OPTION --no-external) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + else() + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + endif() + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${GENHTML_PATH} -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + ${COVERAGE_INFO} + DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + + add_custom_command( + TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." + ) + + # AUTO + if(target_code_coverage_AUTO) + if(NOT TARGET ccov) + add_custom_target(ccov) + endif() + add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) + + if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID + MATCHES "GNU") + if(NOT TARGET ccov-report) + add_custom_target(ccov-report) + endif() + add_dependencies( + ccov-report + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # ALL + if(target_code_coverage_ALL) + if(NOT TARGET ccov-all-processing) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-all-processing + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + endif() +endfunction() + +# Adds code coverage instrumentation to all targets in the current directory and +# any subdirectories. To add coverage instrumentation to only specific targets, +# use `target_code_coverage`. +function(add_code_coverage) + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_compile_options(-fprofile-instr-generate -fcoverage-mapping) + add_link_options(-fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + add_compile_options(-fprofile-arcs -ftest-coverage) + link_libraries(gcov) + endif() + endif() +endfunction() + +# Adds the 'ccov-all' type targets that calls all targets added via +# `target_code_coverage` with the `ALL` parameter, but merges all the coverage +# data from them into a single large report instead of the numerous smaller +# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for +# use with coverage dashboards (e.g. codecov.io, coveralls). +# ~~~ +# Optional: +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! +# ~~~ +function(add_code_coverage_all_targets) + # Argument parsing + set(multi_value_keywords EXCLUDE) + cmake_parse_arguments(add_code_coverage_all_targets "" "" + "${multi_value_keywords}" ${ARGN}) + + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # Merge the profile data for all of the run executables + if(WIN32) + add_custom_target( + ccov-all-processing + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe + merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -sparse $$FILELIST) + else() + add_custom_target( + ccov-all-processing + COMMAND + ${LLVM_PROFDATA_PATH} merge -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) + endif() + + # Regex exclude only available for LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print summary of the code coverage information to the command line + if(WIN32) + add_custom_target( + ccov-all-report + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe + report $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all-report + COMMAND + ${LLVM_COV_PATH} report `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-all-export + COMMAND + ${LLVM_COV_PATH} export `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json + DEPENDS ccov-all-processing) + + # Generate HTML output of all added targets for perusal + if(WIN32) + add_custom_target( + ccov-all + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show + $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all + COMMAND + ${LLVM_COV_PATH} show `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") + + # Nothing required for gcov + add_custom_target(ccov-all-processing COMMAND ;) + + # Exclusion regex string creation + set(EXCLUDE_REGEX) + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + else() + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + endif() + + # Generates HTML output of all targets for perusal + add_custom_target( + ccov-all + COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} + DEPENDS ccov-all-capture) + + endif() + + add_custom_command( + TARGET ccov-all + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." + ) + endif() +endfunction() diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake new file mode 100644 index 00000000..fa6efb89 --- /dev/null +++ b/cmake/cxx.cmake @@ -0,0 +1,98 @@ +# ---------------------------------------------------------------- +# use this in subdirs that compile c++ code +# +macro(xo_include_options target) + # ---------------------------------------------------------------- + # PROJECT_SOURCE_DIR: + # so we can for example write + # #include "ordinaltree/foo.hpp" + # from anywhere in the project + # PROJECT_BINARY_DIR: + # since generated version file will be in build directory, + # need that build directory to also appear in + # compiler's include path + # + target_include_directories( + ${target} PUBLIC + $ # e.g. for #include "indentlog/scope.hpp" + $ + $ # e.g. for #include "Refcounted.hpp" in refcnt/src + $ + $ # e.g. for generated config.hpp file + ) + + # ---------------------------------------------------------------- + # make standard directories for std:: includes explicit + # so that + # (1) they appear in compile_commands.json. + # (2) clangd (run from emacs lsp-mode) can find them + # + if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) + endif() +endmacro() + +# ---------------------------------------------------------------- +# variable +# XO_ADDRESS_SANITIZE +# determines whether to enable address sanitizer for the XO project +# (see toplevel CMakeLists.txt) +# ---------------------------------------------------------------- +if(XO_ADDRESS_SANITIZE) + add_compile_options(-fsanitize=address) + add_link_options(-fsanitize=address) +endif() + +# XO_STANDARD_COMPILE_OPTIONS: use these when XO_ADDRESS_SANITIZE=OFF +set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) + +# XO_ADDRESS_SANITIZE_COMPILE_OPTIONS: use when XO_ADDRESS_SANITIZE=ON +# +# address sanitizer build complains about _FORTIFY_SOURCE redefines +# In file included from :460: +# :1:9: error: '_FORTIFY_SOURCE' macro redefined [-Werror,-Wmacro-redefined] +# #define _FORTIFY_SOURCE 2 +# +set(XO_ADDRESS_SANITIZE_COMPILE_OPTIONS -Werror -Wall -Wextra -Wno-macro-redefined) + +# XO_COMPILE_OPTIONS: use these with xo_compile_options() macro +if(XO_ADDRESS_SANITIZE) + set(XO_COMPILE_OPTIONS ${XO_ADDRESS_SANITIZE_COMPILE_OPTIONS}) +else() + set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) +endif() + +# ---------------------------------------------------------------- +# generally want all the errors+warnings! +# however: address sanitizer generates error on _FORTIFY_SOURCE +# +macro(xo_compile_options target) + target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) +endmacro() + +# ---------------------------------------------------------------- +# use this for a subdir that builds a library +# EXPORT drives .cmake config files intended for consumption +# by higher-level cmake projects via find_package() +# +macro(xo_install_library target) + install( + TARGETS ${target} + EXPORT ${target}Targets + LIBRARY DESTINATION lib COMPONENT Runtime + ARCHIVE DESTINATION lib COMPONENT Development + RUNTIME DESTINATION bin COMPONENT Runtime + PUBLIC_HEADER DESTINATION include COMPONENT Development + BUNDLE DESTINATION bin COMPONENT Runtime + ) +endmacro() + +# ---------------------------------------------------------------- +# use this when relying on indentlog [[https://github.com/rconybea/indentlog]] headers +# +macro(xo_indentlog_dependency target) + find_package(indentlog REQUIRED) + #add_dependencies(${target} indentlog) + target_link_libraries(${target} PUBLIC indentlog) + #target_include_directories(${target} PUBLIC ${indentlog_DIR}/../../../include) +endmacro() diff --git a/cmake/reflectConfig.cmake.in b/cmake/reflectConfig.cmake.in new file mode 100644 index 00000000..e13a2c54 --- /dev/null +++ b/cmake/reflectConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@XO_PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/cmake/run-external-ctest b/cmake/run-external-ctest new file mode 100755 index 00000000..386c45a4 --- /dev/null +++ b/cmake/run-external-ctest @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +# $1 = build directory + +cd $1 +shift + +ctest "${@}" diff --git a/include/reflect/CMakeLists.txt b/include/reflect/CMakeLists.txt new file mode 100644 index 00000000..a7e07dd6 --- /dev/null +++ b/include/reflect/CMakeLists.txt @@ -0,0 +1,32 @@ +# reflect/CMakeLists.txt + +set(SELF_LIBRARY_NAME reflect) + +# build shared library 'reflect' +add_library(${SELF_LIBRARY_NAME} SHARED TypeDescr.cpp TypeDescrExtra.cpp TaggedRcptr.cpp atomic/AtomicTdx.cpp pointer/PointerTdx.cpp vector/VectorTdx.cpp struct/StructTdx.cpp struct/StructMember.cpp init_reflect.cpp) + +set_target_properties(${SELF_LIBRARY_NAME} PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION 1 + PUBLIC_HEADER TypeDescr.hpp) + +# ---------------------------------------------------------------- +# all the errors+warnings! +# +#target_compile_options(${SELF_LIBRARY_NAME} PRIVATE -Werror -Wall -Wextra) +xo_compile_options(${SELF_LIBRARY_NAME}) +xo_include_options(${SELF_LIBRARY_NAME}) + +# ---------------------------------------------------------------- +# internal dependencies: logutil, ... + +target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC refcnt) + +# ---------------------------------------------------------------- +# 3rd party dependency: boost: + +#xo_boost_dependency(${SELF_LIBRARY_NAME}) + +xo_install_library(${SELF_LIBRARY_NAME}) + +# end CMakeLists.txt diff --git a/include/reflect/EstablishTypeDescr.hpp b/include/reflect/EstablishTypeDescr.hpp new file mode 100644 index 00000000..2b0e25a7 --- /dev/null +++ b/include/reflect/EstablishTypeDescr.hpp @@ -0,0 +1,62 @@ +/* file EstablishTypeDescr.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "reflect/TypeDescr.hpp" +#include "reflect/TaggedPtr.hpp" + +namespace xo { + namespace reflect { + class EstablishTypeDescr { + public: + /* implementation method; expect this to be used only within reflect/ library. + * avoids some otherwise-cyclic #include paths + * between specialized headers such as vector/VectorTdx.hpp and this + * EstablishTypeDescr.hpp + */ +#ifdef OBSOLETE + template + static TaggedPtr establish_tp(T * x) { return TaggedPtr(establish(), x); } +#endif + template + static TaggedPtr establish_most_derived_tp(T * x) { return establish()->most_derived_self_tp(x); } + + template + static TypeDescrW establish() { + TypeDescrW td = TypeDescrBase::require(&typeid(T), + type_name(), + nullptr); + +#ifdef NOT_USING + std::function to_self_tp; + + if (std::is_base_of_v) { + /* T is a descendant of SelfTagging (or T = SelfTagging); + * use SelfTagging.self_tp() + */ + to_self_tp = [](void * x) { return reinterpret_cast(x)->self_tp(); }; + } else { + /* T is not a descendant of SelfTagging. + * want to return + */ + to_self_tp = [td](void * x) { return TaggedPtr(td, x); }; + } + + td->assign_to_self_tp(to_self_tp); +#endif + return td; + } + }; /*EstablishTypeDescr*/ + + template + inline TaggedPtr establish_most_derived_tp(T * x) { + return EstablishTypeDescr::establish_most_derived_tp(x); + } + } /*namespace reflect*/ +} /*namespace xo*/ + + +/* end EstablishTypeDescr.hpp */ diff --git a/include/reflect/Metatype.hpp b/include/reflect/Metatype.hpp new file mode 100644 index 00000000..23f3fd10 --- /dev/null +++ b/include/reflect/Metatype.hpp @@ -0,0 +1,38 @@ +/* @file Metatype.hpp */ + +#pragma once + +#include + +namespace xo { + namespace reflect { + enum class Metatype { mt_invalid, mt_atomic, mt_pointer, mt_vector, mt_struct }; + + inline std::ostream & operator<<(std::ostream & os, + Metatype x) { + switch(x) { + case Metatype::mt_invalid: + os << "invalid!"; + break; + case Metatype::mt_atomic: + os << "atomic"; + break; + case Metatype::mt_pointer: + os << "pointer"; + break; + case Metatype::mt_vector: + os << "vector"; + break; + case Metatype::mt_struct: + os << "struct"; + break; + default: + os << "???"; + } + return os; + } /*operator<<*/ + + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end Metatype.hpp */ diff --git a/include/reflect/Reflect.hpp b/include/reflect/Reflect.hpp new file mode 100644 index 00000000..73ef68f3 --- /dev/null +++ b/include/reflect/Reflect.hpp @@ -0,0 +1,235 @@ +/* file Reflect.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "reflect/SelfTagging.hpp" +#include "reflect/EstablishTypeDescr.hpp" +#include "reflect/atomic/AtomicTdx.hpp" +#include "reflect/pointer/PointerTdx.hpp" +#include "reflect/vector/VectorTdx.hpp" +#include "reflect/struct/StructTdx.hpp" +#include "refcnt/Refcounted.hpp" +#include +#include +#include // for std::pair<> + +namespace xo { + namespace reflect { + template + class EstablishTdx { + public: + static std::unique_ptr make() { return AtomicTdx::make(); } + }; /*EstablishTdx*/ + + // ----- xo::ref::rp ----- + + /* definition provide after decl for Reflect {} below */ + template + class EstablishTdx> { + public: + static std::unique_ptr make(); + }; /*EstablishTdx*/ + + // ----- std::array ----- + + /* definition provide after decl for Reflect {} below */ + template + class EstablishTdx> { + public: + static std::unique_ptr make(); + }; /*EstablishTdx*/ + + // ----- std::vector ----- + + /* definition provide after decl for Reflect {} below */ + template + class EstablishTdx> { + public: + static std::unique_ptr make(); + }; /*EstablishTdx*/ + + // ----- std::pair ----- + + /* definition provide after decl for Reflect {} below */ + template + class EstablishTdx> { + public: + static std::unique_ptr make(); + }; /*EstablishTdx*/ + + // ----- MakeTagged ----- + + template + class TaggedPtrMaker { + public: + static TaggedPtr make_tp(T * x); + static TaggedRcptr make_rctp(T * x); + }; + + template<> + class TaggedPtrMaker { + public: + static TaggedPtr make_tp(SelfTagging * x) { + return x->self_tp(); + } /*make_tp*/ + + static TaggedRcptr make_rctp(SelfTagging * x) { + return x->self_tp(); + } /*make_rctp*/ + }; /*TaggedPtrMaker*/ + + // ----- Reflect ----- + + class Reflect { + public: + /* Use: + * using mytype = ...; + * if (Reflect::is_reflected()) { ... } + */ + template + static bool is_reflected() { return TypeDescrBase::is_reflected(&typeid(T)); } + + /* Use: + * using mytype = ...; + * TypeDescrW td = Reflect::require(); + * + * Note: + * To avoid cyclic header dependencies + * (between EstablishTypeDescr.hpp <-> {vector/VectorTdx.hpp etc.}, + * we use a 2-stage setup process: + * + * 1. EstablishTypeDescr::establish() creates a TypeDescr* object + * with lowest-common-denominator .tdextra AtomicTdx. + * (see [reflect/EstablishTypeDescr.hpp]) + * + * 2. Reflect::require() upgrades .tdextra to suitable implementation + * depending on T; this means also need to visit reflection info + * (TypeDescr objects) for nested types to upgrade them too. + * + * This allows template-fu for a compound type (like std::vector), + * implemented in specialized header (like [reflect/struct/VectorTdx.hpp]) to + * refer to reflection info for T without having to pull in all the + * headers needed to properly reflect T (like this [reflect/Reflect.hpp]) + * + */ + template + static TypeDescrW require() { + TypeDescrW retval_td = EstablishTypeDescr::establish(); + + /* mark TypeDescr for T as complete (even though it isn't quite yet), + * so that when we encounter recursive types, reflection terminates. + * For example consider type resulting from code like + * + * typename T; + * using T = std::vector; + * + */ + if (retval_td->mark_complete()) { + /* control here on 2nd+later calls to require(). + * in principle can immediately short-circuit. + */ + } else { + /* control comes here the first time require() runs */ + + auto final_tdx = EstablishTdx::make(); + + retval_td->assign_tdextra(std::move(final_tdx)); + + /* also need to require for each child */ + } + + return retval_td; + } /*require*/ + + /* Use: + * T * xyz = ...; + * TaggedPtr xyz_tp = Reflect::make_tp(xyz); + */ + template + static TaggedPtr make_tp(T * x) { return TaggedPtrMaker::make_tp(x); } + + template + static TaggedRcptr make_rctp(T * x) { return TaggedPtrMaker::make_rctp(x); } + }; /*Reflect*/ + + // ----- MakeTagged ----- + + template + TaggedPtr + TaggedPtrMaker::make_tp(T * x) { + return TaggedPtr(Reflect::require(), x); + } /*make_tp*/ + + template + TaggedRcptr + TaggedPtrMaker::make_rctp(T * x) { + return TaggedRcptr(Reflect::require(), x); + } /*make_rctp*/ + + // ----- xo::ref::rp ----- + + /* declared above before + * class Reflect { .. } + */ + template + std::unique_ptr + EstablishTdx>::make() { + /* need to ensure Object is property reflected. + * + * In practice must be a class type, since has to store refcount + * + supply assoc'd incr/decr methods + */ + Reflect::require(); + + return RefPointerTdx>::make(); + } /*make*/ + + // ----- std::array ----- + + /* declared above before + * class Reflect { .. } + */ + template + std::unique_ptr + EstablishTdx>::make() { + /* need to ensure Element is properly reflected */ + Reflect::require(); + + return StdArrayTdx::make(); + } /*make*/ + + // ----- std::vector ----- + + /* declared above before + * class Reflect { .. } + */ + template + std::unique_ptr + EstablishTdx>::make() { + /* need to ensure Element is properly reflected */ + Reflect::require(); + + return StdVectorTdx::make(); + } /*make*/ + + // ----- std::pair ----- + + /* declared above before + * class Reflect { .. } + */ + template + std::unique_ptr + EstablishTdx>::make() { + /* need to ensure Lhs, Rhs are properly reflected */ + Reflect::require(); + Reflect::require(); + + return StructTdx::pair(); + } /*make*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end Reflect.hpp */ diff --git a/include/reflect/SelfTagging.hpp b/include/reflect/SelfTagging.hpp new file mode 100644 index 00000000..29517e33 --- /dev/null +++ b/include/reflect/SelfTagging.hpp @@ -0,0 +1,31 @@ +/* file SelfTagging.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "refcnt/Refcounted.hpp" +#include "reflect/TypeDescr.hpp" +#include "reflect/TaggedRcptr.hpp" + +namespace xo { + namespace reflect { + /* a self-tagging object uses reflection to preserve type information + * until runtime. Can use the reflected information to traverse + * object representation (e.g. for printing / serialization) + * without repetitive/bulky boilerplate. + * + * For pybind11 need to have concrete (non-template) apis, + * helpful to have various classes inherit SelfTagging + * + * For example see [printjson/PrintJson.hpp] + */ + class SelfTagging : public ref::Refcount { + public: + virtual TaggedRcptr self_tp() = 0; + }; /*SelfTagging*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end SelfTagging.hpp */ diff --git a/include/reflect/StructReflector.hpp b/include/reflect/StructReflector.hpp new file mode 100644 index 00000000..f2237578 --- /dev/null +++ b/include/reflect/StructReflector.hpp @@ -0,0 +1,161 @@ +/* @file StructReflector.hpp */ + +#pragma once + +#include "reflect/Reflect.hpp" +#include "reflect/TypeDescr.hpp" +#include "reflect/struct/StructMember.hpp" +#include "reflect/struct/StructTdx.hpp" +#include + +namespace xo { + namespace reflect { + template + class SelfTagger {}; + + template + struct SelfTagger { + static TaggedPtr self_tp(void * object) { + return (reinterpret_cast(object))->self_tp(); + } + }; + + template + struct SelfTagger { + static TaggedPtr self_tp(void * /*object*/) { assert(false); return TaggedPtr::universal_null(); } + }; + + /* RAII pattern for reflecting a struct. + * + * Use: + * struct Foo { int x_; double y_; }; + * + * StructReflector sr; + * REFLECT_LITERAL_MEMBER(sr, x_); + * REFLECT_LITERAL_MEMBER(sr, y_); + * + * // optional: regardless, reflection will be completed when sr goes out of scope + * sr.require_complete(); + */ + template + class StructReflector { + public: + using struct_t = StructT; + + public: + StructReflector() : td_{EstablishTypeDescr::establish()} {} + ~StructReflector() { + this->require_complete(); + } + + bool is_complete() const { return s_reflected_flag; } + bool is_incomplete() const { return !s_reflected_flag; } + + template + void reflect_member(std::string const & member_name, + MemberT OwnerT::* member_addr) { + + auto accessor + (GeneralStructMemberAccessor::make(member_addr)); + + /* used to do this in GeneralStructMemberAccessor<> ctor, + * but that introduces #include cycle + */ + Reflect::require(); + + this->member_v_.emplace_back(member_name, std::move(accessor)); + } /*reflect_member*/ + + void require_complete() { + if(!s_reflected_flag) { + s_reflected_flag = true; + + constexpr bool have_to_self_tp = std::is_base_of_v; + + /* if self-tagging, can use .self_tp() to get most-derived tagged pointer */ + auto to_self_tp_fn + = ([](void * object) + { + return SelfTagger::self_tp(object); + }); + + auto tdx = StructTdx::make(std::move(this->member_v_), + have_to_self_tp, + to_self_tp_fn); + + this->td_->assign_tdextra(std::move(tdx)); + } + } /*complete*/ + + template + void adopt_ancestors() { + assert(Reflect::is_reflected()); + + TypeDescr ancestor_td = Reflect::require(); + + /* requires that reflection of AncestorT has completed */ + { + assert(ancestor_td->is_struct()); + assert(ancestor_td->complete_flag()); + } + + /* for structs, + * we know that object argument to TypeDescr::n_child() is unused + */ + for (uint32_t i = 0, n = ancestor_td->n_child(nullptr); i < n; ++i) { + StructMember const & member = ancestor_td->struct_member(i); + + this->member_v_.push_back(member.for_descendant()); + } + } /*adopt_ancestors*/ + + private: + /* set irrevocably to true when .complete() runs. + * + * want to reflect a particular type once; + * short-circuit 2nd or later attempts on the same type + */ + static bool s_reflected_flag; + + /* type description object for StructT */ + TypeDescrW td_; + + /* members of StructT (at least those we're choosing to reflect) */ + std::vector member_v_; + }; /*StructReflector*/ + + template + bool StructReflector::s_reflected_flag = false; + } /*namespace reflect*/ + + /* e.g. + * struct Foo { int bar_; }; + * struct Bar : public Foo { .. }; + * + * StructReflector sr; + * REFLECT_EXPLICIT_MEMBER(sr, "bar", &Foo::bar_); + */ +#define REFLECT_EXPLICIT_MEMBER(sr, member_name, member) sr.reflect_member(member_name, member) + + /* e.g. + * struct Foo { int bar_; }; + * + * StructReflector sr; + * REFLECT_LITERAL_MEMBER(sr, bar_); + * + * then REFLECT_LITERAL_MEMBER() expands to something like: + * sr.reflect_member("bar_", &StructReflector::struct_t::bar_) + */ +#define REFLECT_LITERAL_MEMBER(sr, member_name) sr.reflect_member(#member_name, &decltype(sr)::struct_t::member_name) + + /* like REFLECT_LITERAL_MEMBER(), but append trailing underscore + * + * minor convenience, so we can write + * struct Foo { int bar_; }; + * + * StructReflector sr; + * REFLECT_MEMBER(sr, bar); // reflects Foo::bar_ as "bar" + */ +#define REFLECT_MEMBER(sr, member_name) sr.reflect_member(#member_name, &decltype(sr)::struct_t::member_name##_) + +} /*namespace xo*/ diff --git a/include/reflect/TaggedPtr.hpp b/include/reflect/TaggedPtr.hpp new file mode 100644 index 00000000..653c8ad7 --- /dev/null +++ b/include/reflect/TaggedPtr.hpp @@ -0,0 +1,125 @@ +/* @file TaggedPtr.hpp */ + +#pragma once + +#include "reflect/TypeDescr.hpp" +//#include "reflect/EstablishTypeDescr.hpp" +#include + +namespace xo { +namespace reflect { + class TaggedRcptr; /* see [reflect/TaggedRcptr.hpp] */ + + class TaggedPtr { + public: + TaggedPtr(TypeDescr td, void * x) : td_{td}, address_{x} {} + + static TaggedPtr universal_null() { return TaggedPtr(nullptr, nullptr); } + + /* would be clean to put make() here; + * however it leads to cyclic #include paths, + * so put it elsewhere + */ +#ifdef NOT_USING + template + static TaggedPtr make(T * x) { return TaggedPtr(Reflect::require(), x); } +#endif + + /* visit an object tree. calls preorder_visit_fn() on tp, + * and all objects reachable directly-or-indirectly from tp. + * will call preorder_visit_fn() multiple times if there are multiple paths + * to a node. + * + * require: no cycles in object graph -- undefined behavior if a cycle is present + */ + template + static void visit_tree_preorder(TaggedPtr tp, Fn && preorder_visit_fn) { + using std::uint32_t; + + preorder_visit_fn(tp); + + for(uint32_t i = 0, n = tp.n_child(); i < n; ++i) { + visit_tree_preorder(tp.get_child(i), preorder_visit_fn); + } + } /*visit_tree_preorder*/ + + /* visit object graph. calls preorder_visit_fn() on tp in depth-first + * order. detects and silently prunes duplicate/cyclic references. + */ + template + static void visit_graph(TaggedPtr tp, Fn && visit_fn) { + std::unordered_set visited_set; + + visit_graph_aux(tp, visit_fn, &visited_set); + } /*visit_graph*/ + + TypeDescr td() const { return td_; } + void * address() const { return address_; } + + void assign_td(TypeDescr x) { td_ = x; } + void assign_address(void * x) { address_ = x; } + + bool is_universal_null() const { return (td_ == nullptr) && (address_ == nullptr); } + bool is_vector() const { return td_ && td_->is_vector(); } + bool is_struct() const { return td_ && td_->is_struct(); } + + + /* returns pointer-to-T, if in fact this tagged pointer is understood + * to refer to a T-instance; otherwise nullptr + */ + template + T * recover_native() const { return this->td_->recover_native(this->address_); } + + uint32_t n_child() const { + return this->td_->n_child(this->address_); + } /*n_child*/ + + TaggedPtr get_child(uint32_t i) const { + return this->td_->child_tp(i, this->address_); + } /*get_child*/ + + /* require: + * - .is_struct() is true + */ + std::string const & struct_member_name(uint32_t i) const { + return this->td_->struct_member_name(i); + } + + private: + template + static void visit_graph_aux(TaggedPtr tp, + Fn && visit_fn, + std::unordered_set * p_visited_set) + { + if (tp.address() == nullptr) + return; + + if (p_visited_set->find(tp.address()) == p_visited_set->end()) { + p_visited_set->insert(tp.address()); + + visit_fn(tp); + + for (uint32_t i = 0, n = tp.n_child(); i < n; ++i) { + visit_graph_aux(tp.get_child(i), visit_fn, p_visited_set); + } + } + } /*visit_graph_aux*/ + + private: + friend class TaggedRcptr; + + private: + /* describes the actual type stored at *address. + * can be null if .address is null + */ + TypeDescr td_; + /* address with type information preserved at runtime */ + void * address_; + }; /*TaggedPtr*/ + +} /*namespace reflect*/ +} /*namespace xo*/ + +/* end TaggedPtr.hpp */ + + diff --git a/include/reflect/TaggedRcptr.hpp b/include/reflect/TaggedRcptr.hpp new file mode 100644 index 00000000..9ca8b15f --- /dev/null +++ b/include/reflect/TaggedRcptr.hpp @@ -0,0 +1,88 @@ +/* file TaggedRcptr.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "reflect/TaggedPtr.hpp" +// causes #include cycle, reflect/Reflect.hpp includes this header +//#include "reflect/Reflect.hpp" +#include "refcnt/Refcounted.hpp" + +namespace xo { + namespace reflect { + /* Tagged reference-counted pointer. + * Like TaggedPtr, but also maintains reference count. + * + * note that refcounting behavior is lost if assigned to a TaggedPtr variable! + */ + class TaggedRcptr : public TaggedPtr { + public: + using Refcount = ref::Refcount; + + public: + TaggedRcptr(TypeDescr td, Refcount * x) : TaggedPtr(td, x) { + ref::intrusive_ptr_add_ref(x); + } + TaggedRcptr(TaggedRcptr const & x) : TaggedPtr(x) { + ref::intrusive_ptr_add_ref(x.rc_address()); + } + TaggedRcptr(TaggedRcptr && x) : TaggedPtr(std::move(x)) { + /* since we're moving from x, need to make sure x.dtor + * doesn't decrement refcount + */ + x.assign_address(nullptr); + } + ~TaggedRcptr() { + ref::intrusive_ptr_release(this->rc_address()); + } + + /* causes #include cycle, see [reflect/Reflect.hpp] */ +#ifdef NOT_IN_USE + /* require: T --isa--> ref::Refcount */ + template + static TaggedRcptr make(T * x) { return TaggedRcptr(Reflect::require(), x); } +#endif + + Refcount * rc_address() const { + return reinterpret_cast(this->address()); + } /*rc_address*/ + + TaggedRcptr & operator=(TaggedRcptr const & rhs) { + Refcount * x = rhs.rc_address(); + Refcount * old = this->rc_address(); + + TaggedPtr::operator=(rhs); + + if (x != old) { + intrusive_ptr_release(old); + intrusive_ptr_add_ref(x); + } + + return *this; + } /*operator=*/ + + TaggedRcptr & operator=(TaggedRcptr && rhs) { + /* swap pointers + type descriptions; + * then don't need to touch refcounts + */ + std::swap(this->td_, rhs.td_); + std::swap(this->address_, rhs.address_); + + return *this; + } /*operator=*/ + + void display(std::ostream & os) const; + std::string display_string() const; + }; /*TaggedRcptr*/ + + inline std::ostream & operator<<(std::ostream & os, TaggedRcptr const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end TaggedRcptr.hpp */ diff --git a/include/reflect/TypeDescr.hpp b/include/reflect/TypeDescr.hpp new file mode 100644 index 00000000..08614071 --- /dev/null +++ b/include/reflect/TypeDescr.hpp @@ -0,0 +1,302 @@ +/* @file TypeDescr.hpp */ + +#pragma once + +//#include "reflect/atomic/AtomicTdx.hpp" +#include "reflect/TypeDescrExtra.hpp" +#include "cxxutil/demangle.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xo { + namespace reflect { + class TaggedPtr; /* see [reflect/TaggedPtr.hpp] */ + + /* A reflected type is a type for which we keep information around at runtime + * Assign reflected types unique (within an executable) ids, + * allocating consecutively, starting from 1. + * Reserve 0 as a sentinel + */ + class TypeId { + public: + /* allocate a new TypeId value. + * promise: + * - retval.id() > 0 + */ + static TypeId allocate() { return TypeId(s_next_id++); } + + std::uint32_t id() const { return id_; } + + private: + explicit TypeId(std::uint32_t id) : id_{id} {} + + private: + static std::uint32_t s_next_id; + + /* unique index# for this type. + * 0 reserved for sentinel + */ + std::uint32_t id_ = 0; + }; /*TypeId*/ + + inline std::ostream & + operator<<(std::ostream & os, TypeId x) { + os << x.id(); + return os; + } /*operator<<*/ + + /* runtime description of a struct/class instance variable */ + class StructMember; + + class TypeDescrBase; + + using TypeDescr = TypeDescrBase const *; + using TypeDescrW = TypeDescrBase *; + + /* convenience wrapper for a std::type_info pointer. + * works properly with pybind11, since python doens't encounter + * native type_info pointer, it won't try to delete it. + */ + class TypeInfoRef { + public: + explicit TypeInfoRef(std::type_info const * tinfo) : tinfo_{tinfo} {} + TypeInfoRef(TypeInfoRef const & x) = default; + + /* use: + * TypeInfoRef tinfo = TypeInfoRef::make(); + */ + template + TypeInfoRef make() { return TypeInfoRef(&typeid(T)); } + + std::size_t hash_code() const { return this->tinfo_->hash_code(); } + char const * impl_name() const { return this->tinfo_->name(); } + + static bool is_equal(TypeInfoRef x, TypeInfoRef y) noexcept { + if (x.hash_code() != y.hash_code()) + return false; + + return ::strcmp(x.impl_name(), y.impl_name()) == 0; + } /*is_equal*/ + + private: + /* native type_info object for encapsulated type */ + std::type_info const * tinfo_ = nullptr; + }; /*TypeInfoRef*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +namespace std { + template <> struct hash { + std::size_t operator()(xo::reflect::TypeInfoRef x) const noexcept { return x.hash_code(); } + }; +} /*namespace std*/ + +namespace xo { + namespace reflect { + inline bool operator==(TypeInfoRef x, TypeInfoRef y) { return TypeInfoRef::is_equal(x, y); } + inline bool operator!=(TypeInfoRef x, TypeInfoRef y) { return !TypeInfoRef::is_equal(x, y); } + +#ifdef NOT_IN_USE + namespace detail { + class HashTypeInfoRef { + public: + std::size_t operator()(TypeInfoRef x) const noexcept { return x.hash_code(); } + }; /*HashTypeInfoRef*/ + + class EqualTypeInfoRef { + public: + bool operator()(TypeInfoRef x, TypeInfoRef y) const noexcept { return TypeInfoRef::is_equal(x, y); } + }; /*EqualTypeInfoRef*/ + } /*namespace detail*/ +#endif + + class TypeDescrExtra; + + /* run-time description for a native c++ type */ + class TypeDescrBase { + public: + /* type-description objects for a type T is unique, + * --> can always use its address + */ + TypeDescrBase(TypeDescrBase const & x) = delete; + + /* test whether a type has been reflected. + * introducing this for unit testing + */ + static bool is_reflected(std::type_info const * tinfo) { + return (s_type_table_map.find(TypeInfoRef(tinfo)) + != s_type_table_map.end()); + } /*is_reflected*/ + + /* NOTE: + * implementation here will be defeated if std::type_info + * objects violate ODR. This occurs with clang + 2-level namespaces, + * so important to linke with --flat_namespace defined. + * See FAQ + * [Build Issues|Q2 - dynamic_cast> fails] + */ + static TypeDescrW require(std::type_info const * tinfo, + std::string_view canonical_name, + std::unique_ptr tdextra); + + /* print table of reflected types to os */ + static void print_reflected_types(std::ostream & os); + + TypeId id() const { return id_; } + std::type_info const * typeinfo() const { return typeinfo_; } + std::string_view const & canonical_name() const { return canonical_name_; } + std::string_view const & short_name() const { return short_name_; } + bool complete_flag() const { return complete_flag_; } + TypeDescrExtra * tdextra() const { return tdextra_.get(); } + Metatype metatype() const { return tdextra_->metatype(); } + + /* true iff the type represented by *this is the same as the type T. + * + * Warning: comparing typeinfo address can give false negatives. + * suspect this is caused by problems coalescing linker symbols + * in the clang toolchain. + */ + template + bool is_native() const { + return ((this->typeinfo() == &typeid(T)) + || (this->typeinfo()->hash_code() == typeid(T).hash_code()) + || (this->typeinfo()->name() == typeid(T).name())); + } /*is_native*/ + + /* safe downcast -- like dynamic_cast<>, but does not require a source type */ + template + T * recover_native(void * address) const { + if (this->is_native()) { + return reinterpret_cast(address); + } else { + return nullptr; + } + } /*recover_native*/ + + bool is_vector() const { return this->tdextra_->is_vector(); } + bool is_struct() const { return this->tdextra_->is_struct(); } + + /* given a T-instance object, return tagged pointer with T replaced + * by the most-derived-subtype of T to which *object belongs. + * This works only for descendants of reflect::SelfTagging + */ + TaggedPtr most_derived_self_tp(void * object) const; + + /* if generalized vector (std::vector, std::array, ..): + * .n_child() reports #of elements + * if struct/class: + * .n_child() reports #of instance variables (that have been reflected) + */ + uint32_t n_child(void * object) const { return this->tdextra_->n_child(object); } + TaggedPtr child_tp(uint32_t i, void * object) const; + + /* require: + * - .is_struct() = true + * - i in [0 .. .n_child() - 1] + */ + std::string const & struct_member_name(uint32_t i) const { + return this->tdextra_->struct_member_name(i); + } + /* fetch runtime description for i'th reflected instance variable. + * + * require: + * - .is_struct() = true + * - i in [0 .. .n_child() - 1] + */ + StructMember const & struct_member(uint32_t i) const { + StructMember const * sm = this->tdextra_->struct_member(i); + + assert(sm); + return *sm; + } /*struct_member*/ + + void display(std::ostream & os) const; + std::string display_string() const; + + /* mark this TypeDescr complete; + * returns the value of .complete_flag from _before_ + * this call + */ + bool mark_complete(); + + /* call this once to attach extended type information to a type-description + * (e.g. description of struct members for a record type) + */ + void assign_tdextra(std::unique_ptr tdx); + + private: + TypeDescrBase(TypeId id, + std::type_info const * tinfo, + std::string_view canonical_name, + std::unique_ptr tdextra); + + private: + /* invariant: + * - for all TypeDescrImpl instances x: + * - s_type_table_v[x->id()] = x + * - s_type_table_map[TypeInfoRef(x->typeinfo())] = x + */ + + /* hashmap of all TypeDescr instances, indexed by . singleton */ + static std::unordered_map> s_type_table_map; + /* hashmap of (presumed) duplicate TypeInfoRef values. + * This happens with clang sometimes when the same type is referenced + * from multiple modules (i.e. shared libs). + */ + static std::unordered_map s_coalesced_type_table_map; + + /* vector of all TypeDescr instances. singleton. */ + static std::vector s_type_table_v; + + private: + /* unique id# for this type */ + TypeId id_; + /* typeinfo for type T */ + std::type_info const * typeinfo_ = nullptr; + /* canonical name for this type (see demangle.hpp for type_name()) + * e.g. + * xo::option::Px2 + */ + std::string_view canonical_name_; + /* suffix of .canonical_name, just after last ':' + * e.g. + * Px2 + */ + std::string_view short_name_; + /* set to true once final value for .tdextra is established + * intially all TypeDescr objects will use AtomicTdx for .tdextra + * Reflect::require() upgrades .tdextra for particular types. + * When that procedure makes a decision for a type T, + * .complete_flag will be set to true for the corresponding TypeDescrBase instance + */ + bool complete_flag_ = false; + /* additional type information that either: + * (a) isn't universal across all types, + * e.g. dereferencing instance of a pointer type + * (b) can't be captured with template-fu, + * e.g. struct member names + * + * generally .tdextra will be populated some time after TypeDescrBase's ctor exits. + * This is necessary because of (b) above, also because of possibility of recursive + * types. + */ + std::unique_ptr tdextra_; + }; /*TypeDescrBase*/ + + inline std::ostream & + operator<<(std::ostream & os, TypeDescrBase const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end TypeDescr.hpp */ diff --git a/include/reflect/TypeDescrExtra.hpp b/include/reflect/TypeDescrExtra.hpp new file mode 100644 index 00000000..2d2be7fa --- /dev/null +++ b/include/reflect/TypeDescrExtra.hpp @@ -0,0 +1,65 @@ +/* @file TypeDescrExtra.hpp */ + +#pragma once + +#include "reflect/Metatype.hpp" +#include +/* note: this file #include'd into TypeDescr.hpp */ +#include + +namespace xo { + namespace reflect { + /* forward-declaring here. see [reflect/struct/StructMember.hpp] */ + class StructMember; + class TypeDescrBase; + class TaggedPtr; + + /* information associated with a c++ type. + * distinct from TypeDescrImpl: + * 1. want to use reflection to support for runtime polymorphism over similar but + * not directly-related types: for example + * std::vector + * and + * std::list + * are both ordered collections + * 2. some information can't be universally established via template-fu, + * for example struct member names + * 3. descriptions for recursive types require 2-stage construction + * + * A TypeDescrImpl instance will contain a pointer to a suitable + * TypeDescrExtra instance. + * + * The single TypeDescrImpl instance for some type T can be established + * automatically, see Reflect::require(). + * + * A specific TypeDescrExtra instance may be attached in a non-automated way + * later + */ + class TypeDescrExtra { + public: + using uint32_t = std::uint32_t; + + public: + virtual ~TypeDescrExtra() = default; + + bool is_vector() const { return this->metatype() == Metatype::mt_vector; } + bool is_struct() const { return this->metatype() == Metatype::mt_struct; } + + virtual Metatype metatype() const = 0; + /* given a T-instance, report most-derived subtype of T to which *object belongs. + * this works only for types that are derived from reflect::SelfTagging. + */ + virtual TaggedPtr most_derived_self_tp(TypeDescrBase const * object_td, void * object) const; + virtual uint32_t n_child(void * object) const = 0; + virtual TaggedPtr child_tp(uint32_t i, void * object) const = 0; + /* require: + * .is_struct() + */ + virtual std::string const & struct_member_name(uint32_t i) const = 0; + /* nullptr unless *this represents a struct/class type */ + virtual StructMember const * struct_member(uint32_t i) const; + }; /*TypeDescrExtra*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end TypeDescrExtra.hpp */ diff --git a/include/reflect/TypeDrivenMap.hpp b/include/reflect/TypeDrivenMap.hpp new file mode 100644 index 00000000..c38c8739 --- /dev/null +++ b/include/reflect/TypeDrivenMap.hpp @@ -0,0 +1,47 @@ +/* @file TypeDrivenMap.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "reflect/TypeDescr.hpp" +#include + +namespace xo { + namespace reflect { + /* represents a map :: TypeId -> Value */ + template + class TypeDrivenMap { + public: + Value const * lookup(TypeId id) const { return this->lookup_slot(id); } + + Value * require(TypeId id) { return this->require_slot(id); } + Value * require(TypeDescr td) { return this->require_slot(td->id()); } + + private: + Value const * lookup_slot(TypeId id) const { + if (this->contents_v_.size() <= id.id()) + return nullptr; + + return &(this->contents_v_[id.id()]); + } /*lookup_slot*/ + + Value * require_slot(TypeId id) { + if (this->contents_v_.size() <= id.id()) + this->contents_v_.resize(id.id() + 1); + + return &(this->contents_v_[id.id()]); + } /*require_slot*/ + + private: + /* since TypeId/s are unique, compact sequence numbers, + * can efficiently store mapping to Values using a vector indexed by TypeId + */ + std::vector contents_v_; + }; /*TypeDrivenMap*/ + } /*namespace reflect*/ +} /*namespace xo*/ + + +/* end TypeDrivenMap.hpp */ diff --git a/include/reflect/atomic/AtomicTdx.hpp b/include/reflect/atomic/AtomicTdx.hpp new file mode 100644 index 00000000..7b2e042d --- /dev/null +++ b/include/reflect/atomic/AtomicTdx.hpp @@ -0,0 +1,37 @@ +/* @file AtomicTdx.hpp */ + +#pragma once + +#include "reflect/TypeDescrExtra.hpp" +//#include "reflect/TaggedPtr.hpp" +#include + +namespace xo { + namespace reflect { + class TaggedPtr; + + /* Extra type-associated information for an atomic type. + * We use this as degenerate catch-all case for types that aren't known + * to have additional structure (std::vector, std::map, int*, etc.) + */ + class AtomicTdx : public TypeDescrExtra { + public: + virtual ~AtomicTdx() = default; + + static std::unique_ptr make(); + + // ----- Inherited from TypeDescrExtra ----- + + virtual Metatype metatype() const override { return Metatype::mt_atomic; } + virtual uint32_t n_child(void * /*object*/) const override { return 0; } + virtual TaggedPtr child_tp(uint32_t /*i*/, void * /*object*/) const override; + virtual std::string const & struct_member_name(uint32_t i) const override; + //virtual StructMember const * struct_member(uint32_t /*i*/) const override { return nullptr; } + + private: + AtomicTdx() = default; + }; /*TypeDescrExtra*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end AtomicTdx.hpp */ diff --git a/include/reflect/init_reflect.hpp b/include/reflect/init_reflect.hpp new file mode 100644 index 00000000..00506a25 --- /dev/null +++ b/include/reflect/init_reflect.hpp @@ -0,0 +1,22 @@ +/* file init_reflect.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "subsys/Subsystem.hpp" + +namespace xo { + /* tag to represent the reflect/ subsystem within ordered initialization */ + enum S_reflect_tag {}; + + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; +} /*namespace xo*/ + + +/* end init_reflect.hpp */ diff --git a/include/reflect/pointer/PointerTdx.hpp b/include/reflect/pointer/PointerTdx.hpp new file mode 100644 index 00000000..d2d3b868 --- /dev/null +++ b/include/reflect/pointer/PointerTdx.hpp @@ -0,0 +1,76 @@ +/* file PointerTdx.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "reflect/TypeDescrExtra.hpp" +#include "reflect/EstablishTypeDescr.hpp" +#include "indentlog/scope.hpp" + +namespace xo { + namespace reflect { + /* Extra type-associated information for a pointer-like type + * + * Treat a pointer as a container that has 0 or 1 children; + * - 0 children if null + * - 1 child otherwise + */ + class PointerTdx : public TypeDescrExtra { + public: + // ----- Inherited from TypeDescrExtra ----- + + virtual Metatype metatype() const override { return Metatype::mt_pointer; } + virtual uint32_t n_child(void * object) const override = 0; + virtual TaggedPtr child_tp(uint32_t i, void * object) const override = 0; + /* (forbidden) */ + virtual std::string const & struct_member_name(uint32_t i) const override; + }; /*PointerTdx*/ + + // ----- RefPointerTdx ----- + + /* xo::ref::intrusive_ptr for some T */ + template + class RefPointerTdx : public PointerTdx { + public: + using target_t = Pointer; + + static std::unique_ptr make() { + return std::unique_ptr(new RefPointerTdx()); + } /*make*/ + + virtual uint32_t n_child(void * object) const override { + /* e.g: + * target_t = ref::rp + */ + target_t * ptr = reinterpret_cast(object); + + if (*ptr) + return 1; + else + return 0; + } /*n_child*/ + + virtual TaggedPtr child_tp(uint32_t i, void * object) const override { + using xo::tostr; + using xo::xtag; + + target_t * ptr = reinterpret_cast(object); + + if (i > 0) { + throw std::runtime_error(tostr("RefPointerTdx::child_tp" + ": attempt to fetch child #i from a ref::rp", + xtag("T", type_name()), + xtag("i", i), + xtag("n", this->n_child(object)))); + } + + return establish_most_derived_tp(ptr->get()); + } /*child_tp*/ + }; /*RefPointerTdx*/ + + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end PointerTdx.hpp */ diff --git a/include/reflect/struct/StructMember.hpp b/include/reflect/struct/StructMember.hpp new file mode 100644 index 00000000..eaddf3b3 --- /dev/null +++ b/include/reflect/struct/StructMember.hpp @@ -0,0 +1,236 @@ +/* @file StructMember.hpp */ + +#pragma once + +#include "reflect/TypeDescr.hpp" +#include "reflect/EstablishTypeDescr.hpp" +//#include "reflect/Reflect.hpp" +#include "reflect/TaggedPtr.hpp" +#include +#include + +namespace xo { +namespace reflect { + class AbstractStructMemberAccessor { + public: + virtual ~AbstractStructMemberAccessor() = default; + + /* get tagged pointer referring to this member of the object at *struct_addr */ + TaggedPtr member_tp(void * struct_addr) const; + + /* get type-description object for struct + * containing this member. useful for consistency checking. + */ + virtual TypeDescr struct_td() const = 0; + + /* get type-description object for this member + * e.g. if this member represents Foo::bar_ in + * struct Foo { int bar_; }; + * then + * .member_td() => Reflect::require(); + */ + virtual TypeDescr member_td() const = 0; + + /* get address of a particular member, given parent address */ + virtual void * address(void * struct_addr) const = 0; + + virtual std::unique_ptr clone() const = 0; + }; /*AbstractStructMemberAccessor*/ + + /* GeneralStructMemberAccessor + * + * Use this to handle access to possibly-inherited struct members: + * + * struct Foo { int x_; } + * struct Bar { char * y_; } + * struct Quux : public Foo, public Bar { bool z_; } + * + * want to be able to access Bar::y from a Quux instance. + * in example, would use GenericStructMemberAccessor<> + * with: + * StructT = Quux, + * OwnerT = Bar, + * MemberT = char* + * + * Require: + * StructT* is assignable to OwnerT* (because StructT --isa--> OwnerT) + */ + template + class GeneralStructMemberAccessor : public AbstractStructMemberAccessor { + public: + /* pointer to a OwnerT member of type MemberT */ + using Memptr = MemberT OwnerT::*; + + public: + GeneralStructMemberAccessor(Memptr memptr) : member_td_{EstablishTypeDescr::establish()}, + memptr_{memptr} {} + GeneralStructMemberAccessor(GeneralStructMemberAccessor const & x) = default; + virtual ~GeneralStructMemberAccessor() = default; + + static std::unique_ptr make(Memptr memptr) { + return std::unique_ptr(new GeneralStructMemberAccessor(memptr)); } + + /* get member address given address of parent struct + * (i.e. from Struct*, not from OwnerT*) + */ + MemberT * address_impl(StructT * self_addr) const { + OwnerT * owner_addr = self_addr; + + return &(owner_addr->*memptr_); + } /*address_impl*/ + + // ----- Inherited from AbstractStructMemberAccessor ----- + +#ifdef OBSOLETE + virtual TaggedPtr member_tp(void * struct_addr) const override { + /* FIXME: this reports declared type of member, instead of + * (possibly narrower) actual type of member + */ + + return this->member_td_->most_derived_self_tp(this->address(struct_addr)); + //return TaggedPtr(this->member_td_, this->address(struct_addr)); + } /*member_tp*/ +#endif + + virtual TypeDescr struct_td() const override { return EstablishTypeDescr::establish(); } + + virtual TypeDescr member_td() const override { return this->member_td_; } + + virtual void * address(void * struct_addr) const override { + return this->address_impl(reinterpret_cast(struct_addr)); + } /*address*/ + + virtual std::unique_ptr clone() const override { + return std::unique_ptr + (new GeneralStructMemberAccessor(*this)); + } /*clone*/ + + private: + /* type description for MemberT; .memptr is pointer-to-member-of-OwnerT, + * where that member has type MemberT + */ + TypeDescr member_td_ = nullptr; + /* pointer to member of OwnerT */ + Memptr memptr_ = nullptr; + }; /*GeneralStructMemberAccessor*/ + + /* struct-member accessor via delegation, + * to accessor of a parent (or some other ancestor) class. + * + * struct Foo { int x_; } + * struct Bar { char * y_; } + * + * auto bar_x_access = GeneralStructMemberAccessor::make(&Foo::x_); + * + * or equivalently: + * auto foo_x_access = GeneralStructMemberAccessor::make(&Foo::x_); + * auto bar_x_access = AncestorStructMemberAccessor::adopt(foo_x_access); + * + * can use the 2nd form to adopt accessors from an already-reflected ancestor class + * + * Require: + * - StructT -isa-> AncestorT + */ + template + class AncestorStructMemberAccessor : public AbstractStructMemberAccessor { + public: + AncestorStructMemberAccessor(std::unique_ptr ancestor_accessor) + : ancestor_accessor_{std::move(ancestor_accessor)} {} + AncestorStructMemberAccessor(AncestorStructMemberAccessor const & x) = default; + virtual ~AncestorStructMemberAccessor() = default; + + static std::unique_ptr + adopt(std::unique_ptr ancestor_accessor) { + return std::unique_ptr + (new AncestorStructMemberAccessor(std::move(ancestor_accessor))); + } /*adopt*/ + + void * address_impl(StructT * self_addr) const { + /* to use access-via-ancestor, need to convert to ancestor pointer */ + AncestorT * ancestor_addr = self_addr; + + return this->ancestor_accessor_->address(ancestor_addr); + } /*address_impl*/ + + // ----- inherited from AbstractStructMemberAccessor ----- + +#ifdef OBSOLETE + virtual TaggedPtr member_tp(void * struct_addr) const override { + AncestorT * ancestor_addr = reinterpret_cast(struct_addr); + + return this->ancestor_accessor_->member_tp(ancestor_addr); + } /*member_tp*/ +#endif + + virtual TypeDescr struct_td() const override { return EstablishTypeDescr::establish(); } + virtual TypeDescr member_td() const override { return this->ancestor_accessor_->member_td(); } + + virtual void * address(void * struct_addr) const override { + return this->address_impl(reinterpret_cast(struct_addr)); + } + + virtual std::unique_ptr clone() const override { + return std::unique_ptr + (new AncestorStructMemberAccessor(std::move(this->ancestor_accessor_->clone()))); + } /*clone*/ + + private: + /* .ancestor_accessor fetches some particular member of AncestorT */ + std::unique_ptr ancestor_accessor_; + }; /*AncestorStructMemberAccessor*/ + + /* describes a member of a struct/class + * see [reflect/StructReflector.hpp] + */ + class StructMember { + public: + StructMember() = default; + StructMember(std::string const & name, + std::unique_ptr accessor) + : member_name_{name}, accessor_{std::move(accessor)} {} + StructMember(StructMember && x) + : member_name_{std::move(x.member_name_)}, + accessor_{std::move(x.accessor_)} {} + + static StructMember null(); + + std::string const & member_name() const { return member_name_; } + + TaggedPtr get_member_tp(void * struct_addr) const { return this->accessor_->member_tp(struct_addr); } + TypeDescr get_struct_td() const { return this->accessor_->struct_td(); } + TypeDescr get_member_td() const { return this->accessor_->member_td(); } + //void * get_member_addr(void * struct_addr) const { return this->accessor_->address(struct_addr); } + + /* make copy that accesses this member, but starting + * from pointer to some derived class DescendantT, + * instead of from container type StructT known to (but not exposed by) *this + */ + template + StructMember for_descendant() const { + assert(EstablishTypeDescr::establish() == this->get_struct_td()); + + return StructMember(this->member_name(), + std::move(AncestorStructMemberAccessor::adopt + (std::move(this->accessor_->clone())))); + } /*for_descendant*/ + + StructMember & operator=(StructMember && x) { + member_name_ = std::move(x.member_name_); + accessor_ = std::move(x.accessor_); + return *this; + } + + private: + /* member name, e.g. foo if + * struct StructT { MemberT foo; } + */ + std::string member_name_; + /* T recd; + * this->accessor_->address_impl(&recd) ==> &(recd.member) + */ + std::unique_ptr accessor_; + }; /*StructMember*/ +} /*namespace reflect*/ +} /*namespace xo*/ + +/* end StructMember.hpp */ diff --git a/include/reflect/struct/StructTdx.hpp b/include/reflect/struct/StructTdx.hpp new file mode 100644 index 00000000..5e170ef7 --- /dev/null +++ b/include/reflect/struct/StructTdx.hpp @@ -0,0 +1,94 @@ +/* @file StructTdx.hpp */ + +#pragma once + +#include "reflect/TypeDescrExtra.hpp" +#include "reflect/TaggedPtr.hpp" +#include "reflect/struct/StructMember.hpp" +#include +#include +#include + +namespace xo { + namespace reflect { + /* Extra type-associated information for a struct/class. + * We use this to preserve information about memory layout + * at runtime + */ + class StructTdx : public TypeDescrExtra { + public: + /* named ctor idiom. create new instance for struct with given member list + * + * to_self_tp. use this function to support .most_derived_self_tp() + */ + static std::unique_ptr make(std::vector member_v, + bool have_to_self_tp, + std::function to_self_tp); + + /* specialization for std::pair + * coordinates with [reflect/Reflect.hpp] + */ + template + static std::unique_ptr pair() { + using struct_t = std::pair; + + std::vector mv; + { + auto lhs_access + (GeneralStructMemberAccessor::make + (&struct_t::first)); + + mv.push_back(StructMember("first", std::move(lhs_access))); + } + { + auto rhs_access + (GeneralStructMemberAccessor::make + (&struct_t::second)); + + mv.push_back(StructMember("second", std::move(rhs_access))); + } + + std::function null_to_self_tp; + + return make(std::move(mv), + false /*!have_to_self_tp*/, + null_to_self_tp); + } /*pair*/ + + // ----- Inherited from TypeDescrExtra ----- + + virtual Metatype metatype() const override { return Metatype::mt_struct; } + virtual TaggedPtr most_derived_self_tp(TypeDescrBase const * object_td, + void * object) const override { + if (this->have_to_self_tp_) { + return this->to_self_tp_(object); + } else { + return TypeDescrExtra::most_derived_self_tp(object_td, object); + } + } + virtual uint32_t n_child(void * /*object*/) const override { return this->member_v_.size(); } + virtual TaggedPtr child_tp(uint32_t i, void * object) const override; + virtual std::string const & struct_member_name(uint32_t i) const override; + virtual StructMember const * struct_member(uint32_t i) const override; + + private: + StructTdx(std::vector member_v, + bool have_to_self_tp, + std::function to_self_tp) + : member_v_{std::move(member_v)}, + have_to_self_tp_{have_to_self_tp}, + to_self_tp_{std::move(to_self_tp)} {} + + private: + /* per-instance-variable reflection details */ + std::vector member_v_; + /* true if .to_self_tp() is defined */ + bool have_to_self_tp_ = false; + /* get TaggedPtr for most-derived subtype of supplied T-instance */ + std::function to_self_tp_; + }; /*StructTdx*/ + + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end StructTdx.hpp */ diff --git a/include/reflect/vector/VectorTdx.hpp b/include/reflect/vector/VectorTdx.hpp new file mode 100644 index 00000000..4f3309c2 --- /dev/null +++ b/include/reflect/vector/VectorTdx.hpp @@ -0,0 +1,100 @@ +/* file VectorTdx.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "reflect/TypeDescrExtra.hpp" +//#include "reflect/TaggedPtr.hpp" +#include "reflect/EstablishTypeDescr.hpp" +//#include "reflect/TaggedPtr.hpp" +//#include +//#include + +namespace xo { + namespace reflect { + /* Extra type-associated information for a vector/array. + */ + class VectorTdx : public TypeDescrExtra { + public: + /* named ctor idiom. create new instance for a vector type */ + //static std::unique_ptr make(); + + // ----- Inherited from TypeDescrExtra ----- + + virtual Metatype metatype() const override { return Metatype::mt_vector; } + virtual uint32_t n_child(void * object) const override = 0; + virtual TaggedPtr child_tp(uint32_t i, void * object) const override = 0; + /* (forbidden) */ + virtual std::string const & struct_member_name(uint32_t i) const override; + }; /*VectorTdx*/ + + // ----- StlVectorTdx ----- + + /* require: + * - VectorT.size() + * - VectorT[int] :: lvalue + */ + template + class StlVectorTdx : public VectorTdx { + public: + using target_t = VectorT; + + static std::unique_ptr make() { + return std::unique_ptr(new StlVectorTdx()); + } /*make*/ + + virtual uint32_t n_child(void * object) const override { + target_t * vec = reinterpret_cast(object); + + return vec->size(); + } /*n_child*/ + + virtual TaggedPtr child_tp(uint32_t i, void * object) const override { + target_t * vec = reinterpret_cast(object); + + return establish_most_derived_tp(&((*vec)[i])); + } /*child_tp*/ + }; /*StlVectorTdx*/ + + // ----- std::array ----- + + /* coordinates with EstablishTdx>::make(), + * see [reflect/Reflect.hpp] + */ + + template + using StdArrayTdx = StlVectorTdx>; + + // ----- std::vector ----- + + /* coordinates with EstablishTdx>::make() + * see [reflect/Reflect.hpp] + */ + template + class StdVectorTdx : public VectorTdx { + public: + using target_t = std::vector; + + static std::unique_ptr make() { + return std::unique_ptr(new StdVectorTdx()); + } /*make*/ + + virtual uint32_t n_child(void * object) const override { + target_t * vec = reinterpret_cast(object); + + return vec->size(); + } /*n_child*/ + + virtual TaggedPtr child_tp(uint32_t i, void * object) const override { + target_t * vec = reinterpret_cast(object); + + return establish_most_derived_tp(&((*vec)[i])); + } + }; /*StdVectorTdx*/ + } /*namespace reflect*/ + +} /*namespace xo*/ + +/* end VectorTdx.hpp */ diff --git a/repo/README.md b/repo/README.md new file mode 100644 index 00000000..3acdcde4 --- /dev/null +++ b/repo/README.md @@ -0,0 +1,5 @@ + +``` +cd reflect/repo +git submodule add git@github.com/someusername/someproject.git +``` diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt new file mode 100644 index 00000000..e1c1f65f --- /dev/null +++ b/src/reflect/CMakeLists.txt @@ -0,0 +1,52 @@ +# reflect/CMakeLists.txt + +set(SELF_LIBRARY_NAME reflect) +set(SELF_SOURCE_FILES TypeDescr.cpp TypeDescrExtra.cpp TaggedRcptr.cpp atomic/AtomicTdx.cpp pointer/PointerTdx.cpp vector/VectorTdx.cpp struct/StructTdx.cpp struct/StructMember.cpp init_reflect.cpp) + +# build shared library 'reflect' +add_library(${SELF_LIBRARY_NAME} SHARED ${SELF_SOURCE_FILES}) + +set_target_properties(${SELF_LIBRARY_NAME} + PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION 1) + +# ---------------------------------------------------------------- +# all the errors+warnings! +# +#target_compile_options(${SELF_LIBRARY_NAME} PRIVATE -Werror -Wall -Wextra) +xo_compile_options(${SELF_LIBRARY_NAME}) +xo_include_options(${SELF_LIBRARY_NAME}) +xo_install_library(${SELF_LIBRARY_NAME}) + +# ---------------------------------------------------------------- +# dependencies: logutil, ... + +#xo_refcnt_dependency(${SELF_LIBRARY_NAME}) +#xo_indentlog_dependency(${SELF_LIBRARY_NAME}) + +add_dependencies(${SELF_LIBRARY_NAME} refcnt) +target_include_directories( + ${SELF_LIBRARY_NAME} PUBLIC + $ + $ +) +target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC refcnt) + +add_dependencies(${SELF_LIBRARY_NAME} indentlog) +# note: can't use find_package() here, +# because find_package() needs to run successfully before +# dependency gets installed. +target_include_directories( + ${SELF_LIBRARY_NAME} PUBLIC + $ + $ +) +target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC indentlog) + +# ---------------------------------------------------------------- +# 3rd party dependency: boost: + +#xo_boost_dependency(${SELF_LIBRARY_NAME}) + +# end CMakeLists.txt diff --git a/src/reflect/TaggedRcptr.cpp b/src/reflect/TaggedRcptr.cpp new file mode 100644 index 00000000..cc461e0c --- /dev/null +++ b/src/reflect/TaggedRcptr.cpp @@ -0,0 +1,30 @@ +/* file TaggedRcptr.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "TaggedRcptr.hpp" +#include "indentlog/print/tag.hpp" + +namespace xo { + using xo::xtag; + using xo::tostr; + + namespace reflect { + void + TaggedRcptr::display(std::ostream & os) const + { + os << "td()->canonical_name()) + << xtag("addr", this->rc_address()) + << ">"; + } /*display*/ + + std::string + TaggedRcptr::display_string() const { + return tostr(*this); + } /*display_string*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end TaggedRcptr.cpp */ diff --git a/src/reflect/TypeDescr.cpp b/src/reflect/TypeDescr.cpp new file mode 100644 index 00000000..564fec14 --- /dev/null +++ b/src/reflect/TypeDescr.cpp @@ -0,0 +1,194 @@ +/* @file TypeDescr.cpp */ + +#include "TypeDescr.hpp" +#include "TaggedPtr.hpp" +#include "TypeDescrExtra.hpp" +#include "atomic/AtomicTdx.hpp" +#include "indentlog/scope.hpp" + +namespace xo { + using xo::scope; + using xo::xtag; + using xo::tostr; + + namespace reflect { + uint32_t + TypeId::s_next_id = 1; + + std::unordered_map> + TypeDescrBase::s_type_table_map; + + std::unordered_map + TypeDescrBase::s_coalesced_type_table_map; + + std::vector + TypeDescrBase::s_type_table_v; + + TypeDescrW + TypeDescrBase::require(std::type_info const * tinfo, + std::string_view canonical_name, + std::unique_ptr tdextra) + { + /* 1. lookup by tinfo hash_code in s_type_table_map */ + { + auto ix = s_type_table_map.find(TypeInfoRef(tinfo)); + + if ((ix != s_type_table_map.end()) && ix->second) + return ix->second.get(); + } + + /* 2. lookup by tinfo hash_code in s_coalesced_type_table_map */ + { + auto ix = s_coalesced_type_table_map.find(TypeInfoRef(tinfo)); + + if ((ix != s_coalesced_type_table_map.end()) && ix->second) + return ix->second; + } + + /* 3. O(n) lookup by canonical_name, before we create a new slot. + * + * Have to accept that on clang type_info objects aren't always unique (!$@#!!) + * + * TODO: lookup table keyed by canonical_name + */ + for (TypeDescrBase * x : s_type_table_v) { + if (x && (x->canonical_name() == canonical_name)) { + /* 1. assume *x represents the type associated with tinfo. + * 2. *do* store tinfo in s_coalesced_type_table_map[], + * for faster lookup next time + */ + s_coalesced_type_table_map[TypeInfoRef(tinfo)] = x; + + return x; + } + } + + TypeId id = TypeId::allocate(); + + std::unique_ptr & slot = s_type_table_map[TypeInfoRef(tinfo)]; + + slot.reset(new TypeDescrBase(id, + tinfo, + canonical_name, + std::move(tdextra))); + + if (s_type_table_v.size() <= id.id()) + s_type_table_v.resize(id.id() + 1); + + s_type_table_v[id.id()] = slot.get(); + + return slot.get(); + } /*require*/ + + void + TypeDescrBase::print_reflected_types(std::ostream & os) + { + os << "display(os); + } + } + + os << ">\n"; + } /*print_reflected_types*/ + + namespace { + /* readability hack: + * foo::bar::Quux ==> Quux + * but lookout for template names: + * std::pair ==> pair + */ + std::string_view + unqualified_name(std::string_view const & canonical_name) + { + size_t m = canonical_name.find_first_of('<'); + + /* skip ':', but only in range [0..m) */ + size_t p = canonical_name.find_last_of(':', m); + + if (p == std::string_view::npos) { + return canonical_name; + } else { + if ((canonical_name.substr(0, 9) == "std::pair") + || (canonical_name.substr(0, 13) == "std::_1::pair")) + { + return std::string_view("pair"); + } else { + return std::string_view(canonical_name.substr(p+1)); + } + } + } /*unqualified_name*/ + } /*namespace*/ + + TypeDescrBase::TypeDescrBase(TypeId id, + std::type_info const * tinfo, + std::string_view canonical_name, + std::unique_ptr tdextra) + : id_{std::move(id)}, + typeinfo_{tinfo}, + canonical_name_{std::move(canonical_name)}, + short_name_{unqualified_name(canonical_name_)}, + tdextra_{std::move(tdextra)} + { + } + + TaggedPtr + TypeDescrBase::most_derived_self_tp(void * object) const + { + return this->tdextra_->most_derived_self_tp(this, object); + } /*most_derived_self_tp*/ + + TaggedPtr + TypeDescrBase::child_tp(uint32_t i, void * object) const + { + return this->tdextra_->child_tp(i, object); + } /*child_tp*/ + + void + TypeDescrBase::display(std::ostream & os) const + { + os << "metatype()) + << ">"; + } /*display*/ + + std::string + TypeDescrBase::display_string() const + { + return tostr(*this); + } /*display_string*/ + + bool + TypeDescrBase::mark_complete() + { + bool retval = this->complete_flag_; + + this->complete_flag_ = true; + + return retval; + } /*mark_complete*/ + + void + TypeDescrBase::assign_tdextra(std::unique_ptr tdx) + { + scope log(XO_ENTER0(verbose), + xtag("canonical_name", this->canonical_name()), + xtag("tdextra.old", this->tdextra_.get()), + xtag("metatype.old", (this->tdextra_ + ? this->tdextra_->metatype() + : Metatype::mt_invalid)), + xtag("metatype.new", tdx->metatype())); + + this->complete_flag_ = true; + this->tdextra_ = std::move(tdx); + } /*assign_tdextra*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end TypeDescr.cpp */ diff --git a/src/reflect/TypeDescrExtra.cpp b/src/reflect/TypeDescrExtra.cpp new file mode 100644 index 00000000..e53a216b --- /dev/null +++ b/src/reflect/TypeDescrExtra.cpp @@ -0,0 +1,37 @@ +/* file TypeDescrExtra.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "TypeDescrExtra.hpp" +#include "TypeDescr.hpp" +#include "TaggedPtr.hpp" +#include + +namespace xo { + namespace reflect { + TaggedPtr + TypeDescrExtra::most_derived_self_tp(TypeDescrBase const * object_td, + void * object) const + { + return TaggedPtr(object_td, object); + } /*most_derived_self_tp*/ + + std::string const & + TypeDescrExtra::struct_member_name(uint32_t /*i*/) const { + assert(false); + + static std::string s_null; + return s_null; + } /*struct_member_name*/ + + StructMember const * + TypeDescrExtra::struct_member(uint32_t /*i*/) const { + assert(false); + + return nullptr; + } /*struct_member*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end TypeDescrExtra.cpp */ diff --git a/src/reflect/atomic/AtomicTdx.cpp b/src/reflect/atomic/AtomicTdx.cpp new file mode 100644 index 00000000..42ca9e2f --- /dev/null +++ b/src/reflect/atomic/AtomicTdx.cpp @@ -0,0 +1,24 @@ +/* @file AtomicTdx.cpp */ + +#include "atomic/AtomicTdx.hpp" +#include "TaggedPtr.hpp" + +namespace xo { + namespace reflect { + std::unique_ptr AtomicTdx::make() { + return std::unique_ptr(new AtomicTdx()); + } /*make*/ + + TaggedPtr + AtomicTdx::child_tp(uint32_t /*i*/, void * /*object*/) const { + return TaggedPtr::universal_null(); + } /*child_tp*/ + + std::string const & + AtomicTdx::struct_member_name(uint32_t i) const { + return TypeDescrExtra::struct_member_name(i); + } /*struct_member_name*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end AtomicTdx.cpp */ diff --git a/src/reflect/init_reflect.cpp b/src/reflect/init_reflect.cpp new file mode 100644 index 00000000..20dd1441 --- /dev/null +++ b/src/reflect/init_reflect.cpp @@ -0,0 +1,23 @@ +/* file init_reflect.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "init_reflect.hpp" +#include "subsys/Subsystem.hpp" + +namespace xo { + void + InitSubsys::init() + { + /* placeholder -- expecting there to be non-trivial content soon */ + } /*init*/ + + InitEvidence + InitSubsys::require() + { + return Subsystem::provide("reflect", &init); + } /*require*/ +} /*namespace xo*/ + +/* end init_reflect.cpp */ diff --git a/src/reflect/pointer/PointerTdx.cpp b/src/reflect/pointer/PointerTdx.cpp new file mode 100644 index 00000000..cc81e391 --- /dev/null +++ b/src/reflect/pointer/PointerTdx.cpp @@ -0,0 +1,17 @@ +/* file PointerTdx.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "pointer/PointerTdx.hpp" + +namespace xo { + namespace reflect { + std::string const & + PointerTdx::struct_member_name(uint32_t i) const { + return TypeDescrExtra::struct_member_name(i); + } /*struct_member_name*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end PointerTdx.cpp */ diff --git a/src/reflect/struct/StructMember.cpp b/src/reflect/struct/StructMember.cpp new file mode 100644 index 00000000..59d69056 --- /dev/null +++ b/src/reflect/struct/StructMember.cpp @@ -0,0 +1,36 @@ +/* file StructMember.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "struct/StructMember.hpp" +#include "indentlog/scope.hpp" +#include + +namespace xo { + using xo::scope; + using xo::xtag; + + namespace reflect { + static_assert(std::is_move_constructible_v); + + TaggedPtr + AbstractStructMemberAccessor::member_tp(void * struct_addr) const + { + //XO_SCOPE(lscope); + + TaggedPtr retval = (this + ->member_td() + ->most_derived_self_tp(this->address(struct_addr))); + + //lscope.log(xtag("self_td", this->struct_td()->short_name()), + // xtag("member_td.declared", this->member_td()->short_name()), + // xtag("member_td.actual", retval.td()->short_name())); + + return retval; + } /*member_tp*/ + + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end StructMember.cpp */ diff --git a/src/reflect/struct/StructTdx.cpp b/src/reflect/struct/StructTdx.cpp new file mode 100644 index 00000000..593ad388 --- /dev/null +++ b/src/reflect/struct/StructTdx.cpp @@ -0,0 +1,55 @@ +/* @file StructTdx.cpp */ + +#include "struct/StructTdx.hpp" + +namespace xo { + using std::uint32_t; + + namespace reflect { + std::unique_ptr + StructTdx::make(std::vector member_v, + bool have_to_self_tp, + std::function to_self_tp) + { + return std::unique_ptr(new StructTdx(std::move(member_v), + have_to_self_tp, + std::move(to_self_tp))); + } /*make*/ + + TaggedPtr + StructTdx::child_tp(uint32_t i, void * object) const + { + if (i >= this->member_v_.size()) { + /* TODO: raise exception here? */ + return TaggedPtr::universal_null(); + } + + StructMember const & member_info = this->member_v_[i]; + + return member_info.get_member_tp(object); + + } /*get_child*/ + + std::string const & + StructTdx::struct_member_name(uint32_t i) const + { + StructMember const * sm = this->struct_member(i); + + return sm->member_name(); + } /*struct_member_name*/ + + StructMember const * + StructTdx::struct_member(uint32_t i) const + { + if (i >= this->member_v_.size()) { + /* TODO: raise exception here */ + assert(false); + return nullptr; + } + + return &(this->member_v_[i]); + } /*struct_member*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end StructTdx.cpp */ diff --git a/src/reflect/vector/VectorTdx.cpp b/src/reflect/vector/VectorTdx.cpp new file mode 100644 index 00000000..fd2e40e4 --- /dev/null +++ b/src/reflect/vector/VectorTdx.cpp @@ -0,0 +1,20 @@ +/* file VectorTdx.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "vector/VectorTdx.hpp" + +namespace xo { + namespace reflect { + std::string const & + VectorTdx::struct_member_name(uint32_t i) const { + return TypeDescrExtra::struct_member_name(i); + } /*struct_member_name*/ + + } /*namespace reflect*/ + +} /*namespace xo*/ + + +/* end VectorTdx.cpp */ diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..80ce49ac --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,57 @@ +# build unittest reflect/utest + +set(SELF_EXECUTABLE_NAME utest.reflect) +set(SELF_SOURCE_FILES reflect_utest_main.cpp StructReflector.test.cpp VectorTdx.test.cpp StructTdx.test.cpp) + +add_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) +xo_include_options(${SELF_EXECUTABLE_NAME}) + +add_test(NAME ${SELF_EXECUTABLE_NAME} COMMAND ${SELF_EXECUTABLE_NAME}) +target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) + +# ---------------------------------------------------------------- +# generic project dependency + +# PROJECT_SOURCE_DIR: +# so we can for example write +# #include "indentlog/scope.hpp" +# from anywhere in the project +# PROJECT_BINARY_DIR: +# since version file will be in build directory, need that directory +# to also be included in compiler's include path +# +target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC + ${PROJECT_SOURCE_DIR} + ${PROJECT_BINARY_DIR}) + +# ---------------------------------------------------------------- +# internal dependencies: logutil, ... + +target_link_libraries(${SELF_EXECUTABLE_NAME} PUBLIC reflect) + +# ---------------------------------------------------------------- +# 3rd part dependency: catch2: + +find_package(Catch2 2 REQUIRED) + +# need this so that catch2/include appears in compile_commands.json, +# on which lsp integration relies. +# +# See also /nix/store/*-catch2-*/lib/cmake/Catch2/ParseAndAddCatchTests.cmake; +# commands here derived from ^ .cmake file +# +#find_path(CATCH_INCLUDE_DIR "catch2/catch.hpp") +#target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC ${CATCH_INCLUDE_DIR}) + +# ---------------------------------------------------------------- +# make standard directories for std:: includes explicit +# so that +# (1) they appear in compile_commands.json. +# (2) clangd (run from emacs lsp-mode) can find them +# +if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES + ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +endif() + +# end CMakeLists.txt diff --git a/utest/StructReflector.test.cpp b/utest/StructReflector.test.cpp new file mode 100644 index 00000000..5bf87ade --- /dev/null +++ b/utest/StructReflector.test.cpp @@ -0,0 +1,142 @@ +/* file StructReflector.test.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "reflect/Reflect.hpp" +#include "reflect/StructReflector.hpp" +#include + +#define STRINGIFY(x) #x + +namespace xo { + using xo::reflect::Reflect; + using xo::reflect::TaggedPtr; + using xo::reflect::StructReflector; + using xo::reflect::Reflect; + + namespace ut { + namespace { + struct TestStruct0 {}; + struct TestStruct1 {}; + } + + TEST_CASE("struct-reflect-empty", "[reflect]") { + StructReflector sr; + + REQUIRE(Reflect::is_reflected() == false); + REQUIRE(Reflect::is_reflected() == true); + + TestStruct0 recd0; + TaggedPtr tp = Reflect::make_tp(&recd0); + + REQUIRE(tp.address() == &recd0); + REQUIRE(tp.td() == Reflect::require()); + + REQUIRE(tp.n_child() == 0); + + REQUIRE(tp.get_child(0).is_universal_null()); + REQUIRE(tp.get_child(0).td() == nullptr); + REQUIRE(tp.get_child(0).address() == nullptr); + } /*TEST_CASE(struct-reflect-empty)*/ + + namespace { + struct TestStructS1 { int x_; }; + } + + TEST_CASE("struct-reflect-s1", "[reflect]") { + StructReflector sr; + + REQUIRE(Reflect::is_reflected() == true); + + //sr.reflect_member(STRINGIFY(x_), &decltype(sr)::struct_t::x_); + REFLECT_LITERAL_MEMBER(sr, x_); + + REQUIRE(!Reflect::require()->is_struct()); + + sr.require_complete(); + + REQUIRE(Reflect::require()->is_struct()); + } /*TEST_CASE(struct-reflect-s1)*/ + + namespace { + struct TestStructS2 { int x_; }; + } + + TEST_CASE("struct-reflect-s2", "[reflect]") { + StructReflector sr; + + REQUIRE(Reflect::is_reflected() == true); + + //sr.reflect_member(STRINGIFY(x_), &decltype(sr)::struct_t::x_); + REFLECT_MEMBER(sr, x); + + REQUIRE(!Reflect::require()->is_struct()); + + sr.require_complete(); + + REQUIRE(Reflect::require()->is_struct()); + + TestStructS2 recd1{666}; + + TaggedPtr tp = Reflect::make_tp(&recd1); + + REQUIRE(tp.address() == &recd1); + REQUIRE(tp.td() == Reflect::require()); + + REQUIRE(tp.n_child() == 1); + + REQUIRE(tp.get_child(0).td() == Reflect::require()); + REQUIRE(tp.get_child(0).address() == &(recd1.x_)); + + REQUIRE(tp.get_child(1).is_universal_null()); + } /*TEST_CASE(struct-reflect-s2)*/ + + namespace { + struct TestStructS3 { int x_; char y_; double z_; }; + } + + TEST_CASE("struct-reflect-s3", "[reflect]") { + StructReflector sr; + + REQUIRE(Reflect::is_reflected() == true); + + REFLECT_MEMBER(sr, x); + REFLECT_MEMBER(sr, y); + REFLECT_MEMBER(sr, z); + + REQUIRE(!Reflect::require()->is_struct()); + + sr.require_complete(); + + REQUIRE(Reflect::require()->is_struct()); + + /* verify we can traverse reflected instances */ + TestStructS3 recd1{666, 'Y', -1.234}; + + TaggedPtr tp = Reflect::make_tp(&recd1); + + REQUIRE(tp.address() == &recd1); + REQUIRE(tp.td() == Reflect::require()); + + REQUIRE(tp.n_child() == 3); + + REQUIRE(tp.get_child(0).td() == Reflect::require()); + REQUIRE(tp.get_child(0).address() == &(recd1.x_)); + + REQUIRE(tp.get_child(1).td() == Reflect::require()); + REQUIRE(tp.get_child(1).address() == &(recd1.y_)); + + REQUIRE(tp.get_child(2).td() == Reflect::require()); + REQUIRE(tp.get_child(2).address() == &(recd1.z_)); + + REQUIRE(tp.get_child(3).is_universal_null()); + REQUIRE(tp.get_child(3).td() == nullptr); + REQUIRE(tp.get_child(3).address() == nullptr); + + } /*TEST_CASE(struct-reflect-s3)*/ + } /*namespace ut */ +} /*namespace xo*/ + + +/* end StructReflector.test.cpp */ diff --git a/utest/StructTdx.test.cpp b/utest/StructTdx.test.cpp new file mode 100644 index 00000000..c20e2139 --- /dev/null +++ b/utest/StructTdx.test.cpp @@ -0,0 +1,59 @@ +/* file StructTdx.test.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "reflect/Reflect.hpp" +#include + +namespace xo { + using xo::reflect::Reflect; + using xo::reflect::TaggedPtr; + using xo::reflect::TypeDescr; + using xo::reflect::Metatype; + + namespace ut { + TEST_CASE("std-pair-reflect", "[reflect]") { + std::pair p; + + TaggedPtr tp = Reflect::make_tp(&p); + //TypeDescr td = Reflect::require>(); + + REQUIRE(Reflect::is_reflected>() == true); + + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &p); + REQUIRE(tp.is_struct()); + REQUIRE(tp.is_vector() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_struct); + REQUIRE(tp.recover_native>() == &p); + REQUIRE(tp.n_child() == 2); /* struct with 2 members */ + REQUIRE(tp.struct_member_name(0) == "first"); + REQUIRE(tp.struct_member_name(1) == "second"); + + TaggedPtr tp0 = tp.get_child(0); + + REQUIRE(tp0.td()->complete_flag()); + REQUIRE(tp0.address() == &(p.first)); + REQUIRE(!tp0.is_vector()); + REQUIRE(!tp0.is_struct()); + REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp0.recover_native() == &(p.first)); + REQUIRE(tp0.n_child() == 0); + + TaggedPtr tp1 = tp.get_child(1); + + REQUIRE(tp1.td()->complete_flag()); + REQUIRE(tp1.address() == &(p.second)); + REQUIRE(!tp1.is_vector()); + REQUIRE(!tp1.is_struct()); + REQUIRE(tp1.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp1.recover_native() == &(p.second)); + REQUIRE(tp1.n_child() == 0); + + } /*TEST_CASE(std-pair-reflect)*/ + + } /*namespace ut*/ +} /*namespace xo*/ + +/* end VectorTdx.test.cpp */ diff --git a/utest/VectorTdx.test.cpp b/utest/VectorTdx.test.cpp new file mode 100644 index 00000000..3836b4f7 --- /dev/null +++ b/utest/VectorTdx.test.cpp @@ -0,0 +1,181 @@ +/* file VectorTdx.test.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "reflect/Reflect.hpp" +#include + +namespace xo { + using xo::reflect::Reflect; + using xo::reflect::TaggedPtr; + using xo::reflect::TypeDescr; + using xo::reflect::Metatype; + + namespace ut { + TEST_CASE("std-vector-reflect-empty", "[reflect]") { + std::vector v; + + TaggedPtr tp = Reflect::make_tp(&v); + //TypeDescr td = Reflect::require>(); + + REQUIRE(Reflect::is_reflected>() == true); + + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &v); + REQUIRE(tp.is_vector()); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_vector); + REQUIRE(tp.recover_native>() == &v); + REQUIRE(tp.n_child() == 0); /*since empty vector*/ + // REQUIRE(tp.child_td(0) == ... + } /*TEST_CASE(std-vector-reflect-empty)*/ + + TEST_CASE("std-vector-reflect-one", "[reflect]") { + std::vector v = { 1.123 }; + + TaggedPtr tp = Reflect::make_tp(&v); + + REQUIRE(Reflect::is_reflected>() == true); + + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &v); + REQUIRE(tp.is_vector()); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_vector); + REQUIRE(tp.recover_native>() == &v); + REQUIRE(tp.n_child() == 1); + + TaggedPtr tp0 = tp.get_child(0); + + REQUIRE(tp0.td()->complete_flag()); + REQUIRE(tp0.address() == &(v[0])); + REQUIRE(!tp0.is_vector()); + REQUIRE(!tp0.is_struct()); + REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp0.recover_native() == &(v[0])); + REQUIRE(tp0.n_child() == 0); + } /*TEST_CASE(std-vector-reflect-one)*/ + + TEST_CASE("std-vector-reflect-two", "[reflect]") { + std::vector v = { 1.123, 2.234 }; + + TaggedPtr tp = Reflect::make_tp(&v); + + REQUIRE(Reflect::is_reflected>() == true); + + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &v); + REQUIRE(tp.is_vector()); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_vector); + REQUIRE(tp.recover_native>() == &v); + REQUIRE(tp.n_child() == 2); + + TaggedPtr tp0 = tp.get_child(0); + + REQUIRE(tp0.td()->complete_flag()); + REQUIRE(tp0.address() == &(v[0])); + REQUIRE(!tp0.is_vector()); + REQUIRE(!tp0.is_struct()); + REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp0.recover_native() == &(v[0])); + REQUIRE(tp0.n_child() == 0); + + TaggedPtr tp1 = tp.get_child(1); + + REQUIRE(tp1.td()->complete_flag()); + REQUIRE(tp1.address() == &(v[1])); + REQUIRE(!tp1.is_vector()); + REQUIRE(!tp1.is_struct()); + REQUIRE(tp1.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp1.recover_native() == &(v[1])); + REQUIRE(tp1.n_child() == 0); + } /*TEST(std-vector-reflect-two)*/ + + // ----- std::array ----- + + TEST_CASE("std-array-reflect-empty", "[reflect]") { + std::array v; + + TaggedPtr tp = Reflect::make_tp(&v); + //TypeDescr td = Reflect::require>(); + + REQUIRE(Reflect::is_reflected>() == true); + + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &v); + REQUIRE(tp.is_vector()); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_vector); + REQUIRE(tp.recover_native>() == &v); + REQUIRE(tp.n_child() == 0); /*since empty vector*/ + // REQUIRE(tp.child_td(0) == ... + } /*TEST_CASE(std-array-reflect-empty)*/ + + TEST_CASE("std-array-reflect-one", "[reflect]") { + std::array v = { 1.123 }; + + TaggedPtr tp = Reflect::make_tp(&v); + + REQUIRE(Reflect::is_reflected>() == true); + + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &v); + REQUIRE(tp.is_vector()); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_vector); + REQUIRE(tp.recover_native>() == &v); + REQUIRE(tp.n_child() == 1); + + TaggedPtr tp0 = tp.get_child(0); + + REQUIRE(tp0.td()->complete_flag()); + REQUIRE(tp0.address() == &(v[0])); + REQUIRE(!tp0.is_vector()); + REQUIRE(!tp0.is_struct()); + REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp0.recover_native() == &(v[0])); + REQUIRE(tp0.n_child() == 0); + } /*TEST_CASE(std-array-reflect-one)*/ + + TEST_CASE("std-array-reflect-two", "[reflect]") { + std::array v = { 1.123, 2.234 }; + + TaggedPtr tp = Reflect::make_tp(&v); + + REQUIRE(Reflect::is_reflected>() == true); + + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &v); + REQUIRE(tp.is_vector()); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_vector); + REQUIRE(tp.recover_native>() == &v); + REQUIRE(tp.n_child() == 2); + + TaggedPtr tp0 = tp.get_child(0); + + REQUIRE(tp0.td()->complete_flag()); + REQUIRE(tp0.address() == &(v[0])); + REQUIRE(!tp0.is_vector()); + REQUIRE(!tp0.is_struct()); + REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp0.recover_native() == &(v[0])); + REQUIRE(tp0.n_child() == 0); + + TaggedPtr tp1 = tp.get_child(1); + + REQUIRE(tp1.td()->complete_flag()); + REQUIRE(tp1.address() == &(v[1])); + REQUIRE(!tp1.is_vector()); + REQUIRE(!tp1.is_struct()); + REQUIRE(tp1.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp1.recover_native() == &(v[1])); + REQUIRE(tp1.n_child() == 0); + } /*TEST(std-array-reflect-two)*/ + + } /*namespace ut*/ +} /*namespace xo*/ + +/* end VectorTdx.test.cpp */ diff --git a/utest/reflect_utest_main.cpp b/utest/reflect_utest_main.cpp new file mode 100644 index 00000000..91c62d09 --- /dev/null +++ b/utest/reflect_utest_main.cpp @@ -0,0 +1,6 @@ +/* file reflect_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end reflect_utest_main.cpp */ From ab23f431af88de666c69912f76e88514bffbab91 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 25 Sep 2023 17:55:04 -0400 Subject: [PATCH 0134/2693] + build/install notes --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 27fe0373..fb6682d4 100644 --- a/README.md +++ b/README.md @@ -1 +1,19 @@ # reflection library + +### build + install +``` +$ cd reflect +$ mkdir build +$ cd build +$ cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` + +### build for unit test coverage +``` +$ cd refcnt +$ mkdir build-ccov +$ cd build-ccov +$ cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` From 57808cc6142a237e65d2f9893364dc8a3db9a2c3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 25 Sep 2023 17:56:57 -0400 Subject: [PATCH 0135/2693] + .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..6637741b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +build*/* From 4cc5f4049fc96cdf96b2aabda438bbea5bb95900 Mon Sep 17 00:00:00 2001 From: Roland Date: Mon, 25 Sep 2023 17:59:47 -0400 Subject: [PATCH 0136/2693] Create cmake-single-platform.yml --- .github/workflows/cmake-single-platform.yml | 39 +++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/cmake-single-platform.yml diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml new file mode 100644 index 00000000..28c6f783 --- /dev/null +++ b/.github/workflows/cmake-single-platform.yml @@ -0,0 +1,39 @@ +# This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml +name: CMake on a single platform + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Test + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} + From 94788b53f0a14d3389bc9ba88dcd45402f812cbe Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 25 Sep 2023 18:05:44 -0400 Subject: [PATCH 0137/2693] github: action for git submodules, take 1 --- .github/workflows/cmake-single-platform.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 28c6f783..a0fdcdc6 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -22,6 +22,10 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Update submodules + # update/fetch submodules (attached as top-level children of reflect/repo/) + run: git submodule update --init --recursive + - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type @@ -36,4 +40,3 @@ jobs: # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest -C ${{env.BUILD_TYPE}} - From 31e0c14930b4643ef7021ea3f993951e175aa1ed Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 25 Sep 2023 18:14:10 -0400 Subject: [PATCH 0138/2693] github: submodule actions, take 2 --- .github/workflows/cmake-single-platform.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index a0fdcdc6..8cda1cd7 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -20,11 +20,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - - name: Update submodules - # update/fetch submodules (attached as top-level children of reflect/repo/) - run: git submodule update --init --recursive + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: true - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. From d1833c5fa60c76d380ce1a914bf8dd5ec863a955 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 25 Sep 2023 18:16:20 -0400 Subject: [PATCH 0139/2693] github: apt-get catch2 dependency --- .github/workflows/cmake-single-platform.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 8cda1cd7..663a266b 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -20,6 +20,9 @@ jobs: runs-on: ubuntu-latest steps: + - name: Install catch2 + run: sudo apt-get install -y catch2 + - name: Checkout uses: actions/checkout@v3 with: From 8f36e73b18c22ee4cd57caaa985360213e006ebe Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 01:27:13 -0400 Subject: [PATCH 0140/2693] build: remove submodules (moved to reflect-sm project) --- .gitmodules | 9 ---- CMakeLists.txt | 85 -------------------------------------- repo/README.md | 5 --- repo/refcnt | 1 - repo/subsys | 1 - src/reflect/CMakeLists.txt | 6 ++- 6 files changed, 4 insertions(+), 103 deletions(-) delete mode 100644 .gitmodules delete mode 100644 repo/README.md delete mode 160000 repo/refcnt delete mode 160000 repo/subsys diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index ebcf3a27..00000000 --- a/.gitmodules +++ /dev/null @@ -1,9 +0,0 @@ -[submodule "repo/indentlog"] - path = repo/indentlog - url = git@github.com:Rconybea/indentlog.git -[submodule "repo/refcnt"] - path = repo/refcnt - url = git@github.com:Rconybea/refcnt.git -[submodule "repo/subsys"] - path = repo/subsys - url = git@github.com:Rconybea/subsys.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f777c59..078681e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,91 +41,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") -# ---------------------------------------------------------------- -# external projects (need these to exist before add_subdirectory() below) -# -# we are expecting these projects to coexist peacefully in build/local -# (i.e. can run their `make install` steps independently with prefix build/local, -# without any collisions) -# - -include(ExternalProject) - -## ----- indentlog ------ - -# NOTE: we could have cmake handle git interaction, -# but we want source for certain dependencies to live in a location -# that's suitable for accepting changes + coordinated commits. -# In particular, not in the build directory! -# -externalproject_add( - project_indentlog - SOURCE_DIR ${PROJECT_SOURCE_DIR}/repo/indentlog - BINARY_DIR ${PROJECT_BINARY_DIR}/ext/indentlog - INSTALL_DIR ${PROJECT_BINARY_DIR}/local - CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCODE_COVERAGE=${CODE_COVERAGE} -DCMAKE_PREFIX_PATH= -DCMAKE_INSTALL_PREFIX= - BUILD_COMMAND make - INSTALL_COMMAND make install - TEST_BEFORE_INSTALL True -) - -add_library(indentlog INTERFACE IMPORTED) -#set_property(TARGET indentlog PROPERTY IMPORTED_LOCATION ${PROJECT_BINARY_DIR}/local/lib/libindentlog.so) -add_dependencies(indentlog project_indentlog) - -# runs ctest in indentlog build dir -add_test(NAME indentlog COMMAND ${PROJECT_SOURCE_DIR}/cmake/run-external-ctest ${PROJECT_BINARY_DIR}/ext/indentlog) -#target_code_coverage(indentlog EXTERNAL AUTO ALL) - -# ----- subsys ----- - -externalproject_add( - project_subsys - SOURCE_DIR ${PROJECT_SOURCE_DIR}/repo/subsys - BINARY_DIR ${PROJECT_BINARY_DIR}/ext/subsys - INSTALL_DIR ${PROJECT_BINARY_DIR}/local - CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCODE_COVERAGE=${CODE_COVERAGE} -DCMAKE_PREFIX_PATH= -DCMAKE_INSTALL_PREFIX= - BUILD_COMMAND make - INSTALL_COMMAND make install - TEST_BEFORE_INSTALL True -) - -add_library(subsys INTERFACE IMPORTED) -add_dependencies(subsys project_subsys) - -# runs ctest in subsys build dir -add_test(NAME subsys COMMAND ${PROJECT_SOURCE_DIR}/cmake/run-external-ctest ${PROJECT_BINARY_DIR}/ext/subsys) - -# ----- refcnt ----- - -# CMAKE_ARGS -# CMAKE_BUILD_TYPE propagate Debug/Release build type -# CODE_COVERAGE propagate code coverage setting -# CMAKE_PREFIX_PATH path for support cmake files of dependencies (needed for find_package() to work) -# CMAKE_INSTALL_PREFIX install subproject here -# SOURCE_DIR -- where to find already established source code -# BINARY_DIR -- run build for external project here -# INSTALL_DIR -- (temporarily) install external project here -# -externalproject_add( - project_refcnt - SOURCE_DIR ${PROJECT_SOURCE_DIR}/repo/refcnt - BINARY_DIR ${PROJECT_BINARY_DIR}/ext/refcnt - INSTALL_DIR ${PROJECT_BINARY_DIR}/local - CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCODE_COVERAGE=${CODE_COVERAGE} -DCMAKE_PREFIX_PATH= -DCMAKE_INSTALL_PREFIX= - BUILD_COMMAND make - INSTALL_COMMAND make install - TEST_BEFORE_INSTALL True -) - -add_library(refcnt SHARED IMPORTED) -set_property(TARGET refcnt PROPERTY IMPORTED_LOCATION ${PROJECT_BINARY_DIR}/local/lib/librefcnt.so) -add_dependencies(refcnt project_refcnt) -add_dependencies(refcnt project_indentlog) - -# runs ctest in refcnt build dir -add_test(NAME refcnt COMMAND ${PROJECT_SOURCE_DIR}/cmake/run-external-ctest ${PROJECT_BINARY_DIR}/ext/refcnt) - # ---------------------------------------------------------------- # sources diff --git a/repo/README.md b/repo/README.md deleted file mode 100644 index 3acdcde4..00000000 --- a/repo/README.md +++ /dev/null @@ -1,5 +0,0 @@ - -``` -cd reflect/repo -git submodule add git@github.com/someusername/someproject.git -``` diff --git a/repo/refcnt b/repo/refcnt deleted file mode 160000 index 4b3c854f..00000000 --- a/repo/refcnt +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4b3c854fc7872ba404ce4e0af25ecfdc47822ae1 diff --git a/repo/subsys b/repo/subsys deleted file mode 160000 index 1c384e1f..00000000 --- a/repo/subsys +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1c384e1ff0a01885bdf0713be858085814b6ece6 diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index e1c1f65f..799cd10f 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -25,7 +25,8 @@ xo_install_library(${SELF_LIBRARY_NAME}) #xo_refcnt_dependency(${SELF_LIBRARY_NAME}) #xo_indentlog_dependency(${SELF_LIBRARY_NAME}) -add_dependencies(${SELF_LIBRARY_NAME} refcnt) +find_package(refcnt REQUIRED) +#add_dependencies(${SELF_LIBRARY_NAME} refcnt) target_include_directories( ${SELF_LIBRARY_NAME} PUBLIC $ @@ -33,7 +34,8 @@ target_include_directories( ) target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC refcnt) -add_dependencies(${SELF_LIBRARY_NAME} indentlog) +find_package(indentlog REQUIRED) +#add_dependencies(${SELF_LIBRARY_NAME} indentlog) # note: can't use find_package() here, # because find_package() needs to run successfully before # dependency gets installed. From 129e762c7b6ea783425544fdfcd8bb52800f0112 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 01:32:50 -0400 Subject: [PATCH 0141/2693] build: + CMAKE_INSTALL_RPATH + remov externalproject_add() calls --- CMakeLists.txt | 87 ++------------------------------------------------ 1 file changed, 3 insertions(+), 84 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f777c59..dc1646f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,90 +41,9 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") -# ---------------------------------------------------------------- -# external projects (need these to exist before add_subdirectory() below) -# -# we are expecting these projects to coexist peacefully in build/local -# (i.e. can run their `make install` steps independently with prefix build/local, -# without any collisions) -# - -include(ExternalProject) - -## ----- indentlog ------ - -# NOTE: we could have cmake handle git interaction, -# but we want source for certain dependencies to live in a location -# that's suitable for accepting changes + coordinated commits. -# In particular, not in the build directory! -# -externalproject_add( - project_indentlog - SOURCE_DIR ${PROJECT_SOURCE_DIR}/repo/indentlog - BINARY_DIR ${PROJECT_BINARY_DIR}/ext/indentlog - INSTALL_DIR ${PROJECT_BINARY_DIR}/local - CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCODE_COVERAGE=${CODE_COVERAGE} -DCMAKE_PREFIX_PATH= -DCMAKE_INSTALL_PREFIX= - BUILD_COMMAND make - INSTALL_COMMAND make install - TEST_BEFORE_INSTALL True -) - -add_library(indentlog INTERFACE IMPORTED) -#set_property(TARGET indentlog PROPERTY IMPORTED_LOCATION ${PROJECT_BINARY_DIR}/local/lib/libindentlog.so) -add_dependencies(indentlog project_indentlog) - -# runs ctest in indentlog build dir -add_test(NAME indentlog COMMAND ${PROJECT_SOURCE_DIR}/cmake/run-external-ctest ${PROJECT_BINARY_DIR}/ext/indentlog) -#target_code_coverage(indentlog EXTERNAL AUTO ALL) - -# ----- subsys ----- - -externalproject_add( - project_subsys - SOURCE_DIR ${PROJECT_SOURCE_DIR}/repo/subsys - BINARY_DIR ${PROJECT_BINARY_DIR}/ext/subsys - INSTALL_DIR ${PROJECT_BINARY_DIR}/local - CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCODE_COVERAGE=${CODE_COVERAGE} -DCMAKE_PREFIX_PATH= -DCMAKE_INSTALL_PREFIX= - BUILD_COMMAND make - INSTALL_COMMAND make install - TEST_BEFORE_INSTALL True -) - -add_library(subsys INTERFACE IMPORTED) -add_dependencies(subsys project_subsys) - -# runs ctest in subsys build dir -add_test(NAME subsys COMMAND ${PROJECT_SOURCE_DIR}/cmake/run-external-ctest ${PROJECT_BINARY_DIR}/ext/subsys) - -# ----- refcnt ----- - -# CMAKE_ARGS -# CMAKE_BUILD_TYPE propagate Debug/Release build type -# CODE_COVERAGE propagate code coverage setting -# CMAKE_PREFIX_PATH path for support cmake files of dependencies (needed for find_package() to work) -# CMAKE_INSTALL_PREFIX install subproject here -# SOURCE_DIR -- where to find already established source code -# BINARY_DIR -- run build for external project here -# INSTALL_DIR -- (temporarily) install external project here -# -externalproject_add( - project_refcnt - SOURCE_DIR ${PROJECT_SOURCE_DIR}/repo/refcnt - BINARY_DIR ${PROJECT_BINARY_DIR}/ext/refcnt - INSTALL_DIR ${PROJECT_BINARY_DIR}/local - CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCODE_COVERAGE=${CODE_COVERAGE} -DCMAKE_PREFIX_PATH= -DCMAKE_INSTALL_PREFIX= - BUILD_COMMAND make - INSTALL_COMMAND make install - TEST_BEFORE_INSTALL True -) - -add_library(refcnt SHARED IMPORTED) -set_property(TARGET refcnt PROPERTY IMPORTED_LOCATION ${PROJECT_BINARY_DIR}/local/lib/librefcnt.so) -add_dependencies(refcnt project_refcnt) -add_dependencies(refcnt project_indentlog) - -# runs ctest in refcnt build dir -add_test(NAME refcnt COMMAND ${PROJECT_SOURCE_DIR}/cmake/run-external-ctest ${PROJECT_BINARY_DIR}/ext/refcnt) +if(NOT CMAKE_INSTALL_RPATH) + set(CMAKE_INSTALL_RPATH $(CMAKE_INSTALL_PREFIX)/lib CACHE STRING "runpath in installed libraries/executables") +endif() # ---------------------------------------------------------------- # sources From 56154d53af8d87f1e2a6f5432335fecd95a41b1c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 01:42:18 -0400 Subject: [PATCH 0142/2693] build: drop repo/indentlog (was submodule w/ a preceding commit) --- repo/indentlog | 1 - 1 file changed, 1 deletion(-) delete mode 160000 repo/indentlog diff --git a/repo/indentlog b/repo/indentlog deleted file mode 160000 index bf92724a..00000000 --- a/repo/indentlog +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bf92724aa5d123b6acb9b00318e579828fb9ff38 From 06a9f72ef5c08df1b21c275fac4fc04d43f0f8b8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 11:43:12 -0400 Subject: [PATCH 0143/2693] github: + checkout/build/install indentlog --- .github/workflows/cmake-single-platform.yml | 23 +++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 663a266b..9de50c13 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -23,21 +23,36 @@ jobs: - name: Install catch2 run: sudo apt-get install -y catch2 - - name: Checkout + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + run: cmake --install ${{github.workspace}}/build_indentlog + + - name: Checkout reflect uses: actions/checkout@v3 with: submodules: true - - name: Configure CMake + - name: Configure reflect # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - - name: Build + - name: Build reflect # Build your program with the given configuration run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - - name: Test + - name: Test reflect working-directory: ${{github.workspace}}/build # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail From 955462003b4cb6abf85358795dc4bab7734a796a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 11:46:17 -0400 Subject: [PATCH 0144/2693] github: checkout/build/install subsys dep --- .github/workflows/cmake-single-platform.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 9de50c13..81fbb2eb 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -38,6 +38,21 @@ jobs: - name: Install indentlog run: cmake --install ${{github.workspace}}/build_indentlog + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + run: cmake --install ${{github.workspace}}/build_subsys + - name: Checkout reflect uses: actions/checkout@v3 with: From d1be10ab3a5be4f8aa9f890aa0f97b30083079d6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 11:48:02 -0400 Subject: [PATCH 0145/2693] github: checkout/build/install refcnt dep --- .github/workflows/cmake-single-platform.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 81fbb2eb..df05cf9b 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -53,6 +53,21 @@ jobs: - name: Install subsys run: cmake --install ${{github.workspace}}/build_subsys + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + run: cmake --install ${{github.workspace}}/build_refcnt + - name: Checkout reflect uses: actions/checkout@v3 with: From d7232b4da6a9c06ce24f892c65db0b5da143f6ec Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 11:52:18 -0400 Subject: [PATCH 0146/2693] github: fix installdir for sibling xo deps --- .github/workflows/cmake-single-platform.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index df05cf9b..b921565b 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -70,13 +70,11 @@ jobs: - name: Checkout reflect uses: actions/checkout@v3 - with: - submodules: true - name: Configure reflect # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: cmake -B ${{github.workspace}}/build -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - name: Build reflect # Build your program with the given configuration From 5b4d9331c8c60a69e850617e23970d635aa19537 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 12:32:12 -0400 Subject: [PATCH 0147/2693] build: provide typical default value for CMAKE_INSTALL_RPATH --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ed3c4486..a0cdb52c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,6 @@ include(cmake/code-coverage.cmake) # unit test setup enable_testing() - # activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) add_code_coverage() # 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. @@ -39,6 +38,10 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") +if(NOT CMAKE_INSTALL_RPATH) + set(CMAKE_INSTALL_RPATH $(CMAKE_INSTALL_PREFIX)/lib CACHE STRING "runpath in installed libraries/executables") +endif() + # ---------------------------------------------------------------- # sources From 5059252204a534ee8340d0e06a9beafd0b54ba1e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 13:00:46 -0400 Subject: [PATCH 0148/2693] github: use specific build directory for reflect --- .github/workflows/cmake-single-platform.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index b921565b..70826978 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -23,6 +23,7 @@ jobs: - name: Install catch2 run: sudo apt-get install -y catch2 + - name: Clone indentlog uses: actions/checkout@v3 with: @@ -38,6 +39,7 @@ jobs: - name: Install indentlog run: cmake --install ${{github.workspace}}/build_indentlog + - name: Clone subsys uses: actions/checkout@v3 with: @@ -47,6 +49,7 @@ jobs: - name: Configure subsys run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + - name: Build subsys run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} @@ -59,6 +62,7 @@ jobs: repository: Rconybea/refcnt path: repo/refcnt + - name: Configure refcnt run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt @@ -71,17 +75,19 @@ jobs: - name: Checkout reflect uses: actions/checkout@v3 + + - name: Configure reflect # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - name: Build reflect # Build your program with the given configuration - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} - name: Test reflect - working-directory: ${{github.workspace}}/build + working-directory: ${{github.workspace}}/build_reflect # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest -C ${{env.BUILD_TYPE}} From e636c59f62b47eecce81d85aaa65f3b628d385d8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 13:04:24 -0400 Subject: [PATCH 0149/2693] github: try --debug-find --- .github/workflows/cmake-single-platform.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 70826978..a9b9ac4f 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -80,7 +80,7 @@ jobs: - name: Configure reflect # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} --debug-find - name: Build reflect # Build your program with the given configuration From ce7154db57cf13456798158fad570da4fd05b830 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 13:44:21 -0400 Subject: [PATCH 0150/2693] build: use cmake find_package() in CONFIG mode --- src/reflect/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index 799cd10f..af38df15 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -25,7 +25,7 @@ xo_install_library(${SELF_LIBRARY_NAME}) #xo_refcnt_dependency(${SELF_LIBRARY_NAME}) #xo_indentlog_dependency(${SELF_LIBRARY_NAME}) -find_package(refcnt REQUIRED) +find_package(refcnt CONFIG REQUIRED) #add_dependencies(${SELF_LIBRARY_NAME} refcnt) target_include_directories( ${SELF_LIBRARY_NAME} PUBLIC @@ -34,7 +34,7 @@ target_include_directories( ) target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC refcnt) -find_package(indentlog REQUIRED) +find_package(indentlog CONFIG REQUIRED) #add_dependencies(${SELF_LIBRARY_NAME} indentlog) # note: can't use find_package() here, # because find_package() needs to run successfully before From 3d8fd35e82f6373076286aae8ae587bfc220dd6b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 14:03:45 -0400 Subject: [PATCH 0151/2693] build: print cmake version --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index dc1646f0..2ddd1ad2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 3.10) +message(CMAKE_VERSION=${CMAKE_VERSION}) + project(reflect VERSION 0.1) enable_language(CXX) From 4430470f568a44d2b1ec325368cb3a637fe0c33c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 15:08:43 -0400 Subject: [PATCH 0152/2693] github: debug-find while configuring refcnt (cf for reflect) --- .github/workflows/cmake-single-platform.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index a9b9ac4f..58b3e490 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -64,7 +64,7 @@ jobs: - name: Configure refcnt - run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local --debug-find repo/refcnt - name: Build refcnt run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} From 34b7722410c44568e0420c74af2f8f08b7aa9d1c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 15:13:03 -0400 Subject: [PATCH 0153/2693] github: try triggering indentlog failure --- src/reflect/CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index af38df15..d30bd794 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -46,6 +46,15 @@ target_include_directories( ) target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC indentlog) +find_package(refcnt CONFIG REQUIRED) +#add_dependencies(${SELF_LIBRARY_NAME} refcnt) +target_include_directories( + ${SELF_LIBRARY_NAME} PUBLIC + $ + $ +) +target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC refcnt) + # ---------------------------------------------------------------- # 3rd party dependency: boost: From 8e1b51c96f2e825731e28cc7a48ebbaa6f5b732b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 15:21:02 -0400 Subject: [PATCH 0154/2693] github: experiment with --debug-find --- .github/workflows/cmake-single-platform.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 5d74447d..b8f26927 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -32,6 +32,8 @@ jobs: repository: Rconybea/indentlog path: repo/indentlog + + - name: Configure indentlog # configure cmake for indentlog in dedicated build directory. run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog @@ -43,10 +45,12 @@ jobs: # install into ${{github.workspace}}/local run: cmake --install ${{github.workspace}}/build_indentlog + + - name: Configure refcnt # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} --debug-find - name: Build refcnt # Build your program with the given configuration From 75cfc477b8cce8553cf04052d23822cc7c8be811 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 15:45:03 -0400 Subject: [PATCH 0155/2693] github: more experiments w/ --debug-find --- .github/workflows/cmake-single-platform.yml | 6 +++--- src/reflect/CMakeLists.txt | 9 --------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 58b3e490..2271e790 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -31,7 +31,7 @@ jobs: path: repo/indentlog - name: Configure indentlog - run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local --debug-find repo/indentlog - name: Build indentlog run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} @@ -47,7 +47,7 @@ jobs: path: repo/subsys - name: Configure subsys - run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local --debug-find repo/subsys - name: Build subsys @@ -56,13 +56,13 @@ jobs: - name: Install subsys run: cmake --install ${{github.workspace}}/build_subsys + - name: Clone refcnt uses: actions/checkout@v3 with: repository: Rconybea/refcnt path: repo/refcnt - - name: Configure refcnt run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local --debug-find repo/refcnt diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index d30bd794..0590298b 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -25,15 +25,6 @@ xo_install_library(${SELF_LIBRARY_NAME}) #xo_refcnt_dependency(${SELF_LIBRARY_NAME}) #xo_indentlog_dependency(${SELF_LIBRARY_NAME}) -find_package(refcnt CONFIG REQUIRED) -#add_dependencies(${SELF_LIBRARY_NAME} refcnt) -target_include_directories( - ${SELF_LIBRARY_NAME} PUBLIC - $ - $ -) -target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC refcnt) - find_package(indentlog CONFIG REQUIRED) #add_dependencies(${SELF_LIBRARY_NAME} indentlog) # note: can't use find_package() here, From 8c9b541e7d6530f162cddcff6a382d6a115c9a17 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 15:48:17 -0400 Subject: [PATCH 0156/2693] build: specify CONFIG for find_package(indentlog) --- cmake/cxx.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake index 4f2dd2fa..fd9aed25 100644 --- a/cmake/cxx.cmake +++ b/cmake/cxx.cmake @@ -72,6 +72,7 @@ endmacro() # ---------------------------------------------------------------- # use this for a subdir that builds a library +# and supports find_package() # macro(xo_install_library target) install( @@ -89,7 +90,7 @@ endmacro() # use this when relying on indentlog [[https://github.com/rconybea/indentlog]] headers # macro(xo_indentlog_dependency target) - find_package(indentlog REQUIRED) + find_package(indentlog CONFIG REQUIRED) #add_dependencies(${target} indentlog) target_link_libraries(${target} PUBLIC indentlog) #target_include_directories(${target} PUBLIC ${indentlog_DIR}/../../../include) From 2bd1b19388a6b6c684be8422b8f8311b73387c55 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 15:55:03 -0400 Subject: [PATCH 0157/2693] github: more weird build experiments --- .github/workflows/cmake-single-platform.yml | 1 - CMakeLists.txt | 3 +++ src/reflect/CMakeLists.txt | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 2271e790..d31e3138 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -80,7 +80,6 @@ jobs: - name: Configure reflect # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} --debug-find - name: Build reflect # Build your program with the given configuration diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ddd1ad2..b9b28560 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,9 @@ if(NOT CMAKE_INSTALL_RPATH) set(CMAKE_INSTALL_RPATH $(CMAKE_INSTALL_PREFIX)/lib CACHE STRING "runpath in installed libraries/executables") endif() +# early find_package() experiment +find_package(indentlog CONFIG REQUIRED) + # ---------------------------------------------------------------- # sources diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index 0590298b..dd26ec9c 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -25,7 +25,6 @@ xo_install_library(${SELF_LIBRARY_NAME}) #xo_refcnt_dependency(${SELF_LIBRARY_NAME}) #xo_indentlog_dependency(${SELF_LIBRARY_NAME}) -find_package(indentlog CONFIG REQUIRED) #add_dependencies(${SELF_LIBRARY_NAME} indentlog) # note: can't use find_package() here, # because find_package() needs to run successfully before From 4467586ca059b637cd5fb38e6ac4a264efc3fb2a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 15:57:56 -0400 Subject: [PATCH 0158/2693] github: big revert -- adopt refcnt .yml, try to build up from there --- .github/workflows/cmake-single-platform.yml | 57 +++++---------------- 1 file changed, 14 insertions(+), 43 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index d31e3138..b8f26927 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -20,9 +20,11 @@ jobs: runs-on: ubuntu-latest steps: - - name: Install catch2 - run: sudo apt-get install -y catch2 + - uses: actions/checkout@v3 + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 - name: Clone indentlog uses: actions/checkout@v3 @@ -30,63 +32,32 @@ jobs: repository: Rconybea/indentlog path: repo/indentlog + + - name: Configure indentlog - run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local --debug-find repo/indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog - name: Build indentlog run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} - name: Install indentlog + # install into ${{github.workspace}}/local run: cmake --install ${{github.workspace}}/build_indentlog - - name: Clone subsys - uses: actions/checkout@v3 - with: - repository: Rconybea/subsys - path: repo/subsys - - - name: Configure subsys - run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local --debug-find repo/subsys - - - - name: Build subsys - run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} - - - name: Install subsys - run: cmake --install ${{github.workspace}}/build_subsys - - - - name: Clone refcnt - uses: actions/checkout@v3 - with: - repository: Rconybea/refcnt - path: repo/refcnt - name: Configure refcnt - run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local --debug-find repo/refcnt - - - name: Build refcnt - run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} - - - name: Install refcnt - run: cmake --install ${{github.workspace}}/build_refcnt - - - name: Checkout reflect - uses: actions/checkout@v3 - - - - - name: Configure reflect # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} --debug-find - - name: Build reflect + - name: Build refcnt # Build your program with the given configuration - run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} - - name: Test reflect - working-directory: ${{github.workspace}}/build_reflect + - name: Test refcnt + working-directory: ${{github.workspace}}/build_refcnt # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest -C ${{env.BUILD_TYPE}} From 40a894425852b89a4d0620a5d8b513d60f36e4ca Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 16:01:32 -0400 Subject: [PATCH 0159/2693] github: experiment, take 2 --- .github/workflows/cmake-single-platform.yml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index b8f26927..a57b11a5 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -26,14 +26,13 @@ jobs: # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] run: sudo apt-get install -y catch2 + - name: Clone indentlog uses: actions/checkout@v3 with: repository: Rconybea/indentlog path: repo/indentlog - - - name: Configure indentlog # configure cmake for indentlog in dedicated build directory. run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog @@ -46,18 +45,17 @@ jobs: run: cmake --install ${{github.workspace}}/build_indentlog - - - name: Configure refcnt + - name: Configure reflect # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} --debug-find + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_PREFIX_PATH=${{github.workspace}}/xyz -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} --debug-find - - name: Build refcnt + - name: Build reflect # Build your program with the given configuration - run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} - - name: Test refcnt - working-directory: ${{github.workspace}}/build_refcnt + - name: Test reflect + working-directory: ${{github.workspace}}/build_reflect # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest -C ${{env.BUILD_TYPE}} From 7a40acc8b53d8c5d686e0bb049b28c9cb0ecece4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 16:08:21 -0400 Subject: [PATCH 0160/2693] github: experiment 3 --- .github/workflows/cmake-single-platform.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index a57b11a5..7d660c32 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -45,10 +45,10 @@ jobs: run: cmake --install ${{github.workspace}}/build_indentlog - - name: Configure reflect + - name: Configure reflect library # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_PREFIX_PATH=${{github.workspace}}/xyz -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} --debug-find + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} --debug-find - name: Build reflect # Build your program with the given configuration From 031d0bf887edbb6ad46832f8d3b74c35514ab172 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 16:13:10 -0400 Subject: [PATCH 0161/2693] github: experiment 4 --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index b9b28560..02c30bf3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,7 @@ endif() # early find_package() experiment find_package(indentlog CONFIG REQUIRED) +find_package(refcnt2 CONFIG REQUIRED) # ---------------------------------------------------------------- # sources From 0c7f78f721ed0f7afea340b4d1947351f5573ab2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 16:14:36 -0400 Subject: [PATCH 0162/2693] github: experiment 5 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 02c30bf3..cdd7648d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,7 +49,7 @@ endif() # early find_package() experiment find_package(indentlog CONFIG REQUIRED) -find_package(refcnt2 CONFIG REQUIRED) +find_package(refcnt CONFIG REQUIRED) # ---------------------------------------------------------------- # sources From 6e43bb55f04649faea813dff48fe48c443f3c2d3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 16:16:38 -0400 Subject: [PATCH 0163/2693] github: fetch+install refcnt again --- .github/workflows/cmake-single-platform.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 7d660c32..95d1b2b5 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -45,6 +45,24 @@ jobs: run: cmake --install ${{github.workspace}}/build_indentlog + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + - name: Configure reflect library # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 63d5267913b3157ba756c5ddd284c84ca325f5e4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 16:18:54 -0400 Subject: [PATCH 0164/2693] github: fetch+install subsys again --- .github/workflows/cmake-single-platform.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 95d1b2b5..55c366a2 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -45,6 +45,24 @@ jobs: run: cmake --install ${{github.workspace}}/build_indentlog + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + # configure cmake for subsys in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_subsys + + - name: Clone refcnt uses: actions/checkout@v3 with: From efb1b9aa473fd87e4f9211549bc62000016da1ec Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 16:23:46 -0400 Subject: [PATCH 0165/2693] github: try restoring refcnt CMAKE_PREFIX_PATH argument --- .github/workflows/cmake-single-platform.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 55c366a2..24e4b579 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -71,7 +71,7 @@ jobs: - name: Configure refcnt # configure cmake for refcnt in dedicated build directory. - run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt - name: Build refcnt run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} From d349796363163419026d7ced0f46f3702ba4a2df Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 17:11:57 -0400 Subject: [PATCH 0166/2693] initial commit --- .gitignore | 1 + CMakeLists.txt | 17 +++++++++++++++++ README.md | 21 +++++++++++++++++++++ cmake/xo_cxx.cmake | 23 +++++++++++++++++++++++ 4 files changed, 62 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/xo_cxx.cmake diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..378eac25 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..b1480c47 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.10) + +project(xo_macros VERSION 1.0) + +# if any are useful for this project.. +#include (cmake/foo.cmake) + +# ---------------------------------------------------------------- +# cmake export + +set(XO_PROJECT_NAME xo_macros) + +install( + FILES + "cmake/xo_cxx.cmake" + DESTINATION share/cmake/${XO_PROJECT_NAME} +) diff --git a/README.md b/README.md new file mode 100644 index 00000000..a06e0291 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# XO cmake modules + +Collects cmake macros to be shared across XO projects (e.g. indentlog, reflect, kalman, ..) + +## Features + +- support for both manyrepo and monorepo projects +- support for generating cmake `xxxConfig.cmake` files, so cmake `find_package()` works reliably + +## Example + +In some XO project `foo`: +``` +$ cd build +$ cmake -DCMAKE_MODULE_PATH=/usr/local/share/cmake .. +``` + +then in `foo/CMakeLists.txt`: +``` +include(xo_macros/xo_cxx) +``` diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake new file mode 100644 index 00000000..6daf88f9 --- /dev/null +++ b/cmake/xo_cxx.cmake @@ -0,0 +1,23 @@ +set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) +set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) + +macro(xo_copmile_options target) + target_copmile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) +endmacro() + +# dependency on an xo library (including header-only libraries) +# e.g. indentlog +# +# An xo package foo works with cmake +# find_package(foo) +# by providing plugin .cmake files in +# ${PREFIX}/lib/cmake/foo/fooConfig.cmake +# ${PREFIX}/lib/cmake/foo/fooConfigVersion.cmake +# ${PREFIX}/lib/cmake/foo/fooTargets.cmake +# +# dep: name of required dependency, e.g. indentlog +# +macro(xo_internal_dependency target dep) + find_pacakge(${dep} CONFIG REQUIRED) + target_link_libraries(${target} PUBLIC ${dep}) +endmacro() From efa8afa9c2b9924638022a73366b4ac4332ee8bb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 17:24:44 -0400 Subject: [PATCH 0167/2693] github: fetch new xo-cmake dep --- .github/workflows/cmake-single-platform.yml | 27 ++++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 059cf017..7be18768 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -26,16 +26,35 @@ jobs: # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] run: sudo apt-get install -y catch2 - - name: Configure CMake + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.worskpace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Configure self (indentlog) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: cmake -B ${{github.workspace}}/build -DCMAKE_MODULE_PATH=${{github.workspace}/local/share/cmake -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - - name: Build + - name: Build self (indentlog) # Build your program with the given configuration run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - - name: Test + - name: Test self (indentlog) working-directory: ${{github.workspace}}/build # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail From 31abb2ae417c555a3fb9c2eb93c7e45de092dfa6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 17:25:43 -0400 Subject: [PATCH 0168/2693] github: bugfix: typo in .yml --- .github/workflows/cmake-single-platform.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 7be18768..37a27cad 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -48,7 +48,7 @@ jobs: - name: Configure self (indentlog) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build -DCMAKE_MODULE_PATH=${{github.workspace}/local/share/cmake -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: cmake -B ${{github.workspace}}/build -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - name: Build self (indentlog) # Build your program with the given configuration From 27c4535bf6b7b11eb8529b4066ff67132772612b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 17:26:48 -0400 Subject: [PATCH 0169/2693] github: bugfix: typo (#2) in .yml --- .github/workflows/cmake-single-platform.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 37a27cad..7787e3c5 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -38,7 +38,7 @@ jobs: run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake - name: Build xo-cmake (trivial) - run: cmake --build ${{github.worskpace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} - name: Install xo-cmake run: cmake --install ${{github.workspace}}/build_xo-cmake From 5f025d8fc8c12ab8792faf71bc5baa93784efa5a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 09:23:55 -0400 Subject: [PATCH 0170/2693] build: adopt xo-cmake macros as dependency --- .github/workflows/cmake-single-platform.yml | 28 +++++++++++++++++---- CMakeLists.txt | 1 + README.md | 3 ++- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 28c6f783..f654ea9c 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -22,18 +22,36 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Configure CMake + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Configure self (subsys) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: cmake -B ${{github.workspace}}/build -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - - name: Build + - name: Build self (subsys) # Build your program with the given configuration run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - - name: Test + - name: Test self (subsys) working-directory: ${{github.workspace}}/build # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest -C ${{env.BUILD_TYPE}} - diff --git a/CMakeLists.txt b/CMakeLists.txt index c63d6740..4418fe9b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.10) project(subsys VERSION 0.1) enable_language(CXX) +include (xo_macros/xo_cxx) include(cmake/cxx.cmake) include(cmake/code-coverage.cmake) diff --git a/README.md b/README.md index b7701781..29b31f1a 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,8 @@ subsys $ cd subsys $ mkdir build $ cd build -$ cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ INSTALL_PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. $ make $ make install ``` From 870eb57aa79c9ad0369a0471308d0f93df598716 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 09:29:47 -0400 Subject: [PATCH 0171/2693] build: adopt xo-cmake dependency --- .github/workflows/cmake-single-platform.yml | 29 ++++++++++++++++----- CMakeLists.txt | 2 ++ README.md | 14 ++++++++-- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index b8f26927..527d5655 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -26,17 +26,34 @@ jobs: # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] run: sudo apt-get install -y catch2 + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + - name: Clone indentlog uses: actions/checkout@v3 with: repository: Rconybea/indentlog path: repo/indentlog - - - name: Configure indentlog # configure cmake for indentlog in dedicated build directory. - run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog - name: Build indentlog run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} @@ -45,12 +62,12 @@ jobs: # install into ${{github.workspace}}/local run: cmake --install ${{github.workspace}}/build_indentlog + # ---------------------------------------------------------------- - - - name: Configure refcnt + - name: Configure self (refcnt) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} --debug-find + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - name: Build refcnt # Build your program with the given configuration diff --git a/CMakeLists.txt b/CMakeLists.txt index a0cdb52c..c360483c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,8 @@ cmake_minimum_required(VERSION 3.10) project(refcnt VERSION 0.1) enable_language(CXX) +# common XO cmake macros (see proj/xo-cmake) +include(xo_macros/xo_cxx) include(cmake/cxx.cmake) include(cmake/code-coverage.cmake) diff --git a/README.md b/README.md index 0835e7b9..bae1fb2c 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Refcnt is a small shared library supplying intrusive reference counting. ### build + install `indentlog` dependency -see [github/rconybea/indentlog](https://github.com/Rconybea/indentlog) +see [github/Rconybea/indentlog](https://github.com/Rconybea/indentlog) ### copy `refcnt` repository locally ``` @@ -26,11 +26,13 @@ refcnt $ cd refcnt $ mkdir build $ cd build -$ cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. $ make $ make install ``` +`CMAKE_PREFIX_PATH` should point to prefix where `indentlog` is installed + alternatively, if you're a nix user: ``` $ git clone git@github.com:rconybea/xo-nix.git @@ -40,6 +42,14 @@ $ cd xo-nix $ nix-build -A refcnt ``` +### build for unit test coverage +``` +$ cd refcnt +$ mkdir ccov +$ cd ccov +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + ## Examples ### 1 From ebc70db3e734c8abec8344c2c0c5fe282ae9d037 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 09:39:54 -0400 Subject: [PATCH 0172/2693] build: adopt xo-cmake dependency --- .github/workflows/cmake-single-platform.yml | 35 ++++++++++++++++----- CMakeLists.txt | 2 ++ README.md | 4 +-- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 24e4b579..a8ce3a72 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -26,6 +26,24 @@ jobs: # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] run: sudo apt-get install -y catch2 + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- - name: Clone indentlog uses: actions/checkout@v3 @@ -35,7 +53,7 @@ jobs: - name: Configure indentlog # configure cmake for indentlog in dedicated build directory. - run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog - name: Build indentlog run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} @@ -44,6 +62,7 @@ jobs: # install into ${{github.workspace}}/local run: cmake --install ${{github.workspace}}/build_indentlog + # ---------------------------------------------------------------- - name: Clone subsys uses: actions/checkout@v3 @@ -53,7 +72,7 @@ jobs: - name: Configure subsys # configure cmake for subsys in dedicated build directory. - run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys - name: Build subsys run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} @@ -62,6 +81,7 @@ jobs: # install into ${{github.workspace}}/local run: cmake --install ${{github.workspace}}/build_subsys + # ---------------------------------------------------------------- - name: Clone refcnt uses: actions/checkout@v3 @@ -71,7 +91,7 @@ jobs: - name: Configure refcnt # configure cmake for refcnt in dedicated build directory. - run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt - name: Build refcnt run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} @@ -80,17 +100,18 @@ jobs: # install into ${{github.workspace}}/local run: cmake --install ${{github.workspace}}/build_refcnt + # ---------------------------------------------------------------- - - name: Configure reflect library + - name: Configure self (reflect) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} --debug-find + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - - name: Build reflect + - name: Build self (reflect) # Build your program with the given configuration run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} - - name: Test reflect + - name: Test self (reflect) working-directory: ${{github.workspace}}/build_reflect # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail diff --git a/CMakeLists.txt b/CMakeLists.txt index cdd7648d..afa760ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,8 @@ message(CMAKE_VERSION=${CMAKE_VERSION}) project(reflect VERSION 0.1) enable_language(CXX) +# common XO cmake macros (see proj/xo-cmake) +include(xo_macros/xo_cxx) include(cmake/cxx.cmake) include(cmake/code-coverage.cmake) diff --git a/README.md b/README.md index fb6682d4..0cd50f02 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ $ cd reflect $ mkdir build $ cd build -$ cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. $ make $ make install ``` @@ -15,5 +15,5 @@ $ make install $ cd refcnt $ mkdir build-ccov $ cd build-ccov -$ cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. ``` From aac74e276325b0e6497b80f2125a1b174ac0ef0c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 10:29:15 -0400 Subject: [PATCH 0173/2693] build: adopt xo_cmake dependency --- CMakeLists.txt | 1 + README.md | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f53843ac..16cb2202 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ cmake_minimum_required(VERSION 3.10) project(randomgen VERSION 0.1) enable_language(CXX) +include(xo_macros/xo_cxx) include(cmake/cxx.cmake) include(cmake/code-coverage.cmake) diff --git a/README.md b/README.md index 18300542..905f267e 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,7 @@ $ cd randomgen $ mkdir build $ cd build -$ cmake -DCMAKE_PREFIX_PATH=$(HOME)/local .. +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=$(INSTALL_PREFIX) .. $ make $ make install ``` - -# to build + install to /usr/local (deprecated) - -same as above, but set `CMAKE_PREFIX_PATH` to `/usr/local` From 748c7d86e42af77b56436fae6607f98384c26945 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 10:29:59 -0400 Subject: [PATCH 0174/2693] + .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..69e11d47 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# clangd (run via lsp) keeps state here +.cache +# typical build directory +build From aa1256fca7b7eb1c70c1e420212075c4ab359ce0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 11:05:31 -0400 Subject: [PATCH 0175/2693] build: + xo-cmake dependency --- CMakeLists.txt | 2 ++ README.md | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eb5f2cf2..fb740612 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,8 @@ cmake_minimum_required(VERSION 3.10) project(indentlog VERSION 0.1) enable_language(CXX) +# common XO cmake macros (see proj/xo-cmake) +include(xo_macros/xo_cxx) include(cmake/nestlog.cmake) include(cmake/code-coverage.cmake) diff --git a/README.md b/README.md index bd376bd9..d0202a94 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,11 @@ Indentlog is a lightweight header-only library for console logging. ## Getting Started +### build + install `xo-cmake` dependency (cmake macros) + +see [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) +(almost trivial, installs a few `.cmake` files) + ### copy repository locally ``` @@ -32,7 +37,7 @@ indentlog $ cd indentlog $ mkdir build $ cd build -$ cmake -DCMAKE_INSTALL_PREFIX=/usr/local .. +$ cmake -DCMAKE_MODULE_PATH=/usr/local/share/cmake -DCMAKE_INSTALL_PREFIX=/usr/local .. $ make $ make install ``` From bd3f39693349aa7e6159a5f7384dc2ae667009b1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 12:01:27 -0400 Subject: [PATCH 0176/2693] xo-cmake: + xo_include_options2() --- cmake/xo_cxx.cmake | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 6daf88f9..160a1833 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -1,10 +1,49 @@ + set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) -macro(xo_copmile_options target) +# ---------------------------------------------------------------- +# use this in subdirs that compile c++ code +# +macro(xo_include_options2 target) + # ---------------------------------------------------------------- + # PROJECT_SOURCE_DIR: + # so we can for example write + # #include "ordinaltree/foo.hpp" + # from anywhere in the project + # PROJECT_BINARY_DIR: + # since generated version file will be in build directory, + # need that build directory to also appear in + # compiler's include path + # + target_include_directories( + ${target} PUBLIC + $ # e.g. for #include "indentlog/scope.hpp" + $ + $ # e.g. for #include "Refcounted.hpp" in refcnt/src + $ + $ # e.g. for generated .hpp files + ) + + # ---------------------------------------------------------------- + # make standard directories for std:: includes explicit + # so that + # (1) they appear in compile_commands.json. + # (2) clangd (run from emacs lsp-mode) can find them + # + if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) + endif() +endmacro() + +# ---------------------------------------------------------------- +# +macro(xo_compile_options target) target_copmile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) endmacro() +# ---------------------------------------------------------------- +# # dependency on an xo library (including header-only libraries) # e.g. indentlog # @@ -18,6 +57,6 @@ endmacro() # dep: name of required dependency, e.g. indentlog # macro(xo_internal_dependency target dep) - find_pacakge(${dep} CONFIG REQUIRED) + find_package(${dep} CONFIG REQUIRED) target_link_libraries(${target} PUBLIC ${dep}) endmacro() From 3da73f5d3c9beba836ca29c3ed9c6ab22aa6ee2c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 13:18:40 -0400 Subject: [PATCH 0177/2693] xo-cmake: + xo_install_include_tree() --- cmake/xo_cxx.cmake | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 160a1833..d2f95936 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -42,6 +42,13 @@ macro(xo_compile_options target) target_copmile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) endmacro() +# ---------------------------------------------------------------- +# use this to install typical include file subtree +# +macro(xo_install_include_tree) + install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) +endmacro() + # ---------------------------------------------------------------- # # dependency on an xo library (including header-only libraries) From 795d5ddb69ff38ccdb9ffcbe5226af7b194d5ec7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 13:18:59 -0400 Subject: [PATCH 0178/2693] xo-cmake: + xo_install_library2() --- cmake/xo_cxx.cmake | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index d2f95936..6728db82 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -49,6 +49,22 @@ macro(xo_install_include_tree) install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) endmacro() +# ---------------------------------------------------------------- +# use this for a subdir that builds a library +# and supports find_package() +# +macro(xo_install_library2 target) + install( + TARGETS ${target} + EXPORT ${target}Targets + LIBRARY DESTINATION lib COMPONENT Runtime + ARCHIVE DESTINATION lib COMPONENT Development + RUNTIME DESTINATION bin COMPONENT Runtime + PUBLIC_HEADER DESTINATION include COMPONENT Development + BUNDLE DESTINATION bin COMPONENT Runtime + ) +endmacro() + # ---------------------------------------------------------------- # # dependency on an xo library (including header-only libraries) From 8884d7f9aa55f6a77530f7487a5bec0f24860544 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 13:19:11 -0400 Subject: [PATCH 0179/2693] xo-cmake: + xo_export_cmake_config() --- cmake/xo_cxx.cmake | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 6728db82..8f70a590 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -65,6 +65,40 @@ macro(xo_install_library2 target) ) endmacro() +# ---------------------------------------------------------------- + +# for projectname=foo, require: +# cmake/fooConfig.cmake.in +# +# prepares +# ${PREFIX}/lib/cmake/foo/fooConfig.cmake +# ${PREFIX}/lib/cmake/foo/fooConfigVersion.cmake +# ${PREFIX}/lib/cmake/foo/fooTargets.cmake +# +macro(xo_export_cmake_config projectname projectversion projecttargets) + include(CMakePackageConfigHelpers) + write_basic_package_version_file( + "${PROJECT_BINARY_DIR}/${projectname}ConfigVersion.cmake" + VERSION ${projectversion} + COMPATIBILITY AnyNewerVersion + ) + configure_package_config_file( + "${PROJECT_SOURCE_DIR}/cmake/${projectname}Config.cmake.in" + "${PROJECT_BINARY_DIR}/${projectname}Config.cmake" + INSTALL_DESTINATION lib/cmake/${projectname} + ) + install( + EXPORT ${projecttargets} + DESTINATION lib/cmake/${projectname} + ) + install( + FILES + "${PROJECT_BINARY_DIR}/${projectname}ConfigVersion.cmake" + "${PROJECT_BINARY_DIR}/${projectname}Config.cmake" + DESTINATION lib/cmake/${projectname} + ) +endmacro() + # ---------------------------------------------------------------- # # dependency on an xo library (including header-only libraries) From 41c20930278677731acb2a64d65845cfd6a50aa0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 13:19:50 -0400 Subject: [PATCH 0180/2693] build: indentlog: use new xo-cmake macros --- CMakeLists.txt | 69 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 27 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fb740612..86f6de3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,36 +51,51 @@ target_include_directories(indentlog INTERFACE $ ) -include(CMakePackageConfigHelpers) -write_basic_package_version_file("${PROJECT_BINARY_DIR}/indentlogConfigVersion.cmake" - VERSION 0.1 - COMPATIBILITY AnyNewerVersion -) +# ---------------------------------------------------------------- +# provide find_package() support -install( - TARGETS indentlog - EXPORT indentlogTargets - LIBRARY DESTINATION lib COMPONENT Runtime - ARCHIVE DESTINATION lib COMPONENT Development - RUNTIME DESTINATION bin COMPONENT Runtime - PUBLIC_HEADER DESTINATION include COMPONENT Development - BUNDLE DESTINATION bin COMPONENT Runtime - ) +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -include(CMakePackageConfigHelpers) -configure_package_config_file( - "${PROJECT_SOURCE_DIR}/cmake/indentlogConfig.cmake.in" - "${PROJECT_BINARY_DIR}/indentlogConfig.cmake" - INSTALL_DESTINATION lib/cmake/indentlog - ) +# ---------------------------------------------------------------- -install(EXPORT indentlogTargets DESTINATION lib/cmake/indentlog) -install( - FILES - "${PROJECT_BINARY_DIR}/indentlogConfigVersion.cmake" - "${PROJECT_BINARY_DIR}/indentlogConfig.cmake" - DESTINATION lib/cmake/indentlog) -install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) +xo_install_library2(${PROJECT_NAME}) + +#include(CMakePackageConfigHelpers) +#write_basic_package_version_file("${PROJECT_BINARY_DIR}/indentlogConfigVersion.cmake" +# VERSION 0.1 +# COMPATIBILITY AnyNewerVersion +#) +# +#install( +# TARGETS indentlog +# EXPORT indentlogTargets +# LIBRARY DESTINATION lib COMPONENT Runtime +# ARCHIVE DESTINATION lib COMPONENT Development +# RUNTIME DESTINATION bin COMPONENT Runtime +# PUBLIC_HEADER DESTINATION include COMPONENT Development +# BUNDLE DESTINATION bin COMPONENT Runtime +# ) + +#include(CMakePackageConfigHelpers) +#configure_package_config_file( +# "${PROJECT_SOURCE_DIR}/cmake/indentlogConfig.cmake.in" +# "${PROJECT_BINARY_DIR}/indentlogConfig.cmake" +# INSTALL_DESTINATION lib/cmake/indentlog +# ) +# +#install(EXPORT indentlogTargets DESTINATION lib/cmake/indentlog) +#install( +# FILES +# "${PROJECT_BINARY_DIR}/indentlogConfigVersion.cmake" +# "${PROJECT_BINARY_DIR}/indentlogConfig.cmake" +# DESTINATION lib/cmake/indentlog) + +# ---------------------------------------------------------------- +# install .hpp + +xo_install_include_tree() + +# ---------------------------------------------------------------- install(TARGETS hello DESTINATION bin/indentlog/example) install(TARGETS ex1 DESTINATION bin/indentlog/example) From 155e1e5864969d49d0fb56a4138e33ced3055eb9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 13:20:40 -0400 Subject: [PATCH 0181/2693] subsys: build: use new xo-cmake macros --- CMakeLists.txt | 54 ++++++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4418fe9b..0c0b9c1e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,37 +58,43 @@ target_include_directories(subsys INTERFACE $ $ ) -xo_install_library(subsys) +xo_install_library2(subsys) # ---------------------------------------------------------------- -# cmake export -# (so this library works with cmake's find_package()) +# provide find_package() support -set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") -set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -include(CMakePackageConfigHelpers) -write_basic_package_version_file("${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" - VERSION 0.1 - COMPATIBILITY AnyNewerVersion -) - -configure_package_config_file( - "${PROJECT_SOURCE_DIR}/cmake/${XO_PROJECT_NAME}Config.cmake.in" - "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" - INSTALL_DESTINATION lib/cmake/${XO_PROJECT_NAME} - ) - -install(EXPORT ${XO_PROJECT_NAME}Targets DESTINATION lib/cmake/${XO_PROJECT_NAME}) -install( - FILES - "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" - "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" - DESTINATION lib/cmake/${XO_PROJECT_NAME}) +## ---------------------------------------------------------------- +## cmake export +## (so this library works with cmake's find_package()) +# +#set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") +#set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") +# +#include(CMakePackageConfigHelpers) +#write_basic_package_version_file("${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" +# VERSION 0.1 +# COMPATIBILITY AnyNewerVersion +#) +# +#configure_package_config_file( +# "${PROJECT_SOURCE_DIR}/cmake/${XO_PROJECT_NAME}Config.cmake.in" +# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" +# INSTALL_DESTINATION lib/cmake/${XO_PROJECT_NAME} +# ) +# +#install(EXPORT ${XO_PROJECT_NAME}Targets DESTINATION lib/cmake/${XO_PROJECT_NAME}) +#install( +# FILES +# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" +# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" +# DESTINATION lib/cmake/${XO_PROJECT_NAME}) # ---------------------------------------------------------------- # install .hpp -install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) +xo_install_include_tree() +#install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) # end CMakeLists.txt From 6c56ed4aecbf0972d22d2ecdc19c37626e3fc2fc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 13:21:27 -0400 Subject: [PATCH 0182/2693] refcnt: build: use new xo-make macros --- CMakeLists.txt | 64 ++++++++++++++++++++----------------- cmake/refcntConfig.cmake.in | 2 +- src/CMakeLists.txt | 5 +-- 3 files changed, 38 insertions(+), 33 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c360483c..7f7971f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,41 +53,45 @@ add_subdirectory(utest) # ---------------------------------------------------------------- # cmake export -set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") -set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -include(CMakePackageConfigHelpers) -write_basic_package_version_file("${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" - VERSION 0.1 - COMPATIBILITY AnyNewerVersion -) - -#install( -# TARGETS ${XO_PROJECT_NAME} -# EXPORT ${XO_PROJECT_NAME}Targets -# LIBRARY DESTINATION lib COMPONENT Runtime -# ARCHIVE DESTINATION lib COMPONENT Development -# RUNTIME DESTINATION bin COMPONENT Runtime -# PUBLIC_HEADER DESTINATION include COMPONENT Development -# BUNDLE DESTINATION bin COMPONENT Runtime +#set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") +#set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") +# +#include(CMakePackageConfigHelpers) +#write_basic_package_version_file("${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" +# VERSION 0.1 +# COMPATIBILITY AnyNewerVersion +#) +# +##install( +## TARGETS ${XO_PROJECT_NAME} +## EXPORT ${XO_PROJECT_NAME}Targets +## LIBRARY DESTINATION lib COMPONENT Runtime +## ARCHIVE DESTINATION lib COMPONENT Development +## RUNTIME DESTINATION bin COMPONENT Runtime +## PUBLIC_HEADER DESTINATION include COMPONENT Development +## BUNDLE DESTINATION bin COMPONENT Runtime +## ) +# +#configure_package_config_file( +# "${PROJECT_SOURCE_DIR}/cmake/${XO_PROJECT_NAME}Config.cmake.in" +# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" +# INSTALL_DESTINATION lib/cmake/${XO_PROJECT_NAME} # ) - -configure_package_config_file( - "${PROJECT_SOURCE_DIR}/cmake/${XO_PROJECT_NAME}Config.cmake.in" - "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" - INSTALL_DESTINATION lib/cmake/${XO_PROJECT_NAME} - ) - -install(EXPORT ${XO_PROJECT_NAME}Targets DESTINATION lib/cmake/${XO_PROJECT_NAME}) -install( - FILES - "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" - "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" - DESTINATION lib/cmake/${XO_PROJECT_NAME}) +# +#install(EXPORT ${XO_PROJECT_NAME}Targets DESTINATION lib/cmake/${XO_PROJECT_NAME}) +#install( +# FILES +# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" +# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" +# DESTINATION lib/cmake/${XO_PROJECT_NAME}) # ---------------------------------------------------------------- # install .hpp -install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) +xo_install_include_tree() + +#install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) # end CMakeLists.txt diff --git a/cmake/refcntConfig.cmake.in b/cmake/refcntConfig.cmake.in index e13a2c54..9c15f36a 100644 --- a/cmake/refcntConfig.cmake.in +++ b/cmake/refcntConfig.cmake.in @@ -1,4 +1,4 @@ @PACKAGE_INIT@ -include("${CMAKE_CURRENT_LIST_DIR}/@XO_PROJECT_NAME@Targets.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8083cee0..f58170e8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,8 +7,9 @@ set_target_properties(${SELF_LIBRARY_NAME} VERSION ${PROJECT_VERSION} SOVERSION 1) -xo_indentlog_dependency(${SELF_LIBRARY_NAME}) +xo_internal_dependency(${SELF_LIBRARY_NAME} indentlog) +#xo_indentlog_dependency(${SELF_LIBRARY_NAME}) -xo_include_options(${SELF_LIBRARY_NAME}) +xo_include_options2(${SELF_LIBRARY_NAME}) xo_compile_options(${SELF_LIBRARY_NAME}) xo_install_library(${SELF_LIBRARY_NAME}) From eafb897dfd0986f4643c838d9f644410ff00572b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 13:22:02 -0400 Subject: [PATCH 0183/2693] reflect: build: use new xo-cmake macros --- CMakeLists.txt | 92 ++++++++++++++++++++------------------ src/reflect/CMakeLists.txt | 34 +++++++------- 2 files changed, 68 insertions(+), 58 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index afa760ec..d2718ef2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,52 +60,58 @@ add_subdirectory(src/reflect) add_subdirectory(utest) # ---------------------------------------------------------------- -# cmake export: +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +## ---------------------------------------------------------------- +## cmake export: +## +## populate .cmake files in $CMAKE_INSTALL_LIBDIR/cmake/reflect. +## cmake projects that include this directory in $CMAKE_PREFIX_PATH +## can use +## find_package(reflect REQUIRED) +## and +## target_link_libraries(${sometarget} PUBLIC reflect) +## to use the reflect library # -# populate .cmake files in $CMAKE_INSTALL_LIBDIR/cmake/reflect. -# cmake projects that include this directory in $CMAKE_PREFIX_PATH -# can use -# find_package(reflect REQUIRED) -# and -# target_link_libraries(${sometarget} PUBLIC reflect) -# to use the reflect library - -set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") -set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") - -include(CMakePackageConfigHelpers) - -# generates build/reflectConfigVersion.cmake -write_basic_package_version_file( - "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" - VERSION 0.1 - COMPATIBILITY AnyNewerVersion -) - -# generates build/reflectConfig.cmake -configure_package_config_file( - "${PROJECT_SOURCE_DIR}/cmake/${XO_PROJECT_NAME}Config.cmake.in" - "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" - INSTALL_DESTINATION lib/cmake/${XO_PROJECT_NAME} -) - -# creates {reflectTargets.cmake, reflectTargets-noconfig.cmake} in $CMAKE_INSTALL_LIBDIR/cmake/reflect/ -# requires -# install(.. EXPORT reflectTargets ..) +#set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") +#set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") # -install( - EXPORT ${XO_PROJECT_NAME}Targets - DESTINATION lib/cmake/${XO_PROJECT_NAME} -) - -# creates {reflectConfigVersion.cmake, reflectConfig.cmake} in $CMAKE_INSTALL_LIBDIR/cmake/reflect/ -install( - FILES - "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" - "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" - DESTINATION lib/cmake/${XO_PROJECT_NAME}) +#include(CMakePackageConfigHelpers) +# +## generates build/reflectConfigVersion.cmake +#write_basic_package_version_file( +# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" +# VERSION 0.1 +# COMPATIBILITY AnyNewerVersion +#) +# +## generates build/reflectConfig.cmake +#configure_package_config_file( +# "${PROJECT_SOURCE_DIR}/cmake/${XO_PROJECT_NAME}Config.cmake.in" +# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" +# INSTALL_DESTINATION lib/cmake/${XO_PROJECT_NAME} +#) +# +## creates {reflectTargets.cmake, reflectTargets-noconfig.cmake} in $CMAKE_INSTALL_LIBDIR/cmake/reflect/ +## requires +## install(.. EXPORT reflectTargets ..) +## +#install( +# EXPORT ${XO_PROJECT_NAME}Targets +# DESTINATION lib/cmake/${XO_PROJECT_NAME} +#) +# +## creates {reflectConfigVersion.cmake, reflectConfig.cmake} in $CMAKE_INSTALL_LIBDIR/cmake/reflect/ +#install( +# FILES +# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" +# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" +# DESTINATION lib/cmake/${XO_PROJECT_NAME}) # ---------------------------------------------------------------- # install .hpp files -install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/reflect/ DESTINATION include/reflect) +xo_install_include_tree() +#install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/reflect/ DESTINATION include/reflect) diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index dd26ec9c..d4b772e4 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -16,12 +16,18 @@ set_target_properties(${SELF_LIBRARY_NAME} # #target_compile_options(${SELF_LIBRARY_NAME} PRIVATE -Werror -Wall -Wextra) xo_compile_options(${SELF_LIBRARY_NAME}) -xo_include_options(${SELF_LIBRARY_NAME}) +xo_include_options2(${SELF_LIBRARY_NAME}) xo_install_library(${SELF_LIBRARY_NAME}) # ---------------------------------------------------------------- # dependencies: logutil, ... +#target_include_directories( +# ${SELF_LIBRARY_NAME} PUBLIC +# $ +# $ +#) + #xo_refcnt_dependency(${SELF_LIBRARY_NAME}) #xo_indentlog_dependency(${SELF_LIBRARY_NAME}) @@ -29,21 +35,19 @@ xo_install_library(${SELF_LIBRARY_NAME}) # note: can't use find_package() here, # because find_package() needs to run successfully before # dependency gets installed. -target_include_directories( - ${SELF_LIBRARY_NAME} PUBLIC - $ - $ -) -target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC indentlog) +xo_internal_dependency(${SELF_LIBRARY_NAME} indentlog) -find_package(refcnt CONFIG REQUIRED) -#add_dependencies(${SELF_LIBRARY_NAME} refcnt) -target_include_directories( - ${SELF_LIBRARY_NAME} PUBLIC - $ - $ -) -target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC refcnt) +#target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC indentlog) + +xo_internal_dependency(${SELF_LIBRARY_NAME} refcnt) +#find_package(refcnt CONFIG REQUIRED) +##add_dependencies(${SELF_LIBRARY_NAME} refcnt) +#target_include_directories( +# ${SELF_LIBRARY_NAME} PUBLIC +# $ +# $ +#) +#target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC refcnt) # ---------------------------------------------------------------- # 3rd party dependency: boost: From 79bb624ec8bf5199b281bfc48d77aaf27b9646fa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 14:03:55 -0400 Subject: [PATCH 0184/2693] reflect: build: retire superseded cmake macros --- cmake/cxx.cmake | 113 +++++++++++++++++++------------------ src/reflect/CMakeLists.txt | 2 +- utest/CMakeLists.txt | 2 +- 3 files changed, 59 insertions(+), 58 deletions(-) diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake index fa6efb89..4084bac9 100644 --- a/cmake/cxx.cmake +++ b/cmake/cxx.cmake @@ -1,36 +1,36 @@ -# ---------------------------------------------------------------- -# use this in subdirs that compile c++ code +## ---------------------------------------------------------------- +## use this in subdirs that compile c++ code +## +#macro(xo_include_options target) +# # ---------------------------------------------------------------- +# # PROJECT_SOURCE_DIR: +# # so we can for example write +# # #include "ordinaltree/foo.hpp" +# # from anywhere in the project +# # PROJECT_BINARY_DIR: +# # since generated version file will be in build directory, +# # need that build directory to also appear in +# # compiler's include path +# # +# target_include_directories( +# ${target} PUBLIC +# $ # e.g. for #include "indentlog/scope.hpp" +# $ +# $ # e.g. for #include "Refcounted.hpp" in refcnt/src +# $ +# $ # e.g. for generated config.hpp file +# ) # -macro(xo_include_options target) - # ---------------------------------------------------------------- - # PROJECT_SOURCE_DIR: - # so we can for example write - # #include "ordinaltree/foo.hpp" - # from anywhere in the project - # PROJECT_BINARY_DIR: - # since generated version file will be in build directory, - # need that build directory to also appear in - # compiler's include path - # - target_include_directories( - ${target} PUBLIC - $ # e.g. for #include "indentlog/scope.hpp" - $ - $ # e.g. for #include "Refcounted.hpp" in refcnt/src - $ - $ # e.g. for generated config.hpp file - ) - - # ---------------------------------------------------------------- - # make standard directories for std:: includes explicit - # so that - # (1) they appear in compile_commands.json. - # (2) clangd (run from emacs lsp-mode) can find them - # - if(CMAKE_EXPORT_COMPILE_COMMANDS) - set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) - endif() -endmacro() +# # ---------------------------------------------------------------- +# # make standard directories for std:: includes explicit +# # so that +# # (1) they appear in compile_commands.json. +# # (2) clangd (run from emacs lsp-mode) can find them +# # +# if(CMAKE_EXPORT_COMPILE_COMMANDS) +# set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +# endif() +#endmacro() # ---------------------------------------------------------------- # variable @@ -70,29 +70,30 @@ macro(xo_compile_options target) target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) endmacro() -# ---------------------------------------------------------------- -# use this for a subdir that builds a library -# EXPORT drives .cmake config files intended for consumption -# by higher-level cmake projects via find_package() +## ---------------------------------------------------------------- +## use this for a subdir that builds a library +## EXPORT drives .cmake config files intended for consumption +## by higher-level cmake projects via find_package() +## +#macro(xo_install_library target) +# install( +# TARGETS ${target} +# EXPORT ${target}Targets +# LIBRARY DESTINATION lib COMPONENT Runtime +# ARCHIVE DESTINATION lib COMPONENT Development +# RUNTIME DESTINATION bin COMPONENT Runtime +# PUBLIC_HEADER DESTINATION include COMPONENT Development +# BUNDLE DESTINATION bin COMPONENT Runtime +# ) +#endmacro() # -macro(xo_install_library target) - install( - TARGETS ${target} - EXPORT ${target}Targets - LIBRARY DESTINATION lib COMPONENT Runtime - ARCHIVE DESTINATION lib COMPONENT Development - RUNTIME DESTINATION bin COMPONENT Runtime - PUBLIC_HEADER DESTINATION include COMPONENT Development - BUNDLE DESTINATION bin COMPONENT Runtime - ) -endmacro() - -# ---------------------------------------------------------------- -# use this when relying on indentlog [[https://github.com/rconybea/indentlog]] headers +## ---------------------------------------------------------------- +## use this when relying on indentlog [[https://github.com/rconybea/indentlog]] headers +## +#macro(xo_indentlog_dependency target) +# find_package(indentlog REQUIRED) +# #add_dependencies(${target} indentlog) +# target_link_libraries(${target} PUBLIC indentlog) +# #target_include_directories(${target} PUBLIC ${indentlog_DIR}/../../../include) +#endmacro() # -macro(xo_indentlog_dependency target) - find_package(indentlog REQUIRED) - #add_dependencies(${target} indentlog) - target_link_libraries(${target} PUBLIC indentlog) - #target_include_directories(${target} PUBLIC ${indentlog_DIR}/../../../include) -endmacro() diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index d4b772e4..bf709478 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -17,7 +17,7 @@ set_target_properties(${SELF_LIBRARY_NAME} #target_compile_options(${SELF_LIBRARY_NAME} PRIVATE -Werror -Wall -Wextra) xo_compile_options(${SELF_LIBRARY_NAME}) xo_include_options2(${SELF_LIBRARY_NAME}) -xo_install_library(${SELF_LIBRARY_NAME}) +xo_install_library2(${SELF_LIBRARY_NAME}) # ---------------------------------------------------------------- # dependencies: logutil, ... diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 80ce49ac..7eae2825 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -4,7 +4,7 @@ set(SELF_EXECUTABLE_NAME utest.reflect) set(SELF_SOURCE_FILES reflect_utest_main.cpp StructReflector.test.cpp VectorTdx.test.cpp StructTdx.test.cpp) add_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) -xo_include_options(${SELF_EXECUTABLE_NAME}) +xo_include_options2(${SELF_EXECUTABLE_NAME}) add_test(NAME ${SELF_EXECUTABLE_NAME} COMMAND ${SELF_EXECUTABLE_NAME}) target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) From dbb9f38e8d2a6ceb9bfdd3bc4909f1390d77088c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 16:06:18 -0400 Subject: [PATCH 0185/2693] xo-cmake: + xo_toplevel_compile_options() --- cmake/xo_cxx.cmake | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 160a1833..dac88151 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -1,6 +1,32 @@ -set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) -set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) +macro(xo_toplevel_compile_options) + # ---------------------------------------------------------------- + # variable + # XO_ADDRESS_SANITIZE + # determines whether to enable address sanitizer for the XO project + # (see toplevel CMakeLists.txt) + # ---------------------------------------------------------------- + if(XO_ADDRESS_SANITIZE) + add_compile_options(-fsanitize=address) + add_link_options(-fsanitize=address) + endif() + + set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) + # XO_ADDRESS_SANITIZE_COMPILE_OPTIONS: use when XO_ADDRESS_SANITIZE=ON + # + # address sanitizer build complains about _FORTIFY_SOURCE redefines + # In file included from :460: + # :1:9: error: '_FORTIFY_SOURCE' macro redefined [-Werror,-Wmacro-redefined] + # #define _FORTIFY_SOURCE 2 + # + set(XO_ADDRESS_SANITIZE_COMPILE_OPTIONS -Werror -Wall -Wextra -Wno-macro-redefined) + + if(XO_ADDRESS_SANITIZE) + set(XO_COMPILE_OPTIONS ${XO_ADDRESS_SANITIZE_COMPILE_OPTIONS}) + else() + set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) + endif() +endif() # ---------------------------------------------------------------- # use this in subdirs that compile c++ code @@ -38,6 +64,9 @@ endmacro() # ---------------------------------------------------------------- # +# Require: +# needs to be preceded by call to xo_toplevel_compile_options() +# macro(xo_compile_options target) target_copmile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) endmacro() From 7a58d584700d106de9d1efad40e7000f20537a44 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 16:08:43 -0400 Subject: [PATCH 0186/2693] reflect: build: use xo_toplevel_compile_options() to simplify --- CMakeLists.txt | 3 ++ cmake/cxx.cmake | 122 ++++++++++++------------------------------------ 2 files changed, 33 insertions(+), 92 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d2718ef2..7b0240d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,10 +45,13 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") +xo_toplevel_compile_options() + if(NOT CMAKE_INSTALL_RPATH) set(CMAKE_INSTALL_RPATH $(CMAKE_INSTALL_PREFIX)/lib CACHE STRING "runpath in installed libraries/executables") endif() + # early find_package() experiment find_package(indentlog CONFIG REQUIRED) find_package(refcnt CONFIG REQUIRED) diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake index 4084bac9..ede44e35 100644 --- a/cmake/cxx.cmake +++ b/cmake/cxx.cmake @@ -1,99 +1,37 @@ ## ---------------------------------------------------------------- -## use this in subdirs that compile c++ code -## -#macro(xo_include_options target) -# # ---------------------------------------------------------------- -# # PROJECT_SOURCE_DIR: -# # so we can for example write -# # #include "ordinaltree/foo.hpp" -# # from anywhere in the project -# # PROJECT_BINARY_DIR: -# # since generated version file will be in build directory, -# # need that build directory to also appear in -# # compiler's include path -# # -# target_include_directories( -# ${target} PUBLIC -# $ # e.g. for #include "indentlog/scope.hpp" -# $ -# $ # e.g. for #include "Refcounted.hpp" in refcnt/src -# $ -# $ # e.g. for generated config.hpp file -# ) -# -# # ---------------------------------------------------------------- -# # make standard directories for std:: includes explicit -# # so that -# # (1) they appear in compile_commands.json. -# # (2) clangd (run from emacs lsp-mode) can find them -# # -# if(CMAKE_EXPORT_COMPILE_COMMANDS) -# set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) -# endif() -#endmacro() - -# ---------------------------------------------------------------- -# variable -# XO_ADDRESS_SANITIZE -# determines whether to enable address sanitizer for the XO project -# (see toplevel CMakeLists.txt) -# ---------------------------------------------------------------- -if(XO_ADDRESS_SANITIZE) - add_compile_options(-fsanitize=address) - add_link_options(-fsanitize=address) -endif() - -# XO_STANDARD_COMPILE_OPTIONS: use these when XO_ADDRESS_SANITIZE=OFF -set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) - -# XO_ADDRESS_SANITIZE_COMPILE_OPTIONS: use when XO_ADDRESS_SANITIZE=ON -# -# address sanitizer build complains about _FORTIFY_SOURCE redefines -# In file included from :460: -# :1:9: error: '_FORTIFY_SOURCE' macro redefined [-Werror,-Wmacro-redefined] -# #define _FORTIFY_SOURCE 2 -# -set(XO_ADDRESS_SANITIZE_COMPILE_OPTIONS -Werror -Wall -Wextra -Wno-macro-redefined) - -# XO_COMPILE_OPTIONS: use these with xo_compile_options() macro -if(XO_ADDRESS_SANITIZE) - set(XO_COMPILE_OPTIONS ${XO_ADDRESS_SANITIZE_COMPILE_OPTIONS}) -else() - set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) -endif() - -# ---------------------------------------------------------------- -# generally want all the errors+warnings! -# however: address sanitizer generates error on _FORTIFY_SOURCE -# -macro(xo_compile_options target) - target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) -endmacro() - +## variable +## XO_ADDRESS_SANITIZE +## determines whether to enable address sanitizer for the XO project +## (see toplevel CMakeLists.txt) ## ---------------------------------------------------------------- -## use this for a subdir that builds a library -## EXPORT drives .cmake config files intended for consumption -## by higher-level cmake projects via find_package() +#if(XO_ADDRESS_SANITIZE) +# add_compile_options(-fsanitize=address) +# add_link_options(-fsanitize=address) +#endif() +# +## XO_STANDARD_COMPILE_OPTIONS: use these when XO_ADDRESS_SANITIZE=OFF +#set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) +# +## XO_ADDRESS_SANITIZE_COMPILE_OPTIONS: use when XO_ADDRESS_SANITIZE=ON ## -#macro(xo_install_library target) -# install( -# TARGETS ${target} -# EXPORT ${target}Targets -# LIBRARY DESTINATION lib COMPONENT Runtime -# ARCHIVE DESTINATION lib COMPONENT Development -# RUNTIME DESTINATION bin COMPONENT Runtime -# PUBLIC_HEADER DESTINATION include COMPONENT Development -# BUNDLE DESTINATION bin COMPONENT Runtime -# ) -#endmacro() +## address sanitizer build complains about _FORTIFY_SOURCE redefines +## In file included from :460: +## :1:9: error: '_FORTIFY_SOURCE' macro redefined [-Werror,-Wmacro-redefined] +## #define _FORTIFY_SOURCE 2 +## +#set(XO_ADDRESS_SANITIZE_COMPILE_OPTIONS -Werror -Wall -Wextra -Wno-macro-redefined) +# +## XO_COMPILE_OPTIONS: use these with xo_compile_options() macro +#if(XO_ADDRESS_SANITIZE) +# set(XO_COMPILE_OPTIONS ${XO_ADDRESS_SANITIZE_COMPILE_OPTIONS}) +#else() +# set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) +#endif() # ## ---------------------------------------------------------------- -## use this when relying on indentlog [[https://github.com/rconybea/indentlog]] headers +## generally want all the errors+warnings! +## however: address sanitizer generates error on _FORTIFY_SOURCE ## -#macro(xo_indentlog_dependency target) -# find_package(indentlog REQUIRED) -# #add_dependencies(${target} indentlog) -# target_link_libraries(${target} PUBLIC indentlog) -# #target_include_directories(${target} PUBLIC ${indentlog_DIR}/../../../include) +#macro(xo_compile_options target) +# target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) #endmacro() -# From 6d9eb677433a470f7141c41a09a8d64a25281f92 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 16:14:12 -0400 Subject: [PATCH 0187/2693] xo-cmake: bugfix: typos! --- cmake/xo_cxx.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index e435de55..1d1736fe 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -26,7 +26,7 @@ macro(xo_toplevel_compile_options) else() set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) endif() -endif() +endmacro() # ---------------------------------------------------------------- # use this in subdirs that compile c++ code @@ -68,7 +68,7 @@ endmacro() # needs to be preceded by call to xo_toplevel_compile_options() # macro(xo_compile_options target) - target_copmile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) + target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) endmacro() # ---------------------------------------------------------------- From 7f6579bf30e315cda4c60c679ae455dc443f5520 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 16:20:50 -0400 Subject: [PATCH 0188/2693] indentlog: github: fix workflow --- .github/workflows/cmake-single-platform.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 7787e3c5..433a3f9f 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -48,8 +48,7 @@ jobs: - name: Configure self (indentlog) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - + run: cmake -B ${{github.workspace}}/build -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - name: Build self (indentlog) # Build your program with the given configuration run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} From fc1963424ce5d69e5e6f1887ee96979e8438db4a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 16:28:43 -0400 Subject: [PATCH 0189/2693] xo-cmake: + code-coverage.cmake --- cmake/code-coverage.cmake | 678 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 678 insertions(+) create mode 100644 cmake/code-coverage.cmake diff --git a/cmake/code-coverage.cmake b/cmake/code-coverage.cmake new file mode 100644 index 00000000..b6b36064 --- /dev/null +++ b/cmake/code-coverage.cmake @@ -0,0 +1,678 @@ +# +# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +# USAGE: To enable any code coverage instrumentation/targets, the single CMake +# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or +# on the command line. +# +# From this point, there are two primary methods for adding instrumentation to +# targets: +# +# 1 - A blanket instrumentation by calling `add_code_coverage()`, where +# all targets in that directory and all subdirectories are automatically +# instrumented. +# +# 2 - Per-target instrumentation by calling +# `target_code_coverage()`, where the target is given and thus only +# that target is instrumented. This applies to both libraries and executables. +# +# To add coverage targets, such as calling `make ccov` to generate the actual +# coverage information for perusal or consumption, call +# `target_code_coverage()` on an *executable* target. +# +# Example 1: All targets instrumented +# +# In this case, the coverage information reported will will be that of the +# `theLib` library target and `theExe` executable. +# +# 1a: Via global command +# +# ~~~ +# add_code_coverage() # Adds instrumentation to all targets +# +# add_library(theLib lib.cpp) +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target +# # (instrumentation already added via global anyways) +# # for generating code coverage reports. +# ~~~ +# +# 1b: Via target commands +# +# ~~~ +# add_library(theLib lib.cpp) +# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. +# ~~~ +# +# Example 2: Target instrumented, but with regex pattern of files to be excluded +# from report +# +# ~~~ +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ +# +# Example 3: Target added to the 'ccov' and 'ccov-all' targets +# +# ~~~ +# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. +# +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ + +# Options +option( + CODE_COVERAGE + "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" + OFF) + +# Programs +find_program(LLVM_COV_PATH llvm-cov) +find_program(LLVM_PROFDATA_PATH llvm-profdata) +find_program(LCOV_PATH lcov) +find_program(GENHTML_PATH genhtml) +# Hide behind the 'advanced' mode flag for GUI/ccmake +mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) + +# Variables +set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) +set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) + +# Common initialization/checks +if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) + set(CODE_COVERAGE_ADDED ON) + + # Common Targets + add_custom_target( + ccov-preprocessing + COMMAND ${CMAKE_COMMAND} -E make_directory + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} + DEPENDS ccov-clean) + + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + # Messages + message(STATUS "Building with llvm Code Coverage Tools") + + if(NOT LLVM_COV_PATH) + message(FATAL_ERROR "llvm-cov not found! Aborting.") + else() + # Version number checking for 'EXCLUDE' compatibility + execute_process(COMMAND ${LLVM_COV_PATH} --version + OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) + string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION + ${LLVM_COV_VERSION_CALL_OUTPUT}) + + if(LLVM_COV_VERSION VERSION_LESS "7.0.0") + message( + WARNING + "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" + ) + endif() + endif() + + # Targets + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + else() + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + endif() + + # Used to get the shared object file list before doing the main all- + # processing + add_custom_target( + ccov-libs + COMMAND ; + COMMENT "libs ready for coverage report.") + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + # Messages + message(STATUS "Building with lcov Code Coverage Tools") + + if(CMAKE_BUILD_TYPE) + string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) + if(NOT ${upper_build_type} STREQUAL "DEBUG") + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + else() + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() + + # Targets + add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory + ${CMAKE_BINARY_DIR} --zerocounters) + + else() + message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") + endif() +endif() + +# Adds code coverage instrumentation to a library, or instrumentation/targets +# for an executable target. +# ~~~ +# EXECUTABLE ADDED TARGETS: +# GCOV/LCOV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# +# LLVM-COV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report. +# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. +# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. +# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. +# ccov-all-export : Exports the coverage report to a JSON file. +# +# Required: +# TARGET_NAME - Name of the target to generate code coverage for. +# Optional: +# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. +# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. +# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) +# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. +# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. +# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory +# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** +# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output +# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call +# ~~~ +function(target_code_coverage TARGET_NAME) + # Argument parsing + set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) + set(single_value_keywords COVERAGE_TARGET_NAME) + set(multi_value_keywords EXCLUDE OBJECTS ARGS) + cmake_parse_arguments( + target_code_coverage "${options}" "${single_value_keywords}" + "${multi_value_keywords}" ${ARGN}) + + # Set the visibility of target functions to PUBLIC, INTERFACE or default to + # PRIVATE. + if(target_code_coverage_PUBLIC) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY PUBLIC) + elseif(target_code_coverage_INTERFACE) + set(TARGET_VISIBILITY INTERFACE) + set(TARGET_LINK_VISIBILITY INTERFACE) + elseif(target_code_coverage_PLAIN) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY) + else() + set(TARGET_VISIBILITY PRIVATE) + set(TARGET_LINK_VISIBILITY PRIVATE) + endif() + + if(NOT target_code_coverage_COVERAGE_TARGET_NAME) + # If a specific name was given, use that instead. + set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) + endif() + + if(CODE_COVERAGE) + + # Add code coverage instrumentation to the target's linker command + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs + -ftest-coverage) + target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) + endif() + + # Targets + get_target_property(target_type ${TARGET_NAME} TYPE) + + # Add shared library to processing for 'all' targets + if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" >> + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + if(NOT TARGET ccov-libs) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-libs + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # For executables add targets to run and produce output + if(target_type STREQUAL "EXECUTABLE") + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # If there are shared objects to also work with, generate the string to + # add them here + foreach(SO_TARGET ${target_code_coverage_OBJECTS}) + # Check to see if the target is a shared object + if(TARGET ${SO_TARGET}) + get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) + if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") + set(SO_OBJECTS ${SO_OBJECTS} -object=$) + endif() + endif() + endforeach() + + # Run the executable, generating raw profile data Make the run data + # available for further processing. Separated to allow Windows to run + # this target serially. + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E env + LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw + $ ${target_code_coverage_ARGS} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" + ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND + ${CMAKE_COMMAND} -E echo + "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" + >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list + JOB_POOL ccov_serial_pool + DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) + + # Merge the generated profile data so llvm-cov can process it + add_custom_target( + ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_PROFDATA_PATH} merge -sparse + ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o + ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Ignore regex only works on LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print out details of the coverage information to the command line + add_custom_target( + ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Print out a summary of the coverage information to the command line + add_custom_target( + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} report $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} export $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO + "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" + ) + + # Run the executable, generating coverage information + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND $ ${target_code_coverage_ARGS} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + # Generate exclusion string for use + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + if(NOT ${target_code_coverage_EXTERNAL}) + set(EXTERNAL_OPTION --no-external) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + else() + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + endif() + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${GENHTML_PATH} -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + ${COVERAGE_INFO} + DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + + add_custom_command( + TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." + ) + + # AUTO + if(target_code_coverage_AUTO) + if(NOT TARGET ccov) + add_custom_target(ccov) + endif() + add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) + + if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID + MATCHES "GNU") + if(NOT TARGET ccov-report) + add_custom_target(ccov-report) + endif() + add_dependencies( + ccov-report + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # ALL + if(target_code_coverage_ALL) + if(NOT TARGET ccov-all-processing) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-all-processing + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + endif() +endfunction() + +# Adds code coverage instrumentation to all targets in the current directory and +# any subdirectories. To add coverage instrumentation to only specific targets, +# use `target_code_coverage`. +function(add_code_coverage) + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_compile_options(-fprofile-instr-generate -fcoverage-mapping) + add_link_options(-fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + add_compile_options(-fprofile-arcs -ftest-coverage) + link_libraries(gcov) + endif() + endif() +endfunction() + +# Adds the 'ccov-all' type targets that calls all targets added via +# `target_code_coverage` with the `ALL` parameter, but merges all the coverage +# data from them into a single large report instead of the numerous smaller +# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for +# use with coverage dashboards (e.g. codecov.io, coveralls). +# ~~~ +# Optional: +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! +# ~~~ +function(add_code_coverage_all_targets) + # Argument parsing + set(multi_value_keywords EXCLUDE) + cmake_parse_arguments(add_code_coverage_all_targets "" "" + "${multi_value_keywords}" ${ARGN}) + + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # Merge the profile data for all of the run executables + if(WIN32) + add_custom_target( + ccov-all-processing + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe + merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -sparse $$FILELIST) + else() + add_custom_target( + ccov-all-processing + COMMAND + ${LLVM_PROFDATA_PATH} merge -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) + endif() + + # Regex exclude only available for LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print summary of the code coverage information to the command line + if(WIN32) + add_custom_target( + ccov-all-report + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe + report $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all-report + COMMAND + ${LLVM_COV_PATH} report `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-all-export + COMMAND + ${LLVM_COV_PATH} export `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json + DEPENDS ccov-all-processing) + + # Generate HTML output of all added targets for perusal + if(WIN32) + add_custom_target( + ccov-all + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show + $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all + COMMAND + ${LLVM_COV_PATH} show `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") + + # Nothing required for gcov + add_custom_target(ccov-all-processing COMMAND ;) + + # Exclusion regex string creation + set(EXCLUDE_REGEX) + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + else() + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + endif() + + # Generates HTML output of all targets for perusal + add_custom_target( + ccov-all + COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} + DEPENDS ccov-all-capture) + + endif() + + add_custom_command( + TARGET ccov-all + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." + ) + endif() +endfunction() From 6ab05249030dcf074c67e8bb86144d7bfd5c5143 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 16:31:52 -0400 Subject: [PATCH 0190/2693] xo-cmake: bugfix: must install code-coverage.cmake --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index b1480c47..c018ef2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,5 +13,6 @@ set(XO_PROJECT_NAME xo_macros) install( FILES "cmake/xo_cxx.cmake" + "cmake/code-coverage.cmake" DESTINATION share/cmake/${XO_PROJECT_NAME} ) From ef0b02f4cae31cd7f3e24f2afd6c126fca62e6d1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 16:32:29 -0400 Subject: [PATCH 0191/2693] reflect: use xo_macros code coverage --- CMakeLists.txt | 3 +-- cmake/cxx.cmake | 37 ------------------------------------- 2 files changed, 1 insertion(+), 39 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b0240d0..4ca908c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,7 @@ enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) include(xo_macros/xo_cxx) -include(cmake/cxx.cmake) -include(cmake/code-coverage.cmake) +include(xo_macros/code-coverage) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake index ede44e35..e69de29b 100644 --- a/cmake/cxx.cmake +++ b/cmake/cxx.cmake @@ -1,37 +0,0 @@ -## ---------------------------------------------------------------- -## variable -## XO_ADDRESS_SANITIZE -## determines whether to enable address sanitizer for the XO project -## (see toplevel CMakeLists.txt) -## ---------------------------------------------------------------- -#if(XO_ADDRESS_SANITIZE) -# add_compile_options(-fsanitize=address) -# add_link_options(-fsanitize=address) -#endif() -# -## XO_STANDARD_COMPILE_OPTIONS: use these when XO_ADDRESS_SANITIZE=OFF -#set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) -# -## XO_ADDRESS_SANITIZE_COMPILE_OPTIONS: use when XO_ADDRESS_SANITIZE=ON -## -## address sanitizer build complains about _FORTIFY_SOURCE redefines -## In file included from :460: -## :1:9: error: '_FORTIFY_SOURCE' macro redefined [-Werror,-Wmacro-redefined] -## #define _FORTIFY_SOURCE 2 -## -#set(XO_ADDRESS_SANITIZE_COMPILE_OPTIONS -Werror -Wall -Wextra -Wno-macro-redefined) -# -## XO_COMPILE_OPTIONS: use these with xo_compile_options() macro -#if(XO_ADDRESS_SANITIZE) -# set(XO_COMPILE_OPTIONS ${XO_ADDRESS_SANITIZE_COMPILE_OPTIONS}) -#else() -# set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) -#endif() -# -## ---------------------------------------------------------------- -## generally want all the errors+warnings! -## however: address sanitizer generates error on _FORTIFY_SOURCE -## -#macro(xo_compile_options target) -# target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) -#endmacro() From e8a79ebb57967e8db79c2a0052018005e0ca45b2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 16:33:05 -0400 Subject: [PATCH 0192/2693] reflect: retire superseded .cmake files --- cmake/code-coverage.cmake | 678 -------------------------------------- cmake/cxx.cmake | 0 2 files changed, 678 deletions(-) delete mode 100644 cmake/code-coverage.cmake delete mode 100644 cmake/cxx.cmake diff --git a/cmake/code-coverage.cmake b/cmake/code-coverage.cmake deleted file mode 100644 index b6b36064..00000000 --- a/cmake/code-coverage.cmake +++ /dev/null @@ -1,678 +0,0 @@ -# -# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy of -# the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations under -# the License. - -# USAGE: To enable any code coverage instrumentation/targets, the single CMake -# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or -# on the command line. -# -# From this point, there are two primary methods for adding instrumentation to -# targets: -# -# 1 - A blanket instrumentation by calling `add_code_coverage()`, where -# all targets in that directory and all subdirectories are automatically -# instrumented. -# -# 2 - Per-target instrumentation by calling -# `target_code_coverage()`, where the target is given and thus only -# that target is instrumented. This applies to both libraries and executables. -# -# To add coverage targets, such as calling `make ccov` to generate the actual -# coverage information for perusal or consumption, call -# `target_code_coverage()` on an *executable* target. -# -# Example 1: All targets instrumented -# -# In this case, the coverage information reported will will be that of the -# `theLib` library target and `theExe` executable. -# -# 1a: Via global command -# -# ~~~ -# add_code_coverage() # Adds instrumentation to all targets -# -# add_library(theLib lib.cpp) -# -# add_executable(theExe main.cpp) -# target_link_libraries(theExe PRIVATE theLib) -# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target -# # (instrumentation already added via global anyways) -# # for generating code coverage reports. -# ~~~ -# -# 1b: Via target commands -# -# ~~~ -# add_library(theLib lib.cpp) -# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. -# -# add_executable(theExe main.cpp) -# target_link_libraries(theExe PRIVATE theLib) -# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. -# ~~~ -# -# Example 2: Target instrumented, but with regex pattern of files to be excluded -# from report -# -# ~~~ -# add_executable(theExe main.cpp non_covered.cpp) -# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. -# ~~~ -# -# Example 3: Target added to the 'ccov' and 'ccov-all' targets -# -# ~~~ -# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. -# -# add_executable(theExe main.cpp non_covered.cpp) -# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. -# ~~~ - -# Options -option( - CODE_COVERAGE - "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" - OFF) - -# Programs -find_program(LLVM_COV_PATH llvm-cov) -find_program(LLVM_PROFDATA_PATH llvm-profdata) -find_program(LCOV_PATH lcov) -find_program(GENHTML_PATH genhtml) -# Hide behind the 'advanced' mode flag for GUI/ccmake -mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) - -# Variables -set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) -set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) - -# Common initialization/checks -if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) - set(CODE_COVERAGE_ADDED ON) - - # Common Targets - add_custom_target( - ccov-preprocessing - COMMAND ${CMAKE_COMMAND} -E make_directory - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} - DEPENDS ccov-clean) - - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - # Messages - message(STATUS "Building with llvm Code Coverage Tools") - - if(NOT LLVM_COV_PATH) - message(FATAL_ERROR "llvm-cov not found! Aborting.") - else() - # Version number checking for 'EXCLUDE' compatibility - execute_process(COMMAND ${LLVM_COV_PATH} --version - OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) - string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION - ${LLVM_COV_VERSION_CALL_OUTPUT}) - - if(LLVM_COV_VERSION VERSION_LESS "7.0.0") - message( - WARNING - "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" - ) - endif() - endif() - - # Targets - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-clean - COMMAND ${CMAKE_COMMAND} -E remove -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND ${CMAKE_COMMAND} -E remove -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) - else() - add_custom_target( - ccov-clean - COMMAND ${CMAKE_COMMAND} -E rm -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND ${CMAKE_COMMAND} -E rm -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) - endif() - - # Used to get the shared object file list before doing the main all- - # processing - add_custom_target( - ccov-libs - COMMAND ; - COMMENT "libs ready for coverage report.") - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - # Messages - message(STATUS "Building with lcov Code Coverage Tools") - - if(CMAKE_BUILD_TYPE) - string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) - if(NOT ${upper_build_type} STREQUAL "DEBUG") - message( - WARNING - "Code coverage results with an optimized (non-Debug) build may be misleading" - ) - endif() - else() - message( - WARNING - "Code coverage results with an optimized (non-Debug) build may be misleading" - ) - endif() - if(NOT LCOV_PATH) - message(FATAL_ERROR "lcov not found! Aborting...") - endif() - if(NOT GENHTML_PATH) - message(FATAL_ERROR "genhtml not found! Aborting...") - endif() - - # Targets - add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory - ${CMAKE_BINARY_DIR} --zerocounters) - - else() - message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") - endif() -endif() - -# Adds code coverage instrumentation to a library, or instrumentation/targets -# for an executable target. -# ~~~ -# EXECUTABLE ADDED TARGETS: -# GCOV/LCOV: -# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. -# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. -# -# LLVM-COV: -# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-${TARGET_NAME} : Generates HTML code coverage report. -# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. -# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. -# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. -# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. -# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. -# ccov-all-export : Exports the coverage report to a JSON file. -# -# Required: -# TARGET_NAME - Name of the target to generate code coverage for. -# Optional: -# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. -# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. -# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) -# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. -# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. -# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory -# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. -# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** -# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output -# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call -# ~~~ -function(target_code_coverage TARGET_NAME) - # Argument parsing - set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) - set(single_value_keywords COVERAGE_TARGET_NAME) - set(multi_value_keywords EXCLUDE OBJECTS ARGS) - cmake_parse_arguments( - target_code_coverage "${options}" "${single_value_keywords}" - "${multi_value_keywords}" ${ARGN}) - - # Set the visibility of target functions to PUBLIC, INTERFACE or default to - # PRIVATE. - if(target_code_coverage_PUBLIC) - set(TARGET_VISIBILITY PUBLIC) - set(TARGET_LINK_VISIBILITY PUBLIC) - elseif(target_code_coverage_INTERFACE) - set(TARGET_VISIBILITY INTERFACE) - set(TARGET_LINK_VISIBILITY INTERFACE) - elseif(target_code_coverage_PLAIN) - set(TARGET_VISIBILITY PUBLIC) - set(TARGET_LINK_VISIBILITY) - else() - set(TARGET_VISIBILITY PRIVATE) - set(TARGET_LINK_VISIBILITY PRIVATE) - endif() - - if(NOT target_code_coverage_COVERAGE_TARGET_NAME) - # If a specific name was given, use that instead. - set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) - endif() - - if(CODE_COVERAGE) - - # Add code coverage instrumentation to the target's linker command - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} - -fprofile-instr-generate -fcoverage-mapping) - target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} - -fprofile-instr-generate -fcoverage-mapping) - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs - -ftest-coverage) - target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) - endif() - - # Targets - get_target_property(target_type ${TARGET_NAME} TYPE) - - # Add shared library to processing for 'all' targets - if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${CMAKE_COMMAND} -E echo "-object=$" >> - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - DEPENDS ccov-preprocessing ${TARGET_NAME}) - - if(NOT TARGET ccov-libs) - message( - FATAL_ERROR - "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." - ) - endif() - - add_dependencies(ccov-libs - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - - # For executables add targets to run and produce output - if(target_type STREQUAL "EXECUTABLE") - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - - # If there are shared objects to also work with, generate the string to - # add them here - foreach(SO_TARGET ${target_code_coverage_OBJECTS}) - # Check to see if the target is a shared object - if(TARGET ${SO_TARGET}) - get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) - if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") - set(SO_OBJECTS ${SO_OBJECTS} -object=$) - endif() - endif() - endforeach() - - # Run the executable, generating raw profile data Make the run data - # available for further processing. Separated to allow Windows to run - # this target serially. - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${CMAKE_COMMAND} -E env - LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw - $ ${target_code_coverage_ARGS} - COMMAND - ${CMAKE_COMMAND} -E echo "-object=$" - ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND - ${CMAKE_COMMAND} -E echo - "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" - >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list - JOB_POOL ccov_serial_pool - DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) - - # Merge the generated profile data so llvm-cov can process it - add_custom_target( - ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_PROFDATA_PATH} merge -sparse - ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o - ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Ignore regex only works on LLVM >= 7 - if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") - foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} - -ignore-filename-regex='${EXCLUDE_ITEM}') - endforeach() - endif() - - # Print out details of the coverage information to the command line - add_custom_target( - ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} show $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -show-line-counts-or-regions ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Print out a summary of the coverage information to the command line - add_custom_target( - ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} report $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Export coverage information so continuous integration tools (e.g. - # Jenkins) can consume it - add_custom_target( - ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} export $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -format="text" ${EXCLUDE_REGEX} > - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Generates HTML output of the coverage information for perusal - add_custom_target( - ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} show $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - set(COVERAGE_INFO - "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" - ) - - # Run the executable, generating coverage information - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND $ ${target_code_coverage_ARGS} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - - # Generate exclusion string for use - foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} - '${EXCLUDE_ITEM}') - endforeach() - - if(EXCLUDE_REGEX) - set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file - ${COVERAGE_INFO}) - else() - set(EXCLUDE_COMMAND ;) - endif() - - if(NOT ${target_code_coverage_EXTERNAL}) - set(EXTERNAL_OPTION --no-external) - endif() - - # Capture coverage data - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters - COMMAND $ ${target_code_coverage_ARGS} - COMMAND - ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory - ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file - ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - else() - add_custom_target( - ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters - COMMAND $ ${target_code_coverage_ARGS} - COMMAND - ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory - ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file - ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - endif() - - # Generates HTML output of the coverage information for perusal - add_custom_target( - ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${GENHTML_PATH} -o - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} - ${COVERAGE_INFO} - DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - - add_custom_command( - TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - POST_BUILD - COMMAND ; - COMMENT - "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." - ) - - # AUTO - if(target_code_coverage_AUTO) - if(NOT TARGET ccov) - add_custom_target(ccov) - endif() - add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) - - if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID - MATCHES "GNU") - if(NOT TARGET ccov-report) - add_custom_target(ccov-report) - endif() - add_dependencies( - ccov-report - ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - - # ALL - if(target_code_coverage_ALL) - if(NOT TARGET ccov-all-processing) - message( - FATAL_ERROR - "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." - ) - endif() - - add_dependencies(ccov-all-processing - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - endif() -endfunction() - -# Adds code coverage instrumentation to all targets in the current directory and -# any subdirectories. To add coverage instrumentation to only specific targets, -# use `target_code_coverage`. -function(add_code_coverage) - if(CODE_COVERAGE) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - add_compile_options(-fprofile-instr-generate -fcoverage-mapping) - add_link_options(-fprofile-instr-generate -fcoverage-mapping) - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - add_compile_options(-fprofile-arcs -ftest-coverage) - link_libraries(gcov) - endif() - endif() -endfunction() - -# Adds the 'ccov-all' type targets that calls all targets added via -# `target_code_coverage` with the `ALL` parameter, but merges all the coverage -# data from them into a single large report instead of the numerous smaller -# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for -# use with coverage dashboards (e.g. codecov.io, coveralls). -# ~~~ -# Optional: -# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! -# ~~~ -function(add_code_coverage_all_targets) - # Argument parsing - set(multi_value_keywords EXCLUDE) - cmake_parse_arguments(add_code_coverage_all_targets "" "" - "${multi_value_keywords}" ${ARGN}) - - if(CODE_COVERAGE) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - - # Merge the profile data for all of the run executables - if(WIN32) - add_custom_target( - ccov-all-processing - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe - merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -sparse $$FILELIST) - else() - add_custom_target( - ccov-all-processing - COMMAND - ${LLVM_PROFDATA_PATH} merge -o - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) - endif() - - # Regex exclude only available for LLVM >= 7 - if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") - foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} - -ignore-filename-regex='${EXCLUDE_ITEM}') - endforeach() - endif() - - # Print summary of the code coverage information to the command line - if(WIN32) - add_custom_target( - ccov-all-report - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe - report $$FILELIST - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - else() - add_custom_target( - ccov-all-report - COMMAND - ${LLVM_COV_PATH} report `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - endif() - - # Export coverage information so continuous integration tools (e.g. - # Jenkins) can consume it - add_custom_target( - ccov-all-export - COMMAND - ${LLVM_COV_PATH} export `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -format="text" ${EXCLUDE_REGEX} > - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json - DEPENDS ccov-all-processing) - - # Generate HTML output of all added targets for perusal - if(WIN32) - add_custom_target( - ccov-all - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show - $$FILELIST - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - else() - add_custom_target( - ccov-all - COMMAND - ${LLVM_COV_PATH} show `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - endif() - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") - - # Nothing required for gcov - add_custom_target(ccov-all-processing COMMAND ;) - - # Exclusion regex string creation - set(EXCLUDE_REGEX) - foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} - '${EXCLUDE_ITEM}') - endforeach() - - if(EXCLUDE_REGEX) - set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file - ${COVERAGE_INFO}) - else() - set(EXCLUDE_COMMAND ;) - endif() - - # Capture coverage data - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-all-capture - COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture - --output-file ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ccov-all-processing) - else() - add_custom_target( - ccov-all-capture - COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture - --output-file ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ccov-all-processing) - endif() - - # Generates HTML output of all targets for perusal - add_custom_target( - ccov-all - COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} - DEPENDS ccov-all-capture) - - endif() - - add_custom_command( - TARGET ccov-all - POST_BUILD - COMMAND ; - COMMENT - "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." - ) - endif() -endfunction() diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake deleted file mode 100644 index e69de29b..00000000 From 33f96604ee9052f540fd428ee9c34a22d4d2528e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 16:38:01 -0400 Subject: [PATCH 0193/2693] build: cosmetic: remove dead comments --- CMakeLists.txt | 48 -------------------------------------- src/reflect/CMakeLists.txt | 24 ------------------- 2 files changed, 72 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4ca908c6..407d67c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,7 +50,6 @@ if(NOT CMAKE_INSTALL_RPATH) set(CMAKE_INSTALL_RPATH $(CMAKE_INSTALL_PREFIX)/lib CACHE STRING "runpath in installed libraries/executables") endif() - # early find_package() experiment find_package(indentlog CONFIG REQUIRED) find_package(refcnt CONFIG REQUIRED) @@ -66,54 +65,7 @@ add_subdirectory(utest) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -## ---------------------------------------------------------------- -## cmake export: -## -## populate .cmake files in $CMAKE_INSTALL_LIBDIR/cmake/reflect. -## cmake projects that include this directory in $CMAKE_PREFIX_PATH -## can use -## find_package(reflect REQUIRED) -## and -## target_link_libraries(${sometarget} PUBLIC reflect) -## to use the reflect library -# -#set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") -#set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") -# -#include(CMakePackageConfigHelpers) -# -## generates build/reflectConfigVersion.cmake -#write_basic_package_version_file( -# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" -# VERSION 0.1 -# COMPATIBILITY AnyNewerVersion -#) -# -## generates build/reflectConfig.cmake -#configure_package_config_file( -# "${PROJECT_SOURCE_DIR}/cmake/${XO_PROJECT_NAME}Config.cmake.in" -# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" -# INSTALL_DESTINATION lib/cmake/${XO_PROJECT_NAME} -#) -# -## creates {reflectTargets.cmake, reflectTargets-noconfig.cmake} in $CMAKE_INSTALL_LIBDIR/cmake/reflect/ -## requires -## install(.. EXPORT reflectTargets ..) -## -#install( -# EXPORT ${XO_PROJECT_NAME}Targets -# DESTINATION lib/cmake/${XO_PROJECT_NAME} -#) -# -## creates {reflectConfigVersion.cmake, reflectConfig.cmake} in $CMAKE_INSTALL_LIBDIR/cmake/reflect/ -#install( -# FILES -# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" -# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" -# DESTINATION lib/cmake/${XO_PROJECT_NAME}) - # ---------------------------------------------------------------- # install .hpp files xo_install_include_tree() -#install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/reflect/ DESTINATION include/reflect) diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index bf709478..0b5c710e 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -22,32 +22,8 @@ xo_install_library2(${SELF_LIBRARY_NAME}) # ---------------------------------------------------------------- # dependencies: logutil, ... -#target_include_directories( -# ${SELF_LIBRARY_NAME} PUBLIC -# $ -# $ -#) - -#xo_refcnt_dependency(${SELF_LIBRARY_NAME}) -#xo_indentlog_dependency(${SELF_LIBRARY_NAME}) - -#add_dependencies(${SELF_LIBRARY_NAME} indentlog) -# note: can't use find_package() here, -# because find_package() needs to run successfully before -# dependency gets installed. xo_internal_dependency(${SELF_LIBRARY_NAME} indentlog) - -#target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC indentlog) - xo_internal_dependency(${SELF_LIBRARY_NAME} refcnt) -#find_package(refcnt CONFIG REQUIRED) -##add_dependencies(${SELF_LIBRARY_NAME} refcnt) -#target_include_directories( -# ${SELF_LIBRARY_NAME} PUBLIC -# $ -# $ -#) -#target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC refcnt) # ---------------------------------------------------------------- # 3rd party dependency: boost: From b75e3b8fb2d6d5cb755a3b3682a96fbae370ca2e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 16:38:15 -0400 Subject: [PATCH 0194/2693] reflect: build: utest streamlining --- utest/CMakeLists.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 7eae2825..2e1ba51f 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -20,14 +20,15 @@ target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) # since version file will be in build directory, need that directory # to also be included in compiler's include path # -target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC - ${PROJECT_SOURCE_DIR} - ${PROJECT_BINARY_DIR}) +xo_include_options2(${SELF_EXECUTABLE_NAME}) +#target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC +# ${PROJECT_SOURCE_DIR} +# ${PROJECT_BINARY_DIR}) # ---------------------------------------------------------------- # internal dependencies: logutil, ... -target_link_libraries(${SELF_EXECUTABLE_NAME} PUBLIC reflect) +xo_internal_dependency(${SELF_EXECUTABLE_NAME} reflect) # ---------------------------------------------------------------- # 3rd part dependency: catch2: From 0c9d13b7eb0a915604a44ea417552f243e27f5f0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 16:54:26 -0400 Subject: [PATCH 0195/2693] xo-cmake: + xo_self_dependency() --- cmake/xo_cxx.cmake | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 1d1736fe..018f75fe 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -146,3 +146,14 @@ macro(xo_internal_dependency target dep) find_package(${dep} CONFIG REQUIRED) target_link_libraries(${target} PUBLIC ${dep}) endmacro() + +# dependency on target provided from this codebase. +# +# 1. don't need find_package() in this case, since details of dep targets +# must be known to cmake for it to build them. +# 2. in any case, can't use find_package() when cmake runs, +# because supporting .cmake files haven't been generated yet +# +macro(xo_self_dependency target dep) + target_link_libraries(${target} PUBLIC ${dep}) +endmacro() From 25712169ee585da4e61bbc78f4d2b0318e28b149 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 16:54:48 -0400 Subject: [PATCH 0196/2693] reflect: build: streamline --- CMakeLists.txt | 4 ++-- utest/CMakeLists.txt | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 407d67c6..ec69e9c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,8 +51,8 @@ if(NOT CMAKE_INSTALL_RPATH) endif() # early find_package() experiment -find_package(indentlog CONFIG REQUIRED) -find_package(refcnt CONFIG REQUIRED) +#find_package(indentlog CONFIG REQUIRED) +#find_package(refcnt CONFIG REQUIRED) # ---------------------------------------------------------------- # sources diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 2e1ba51f..75f16ee5 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -28,10 +28,11 @@ xo_include_options2(${SELF_EXECUTABLE_NAME}) # ---------------------------------------------------------------- # internal dependencies: logutil, ... -xo_internal_dependency(${SELF_EXECUTABLE_NAME} reflect) +# this won't work. when cmake runs, the Config files for reflect haven't been created yet. +xo_self_dependency(${SELF_EXECUTABLE_NAME} reflect) # ---------------------------------------------------------------- -# 3rd part dependency: catch2: +# 3rd party dependency: catch2: find_package(Catch2 2 REQUIRED) From a35770dc78d033659230449b823d743c36d7d9f2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 17:07:49 -0400 Subject: [PATCH 0197/2693] refcnt: build: streamline, using xo-cmake macros --- CMakeLists.txt | 38 +-- cmake/code-coverage.cmake | 678 -------------------------------------- src/CMakeLists.txt | 3 +- utest/CMakeLists.txt | 5 +- 4 files changed, 6 insertions(+), 718 deletions(-) delete mode 100644 cmake/code-coverage.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f7971f6..54ec054d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,8 +7,8 @@ enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) include(xo_macros/xo_cxx) -include(cmake/cxx.cmake) -include(cmake/code-coverage.cmake) +#include(cmake/cxx.cmake) +include(xo_macros/code-coverage) # ---------------------------------------------------------------- # unit test setup @@ -55,43 +55,9 @@ add_subdirectory(utest) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -#set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") -#set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") -# -#include(CMakePackageConfigHelpers) -#write_basic_package_version_file("${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" -# VERSION 0.1 -# COMPATIBILITY AnyNewerVersion -#) -# -##install( -## TARGETS ${XO_PROJECT_NAME} -## EXPORT ${XO_PROJECT_NAME}Targets -## LIBRARY DESTINATION lib COMPONENT Runtime -## ARCHIVE DESTINATION lib COMPONENT Development -## RUNTIME DESTINATION bin COMPONENT Runtime -## PUBLIC_HEADER DESTINATION include COMPONENT Development -## BUNDLE DESTINATION bin COMPONENT Runtime -## ) -# -#configure_package_config_file( -# "${PROJECT_SOURCE_DIR}/cmake/${XO_PROJECT_NAME}Config.cmake.in" -# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" -# INSTALL_DESTINATION lib/cmake/${XO_PROJECT_NAME} -# ) -# -#install(EXPORT ${XO_PROJECT_NAME}Targets DESTINATION lib/cmake/${XO_PROJECT_NAME}) -#install( -# FILES -# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" -# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" -# DESTINATION lib/cmake/${XO_PROJECT_NAME}) - # ---------------------------------------------------------------- # install .hpp xo_install_include_tree() -#install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) - # end CMakeLists.txt diff --git a/cmake/code-coverage.cmake b/cmake/code-coverage.cmake deleted file mode 100644 index b6b36064..00000000 --- a/cmake/code-coverage.cmake +++ /dev/null @@ -1,678 +0,0 @@ -# -# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy of -# the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations under -# the License. - -# USAGE: To enable any code coverage instrumentation/targets, the single CMake -# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or -# on the command line. -# -# From this point, there are two primary methods for adding instrumentation to -# targets: -# -# 1 - A blanket instrumentation by calling `add_code_coverage()`, where -# all targets in that directory and all subdirectories are automatically -# instrumented. -# -# 2 - Per-target instrumentation by calling -# `target_code_coverage()`, where the target is given and thus only -# that target is instrumented. This applies to both libraries and executables. -# -# To add coverage targets, such as calling `make ccov` to generate the actual -# coverage information for perusal or consumption, call -# `target_code_coverage()` on an *executable* target. -# -# Example 1: All targets instrumented -# -# In this case, the coverage information reported will will be that of the -# `theLib` library target and `theExe` executable. -# -# 1a: Via global command -# -# ~~~ -# add_code_coverage() # Adds instrumentation to all targets -# -# add_library(theLib lib.cpp) -# -# add_executable(theExe main.cpp) -# target_link_libraries(theExe PRIVATE theLib) -# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target -# # (instrumentation already added via global anyways) -# # for generating code coverage reports. -# ~~~ -# -# 1b: Via target commands -# -# ~~~ -# add_library(theLib lib.cpp) -# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. -# -# add_executable(theExe main.cpp) -# target_link_libraries(theExe PRIVATE theLib) -# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. -# ~~~ -# -# Example 2: Target instrumented, but with regex pattern of files to be excluded -# from report -# -# ~~~ -# add_executable(theExe main.cpp non_covered.cpp) -# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. -# ~~~ -# -# Example 3: Target added to the 'ccov' and 'ccov-all' targets -# -# ~~~ -# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. -# -# add_executable(theExe main.cpp non_covered.cpp) -# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. -# ~~~ - -# Options -option( - CODE_COVERAGE - "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" - OFF) - -# Programs -find_program(LLVM_COV_PATH llvm-cov) -find_program(LLVM_PROFDATA_PATH llvm-profdata) -find_program(LCOV_PATH lcov) -find_program(GENHTML_PATH genhtml) -# Hide behind the 'advanced' mode flag for GUI/ccmake -mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) - -# Variables -set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) -set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) - -# Common initialization/checks -if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) - set(CODE_COVERAGE_ADDED ON) - - # Common Targets - add_custom_target( - ccov-preprocessing - COMMAND ${CMAKE_COMMAND} -E make_directory - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} - DEPENDS ccov-clean) - - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - # Messages - message(STATUS "Building with llvm Code Coverage Tools") - - if(NOT LLVM_COV_PATH) - message(FATAL_ERROR "llvm-cov not found! Aborting.") - else() - # Version number checking for 'EXCLUDE' compatibility - execute_process(COMMAND ${LLVM_COV_PATH} --version - OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) - string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION - ${LLVM_COV_VERSION_CALL_OUTPUT}) - - if(LLVM_COV_VERSION VERSION_LESS "7.0.0") - message( - WARNING - "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" - ) - endif() - endif() - - # Targets - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-clean - COMMAND ${CMAKE_COMMAND} -E remove -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND ${CMAKE_COMMAND} -E remove -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) - else() - add_custom_target( - ccov-clean - COMMAND ${CMAKE_COMMAND} -E rm -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND ${CMAKE_COMMAND} -E rm -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) - endif() - - # Used to get the shared object file list before doing the main all- - # processing - add_custom_target( - ccov-libs - COMMAND ; - COMMENT "libs ready for coverage report.") - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - # Messages - message(STATUS "Building with lcov Code Coverage Tools") - - if(CMAKE_BUILD_TYPE) - string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) - if(NOT ${upper_build_type} STREQUAL "DEBUG") - message( - WARNING - "Code coverage results with an optimized (non-Debug) build may be misleading" - ) - endif() - else() - message( - WARNING - "Code coverage results with an optimized (non-Debug) build may be misleading" - ) - endif() - if(NOT LCOV_PATH) - message(FATAL_ERROR "lcov not found! Aborting...") - endif() - if(NOT GENHTML_PATH) - message(FATAL_ERROR "genhtml not found! Aborting...") - endif() - - # Targets - add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory - ${CMAKE_BINARY_DIR} --zerocounters) - - else() - message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") - endif() -endif() - -# Adds code coverage instrumentation to a library, or instrumentation/targets -# for an executable target. -# ~~~ -# EXECUTABLE ADDED TARGETS: -# GCOV/LCOV: -# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. -# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. -# -# LLVM-COV: -# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-${TARGET_NAME} : Generates HTML code coverage report. -# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. -# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. -# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. -# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. -# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. -# ccov-all-export : Exports the coverage report to a JSON file. -# -# Required: -# TARGET_NAME - Name of the target to generate code coverage for. -# Optional: -# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. -# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. -# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) -# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. -# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. -# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory -# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. -# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** -# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output -# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call -# ~~~ -function(target_code_coverage TARGET_NAME) - # Argument parsing - set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) - set(single_value_keywords COVERAGE_TARGET_NAME) - set(multi_value_keywords EXCLUDE OBJECTS ARGS) - cmake_parse_arguments( - target_code_coverage "${options}" "${single_value_keywords}" - "${multi_value_keywords}" ${ARGN}) - - # Set the visibility of target functions to PUBLIC, INTERFACE or default to - # PRIVATE. - if(target_code_coverage_PUBLIC) - set(TARGET_VISIBILITY PUBLIC) - set(TARGET_LINK_VISIBILITY PUBLIC) - elseif(target_code_coverage_INTERFACE) - set(TARGET_VISIBILITY INTERFACE) - set(TARGET_LINK_VISIBILITY INTERFACE) - elseif(target_code_coverage_PLAIN) - set(TARGET_VISIBILITY PUBLIC) - set(TARGET_LINK_VISIBILITY) - else() - set(TARGET_VISIBILITY PRIVATE) - set(TARGET_LINK_VISIBILITY PRIVATE) - endif() - - if(NOT target_code_coverage_COVERAGE_TARGET_NAME) - # If a specific name was given, use that instead. - set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) - endif() - - if(CODE_COVERAGE) - - # Add code coverage instrumentation to the target's linker command - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} - -fprofile-instr-generate -fcoverage-mapping) - target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} - -fprofile-instr-generate -fcoverage-mapping) - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs - -ftest-coverage) - target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) - endif() - - # Targets - get_target_property(target_type ${TARGET_NAME} TYPE) - - # Add shared library to processing for 'all' targets - if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${CMAKE_COMMAND} -E echo "-object=$" >> - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - DEPENDS ccov-preprocessing ${TARGET_NAME}) - - if(NOT TARGET ccov-libs) - message( - FATAL_ERROR - "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." - ) - endif() - - add_dependencies(ccov-libs - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - - # For executables add targets to run and produce output - if(target_type STREQUAL "EXECUTABLE") - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - - # If there are shared objects to also work with, generate the string to - # add them here - foreach(SO_TARGET ${target_code_coverage_OBJECTS}) - # Check to see if the target is a shared object - if(TARGET ${SO_TARGET}) - get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) - if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") - set(SO_OBJECTS ${SO_OBJECTS} -object=$) - endif() - endif() - endforeach() - - # Run the executable, generating raw profile data Make the run data - # available for further processing. Separated to allow Windows to run - # this target serially. - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${CMAKE_COMMAND} -E env - LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw - $ ${target_code_coverage_ARGS} - COMMAND - ${CMAKE_COMMAND} -E echo "-object=$" - ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND - ${CMAKE_COMMAND} -E echo - "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" - >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list - JOB_POOL ccov_serial_pool - DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) - - # Merge the generated profile data so llvm-cov can process it - add_custom_target( - ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_PROFDATA_PATH} merge -sparse - ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o - ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Ignore regex only works on LLVM >= 7 - if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") - foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} - -ignore-filename-regex='${EXCLUDE_ITEM}') - endforeach() - endif() - - # Print out details of the coverage information to the command line - add_custom_target( - ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} show $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -show-line-counts-or-regions ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Print out a summary of the coverage information to the command line - add_custom_target( - ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} report $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Export coverage information so continuous integration tools (e.g. - # Jenkins) can consume it - add_custom_target( - ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} export $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -format="text" ${EXCLUDE_REGEX} > - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Generates HTML output of the coverage information for perusal - add_custom_target( - ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} show $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - set(COVERAGE_INFO - "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" - ) - - # Run the executable, generating coverage information - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND $ ${target_code_coverage_ARGS} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - - # Generate exclusion string for use - foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} - '${EXCLUDE_ITEM}') - endforeach() - - if(EXCLUDE_REGEX) - set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file - ${COVERAGE_INFO}) - else() - set(EXCLUDE_COMMAND ;) - endif() - - if(NOT ${target_code_coverage_EXTERNAL}) - set(EXTERNAL_OPTION --no-external) - endif() - - # Capture coverage data - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters - COMMAND $ ${target_code_coverage_ARGS} - COMMAND - ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory - ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file - ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - else() - add_custom_target( - ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters - COMMAND $ ${target_code_coverage_ARGS} - COMMAND - ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory - ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file - ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - endif() - - # Generates HTML output of the coverage information for perusal - add_custom_target( - ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${GENHTML_PATH} -o - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} - ${COVERAGE_INFO} - DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - - add_custom_command( - TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - POST_BUILD - COMMAND ; - COMMENT - "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." - ) - - # AUTO - if(target_code_coverage_AUTO) - if(NOT TARGET ccov) - add_custom_target(ccov) - endif() - add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) - - if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID - MATCHES "GNU") - if(NOT TARGET ccov-report) - add_custom_target(ccov-report) - endif() - add_dependencies( - ccov-report - ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - - # ALL - if(target_code_coverage_ALL) - if(NOT TARGET ccov-all-processing) - message( - FATAL_ERROR - "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." - ) - endif() - - add_dependencies(ccov-all-processing - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - endif() -endfunction() - -# Adds code coverage instrumentation to all targets in the current directory and -# any subdirectories. To add coverage instrumentation to only specific targets, -# use `target_code_coverage`. -function(add_code_coverage) - if(CODE_COVERAGE) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - add_compile_options(-fprofile-instr-generate -fcoverage-mapping) - add_link_options(-fprofile-instr-generate -fcoverage-mapping) - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - add_compile_options(-fprofile-arcs -ftest-coverage) - link_libraries(gcov) - endif() - endif() -endfunction() - -# Adds the 'ccov-all' type targets that calls all targets added via -# `target_code_coverage` with the `ALL` parameter, but merges all the coverage -# data from them into a single large report instead of the numerous smaller -# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for -# use with coverage dashboards (e.g. codecov.io, coveralls). -# ~~~ -# Optional: -# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! -# ~~~ -function(add_code_coverage_all_targets) - # Argument parsing - set(multi_value_keywords EXCLUDE) - cmake_parse_arguments(add_code_coverage_all_targets "" "" - "${multi_value_keywords}" ${ARGN}) - - if(CODE_COVERAGE) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - - # Merge the profile data for all of the run executables - if(WIN32) - add_custom_target( - ccov-all-processing - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe - merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -sparse $$FILELIST) - else() - add_custom_target( - ccov-all-processing - COMMAND - ${LLVM_PROFDATA_PATH} merge -o - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) - endif() - - # Regex exclude only available for LLVM >= 7 - if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") - foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} - -ignore-filename-regex='${EXCLUDE_ITEM}') - endforeach() - endif() - - # Print summary of the code coverage information to the command line - if(WIN32) - add_custom_target( - ccov-all-report - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe - report $$FILELIST - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - else() - add_custom_target( - ccov-all-report - COMMAND - ${LLVM_COV_PATH} report `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - endif() - - # Export coverage information so continuous integration tools (e.g. - # Jenkins) can consume it - add_custom_target( - ccov-all-export - COMMAND - ${LLVM_COV_PATH} export `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -format="text" ${EXCLUDE_REGEX} > - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json - DEPENDS ccov-all-processing) - - # Generate HTML output of all added targets for perusal - if(WIN32) - add_custom_target( - ccov-all - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show - $$FILELIST - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - else() - add_custom_target( - ccov-all - COMMAND - ${LLVM_COV_PATH} show `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - endif() - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") - - # Nothing required for gcov - add_custom_target(ccov-all-processing COMMAND ;) - - # Exclusion regex string creation - set(EXCLUDE_REGEX) - foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} - '${EXCLUDE_ITEM}') - endforeach() - - if(EXCLUDE_REGEX) - set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file - ${COVERAGE_INFO}) - else() - set(EXCLUDE_COMMAND ;) - endif() - - # Capture coverage data - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-all-capture - COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture - --output-file ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ccov-all-processing) - else() - add_custom_target( - ccov-all-capture - COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture - --output-file ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ccov-all-processing) - endif() - - # Generates HTML output of all targets for perusal - add_custom_target( - ccov-all - COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} - DEPENDS ccov-all-capture) - - endif() - - add_custom_command( - TARGET ccov-all - POST_BUILD - COMMAND ; - COMMENT - "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." - ) - endif() -endfunction() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f58170e8..0a60fbea 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,8 +8,7 @@ set_target_properties(${SELF_LIBRARY_NAME} SOVERSION 1) xo_internal_dependency(${SELF_LIBRARY_NAME} indentlog) -#xo_indentlog_dependency(${SELF_LIBRARY_NAME}) xo_include_options2(${SELF_LIBRARY_NAME}) xo_compile_options(${SELF_LIBRARY_NAME}) -xo_install_library(${SELF_LIBRARY_NAME}) +xo_install_library2(${SELF_LIBRARY_NAME}) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 74706592..1f5ced17 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -5,7 +5,7 @@ set(SELF_EXECUTABLE_NAME utest.refcnt) # These tests can use the Catch2-provided main set(SELF_SOURCE_FILES intrusive_ptr.test.cpp refcnt_utest_main.cpp) add_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) -xo_include_options(${SELF_EXECUTABLE_NAME}) +xo_include_options2(${SELF_EXECUTABLE_NAME}) add_test(NAME ${SELF_EXECUTABLE_NAME} COMMAND ${SELF_EXECUTABLE_NAME}) target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) @@ -30,7 +30,8 @@ target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC # ---------------------------------------------------------------- # internal dependencies: refcnt, ... -target_link_libraries(${SELF_EXECUTABLE_NAME} PUBLIC refcnt) +xo_self_dependency(${SELF_EXECUTABLE_NAME} refcnt) +#target_link_libraries(${SELF_EXECUTABLE_NAME} PUBLIC refcnt) # ---------------------------------------------------------------- # 3rd part dependency: catch2: From 31d8ce44def547b27e8a9c0eec52442899a30a06 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 17:20:26 -0400 Subject: [PATCH 0198/2693] xo-cmake: + xo_include_headeronly_options2() --- cmake/xo_cxx.cmake | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 018f75fe..1e5d8897 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -29,7 +29,8 @@ macro(xo_toplevel_compile_options) endmacro() # ---------------------------------------------------------------- -# use this in subdirs that compile c++ code +# use this in subdirs that compile c++ code. +# do not use for header-only subsystems; see xo_include_headeronly_options2() # macro(xo_include_options2 target) # ---------------------------------------------------------------- @@ -62,6 +63,37 @@ macro(xo_include_options2 target) endif() endmacro() +macro(xo_include_headeronly_options2 target) + # ---------------------------------------------------------------- + # PROJECT_SOURCE_DIR: + # so we can for example write + # #include "ordinaltree/foo.hpp" + # from anywhere in the project + # PROJECT_BINARY_DIR: + # since generated version file will be in build directory, + # need that build directory to also appear in + # compiler's include path + # + target_include_directories( + ${target} INTERFACE + $ # e.g. for #include "indentlog/scope.hpp" + $ + $ # e.g. for #include "Refcounted.hpp" in refcnt/src + $ + $ # e.g. for generated .hpp files + ) + + # ---------------------------------------------------------------- + # make standard directories for std:: includes explicit + # so that + # (1) they appear in compile_commands.json. + # (2) clangd (run from emacs lsp-mode) can find them + # + if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) + endif() +endmacro() + # ---------------------------------------------------------------- # # Require: From bc4335af82b838378780140197a0c22e1e0b0531 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 17:21:17 -0400 Subject: [PATCH 0199/2693] subsys: build: streamline using xo-cmake macros --- CMakeLists.txt | 40 ++++++++-------------------------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c0b9c1e..61140a28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(subsys VERSION 0.1) enable_language(CXX) include (xo_macros/xo_cxx) -include(cmake/cxx.cmake) +#include(cmake/cxx.cmake) include(cmake/code-coverage.cmake) enable_testing() @@ -29,6 +29,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") +xo_toplevel_compile_options() + # ---------------------------------------------------------------- # - author's convenience: default install prefix to /home/$USER/local # - otherwise use -DCMAKE_INSTALL_PREFIX=/path/to/somewhere @@ -54,10 +56,11 @@ endif() # installing header-only library add_library(subsys INTERFACE) -target_include_directories(subsys INTERFACE - $ - $ -) +xo_include_headeronly_options2(subsys) +#target_include_directories(subsys INTERFACE +# $ +# $ +#) xo_install_library2(subsys) # ---------------------------------------------------------------- @@ -65,36 +68,9 @@ xo_install_library2(subsys) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -## ---------------------------------------------------------------- -## cmake export -## (so this library works with cmake's find_package()) -# -#set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") -#set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") -# -#include(CMakePackageConfigHelpers) -#write_basic_package_version_file("${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" -# VERSION 0.1 -# COMPATIBILITY AnyNewerVersion -#) -# -#configure_package_config_file( -# "${PROJECT_SOURCE_DIR}/cmake/${XO_PROJECT_NAME}Config.cmake.in" -# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" -# INSTALL_DESTINATION lib/cmake/${XO_PROJECT_NAME} -# ) -# -#install(EXPORT ${XO_PROJECT_NAME}Targets DESTINATION lib/cmake/${XO_PROJECT_NAME}) -#install( -# FILES -# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" -# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" -# DESTINATION lib/cmake/${XO_PROJECT_NAME}) - # ---------------------------------------------------------------- # install .hpp xo_install_include_tree() -#install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) # end CMakeLists.txt From a2e3bda4ff1549d8a826b2971fb62113c34c6946 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 17:21:51 -0400 Subject: [PATCH 0200/2693] refcnt: build: bugfix: + xo_toplevel_compile_options() --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 54ec054d..3c5a6fc6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") +xo_toplevel_compile_options() + if(NOT CMAKE_INSTALL_RPATH) set(CMAKE_INSTALL_RPATH $(CMAKE_INSTALL_PREFIX)/lib CACHE STRING "runpath in installed libraries/executables") endif() From 49567a59a150370b219793d1d588fd99e5ca9a1c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 17:30:13 -0400 Subject: [PATCH 0201/2693] indentlog: build: streamline using xo-cmake macros --- CMakeLists.txt | 42 ++++++------------------------------ example/ex1/CMakeLists.txt | 2 +- example/ex2/CMakeLists.txt | 2 +- example/ex3/CMakeLists.txt | 2 +- example/ex4/CMakeLists.txt | 2 +- example/hello/CMakeLists.txt | 2 +- utest/CMakeLists.txt | 2 +- 7 files changed, 12 insertions(+), 42 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 86f6de3a..c8c5c655 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,8 +5,7 @@ enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) include(xo_macros/xo_cxx) -include(cmake/nestlog.cmake) -include(cmake/code-coverage.cmake) +include(xo_macros/code-coverage) enable_testing() # activate code coverage for all executables + libraries (when -DCODE_COVERAGE=ON) @@ -46,10 +45,11 @@ add_subdirectory(utest) # see [[https://stackoverflow.com/questions/47718485/install-and-export-interface-only-library-cmake]] # add_library(indentlog INTERFACE) -target_include_directories(indentlog INTERFACE - $ - $ - ) +xo_include_headeronly_options2(indentlog) +#target_include_directories(indentlog INTERFACE +# $ +# $ +# ) # ---------------------------------------------------------------- # provide find_package() support @@ -60,36 +60,6 @@ xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets xo_install_library2(${PROJECT_NAME}) -#include(CMakePackageConfigHelpers) -#write_basic_package_version_file("${PROJECT_BINARY_DIR}/indentlogConfigVersion.cmake" -# VERSION 0.1 -# COMPATIBILITY AnyNewerVersion -#) -# -#install( -# TARGETS indentlog -# EXPORT indentlogTargets -# LIBRARY DESTINATION lib COMPONENT Runtime -# ARCHIVE DESTINATION lib COMPONENT Development -# RUNTIME DESTINATION bin COMPONENT Runtime -# PUBLIC_HEADER DESTINATION include COMPONENT Development -# BUNDLE DESTINATION bin COMPONENT Runtime -# ) - -#include(CMakePackageConfigHelpers) -#configure_package_config_file( -# "${PROJECT_SOURCE_DIR}/cmake/indentlogConfig.cmake.in" -# "${PROJECT_BINARY_DIR}/indentlogConfig.cmake" -# INSTALL_DESTINATION lib/cmake/indentlog -# ) -# -#install(EXPORT indentlogTargets DESTINATION lib/cmake/indentlog) -#install( -# FILES -# "${PROJECT_BINARY_DIR}/indentlogConfigVersion.cmake" -# "${PROJECT_BINARY_DIR}/indentlogConfig.cmake" -# DESTINATION lib/cmake/indentlog) - # ---------------------------------------------------------------- # install .hpp diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt index 6af29d79..a9d1b27d 100644 --- a/example/ex1/CMakeLists.txt +++ b/example/ex1/CMakeLists.txt @@ -1,2 +1,2 @@ add_executable(ex1 ex1.cpp) -xo_include_options(ex1) +xo_include_options2(ex1) diff --git a/example/ex2/CMakeLists.txt b/example/ex2/CMakeLists.txt index 1ac5735a..20026b03 100644 --- a/example/ex2/CMakeLists.txt +++ b/example/ex2/CMakeLists.txt @@ -1,2 +1,2 @@ add_executable(ex2 ex2.cpp) -xo_include_options(ex2) +xo_include_options2(ex2) diff --git a/example/ex3/CMakeLists.txt b/example/ex3/CMakeLists.txt index d81e0e99..9791a931 100644 --- a/example/ex3/CMakeLists.txt +++ b/example/ex3/CMakeLists.txt @@ -1,2 +1,2 @@ add_executable(ex3 ex3.cpp) -xo_include_options(ex3) +xo_include_options2(ex3) diff --git a/example/ex4/CMakeLists.txt b/example/ex4/CMakeLists.txt index 02c1f301..2a6b9c31 100644 --- a/example/ex4/CMakeLists.txt +++ b/example/ex4/CMakeLists.txt @@ -1,2 +1,2 @@ add_executable(ex4 ex4.cpp) -xo_include_options(ex4) +xo_include_options2(ex4) diff --git a/example/hello/CMakeLists.txt b/example/hello/CMakeLists.txt index d6f694e6..1ff23000 100644 --- a/example/hello/CMakeLists.txt +++ b/example/hello/CMakeLists.txt @@ -1,2 +1,2 @@ add_executable(hello hello.cpp) -xo_include_options(hello) +xo_include_options2(hello) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index c6e6e596..fbbeb9d7 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -7,7 +7,7 @@ set(SELF_SOURCE_FILES indentlog_utest_main.cpp) add_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) -xo_include_options(${SELF_EXECUTABLE_NAME}) +xo_include_options2(${SELF_EXECUTABLE_NAME}) add_test(NAME ${SELF_EXECUTABLE_NAME} COMMAND ${SELF_EXECUTABLE_NAME}) target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) From a8ec3021ebfadbd87a1427551d21e431bc358aa6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 17:33:43 -0400 Subject: [PATCH 0202/2693] subsys: build tidy --- CMakeLists.txt | 5 +- cmake/code-coverage.cmake | 678 -------------------------------------- cmake/cxx.cmake | 96 ------ 3 files changed, 2 insertions(+), 777 deletions(-) delete mode 100644 cmake/code-coverage.cmake delete mode 100644 cmake/cxx.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 61140a28..126d305b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,9 +3,8 @@ cmake_minimum_required(VERSION 3.10) project(subsys VERSION 0.1) enable_language(CXX) -include (xo_macros/xo_cxx) -#include(cmake/cxx.cmake) -include(cmake/code-coverage.cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) enable_testing() # activate code coverage for all executables + libraries (when -DCODE_COVERAGE=ON) diff --git a/cmake/code-coverage.cmake b/cmake/code-coverage.cmake deleted file mode 100644 index b6b36064..00000000 --- a/cmake/code-coverage.cmake +++ /dev/null @@ -1,678 +0,0 @@ -# -# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy of -# the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations under -# the License. - -# USAGE: To enable any code coverage instrumentation/targets, the single CMake -# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or -# on the command line. -# -# From this point, there are two primary methods for adding instrumentation to -# targets: -# -# 1 - A blanket instrumentation by calling `add_code_coverage()`, where -# all targets in that directory and all subdirectories are automatically -# instrumented. -# -# 2 - Per-target instrumentation by calling -# `target_code_coverage()`, where the target is given and thus only -# that target is instrumented. This applies to both libraries and executables. -# -# To add coverage targets, such as calling `make ccov` to generate the actual -# coverage information for perusal or consumption, call -# `target_code_coverage()` on an *executable* target. -# -# Example 1: All targets instrumented -# -# In this case, the coverage information reported will will be that of the -# `theLib` library target and `theExe` executable. -# -# 1a: Via global command -# -# ~~~ -# add_code_coverage() # Adds instrumentation to all targets -# -# add_library(theLib lib.cpp) -# -# add_executable(theExe main.cpp) -# target_link_libraries(theExe PRIVATE theLib) -# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target -# # (instrumentation already added via global anyways) -# # for generating code coverage reports. -# ~~~ -# -# 1b: Via target commands -# -# ~~~ -# add_library(theLib lib.cpp) -# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. -# -# add_executable(theExe main.cpp) -# target_link_libraries(theExe PRIVATE theLib) -# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. -# ~~~ -# -# Example 2: Target instrumented, but with regex pattern of files to be excluded -# from report -# -# ~~~ -# add_executable(theExe main.cpp non_covered.cpp) -# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. -# ~~~ -# -# Example 3: Target added to the 'ccov' and 'ccov-all' targets -# -# ~~~ -# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. -# -# add_executable(theExe main.cpp non_covered.cpp) -# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. -# ~~~ - -# Options -option( - CODE_COVERAGE - "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" - OFF) - -# Programs -find_program(LLVM_COV_PATH llvm-cov) -find_program(LLVM_PROFDATA_PATH llvm-profdata) -find_program(LCOV_PATH lcov) -find_program(GENHTML_PATH genhtml) -# Hide behind the 'advanced' mode flag for GUI/ccmake -mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) - -# Variables -set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) -set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) - -# Common initialization/checks -if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) - set(CODE_COVERAGE_ADDED ON) - - # Common Targets - add_custom_target( - ccov-preprocessing - COMMAND ${CMAKE_COMMAND} -E make_directory - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} - DEPENDS ccov-clean) - - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - # Messages - message(STATUS "Building with llvm Code Coverage Tools") - - if(NOT LLVM_COV_PATH) - message(FATAL_ERROR "llvm-cov not found! Aborting.") - else() - # Version number checking for 'EXCLUDE' compatibility - execute_process(COMMAND ${LLVM_COV_PATH} --version - OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) - string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION - ${LLVM_COV_VERSION_CALL_OUTPUT}) - - if(LLVM_COV_VERSION VERSION_LESS "7.0.0") - message( - WARNING - "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" - ) - endif() - endif() - - # Targets - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-clean - COMMAND ${CMAKE_COMMAND} -E remove -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND ${CMAKE_COMMAND} -E remove -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) - else() - add_custom_target( - ccov-clean - COMMAND ${CMAKE_COMMAND} -E rm -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND ${CMAKE_COMMAND} -E rm -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) - endif() - - # Used to get the shared object file list before doing the main all- - # processing - add_custom_target( - ccov-libs - COMMAND ; - COMMENT "libs ready for coverage report.") - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - # Messages - message(STATUS "Building with lcov Code Coverage Tools") - - if(CMAKE_BUILD_TYPE) - string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) - if(NOT ${upper_build_type} STREQUAL "DEBUG") - message( - WARNING - "Code coverage results with an optimized (non-Debug) build may be misleading" - ) - endif() - else() - message( - WARNING - "Code coverage results with an optimized (non-Debug) build may be misleading" - ) - endif() - if(NOT LCOV_PATH) - message(FATAL_ERROR "lcov not found! Aborting...") - endif() - if(NOT GENHTML_PATH) - message(FATAL_ERROR "genhtml not found! Aborting...") - endif() - - # Targets - add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory - ${CMAKE_BINARY_DIR} --zerocounters) - - else() - message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") - endif() -endif() - -# Adds code coverage instrumentation to a library, or instrumentation/targets -# for an executable target. -# ~~~ -# EXECUTABLE ADDED TARGETS: -# GCOV/LCOV: -# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. -# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. -# -# LLVM-COV: -# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-${TARGET_NAME} : Generates HTML code coverage report. -# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. -# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. -# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. -# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. -# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. -# ccov-all-export : Exports the coverage report to a JSON file. -# -# Required: -# TARGET_NAME - Name of the target to generate code coverage for. -# Optional: -# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. -# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. -# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) -# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. -# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. -# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory -# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. -# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** -# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output -# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call -# ~~~ -function(target_code_coverage TARGET_NAME) - # Argument parsing - set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) - set(single_value_keywords COVERAGE_TARGET_NAME) - set(multi_value_keywords EXCLUDE OBJECTS ARGS) - cmake_parse_arguments( - target_code_coverage "${options}" "${single_value_keywords}" - "${multi_value_keywords}" ${ARGN}) - - # Set the visibility of target functions to PUBLIC, INTERFACE or default to - # PRIVATE. - if(target_code_coverage_PUBLIC) - set(TARGET_VISIBILITY PUBLIC) - set(TARGET_LINK_VISIBILITY PUBLIC) - elseif(target_code_coverage_INTERFACE) - set(TARGET_VISIBILITY INTERFACE) - set(TARGET_LINK_VISIBILITY INTERFACE) - elseif(target_code_coverage_PLAIN) - set(TARGET_VISIBILITY PUBLIC) - set(TARGET_LINK_VISIBILITY) - else() - set(TARGET_VISIBILITY PRIVATE) - set(TARGET_LINK_VISIBILITY PRIVATE) - endif() - - if(NOT target_code_coverage_COVERAGE_TARGET_NAME) - # If a specific name was given, use that instead. - set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) - endif() - - if(CODE_COVERAGE) - - # Add code coverage instrumentation to the target's linker command - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} - -fprofile-instr-generate -fcoverage-mapping) - target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} - -fprofile-instr-generate -fcoverage-mapping) - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs - -ftest-coverage) - target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) - endif() - - # Targets - get_target_property(target_type ${TARGET_NAME} TYPE) - - # Add shared library to processing for 'all' targets - if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${CMAKE_COMMAND} -E echo "-object=$" >> - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - DEPENDS ccov-preprocessing ${TARGET_NAME}) - - if(NOT TARGET ccov-libs) - message( - FATAL_ERROR - "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." - ) - endif() - - add_dependencies(ccov-libs - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - - # For executables add targets to run and produce output - if(target_type STREQUAL "EXECUTABLE") - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - - # If there are shared objects to also work with, generate the string to - # add them here - foreach(SO_TARGET ${target_code_coverage_OBJECTS}) - # Check to see if the target is a shared object - if(TARGET ${SO_TARGET}) - get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) - if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") - set(SO_OBJECTS ${SO_OBJECTS} -object=$) - endif() - endif() - endforeach() - - # Run the executable, generating raw profile data Make the run data - # available for further processing. Separated to allow Windows to run - # this target serially. - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${CMAKE_COMMAND} -E env - LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw - $ ${target_code_coverage_ARGS} - COMMAND - ${CMAKE_COMMAND} -E echo "-object=$" - ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND - ${CMAKE_COMMAND} -E echo - "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" - >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list - JOB_POOL ccov_serial_pool - DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) - - # Merge the generated profile data so llvm-cov can process it - add_custom_target( - ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_PROFDATA_PATH} merge -sparse - ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o - ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Ignore regex only works on LLVM >= 7 - if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") - foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} - -ignore-filename-regex='${EXCLUDE_ITEM}') - endforeach() - endif() - - # Print out details of the coverage information to the command line - add_custom_target( - ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} show $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -show-line-counts-or-regions ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Print out a summary of the coverage information to the command line - add_custom_target( - ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} report $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Export coverage information so continuous integration tools (e.g. - # Jenkins) can consume it - add_custom_target( - ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} export $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -format="text" ${EXCLUDE_REGEX} > - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Generates HTML output of the coverage information for perusal - add_custom_target( - ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} show $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - set(COVERAGE_INFO - "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" - ) - - # Run the executable, generating coverage information - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND $ ${target_code_coverage_ARGS} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - - # Generate exclusion string for use - foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} - '${EXCLUDE_ITEM}') - endforeach() - - if(EXCLUDE_REGEX) - set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file - ${COVERAGE_INFO}) - else() - set(EXCLUDE_COMMAND ;) - endif() - - if(NOT ${target_code_coverage_EXTERNAL}) - set(EXTERNAL_OPTION --no-external) - endif() - - # Capture coverage data - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters - COMMAND $ ${target_code_coverage_ARGS} - COMMAND - ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory - ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file - ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - else() - add_custom_target( - ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters - COMMAND $ ${target_code_coverage_ARGS} - COMMAND - ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory - ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file - ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - endif() - - # Generates HTML output of the coverage information for perusal - add_custom_target( - ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${GENHTML_PATH} -o - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} - ${COVERAGE_INFO} - DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - - add_custom_command( - TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - POST_BUILD - COMMAND ; - COMMENT - "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." - ) - - # AUTO - if(target_code_coverage_AUTO) - if(NOT TARGET ccov) - add_custom_target(ccov) - endif() - add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) - - if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID - MATCHES "GNU") - if(NOT TARGET ccov-report) - add_custom_target(ccov-report) - endif() - add_dependencies( - ccov-report - ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - - # ALL - if(target_code_coverage_ALL) - if(NOT TARGET ccov-all-processing) - message( - FATAL_ERROR - "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." - ) - endif() - - add_dependencies(ccov-all-processing - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - endif() -endfunction() - -# Adds code coverage instrumentation to all targets in the current directory and -# any subdirectories. To add coverage instrumentation to only specific targets, -# use `target_code_coverage`. -function(add_code_coverage) - if(CODE_COVERAGE) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - add_compile_options(-fprofile-instr-generate -fcoverage-mapping) - add_link_options(-fprofile-instr-generate -fcoverage-mapping) - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - add_compile_options(-fprofile-arcs -ftest-coverage) - link_libraries(gcov) - endif() - endif() -endfunction() - -# Adds the 'ccov-all' type targets that calls all targets added via -# `target_code_coverage` with the `ALL` parameter, but merges all the coverage -# data from them into a single large report instead of the numerous smaller -# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for -# use with coverage dashboards (e.g. codecov.io, coveralls). -# ~~~ -# Optional: -# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! -# ~~~ -function(add_code_coverage_all_targets) - # Argument parsing - set(multi_value_keywords EXCLUDE) - cmake_parse_arguments(add_code_coverage_all_targets "" "" - "${multi_value_keywords}" ${ARGN}) - - if(CODE_COVERAGE) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - - # Merge the profile data for all of the run executables - if(WIN32) - add_custom_target( - ccov-all-processing - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe - merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -sparse $$FILELIST) - else() - add_custom_target( - ccov-all-processing - COMMAND - ${LLVM_PROFDATA_PATH} merge -o - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) - endif() - - # Regex exclude only available for LLVM >= 7 - if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") - foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} - -ignore-filename-regex='${EXCLUDE_ITEM}') - endforeach() - endif() - - # Print summary of the code coverage information to the command line - if(WIN32) - add_custom_target( - ccov-all-report - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe - report $$FILELIST - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - else() - add_custom_target( - ccov-all-report - COMMAND - ${LLVM_COV_PATH} report `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - endif() - - # Export coverage information so continuous integration tools (e.g. - # Jenkins) can consume it - add_custom_target( - ccov-all-export - COMMAND - ${LLVM_COV_PATH} export `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -format="text" ${EXCLUDE_REGEX} > - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json - DEPENDS ccov-all-processing) - - # Generate HTML output of all added targets for perusal - if(WIN32) - add_custom_target( - ccov-all - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show - $$FILELIST - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - else() - add_custom_target( - ccov-all - COMMAND - ${LLVM_COV_PATH} show `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - endif() - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") - - # Nothing required for gcov - add_custom_target(ccov-all-processing COMMAND ;) - - # Exclusion regex string creation - set(EXCLUDE_REGEX) - foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} - '${EXCLUDE_ITEM}') - endforeach() - - if(EXCLUDE_REGEX) - set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file - ${COVERAGE_INFO}) - else() - set(EXCLUDE_COMMAND ;) - endif() - - # Capture coverage data - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-all-capture - COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture - --output-file ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ccov-all-processing) - else() - add_custom_target( - ccov-all-capture - COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture - --output-file ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ccov-all-processing) - endif() - - # Generates HTML output of all targets for perusal - add_custom_target( - ccov-all - COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} - DEPENDS ccov-all-capture) - - endif() - - add_custom_command( - TARGET ccov-all - POST_BUILD - COMMAND ; - COMMENT - "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." - ) - endif() -endfunction() diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake deleted file mode 100644 index 7fa32372..00000000 --- a/cmake/cxx.cmake +++ /dev/null @@ -1,96 +0,0 @@ -# ---------------------------------------------------------------- -# use this in subdirs that compile c++ code -# -macro(xo_include_options target) - # ---------------------------------------------------------------- - # PROJECT_SOURCE_DIR: - # so we can for example write - # #include "ordinaltree/foo.hpp" - # from anywhere in the project - # PROJECT_BINARY_DIR: - # since generated version file will be in build directory, - # need that build directory to also appear in - # compiler's include path - # - target_include_directories( - ${target} PUBLIC - $ # e.g. for #include "indentlog/scope.hpp" - $ - $ # e.g. for #include "Refcounted.hpp" in refcnt/src - $ - $ # e.g. for generated config.hpp file - ) - - # ---------------------------------------------------------------- - # make standard directories for std:: includes explicit - # so that - # (1) they appear in compile_commands.json. - # (2) clangd (run from emacs lsp-mode) can find them - # - if(CMAKE_EXPORT_COMPILE_COMMANDS) - set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) - endif() -endmacro() - -# ---------------------------------------------------------------- -# variable -# XO_ADDRESS_SANITIZE -# determines whether to enable address sanitizer for the XO project -# (see toplevel CMakeLists.txt) -# ---------------------------------------------------------------- -if(XO_ADDRESS_SANITIZE) - add_compile_options(-fsanitize=address) - add_link_options(-fsanitize=address) -endif() - -# XO_STANDARD_COMPILE_OPTIONS: use these when XO_ADDRESS_SANITIZE=OFF -set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) - -# XO_ADDRESS_SANITIZE_COMPILE_OPTIONS: use when XO_ADDRESS_SANITIZE=ON -# -# address sanitizer build complains about _FORTIFY_SOURCE redefines -# In file included from :460: -# :1:9: error: '_FORTIFY_SOURCE' macro redefined [-Werror,-Wmacro-redefined] -# #define _FORTIFY_SOURCE 2 -# -set(XO_ADDRESS_SANITIZE_COMPILE_OPTIONS -Werror -Wall -Wextra -Wno-macro-redefined) - -# XO_COMPILE_OPTIONS: use these with xo_compile_options() macro -if(XO_ADDRESS_SANITIZE) - set(XO_COMPILE_OPTIONS ${XO_ADDRESS_SANITIZE_COMPILE_OPTIONS}) -else() - set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) -endif() - -# ---------------------------------------------------------------- -# generally want all the errors+warnings! -# however: address sanitizer generates error on _FORTIFY_SOURCE -# -macro(xo_compile_options target) - target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) -endmacro() - -# ---------------------------------------------------------------- -# use this for a subdir that builds a library -# -macro(xo_install_library target) - install( - TARGETS ${target} - EXPORT ${target}Targets - LIBRARY DESTINATION lib COMPONENT Runtime - ARCHIVE DESTINATION lib COMPONENT Development - RUNTIME DESTINATION bin COMPONENT Runtime - PUBLIC_HEADER DESTINATION include COMPONENT Development - BUNDLE DESTINATION bin COMPONENT Runtime - ) -endmacro() - -# ---------------------------------------------------------------- -# use this when relying on indentlog [[https://github.com/rconybea/indentlog]] headers -# -macro(xo_indentlogg_dependency target) - find_package(indentlog REQUIRED) - #add_dependencies(${target} indentlog) - target_link_libraries(${target} PUBLIC indentlog) - #target_include_directories(${target} PUBLIC ${indentlog_DIR}/../../../include) -endmacro() From 6d09431d407206ea82609dd7f37630219849db66 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 17:34:19 -0400 Subject: [PATCH 0203/2693] refcnt: build tidy --- CMakeLists.txt | 1 - cmake/cxx.cmake | 97 ------------------------------------------------- 2 files changed, 98 deletions(-) delete mode 100644 cmake/cxx.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c5a6fc6..bc8e2ef1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,6 @@ enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) include(xo_macros/xo_cxx) -#include(cmake/cxx.cmake) include(xo_macros/code-coverage) # ---------------------------------------------------------------- diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake deleted file mode 100644 index fd9aed25..00000000 --- a/cmake/cxx.cmake +++ /dev/null @@ -1,97 +0,0 @@ -# ---------------------------------------------------------------- -# use this in subdirs that compile c++ code -# -macro(xo_include_options target) - # ---------------------------------------------------------------- - # PROJECT_SOURCE_DIR: - # so we can for example write - # #include "ordinaltree/foo.hpp" - # from anywhere in the project - # PROJECT_BINARY_DIR: - # since generated version file will be in build directory, - # need that build directory to also appear in - # compiler's include path - # - target_include_directories( - ${target} PUBLIC - $ # e.g. for #include "indentlog/scope.hpp" - $ - $ # e.g. for #include "Refcounted.hpp" in refcnt/src - $ - $ # e.g. for generated config.hpp file - ) - - # ---------------------------------------------------------------- - # make standard directories for std:: includes explicit - # so that - # (1) they appear in compile_commands.json. - # (2) clangd (run from emacs lsp-mode) can find them - # - if(CMAKE_EXPORT_COMPILE_COMMANDS) - set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) - endif() -endmacro() - -# ---------------------------------------------------------------- -# variable -# XO_ADDRESS_SANITIZE -# determines whether to enable address sanitizer for the XO project -# (see toplevel CMakeLists.txt) -# ---------------------------------------------------------------- -if(XO_ADDRESS_SANITIZE) - add_compile_options(-fsanitize=address) - add_link_options(-fsanitize=address) -endif() - -# XO_STANDARD_COMPILE_OPTIONS: use these when XO_ADDRESS_SANITIZE=OFF -set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) - -# XO_ADDRESS_SANITIZE_COMPILE_OPTIONS: use when XO_ADDRESS_SANITIZE=ON -# -# address sanitizer build complains about _FORTIFY_SOURCE redefines -# In file included from :460: -# :1:9: error: '_FORTIFY_SOURCE' macro redefined [-Werror,-Wmacro-redefined] -# #define _FORTIFY_SOURCE 2 -# -set(XO_ADDRESS_SANITIZE_COMPILE_OPTIONS -Werror -Wall -Wextra -Wno-macro-redefined) - -# XO_COMPILE_OPTIONS: use these with xo_compile_options() macro -if(XO_ADDRESS_SANITIZE) - set(XO_COMPILE_OPTIONS ${XO_ADDRESS_SANITIZE_COMPILE_OPTIONS}) -else() - set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) -endif() - -# ---------------------------------------------------------------- -# generally want all the errors+warnings! -# however: address sanitizer generates error on _FORTIFY_SOURCE -# -macro(xo_compile_options target) - target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) -endmacro() - -# ---------------------------------------------------------------- -# use this for a subdir that builds a library -# and supports find_package() -# -macro(xo_install_library target) - install( - TARGETS ${target} - EXPORT ${target}Targets - LIBRARY DESTINATION lib COMPONENT Runtime - ARCHIVE DESTINATION lib COMPONENT Development - RUNTIME DESTINATION bin COMPONENT Runtime - PUBLIC_HEADER DESTINATION include COMPONENT Development - BUNDLE DESTINATION bin COMPONENT Runtime - ) -endmacro() - -# ---------------------------------------------------------------- -# use this when relying on indentlog [[https://github.com/rconybea/indentlog]] headers -# -macro(xo_indentlog_dependency target) - find_package(indentlog CONFIG REQUIRED) - #add_dependencies(${target} indentlog) - target_link_libraries(${target} PUBLIC indentlog) - #target_include_directories(${target} PUBLIC ${indentlog_DIR}/../../../include) -endmacro() From f4442c07a7b3ce23e1e883ddde011be8e28b5cac Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 17:54:02 -0400 Subject: [PATCH 0204/2693] xo-cmake: xo_toplevel_compile_options ++ set CMAKE_INSTALL_RPATH --- cmake/xo_cxx.cmake | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index e435de55..2307b06f 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -26,6 +26,11 @@ macro(xo_toplevel_compile_options) else() set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) endif() + + if(NOT CMAKE_INSTALL_RPATH) + set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib CACHE STRING + "runpath in installed libraries/executables") + endif() endif() # ---------------------------------------------------------------- From fb5bb1b03a1f2a6b9250dab078f0810c019ba527 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 17:57:54 -0400 Subject: [PATCH 0205/2693] randomgen: build streamlining using xo-cmake macros --- CMakeLists.txt | 15 ++++++--------- example/ex1/CMakeLists.txt | 2 +- example/ex2/CMakeLists.txt | 4 ++-- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 16cb2202..e27486bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,15 +6,13 @@ project(randomgen VERSION 0.1) enable_language(CXX) include(xo_macros/xo_cxx) -include(cmake/cxx.cmake) -include(cmake/code-coverage.cmake) +include(xo_macros/code-coverage) +#include(cmake/cxx.cmake) # ---------------------------------------------------------------- # unit test setup enable_testing() - - # activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) add_code_coverage() # 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. @@ -50,9 +48,8 @@ endif() if(NOT CMAKE_INSTALL_PREFIX) set(CMAKE_INSTALL_PREFIX /home/${USER}/local CACHE STRING "install directory") endif() -if(NOT CMAKE_INSTALL_RPATH) - set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib CACHE STRING "runpath in installed libraries/executables") -endif() + +xo_toplevel_compile_options() # ---------------------------------------------------------------- # external dependencies @@ -60,7 +57,7 @@ endif() # set CMAKE_INSTALL_PREFIX to analog of /usr # to use .cmake assistants from /usr/lib/cmake/indentlog # -find_package(indentlog REQUIRED) +#find_package(indentlog REQUIRED) # ---------------------------------------------------------------- @@ -69,6 +66,6 @@ add_subdirectory(example) # ---------------------------------------------------------------- -install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) +xo_install_include_tree() install(TARGETS ex1 DESTINATION bin/randomgen/example) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt index 6af29d79..a9d1b27d 100644 --- a/example/ex1/CMakeLists.txt +++ b/example/ex1/CMakeLists.txt @@ -1,2 +1,2 @@ add_executable(ex1 ex1.cpp) -xo_include_options(ex1) +xo_include_options2(ex1) diff --git a/example/ex2/CMakeLists.txt b/example/ex2/CMakeLists.txt index bab03164..ee6687f2 100644 --- a/example/ex2/CMakeLists.txt +++ b/example/ex2/CMakeLists.txt @@ -1,3 +1,3 @@ add_executable(ex2 ex2.cpp) -xo_include_options(ex2) -xo_indentlog_dependency(ex2) +xo_include_options2(ex2) +xo_internal_dependency(ex2 indentlog) From 66d8b439416e80918c4ff5b1cdea4b8f8e32ca6a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 17:59:12 -0400 Subject: [PATCH 0206/2693] indentlog: build: streamline using xo-cmake macros --- CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c8c5c655..db51a3d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,9 +34,8 @@ endif() if(NOT CMAKE_INSTALL_PREFIX) set(CMAKE_INSTALL_PREFIX /home/${USER}/local CACHE STRING "install directory") endif() -if(NOT CMAKE_INSTALL_RPATH) - set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib CACHE STRING "runpath in installed libraries/executables") -endif() + +xo_toplevel_compile_options() add_subdirectory(example) add_subdirectory(utest) From 29d5d68319a2563ed35e6fdc673d0f1fa30c5502 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 17:59:47 -0400 Subject: [PATCH 0207/2693] refcnt: build: xo-macros defaults CMAKE_MODULE_PATH --- CMakeLists.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bc8e2ef1..1b0b2a73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,10 +41,6 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") xo_toplevel_compile_options() -if(NOT CMAKE_INSTALL_RPATH) - set(CMAKE_INSTALL_RPATH $(CMAKE_INSTALL_PREFIX)/lib CACHE STRING "runpath in installed libraries/executables") -endif() - # ---------------------------------------------------------------- # sources From ee054b6f37c8f042c2e108ad8ca75c14b733517f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:00:16 -0400 Subject: [PATCH 0208/2693] reflect: build: xo-macros defaults CMAKE_MODULE_PATH --- CMakeLists.txt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ec69e9c5..85a6b475 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,14 +46,6 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") xo_toplevel_compile_options() -if(NOT CMAKE_INSTALL_RPATH) - set(CMAKE_INSTALL_RPATH $(CMAKE_INSTALL_PREFIX)/lib CACHE STRING "runpath in installed libraries/executables") -endif() - -# early find_package() experiment -#find_package(indentlog CONFIG REQUIRED) -#find_package(refcnt CONFIG REQUIRED) - # ---------------------------------------------------------------- # sources From 4012d7387f2e371e95e92546806d1b6be6630008 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:00:48 -0400 Subject: [PATCH 0209/2693] subsys: build: xo-macros default CMAKE_MODULE_PATH --- CMakeLists.txt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 126d305b..d308603a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,8 +28,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") -xo_toplevel_compile_options() - # ---------------------------------------------------------------- # - author's convenience: default install prefix to /home/$USER/local # - otherwise use -DCMAKE_INSTALL_PREFIX=/path/to/somewhere @@ -44,9 +42,8 @@ endif() if(NOT CMAKE_INSTALL_PREFIX) set(CMAKE_INSTALL_PREFIX /home/${USER}/local CACHE STRING "install directory") endif() -if(NOT CMAKE_INSTALL_RPATH) - set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib CACHE STRING "runpath in installed libraries/executables") -endif() + +xo_toplevel_compile_options() #add_subdirectory(example) #add_subdirectory(utest) From 3d9d49f617de3aea350e7383ad1075a4cab14870 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:10:31 -0400 Subject: [PATCH 0210/2693] xo-cmake: consolidate CMAKE_CXX_STANDARD setting --- cmake/xo_cxx.cmake | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index c7f163d2..b02ad99a 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -1,5 +1,12 @@ macro(xo_toplevel_compile_options) + if(NOT DEFINED CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 20) + endif() + if(NOT DEFINED CMAKE_CXX_STANDARD_REQUIRED) + set(CMAKE_CXX_STANDARD_REQUIRED True) + endif() + # ---------------------------------------------------------------- # variable # XO_ADDRESS_SANITIZE From 3ec624bd75d0801ffcb28fdf57f74a205cede9aa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:11:37 -0400 Subject: [PATCH 0211/2693] subsys: consolidate CMAKE_CXX_STANDARD setting --- CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d308603a..24b9e54a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,12 +19,6 @@ add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) set(XO_PROJECT_NAME subsys) -if(NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 20) -endif() - -set(CMAKE_CXX_STANDARD_REQUIRED True) - # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") From 3ab6046e2870edf5129417fe890950440ca7ca1c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:12:15 -0400 Subject: [PATCH 0212/2693] reflect: consolidate CMAKE_CXX_STANDARD setting --- CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 85a6b475..d338456c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,12 +35,6 @@ set(PROJECT_CXX_FLAGS "") add_definitions(${PROJECT_CXX_FLAGS}) -if(NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 20) -endif() - -set(CMAKE_CXX_STANDARD_REQUIRED True) - # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") From c361c5d4ca988c8d67120a633c8ba4ddd29e0dab Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:13:15 -0400 Subject: [PATCH 0213/2693] refcnt: consolidate CMAKE_CXX_STANDARD setting --- CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b0b2a73..733ff9f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,12 +30,6 @@ set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") add_definitions(${PROJECT_CXX_FLAGS}) -if(NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 20) -endif() - -set(CMAKE_CXX_STANDARD_REQUIRED True) - # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") From 50b787759391477b66810471812812037b1f9c45 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:13:46 -0400 Subject: [PATCH 0214/2693] indentlog: consolidate CMAKE_CXX_STANDARD setting --- CMakeLists.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index db51a3d9..423b00e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,9 +18,6 @@ add_code_coverage() # add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED True) - # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") From cf4a928783ed0595d131ea721da435eab1263686 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:14:37 -0400 Subject: [PATCH 0215/2693] randomgen: consolidate CMAKE_CXX_STANDARD setting --- CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e27486bb..83076439 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,12 +29,6 @@ set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") add_definitions(${PROJECT_CXX_FLAGS}) -if(NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 20) -endif() - -set(CMAKE_CXX_STANDARD_REQUIRED True) - # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") From 401fc257e88ff2178b9f129f476e50836095ea98 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:21:27 -0400 Subject: [PATCH 0216/2693] xo-cmake: consolidate CMAKE_EXPORT_COMPILE_COMMANDS --- cmake/xo_cxx.cmake | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index b02ad99a..53cccb58 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -34,6 +34,13 @@ macro(xo_toplevel_compile_options) set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) endif() + # writes ${PROJECT_BINARY_DIR}/compile_commands.json; + # (symlink from toplevel git dir to tell LSP how to build) + # + if(NOT DEFINED CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") + endif() + if(NOT CMAKE_INSTALL_RPATH) set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib CACHE STRING "runpath in installed libraries/executables") From 11092bc4fd0ce795e62f058a632ab0fe4d947c8a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:22:08 -0400 Subject: [PATCH 0217/2693] randomgen: consolidate CMAKE_EXPORT_COMPILE_COMMANDS + tidy --- CMakeLists.txt | 11 - cmake/code-coverage.cmake | 678 -------------------------------------- cmake/cxx.cmake | 40 --- 3 files changed, 729 deletions(-) delete mode 100644 cmake/code-coverage.cmake delete mode 100644 cmake/cxx.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 83076439..5653ec33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,20 +29,9 @@ set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") add_definitions(${PROJECT_CXX_FLAGS}) -# always write compile_commands.json -set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") - # ---------------------------------------------------------------- # default install -if(NOT USER) - set(USER $ENV{USER}) -endif() - -if(NOT CMAKE_INSTALL_PREFIX) - set(CMAKE_INSTALL_PREFIX /home/${USER}/local CACHE STRING "install directory") -endif() - xo_toplevel_compile_options() # ---------------------------------------------------------------- diff --git a/cmake/code-coverage.cmake b/cmake/code-coverage.cmake deleted file mode 100644 index b6b36064..00000000 --- a/cmake/code-coverage.cmake +++ /dev/null @@ -1,678 +0,0 @@ -# -# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy of -# the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations under -# the License. - -# USAGE: To enable any code coverage instrumentation/targets, the single CMake -# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or -# on the command line. -# -# From this point, there are two primary methods for adding instrumentation to -# targets: -# -# 1 - A blanket instrumentation by calling `add_code_coverage()`, where -# all targets in that directory and all subdirectories are automatically -# instrumented. -# -# 2 - Per-target instrumentation by calling -# `target_code_coverage()`, where the target is given and thus only -# that target is instrumented. This applies to both libraries and executables. -# -# To add coverage targets, such as calling `make ccov` to generate the actual -# coverage information for perusal or consumption, call -# `target_code_coverage()` on an *executable* target. -# -# Example 1: All targets instrumented -# -# In this case, the coverage information reported will will be that of the -# `theLib` library target and `theExe` executable. -# -# 1a: Via global command -# -# ~~~ -# add_code_coverage() # Adds instrumentation to all targets -# -# add_library(theLib lib.cpp) -# -# add_executable(theExe main.cpp) -# target_link_libraries(theExe PRIVATE theLib) -# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target -# # (instrumentation already added via global anyways) -# # for generating code coverage reports. -# ~~~ -# -# 1b: Via target commands -# -# ~~~ -# add_library(theLib lib.cpp) -# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. -# -# add_executable(theExe main.cpp) -# target_link_libraries(theExe PRIVATE theLib) -# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. -# ~~~ -# -# Example 2: Target instrumented, but with regex pattern of files to be excluded -# from report -# -# ~~~ -# add_executable(theExe main.cpp non_covered.cpp) -# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. -# ~~~ -# -# Example 3: Target added to the 'ccov' and 'ccov-all' targets -# -# ~~~ -# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. -# -# add_executable(theExe main.cpp non_covered.cpp) -# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. -# ~~~ - -# Options -option( - CODE_COVERAGE - "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" - OFF) - -# Programs -find_program(LLVM_COV_PATH llvm-cov) -find_program(LLVM_PROFDATA_PATH llvm-profdata) -find_program(LCOV_PATH lcov) -find_program(GENHTML_PATH genhtml) -# Hide behind the 'advanced' mode flag for GUI/ccmake -mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) - -# Variables -set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) -set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) - -# Common initialization/checks -if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) - set(CODE_COVERAGE_ADDED ON) - - # Common Targets - add_custom_target( - ccov-preprocessing - COMMAND ${CMAKE_COMMAND} -E make_directory - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} - DEPENDS ccov-clean) - - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - # Messages - message(STATUS "Building with llvm Code Coverage Tools") - - if(NOT LLVM_COV_PATH) - message(FATAL_ERROR "llvm-cov not found! Aborting.") - else() - # Version number checking for 'EXCLUDE' compatibility - execute_process(COMMAND ${LLVM_COV_PATH} --version - OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) - string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION - ${LLVM_COV_VERSION_CALL_OUTPUT}) - - if(LLVM_COV_VERSION VERSION_LESS "7.0.0") - message( - WARNING - "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" - ) - endif() - endif() - - # Targets - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-clean - COMMAND ${CMAKE_COMMAND} -E remove -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND ${CMAKE_COMMAND} -E remove -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) - else() - add_custom_target( - ccov-clean - COMMAND ${CMAKE_COMMAND} -E rm -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND ${CMAKE_COMMAND} -E rm -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) - endif() - - # Used to get the shared object file list before doing the main all- - # processing - add_custom_target( - ccov-libs - COMMAND ; - COMMENT "libs ready for coverage report.") - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - # Messages - message(STATUS "Building with lcov Code Coverage Tools") - - if(CMAKE_BUILD_TYPE) - string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) - if(NOT ${upper_build_type} STREQUAL "DEBUG") - message( - WARNING - "Code coverage results with an optimized (non-Debug) build may be misleading" - ) - endif() - else() - message( - WARNING - "Code coverage results with an optimized (non-Debug) build may be misleading" - ) - endif() - if(NOT LCOV_PATH) - message(FATAL_ERROR "lcov not found! Aborting...") - endif() - if(NOT GENHTML_PATH) - message(FATAL_ERROR "genhtml not found! Aborting...") - endif() - - # Targets - add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory - ${CMAKE_BINARY_DIR} --zerocounters) - - else() - message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") - endif() -endif() - -# Adds code coverage instrumentation to a library, or instrumentation/targets -# for an executable target. -# ~~~ -# EXECUTABLE ADDED TARGETS: -# GCOV/LCOV: -# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. -# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. -# -# LLVM-COV: -# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-${TARGET_NAME} : Generates HTML code coverage report. -# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. -# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. -# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. -# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. -# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. -# ccov-all-export : Exports the coverage report to a JSON file. -# -# Required: -# TARGET_NAME - Name of the target to generate code coverage for. -# Optional: -# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. -# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. -# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) -# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. -# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. -# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory -# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. -# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** -# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output -# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call -# ~~~ -function(target_code_coverage TARGET_NAME) - # Argument parsing - set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) - set(single_value_keywords COVERAGE_TARGET_NAME) - set(multi_value_keywords EXCLUDE OBJECTS ARGS) - cmake_parse_arguments( - target_code_coverage "${options}" "${single_value_keywords}" - "${multi_value_keywords}" ${ARGN}) - - # Set the visibility of target functions to PUBLIC, INTERFACE or default to - # PRIVATE. - if(target_code_coverage_PUBLIC) - set(TARGET_VISIBILITY PUBLIC) - set(TARGET_LINK_VISIBILITY PUBLIC) - elseif(target_code_coverage_INTERFACE) - set(TARGET_VISIBILITY INTERFACE) - set(TARGET_LINK_VISIBILITY INTERFACE) - elseif(target_code_coverage_PLAIN) - set(TARGET_VISIBILITY PUBLIC) - set(TARGET_LINK_VISIBILITY) - else() - set(TARGET_VISIBILITY PRIVATE) - set(TARGET_LINK_VISIBILITY PRIVATE) - endif() - - if(NOT target_code_coverage_COVERAGE_TARGET_NAME) - # If a specific name was given, use that instead. - set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) - endif() - - if(CODE_COVERAGE) - - # Add code coverage instrumentation to the target's linker command - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} - -fprofile-instr-generate -fcoverage-mapping) - target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} - -fprofile-instr-generate -fcoverage-mapping) - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs - -ftest-coverage) - target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) - endif() - - # Targets - get_target_property(target_type ${TARGET_NAME} TYPE) - - # Add shared library to processing for 'all' targets - if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${CMAKE_COMMAND} -E echo "-object=$" >> - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - DEPENDS ccov-preprocessing ${TARGET_NAME}) - - if(NOT TARGET ccov-libs) - message( - FATAL_ERROR - "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." - ) - endif() - - add_dependencies(ccov-libs - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - - # For executables add targets to run and produce output - if(target_type STREQUAL "EXECUTABLE") - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - - # If there are shared objects to also work with, generate the string to - # add them here - foreach(SO_TARGET ${target_code_coverage_OBJECTS}) - # Check to see if the target is a shared object - if(TARGET ${SO_TARGET}) - get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) - if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") - set(SO_OBJECTS ${SO_OBJECTS} -object=$) - endif() - endif() - endforeach() - - # Run the executable, generating raw profile data Make the run data - # available for further processing. Separated to allow Windows to run - # this target serially. - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${CMAKE_COMMAND} -E env - LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw - $ ${target_code_coverage_ARGS} - COMMAND - ${CMAKE_COMMAND} -E echo "-object=$" - ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND - ${CMAKE_COMMAND} -E echo - "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" - >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list - JOB_POOL ccov_serial_pool - DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) - - # Merge the generated profile data so llvm-cov can process it - add_custom_target( - ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_PROFDATA_PATH} merge -sparse - ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o - ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Ignore regex only works on LLVM >= 7 - if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") - foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} - -ignore-filename-regex='${EXCLUDE_ITEM}') - endforeach() - endif() - - # Print out details of the coverage information to the command line - add_custom_target( - ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} show $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -show-line-counts-or-regions ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Print out a summary of the coverage information to the command line - add_custom_target( - ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} report $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Export coverage information so continuous integration tools (e.g. - # Jenkins) can consume it - add_custom_target( - ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} export $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -format="text" ${EXCLUDE_REGEX} > - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Generates HTML output of the coverage information for perusal - add_custom_target( - ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} show $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - set(COVERAGE_INFO - "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" - ) - - # Run the executable, generating coverage information - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND $ ${target_code_coverage_ARGS} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - - # Generate exclusion string for use - foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} - '${EXCLUDE_ITEM}') - endforeach() - - if(EXCLUDE_REGEX) - set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file - ${COVERAGE_INFO}) - else() - set(EXCLUDE_COMMAND ;) - endif() - - if(NOT ${target_code_coverage_EXTERNAL}) - set(EXTERNAL_OPTION --no-external) - endif() - - # Capture coverage data - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters - COMMAND $ ${target_code_coverage_ARGS} - COMMAND - ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory - ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file - ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - else() - add_custom_target( - ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters - COMMAND $ ${target_code_coverage_ARGS} - COMMAND - ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory - ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file - ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - endif() - - # Generates HTML output of the coverage information for perusal - add_custom_target( - ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${GENHTML_PATH} -o - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} - ${COVERAGE_INFO} - DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - - add_custom_command( - TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - POST_BUILD - COMMAND ; - COMMENT - "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." - ) - - # AUTO - if(target_code_coverage_AUTO) - if(NOT TARGET ccov) - add_custom_target(ccov) - endif() - add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) - - if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID - MATCHES "GNU") - if(NOT TARGET ccov-report) - add_custom_target(ccov-report) - endif() - add_dependencies( - ccov-report - ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - - # ALL - if(target_code_coverage_ALL) - if(NOT TARGET ccov-all-processing) - message( - FATAL_ERROR - "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." - ) - endif() - - add_dependencies(ccov-all-processing - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - endif() -endfunction() - -# Adds code coverage instrumentation to all targets in the current directory and -# any subdirectories. To add coverage instrumentation to only specific targets, -# use `target_code_coverage`. -function(add_code_coverage) - if(CODE_COVERAGE) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - add_compile_options(-fprofile-instr-generate -fcoverage-mapping) - add_link_options(-fprofile-instr-generate -fcoverage-mapping) - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - add_compile_options(-fprofile-arcs -ftest-coverage) - link_libraries(gcov) - endif() - endif() -endfunction() - -# Adds the 'ccov-all' type targets that calls all targets added via -# `target_code_coverage` with the `ALL` parameter, but merges all the coverage -# data from them into a single large report instead of the numerous smaller -# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for -# use with coverage dashboards (e.g. codecov.io, coveralls). -# ~~~ -# Optional: -# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! -# ~~~ -function(add_code_coverage_all_targets) - # Argument parsing - set(multi_value_keywords EXCLUDE) - cmake_parse_arguments(add_code_coverage_all_targets "" "" - "${multi_value_keywords}" ${ARGN}) - - if(CODE_COVERAGE) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - - # Merge the profile data for all of the run executables - if(WIN32) - add_custom_target( - ccov-all-processing - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe - merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -sparse $$FILELIST) - else() - add_custom_target( - ccov-all-processing - COMMAND - ${LLVM_PROFDATA_PATH} merge -o - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) - endif() - - # Regex exclude only available for LLVM >= 7 - if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") - foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} - -ignore-filename-regex='${EXCLUDE_ITEM}') - endforeach() - endif() - - # Print summary of the code coverage information to the command line - if(WIN32) - add_custom_target( - ccov-all-report - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe - report $$FILELIST - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - else() - add_custom_target( - ccov-all-report - COMMAND - ${LLVM_COV_PATH} report `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - endif() - - # Export coverage information so continuous integration tools (e.g. - # Jenkins) can consume it - add_custom_target( - ccov-all-export - COMMAND - ${LLVM_COV_PATH} export `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -format="text" ${EXCLUDE_REGEX} > - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json - DEPENDS ccov-all-processing) - - # Generate HTML output of all added targets for perusal - if(WIN32) - add_custom_target( - ccov-all - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show - $$FILELIST - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - else() - add_custom_target( - ccov-all - COMMAND - ${LLVM_COV_PATH} show `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - endif() - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") - - # Nothing required for gcov - add_custom_target(ccov-all-processing COMMAND ;) - - # Exclusion regex string creation - set(EXCLUDE_REGEX) - foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} - '${EXCLUDE_ITEM}') - endforeach() - - if(EXCLUDE_REGEX) - set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file - ${COVERAGE_INFO}) - else() - set(EXCLUDE_COMMAND ;) - endif() - - # Capture coverage data - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-all-capture - COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture - --output-file ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ccov-all-processing) - else() - add_custom_target( - ccov-all-capture - COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture - --output-file ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ccov-all-processing) - endif() - - # Generates HTML output of all targets for perusal - add_custom_target( - ccov-all - COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} - DEPENDS ccov-all-capture) - - endif() - - add_custom_command( - TARGET ccov-all - POST_BUILD - COMMAND ; - COMMENT - "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." - ) - endif() -endfunction() diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake deleted file mode 100644 index e01e6801..00000000 --- a/cmake/cxx.cmake +++ /dev/null @@ -1,40 +0,0 @@ -# ---------------------------------------------------------------- -# use this in subdirs that compile c++ code -# -macro(xo_include_options target) - # ---------------------------------------------------------------- - # PROJECT_SOURCE_DIR: - # so we can for example write - # #include "ordinaltree/foo.hpp" - # from anywhere in the project - # PROJECT_BINARY_DIR: - # since generated version file will be in build directory, - # need that build directory to also appear in - # compiler's include path - # - target_include_directories( - ${target} PUBLIC - ${PROJECT_SOURCE_DIR}/include - ${PROJECT_BINARY_DIR} - ) - - # ---------------------------------------------------------------- - # make standard directories for std:: includes explicit - # so that - # (1) they appear in compile_commands.json. - # (2) clangd (run from emacs lsp-mode) can find them - # - if(CMAKE_EXPORT_COMPILE_COMMANDS) - set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) - endif() -endmacro() - -# ---------------------------------------------------------------- -# use this when relying on indentlog [[https://github.com/rconybea/indentlog]] headers -# -macro(xo_indentlog_dependency target) - find_package(indentlog REQUIRED) - #add_dependencies(${target} indentlog) - target_link_libraries(${target} PUBLIC indentlog) - #target_include_directories(${target} PUBLIC ${indentlog_DIR}/../../../include) -endmacro() From 4600ebcb213914af4637b20eaad5cae5bd667dff Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:22:54 -0400 Subject: [PATCH 0218/2693] subsys: consolidate CMAKE_EXPORT_COMPILE_COMMANDS + tidy --- CMakeLists.txt | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 24b9e54a..189db40a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,38 +19,13 @@ add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) set(XO_PROJECT_NAME subsys) -# always write compile_commands.json -set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") - -# ---------------------------------------------------------------- -# - author's convenience: default install prefix to /home/$USER/local -# - otherwise use -DCMAKE_INSTALL_PREFIX=/path/to/somewhere - -if(NOT USER) - set(USER $ENV{USER}) -endif() - -# hmm. this works if explicitly given with cmake: -# cmake -DCMAKE_INSTALL_PREFIX=/home/roland/local path/to/source -# but not as default -if(NOT CMAKE_INSTALL_PREFIX) - set(CMAKE_INSTALL_PREFIX /home/${USER}/local CACHE STRING "install directory") -endif() - xo_toplevel_compile_options() -#add_subdirectory(example) -#add_subdirectory(utest) - # ---------------------------------------------------------------- # installing header-only library add_library(subsys INTERFACE) xo_include_headeronly_options2(subsys) -#target_include_directories(subsys INTERFACE -# $ -# $ -#) xo_install_library2(subsys) # ---------------------------------------------------------------- From afd595185b5d4b879a19eb03af3d8e971feffdcc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:23:21 -0400 Subject: [PATCH 0219/2693] indentlog: consolidate CMAKE_EXPORT_COMPILE_COMMANDS + tidy --- CMakeLists.txt | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 423b00e4..b8553e93 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,20 +18,6 @@ add_code_coverage() # add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) -# always write compile_commands.json -set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") - -if(NOT USER) - set(USER $ENV{USER}) -endif() - -# hmm. this works if explicitly given with cmake: -# cmake -DCMAKE_INSTALL_PREFIX=/home/roland/local path/to/source -# but not as default -if(NOT CMAKE_INSTALL_PREFIX) - set(CMAKE_INSTALL_PREFIX /home/${USER}/local CACHE STRING "install directory") -endif() - xo_toplevel_compile_options() add_subdirectory(example) From 277935c25e202124a4973476dac62c33e3f1ff5c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:23:40 -0400 Subject: [PATCH 0220/2693] randomgen: consolidate CMAKE_EXPORT_COMPILE_COMMANDS --- CMakeLists.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 733ff9f9..aef9d414 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,9 +30,6 @@ set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") add_definitions(${PROJECT_CXX_FLAGS}) -# always write compile_commands.json -set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") - xo_toplevel_compile_options() # ---------------------------------------------------------------- From 5972be91da909a87ee856d1e940605f9f672f21a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:23:57 -0400 Subject: [PATCH 0221/2693] randomgen: consolidate CMAKE_EXPORT_COMPILE_COMMANDS --- CMakeLists.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d338456c..30a22fb6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,9 +35,6 @@ set(PROJECT_CXX_FLAGS "") add_definitions(${PROJECT_CXX_FLAGS}) -# always write compile_commands.json -set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") - xo_toplevel_compile_options() # ---------------------------------------------------------------- From f38f48943762a2bc72efd27ef1ee7dbab7ce6ee2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 19:40:39 -0400 Subject: [PATCH 0222/2693] xo-cmake: + xo_add_shared_library() + misc --- cmake/xo_cxx.cmake | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 53cccb58..7fe1c65d 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -47,11 +47,7 @@ macro(xo_toplevel_compile_options) endif() endmacro() -# ---------------------------------------------------------------- -# use this in subdirs that compile c++ code. -# do not use for header-only subsystems; see xo_include_headeronly_options2() -# -macro(xo_include_options2 target) +macro(xo_include_headeronly_options2 target) # ---------------------------------------------------------------- # PROJECT_SOURCE_DIR: # so we can for example write @@ -63,7 +59,7 @@ macro(xo_include_options2 target) # compiler's include path # target_include_directories( - ${target} PUBLIC + ${target} INTERFACE $ # e.g. for #include "indentlog/scope.hpp" $ $ # e.g. for #include "Refcounted.hpp" in refcnt/src @@ -82,7 +78,37 @@ macro(xo_include_options2 target) endif() endmacro() -macro(xo_include_headeronly_options2 target) +# ---------------------------------------------------------------- +# use this for a shared library. +# +macro(xo_add_shared_library target targetversion soversion sources) + add_library(${target} SHARED ${sources}) + foreach(arg IN ITEMS ${ARGN}) + #message("target=${target}; arg=${arg}") + + # to use PUBLIC here would need to split: + # $ + # $ + # but shouldn't need that, since we arrange to install includes via + # xo_include_options2() below + # + target_sources(${target} PRIVATE ${arg}) + endforeach() + set_target_properties( + ${target} + PROPERTIES + VERSION ${targetversion} + SOVERSION ${soversion}) + xo_compile_options(${target}) + xo_include_options2(${target}) + xo_install_library2(${target}) +endmacro() + +# ---------------------------------------------------------------- +# use this in subdirs that compile c++ code. +# do not use for header-only subsystems; see xo_include_headeronly_options2() +# +macro(xo_include_options2 target) # ---------------------------------------------------------------- # PROJECT_SOURCE_DIR: # so we can for example write @@ -94,7 +120,7 @@ macro(xo_include_headeronly_options2 target) # compiler's include path # target_include_directories( - ${target} INTERFACE + ${target} PUBLIC $ # e.g. for #include "indentlog/scope.hpp" $ $ # e.g. for #include "Refcounted.hpp" in refcnt/src From 4e0c8e92f236ebfce2b345b686f8b45fc26ba8b7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 19:41:26 -0400 Subject: [PATCH 0223/2693] refcnt: use xo_add_shared_library() from xo-cmake --- src/CMakeLists.txt | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0a60fbea..9d006fd9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,14 +1,5 @@ set(SELF_LIBRARY_NAME refcnt) set(SELF_SOURCE_FILES Refcounted.cpp Displayable.cpp) -add_library(${SELF_LIBRARY_NAME} SHARED ${SELF_SOURCE_FILES}) - -set_target_properties(${SELF_LIBRARY_NAME} - PROPERTIES - VERSION ${PROJECT_VERSION} - SOVERSION 1) +xo_add_shared_library(${SELF_LIBRARY_NAME} ${PROJECT_VERSION} 1 ${SELF_SOURCE_FILES}) xo_internal_dependency(${SELF_LIBRARY_NAME} indentlog) - -xo_include_options2(${SELF_LIBRARY_NAME}) -xo_compile_options(${SELF_LIBRARY_NAME}) -xo_install_library2(${SELF_LIBRARY_NAME}) From 94d77bf809900b2458f87dfe43d8698b1910230c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 19:42:52 -0400 Subject: [PATCH 0224/2693] reflect: use xo_add_shared_library() + misc. --- CMakeLists.txt | 2 -- src/reflect/CMakeLists.txt | 16 +--------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 30a22fb6..0402062d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,10 +29,8 @@ add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* # ---------------------------------------------------------------- # c++ settings -set(XO_PROJECT_NAME reflect) set(PROJECT_CXX_FLAGS "") #set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") - add_definitions(${PROJECT_CXX_FLAGS}) xo_toplevel_compile_options() diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index 0b5c710e..6056ebca 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -3,21 +3,7 @@ set(SELF_LIBRARY_NAME reflect) set(SELF_SOURCE_FILES TypeDescr.cpp TypeDescrExtra.cpp TaggedRcptr.cpp atomic/AtomicTdx.cpp pointer/PointerTdx.cpp vector/VectorTdx.cpp struct/StructTdx.cpp struct/StructMember.cpp init_reflect.cpp) -# build shared library 'reflect' -add_library(${SELF_LIBRARY_NAME} SHARED ${SELF_SOURCE_FILES}) - -set_target_properties(${SELF_LIBRARY_NAME} - PROPERTIES - VERSION ${PROJECT_VERSION} - SOVERSION 1) - -# ---------------------------------------------------------------- -# all the errors+warnings! -# -#target_compile_options(${SELF_LIBRARY_NAME} PRIVATE -Werror -Wall -Wextra) -xo_compile_options(${SELF_LIBRARY_NAME}) -xo_include_options2(${SELF_LIBRARY_NAME}) -xo_install_library2(${SELF_LIBRARY_NAME}) +xo_add_shared_library(${SELF_LIBRARY_NAME} ${PROJECT_VERSION} 1 ${SELF_SOURCE_FILES}) # ---------------------------------------------------------------- # dependencies: logutil, ... From 8a325bab4b4b4876c3bd63adcfb71957ec5c31a9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 3 Oct 2023 16:59:44 -0400 Subject: [PATCH 0225/2693] make catch2 include path precise --- utest/CMakeLists.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index c6e6e596..a135895a 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -15,7 +15,18 @@ target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) # ---------------------------------------------------------------- # 3rd party dependency: catch2 +# 1. using target_link_libraries(.. Catch2) here doesn't work; +# build tries to link to a Catch2 library. +# perhaps Catch2 target not declared INTERFACE? +# 2. nix build doesn't need this; dependency on catch2 +# puts include directory in NIX_CFLAGS_COMPILE, so stuff 'just works' +# find_package(Catch2 2 REQUIRED) +set(Catch2_INCLUDES "${Catch2_DIR}/../..") +target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC ${Catch2_INCLUDES}) + +#xo_internal_dependency(${SELF_EXECUTABLE_NAME} Catch2) +#target_link_libraries(${SELF_EXECUTABLE_NAME} PUBLIC Catch2) # ---------------------------------------------------------------- # make standard directories for std:: includes explicit From 2fc5a385fda05db2c87425197bc902e64bd9c2b1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 3 Oct 2023 17:00:05 -0400 Subject: [PATCH 0226/2693] + .gitignore --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..81c5ae6d --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# symlink to ${my_build_directory}/compile_commands.json to make LSP work +compile_commands.json +# lsp keeps state here +.cache +# typical build dirs +build +ccov From abec2b994edc734d119eacbf7825780fde7fecb8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 3 Oct 2023 17:01:57 -0400 Subject: [PATCH 0227/2693] + MARKDOWN + TODO --- MARKDOWN | 31 +++++++++++++++++++++++++++++++ TODO | 5 +++++ 2 files changed, 36 insertions(+) create mode 100644 MARKDOWN create mode 100644 TODO diff --git a/MARKDOWN b/MARKDOWN new file mode 100644 index 00000000..990d8275 --- /dev/null +++ b/MARKDOWN @@ -0,0 +1,31 @@ +# heading level 1 +## heading level 2 +###### heading level 6 + +blank line new paragraph +two spaces at eod force line break + +**bold** bold text +__bold__ also bold text (but don't embed within a word) + +*italics* + +***bolditalic*** + +> text to blockquote +> +> + more paragraphs + +> text to blockquote +> +>> with nested blockquote + +- bullets also can prefix with + or * + +1. numbered lists + +indent 4 spaces (or 1 tab) for code blocks + +`inline code` + +--- on a line by itself -> horizontal rule diff --git a/TODO b/TODO new file mode 100644 index 00000000..bc404053 --- /dev/null +++ b/TODO @@ -0,0 +1,5 @@ + + +sphinx_markdown_builder + +https://stackoverflow.com/questions/13396856/markdown-output-for-sphinx-based-documentation From 19ca2b6a340b26f6227e2bb80e425792cc6504a0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 3 Oct 2023 17:09:26 -0400 Subject: [PATCH 0228/2693] (abandoned experiment with get_target_property()) --- utest/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 19bf25e6..466cbe0d 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -22,7 +22,8 @@ target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) # puts include directory in NIX_CFLAGS_COMPILE, so stuff 'just works' # find_package(Catch2 2 REQUIRED) -set(Catch2_INCLUDES "${Catch2_DIR}/../..") +set(Catch2_INCLUDES "${Catch2_DIR}/../../../include") +#get_target_property(Catch2_INCLUDES Catch2 INCLUDE_DIRECTORIES) target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC ${Catch2_INCLUDES}) #xo_internal_dependency(${SELF_EXECUTABLE_NAME} Catch2) From dfbc753fd6faae93f6df85b42cddb3378c1bb9e4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 3 Oct 2023 17:16:52 -0400 Subject: [PATCH 0229/2693] try canonical cmake form for catch2 dep --- utest/CMakeLists.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 466cbe0d..b94f1dd5 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -22,9 +22,10 @@ target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) # puts include directory in NIX_CFLAGS_COMPILE, so stuff 'just works' # find_package(Catch2 2 REQUIRED) -set(Catch2_INCLUDES "${Catch2_DIR}/../../../include") -#get_target_property(Catch2_INCLUDES Catch2 INCLUDE_DIRECTORIES) -target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC ${Catch2_INCLUDES}) +target_link_libraries(${SELF_EXECUTABLE_NAME} Catch2::Catch2) + +#set(Catch2_INCLUDES "${Catch2_DIR}/../../../include") +#target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC ${Catch2_INCLUDES}) #xo_internal_dependency(${SELF_EXECUTABLE_NAME} Catch2) #target_link_libraries(${SELF_EXECUTABLE_NAME} PUBLIC Catch2) From 2b10fec175580c033a60efb042fb9a8903843ba9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 3 Oct 2023 17:22:42 -0400 Subject: [PATCH 0230/2693] + xo_external_target_dependency --- cmake/xo_cxx.cmake | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 7fe1c65d..66ac7962 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -224,6 +224,20 @@ macro(xo_internal_dependency target dep) target_link_libraries(${target} PUBLIC ${dep}) endmacro() +# dependency on namespaced target +# e.g. +# add_library(foo ..) or add_executable(foo ...) +# then +# xo_external_namespaced_dependency(foo Catch2 Catch2::Catch2) +# equivalent to +# find_package(Catch2 CONFIG REQUIRED) +# target_link_libraries(foo PUBLIC Catch2::Catch2) +# +macro(xo_external_target_dependency target pkg pkgtarget) + find_package(${pkg} CONFIG REQUIRED) + target_link_libraries(${target} PUBLIC ${pkgtarget}) +endmacro() + # dependency on target provided from this codebase. # # 1. don't need find_package() in this case, since details of dep targets @@ -234,3 +248,6 @@ endmacro() macro(xo_self_dependency target dep) target_link_libraries(${target} PUBLIC ${dep}) endmacro() + +# ---------------------------------------------------------------- +# From dfc7aa23d7815e65b099b1e6790fd05e92c6bc60 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 3 Oct 2023 17:27:35 -0400 Subject: [PATCH 0231/2693] indentlog: use xo_external_target_dependency() for catch2 --- utest/CMakeLists.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index b94f1dd5..1b92e999 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -15,14 +15,17 @@ target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) # ---------------------------------------------------------------- # 3rd party dependency: catch2 +xo_external_target_dependency(${SELF_EXECUTABLE_NAME} Catch2 Catch2::Catch2) + # 1. using target_link_libraries(.. Catch2) here doesn't work; # build tries to link to a Catch2 library. # perhaps Catch2 target not declared INTERFACE? # 2. nix build doesn't need this; dependency on catch2 # puts include directory in NIX_CFLAGS_COMPILE, so stuff 'just works' # -find_package(Catch2 2 REQUIRED) -target_link_libraries(${SELF_EXECUTABLE_NAME} Catch2::Catch2) + +#find_package(Catch2 2 REQUIRED) +#target_link_libraries(${SELF_EXECUTABLE_NAME} Catch2::Catch2) #set(Catch2_INCLUDES "${Catch2_DIR}/../../../include") #target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC ${Catch2_INCLUDES}) From ac265dc2b709eb97c22a5e8d004369e6928671af Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 3 Oct 2023 17:36:50 -0400 Subject: [PATCH 0232/2693] build: clenaup dependencies --- src/CMakeLists.txt | 3 ++- utest/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8083cee0..43fe7a82 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,7 +7,8 @@ set_target_properties(${SELF_LIBRARY_NAME} VERSION ${PROJECT_VERSION} SOVERSION 1) -xo_indentlog_dependency(${SELF_LIBRARY_NAME}) +xo_internal_dependency(${SELF_LIBRARY_NAME} indentlog) +#xo_indentlog_dependency(${SELF_LIBRARY_NAME}) xo_include_options(${SELF_LIBRARY_NAME}) xo_compile_options(${SELF_LIBRARY_NAME}) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 74706592..cfdf594a 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -35,7 +35,7 @@ target_link_libraries(${SELF_EXECUTABLE_NAME} PUBLIC refcnt) # ---------------------------------------------------------------- # 3rd part dependency: catch2: -find_package(Catch2 2 REQUIRED) +xo_external_target_dependency(${SELF_EXECUTABLE_NAME} Catch2 Catch2::Catch2) # need this so that catch2/include appears in compile_commands.json, # on which lsp integration relies. From 592447fa153ea2ccfc3bb65bb0ed9f6ba6db0789 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 3 Oct 2023 17:37:50 -0400 Subject: [PATCH 0233/2693] + .gitignore --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..86d062ab --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# symlink to ${mybuilddirectory}/compile_commands.json for LSP +compile_commands.json +# LSP keeps state here +.cache +# typical build directories +build +ccov From 4e88ca9820e2dc1c0bd610816af33fb5cd6bc7be Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 3 Oct 2023 21:55:52 -0400 Subject: [PATCH 0234/2693] build: refcnt: streamline utest build --- utest/CMakeLists.txt | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index cfdf594a..ef9a4006 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -1,11 +1,11 @@ # build unittest 'refcnt/utest/utest.refcnt set(SELF_EXECUTABLE_NAME utest.refcnt) - # These tests can use the Catch2-provided main set(SELF_SOURCE_FILES intrusive_ptr.test.cpp refcnt_utest_main.cpp) + add_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) -xo_include_options(${SELF_EXECUTABLE_NAME}) +xo_include_options2(${SELF_EXECUTABLE_NAME}) add_test(NAME ${SELF_EXECUTABLE_NAME} COMMAND ${SELF_EXECUTABLE_NAME}) target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) @@ -15,17 +15,17 @@ target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) # ---------------------------------------------------------------- # generic project dependency -# PROJECT_SOURCE_DIR: -# so we can for example write -# #include "indentlog/scope.hpp" -# from anywhere in the project -# PROJECT_BINARY_DIR: -# since version file will be in build directory, need that directory -# to also be included in compiler's include path -# -target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC - ${PROJECT_SOURCE_DIR} - ${PROJECT_BINARY_DIR}) +## PROJECT_SOURCE_DIR: +## so we can for example write +## #include "indentlog/scope.hpp" +## from anywhere in the project +## PROJECT_BINARY_DIR: +## since version file will be in build directory, need that directory +## to also be included in compiler's include path +## +#target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC +# ${PROJECT_SOURCE_DIR} +# ${PROJECT_BINARY_DIR}) # ---------------------------------------------------------------- # internal dependencies: refcnt, ... @@ -33,7 +33,7 @@ target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC target_link_libraries(${SELF_EXECUTABLE_NAME} PUBLIC refcnt) # ---------------------------------------------------------------- -# 3rd part dependency: catch2: +# 3rd party dependency: catch2: xo_external_target_dependency(${SELF_EXECUTABLE_NAME} Catch2 Catch2::Catch2) From 415eeaaca1476754e28f538e0a998515342bb587 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 3 Oct 2023 22:18:16 -0400 Subject: [PATCH 0235/2693] build tweaks --- .github/workflows/cmake-single-platform.yml | 3 ++- utest/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index a8ce3a72..77554e15 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -20,7 +20,8 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: checkout source + uses: actions/checkout@v3 - name: Install catch2 # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 7eae2825..990802e3 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -27,7 +27,7 @@ target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC # ---------------------------------------------------------------- # internal dependencies: logutil, ... -target_link_libraries(${SELF_EXECUTABLE_NAME} PUBLIC reflect) +xo_self_dependency(${SELF_EXECUTABLE_NAME} reflect) # ---------------------------------------------------------------- # 3rd part dependency: catch2: From ee436ca9e96ec2c0f96edbb73e79debf11979867 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 3 Oct 2023 22:22:10 -0400 Subject: [PATCH 0236/2693] build: technical fix to catch2 dep --- utest/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 09444df5..291f6d16 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -33,7 +33,7 @@ xo_self_dependency(${SELF_EXECUTABLE_NAME} reflect) # ---------------------------------------------------------------- # 3rd party dependency: catch2: -find_package(Catch2 2 REQUIRED) +xo_external_target_dependency(${SELF_EXECUTABLE_NAME} Catch2 Catch2::Catch2) # need this so that catch2/include appears in compile_commands.json, # on which lsp integration relies. From e27c64ce15b3ea96bec488d035d6207989f98c17 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 00:01:55 -0400 Subject: [PATCH 0237/2693] build: standardizing --- CMakeLists.txt | 22 +- README.md | 3 +- cmake/code-coverage.cmake | 678 -------------------------------------- cmake/nestlog.cmake | 30 -- 4 files changed, 11 insertions(+), 722 deletions(-) delete mode 100644 cmake/code-coverage.cmake delete mode 100644 cmake/nestlog.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index b8553e93..5a780b05 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,5 @@ +# indentlog/CMakeLists.txt + cmake_minimum_required(VERSION 3.10) project(indentlog VERSION 0.1) @@ -7,6 +9,9 @@ enable_language(CXX) include(xo_macros/xo_cxx) include(xo_macros/code-coverage) +# ---------------------------------------------------------------- +# unit test setup + enable_testing() # activate code coverage for all executables + libraries (when -DCODE_COVERAGE=ON) add_code_coverage() @@ -18,6 +23,8 @@ add_code_coverage() # add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) +# ---------------------------------------------------------------- + xo_toplevel_compile_options() add_subdirectory(example) @@ -28,24 +35,13 @@ add_subdirectory(utest) # add_library(indentlog INTERFACE) xo_include_headeronly_options2(indentlog) -#target_include_directories(indentlog INTERFACE -# $ -# $ -# ) - -# ---------------------------------------------------------------- -# provide find_package() support - -xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- +# standard install + provide find_package() support xo_install_library2(${PROJECT_NAME}) - -# ---------------------------------------------------------------- -# install .hpp - xo_install_include_tree() +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- diff --git a/README.md b/README.md index d0202a94..9bc7e7aa 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,8 @@ indentlog $ cd indentlog $ mkdir build $ cd build -$ cmake -DCMAKE_MODULE_PATH=/usr/local/share/cmake -DCMAKE_INSTALL_PREFIX=/usr/local .. +$ PREFIX=/usr/local # for example +$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} .. $ make $ make install ``` diff --git a/cmake/code-coverage.cmake b/cmake/code-coverage.cmake deleted file mode 100644 index b6b36064..00000000 --- a/cmake/code-coverage.cmake +++ /dev/null @@ -1,678 +0,0 @@ -# -# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy of -# the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations under -# the License. - -# USAGE: To enable any code coverage instrumentation/targets, the single CMake -# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or -# on the command line. -# -# From this point, there are two primary methods for adding instrumentation to -# targets: -# -# 1 - A blanket instrumentation by calling `add_code_coverage()`, where -# all targets in that directory and all subdirectories are automatically -# instrumented. -# -# 2 - Per-target instrumentation by calling -# `target_code_coverage()`, where the target is given and thus only -# that target is instrumented. This applies to both libraries and executables. -# -# To add coverage targets, such as calling `make ccov` to generate the actual -# coverage information for perusal or consumption, call -# `target_code_coverage()` on an *executable* target. -# -# Example 1: All targets instrumented -# -# In this case, the coverage information reported will will be that of the -# `theLib` library target and `theExe` executable. -# -# 1a: Via global command -# -# ~~~ -# add_code_coverage() # Adds instrumentation to all targets -# -# add_library(theLib lib.cpp) -# -# add_executable(theExe main.cpp) -# target_link_libraries(theExe PRIVATE theLib) -# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target -# # (instrumentation already added via global anyways) -# # for generating code coverage reports. -# ~~~ -# -# 1b: Via target commands -# -# ~~~ -# add_library(theLib lib.cpp) -# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. -# -# add_executable(theExe main.cpp) -# target_link_libraries(theExe PRIVATE theLib) -# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. -# ~~~ -# -# Example 2: Target instrumented, but with regex pattern of files to be excluded -# from report -# -# ~~~ -# add_executable(theExe main.cpp non_covered.cpp) -# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. -# ~~~ -# -# Example 3: Target added to the 'ccov' and 'ccov-all' targets -# -# ~~~ -# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. -# -# add_executable(theExe main.cpp non_covered.cpp) -# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. -# ~~~ - -# Options -option( - CODE_COVERAGE - "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" - OFF) - -# Programs -find_program(LLVM_COV_PATH llvm-cov) -find_program(LLVM_PROFDATA_PATH llvm-profdata) -find_program(LCOV_PATH lcov) -find_program(GENHTML_PATH genhtml) -# Hide behind the 'advanced' mode flag for GUI/ccmake -mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) - -# Variables -set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) -set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) - -# Common initialization/checks -if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) - set(CODE_COVERAGE_ADDED ON) - - # Common Targets - add_custom_target( - ccov-preprocessing - COMMAND ${CMAKE_COMMAND} -E make_directory - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} - DEPENDS ccov-clean) - - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - # Messages - message(STATUS "Building with llvm Code Coverage Tools") - - if(NOT LLVM_COV_PATH) - message(FATAL_ERROR "llvm-cov not found! Aborting.") - else() - # Version number checking for 'EXCLUDE' compatibility - execute_process(COMMAND ${LLVM_COV_PATH} --version - OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) - string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION - ${LLVM_COV_VERSION_CALL_OUTPUT}) - - if(LLVM_COV_VERSION VERSION_LESS "7.0.0") - message( - WARNING - "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" - ) - endif() - endif() - - # Targets - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-clean - COMMAND ${CMAKE_COMMAND} -E remove -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND ${CMAKE_COMMAND} -E remove -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) - else() - add_custom_target( - ccov-clean - COMMAND ${CMAKE_COMMAND} -E rm -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND ${CMAKE_COMMAND} -E rm -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) - endif() - - # Used to get the shared object file list before doing the main all- - # processing - add_custom_target( - ccov-libs - COMMAND ; - COMMENT "libs ready for coverage report.") - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - # Messages - message(STATUS "Building with lcov Code Coverage Tools") - - if(CMAKE_BUILD_TYPE) - string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) - if(NOT ${upper_build_type} STREQUAL "DEBUG") - message( - WARNING - "Code coverage results with an optimized (non-Debug) build may be misleading" - ) - endif() - else() - message( - WARNING - "Code coverage results with an optimized (non-Debug) build may be misleading" - ) - endif() - if(NOT LCOV_PATH) - message(FATAL_ERROR "lcov not found! Aborting...") - endif() - if(NOT GENHTML_PATH) - message(FATAL_ERROR "genhtml not found! Aborting...") - endif() - - # Targets - add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory - ${CMAKE_BINARY_DIR} --zerocounters) - - else() - message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") - endif() -endif() - -# Adds code coverage instrumentation to a library, or instrumentation/targets -# for an executable target. -# ~~~ -# EXECUTABLE ADDED TARGETS: -# GCOV/LCOV: -# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. -# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. -# -# LLVM-COV: -# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-${TARGET_NAME} : Generates HTML code coverage report. -# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. -# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. -# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. -# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. -# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. -# ccov-all-export : Exports the coverage report to a JSON file. -# -# Required: -# TARGET_NAME - Name of the target to generate code coverage for. -# Optional: -# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. -# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. -# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) -# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. -# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. -# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory -# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. -# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** -# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output -# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call -# ~~~ -function(target_code_coverage TARGET_NAME) - # Argument parsing - set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) - set(single_value_keywords COVERAGE_TARGET_NAME) - set(multi_value_keywords EXCLUDE OBJECTS ARGS) - cmake_parse_arguments( - target_code_coverage "${options}" "${single_value_keywords}" - "${multi_value_keywords}" ${ARGN}) - - # Set the visibility of target functions to PUBLIC, INTERFACE or default to - # PRIVATE. - if(target_code_coverage_PUBLIC) - set(TARGET_VISIBILITY PUBLIC) - set(TARGET_LINK_VISIBILITY PUBLIC) - elseif(target_code_coverage_INTERFACE) - set(TARGET_VISIBILITY INTERFACE) - set(TARGET_LINK_VISIBILITY INTERFACE) - elseif(target_code_coverage_PLAIN) - set(TARGET_VISIBILITY PUBLIC) - set(TARGET_LINK_VISIBILITY) - else() - set(TARGET_VISIBILITY PRIVATE) - set(TARGET_LINK_VISIBILITY PRIVATE) - endif() - - if(NOT target_code_coverage_COVERAGE_TARGET_NAME) - # If a specific name was given, use that instead. - set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) - endif() - - if(CODE_COVERAGE) - - # Add code coverage instrumentation to the target's linker command - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} - -fprofile-instr-generate -fcoverage-mapping) - target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} - -fprofile-instr-generate -fcoverage-mapping) - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs - -ftest-coverage) - target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) - endif() - - # Targets - get_target_property(target_type ${TARGET_NAME} TYPE) - - # Add shared library to processing for 'all' targets - if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${CMAKE_COMMAND} -E echo "-object=$" >> - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - DEPENDS ccov-preprocessing ${TARGET_NAME}) - - if(NOT TARGET ccov-libs) - message( - FATAL_ERROR - "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." - ) - endif() - - add_dependencies(ccov-libs - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - - # For executables add targets to run and produce output - if(target_type STREQUAL "EXECUTABLE") - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - - # If there are shared objects to also work with, generate the string to - # add them here - foreach(SO_TARGET ${target_code_coverage_OBJECTS}) - # Check to see if the target is a shared object - if(TARGET ${SO_TARGET}) - get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) - if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") - set(SO_OBJECTS ${SO_OBJECTS} -object=$) - endif() - endif() - endforeach() - - # Run the executable, generating raw profile data Make the run data - # available for further processing. Separated to allow Windows to run - # this target serially. - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${CMAKE_COMMAND} -E env - LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw - $ ${target_code_coverage_ARGS} - COMMAND - ${CMAKE_COMMAND} -E echo "-object=$" - ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND - ${CMAKE_COMMAND} -E echo - "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" - >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list - JOB_POOL ccov_serial_pool - DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) - - # Merge the generated profile data so llvm-cov can process it - add_custom_target( - ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_PROFDATA_PATH} merge -sparse - ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o - ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Ignore regex only works on LLVM >= 7 - if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") - foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} - -ignore-filename-regex='${EXCLUDE_ITEM}') - endforeach() - endif() - - # Print out details of the coverage information to the command line - add_custom_target( - ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} show $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -show-line-counts-or-regions ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Print out a summary of the coverage information to the command line - add_custom_target( - ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} report $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Export coverage information so continuous integration tools (e.g. - # Jenkins) can consume it - add_custom_target( - ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} export $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -format="text" ${EXCLUDE_REGEX} > - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Generates HTML output of the coverage information for perusal - add_custom_target( - ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} show $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - set(COVERAGE_INFO - "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" - ) - - # Run the executable, generating coverage information - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND $ ${target_code_coverage_ARGS} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - - # Generate exclusion string for use - foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} - '${EXCLUDE_ITEM}') - endforeach() - - if(EXCLUDE_REGEX) - set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file - ${COVERAGE_INFO}) - else() - set(EXCLUDE_COMMAND ;) - endif() - - if(NOT ${target_code_coverage_EXTERNAL}) - set(EXTERNAL_OPTION --no-external) - endif() - - # Capture coverage data - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters - COMMAND $ ${target_code_coverage_ARGS} - COMMAND - ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory - ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file - ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - else() - add_custom_target( - ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters - COMMAND $ ${target_code_coverage_ARGS} - COMMAND - ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory - ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file - ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - endif() - - # Generates HTML output of the coverage information for perusal - add_custom_target( - ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${GENHTML_PATH} -o - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} - ${COVERAGE_INFO} - DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - - add_custom_command( - TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - POST_BUILD - COMMAND ; - COMMENT - "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." - ) - - # AUTO - if(target_code_coverage_AUTO) - if(NOT TARGET ccov) - add_custom_target(ccov) - endif() - add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) - - if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID - MATCHES "GNU") - if(NOT TARGET ccov-report) - add_custom_target(ccov-report) - endif() - add_dependencies( - ccov-report - ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - - # ALL - if(target_code_coverage_ALL) - if(NOT TARGET ccov-all-processing) - message( - FATAL_ERROR - "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." - ) - endif() - - add_dependencies(ccov-all-processing - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - endif() -endfunction() - -# Adds code coverage instrumentation to all targets in the current directory and -# any subdirectories. To add coverage instrumentation to only specific targets, -# use `target_code_coverage`. -function(add_code_coverage) - if(CODE_COVERAGE) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - add_compile_options(-fprofile-instr-generate -fcoverage-mapping) - add_link_options(-fprofile-instr-generate -fcoverage-mapping) - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - add_compile_options(-fprofile-arcs -ftest-coverage) - link_libraries(gcov) - endif() - endif() -endfunction() - -# Adds the 'ccov-all' type targets that calls all targets added via -# `target_code_coverage` with the `ALL` parameter, but merges all the coverage -# data from them into a single large report instead of the numerous smaller -# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for -# use with coverage dashboards (e.g. codecov.io, coveralls). -# ~~~ -# Optional: -# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! -# ~~~ -function(add_code_coverage_all_targets) - # Argument parsing - set(multi_value_keywords EXCLUDE) - cmake_parse_arguments(add_code_coverage_all_targets "" "" - "${multi_value_keywords}" ${ARGN}) - - if(CODE_COVERAGE) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - - # Merge the profile data for all of the run executables - if(WIN32) - add_custom_target( - ccov-all-processing - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe - merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -sparse $$FILELIST) - else() - add_custom_target( - ccov-all-processing - COMMAND - ${LLVM_PROFDATA_PATH} merge -o - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) - endif() - - # Regex exclude only available for LLVM >= 7 - if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") - foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} - -ignore-filename-regex='${EXCLUDE_ITEM}') - endforeach() - endif() - - # Print summary of the code coverage information to the command line - if(WIN32) - add_custom_target( - ccov-all-report - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe - report $$FILELIST - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - else() - add_custom_target( - ccov-all-report - COMMAND - ${LLVM_COV_PATH} report `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - endif() - - # Export coverage information so continuous integration tools (e.g. - # Jenkins) can consume it - add_custom_target( - ccov-all-export - COMMAND - ${LLVM_COV_PATH} export `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -format="text" ${EXCLUDE_REGEX} > - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json - DEPENDS ccov-all-processing) - - # Generate HTML output of all added targets for perusal - if(WIN32) - add_custom_target( - ccov-all - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show - $$FILELIST - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - else() - add_custom_target( - ccov-all - COMMAND - ${LLVM_COV_PATH} show `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - endif() - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") - - # Nothing required for gcov - add_custom_target(ccov-all-processing COMMAND ;) - - # Exclusion regex string creation - set(EXCLUDE_REGEX) - foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} - '${EXCLUDE_ITEM}') - endforeach() - - if(EXCLUDE_REGEX) - set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file - ${COVERAGE_INFO}) - else() - set(EXCLUDE_COMMAND ;) - endif() - - # Capture coverage data - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-all-capture - COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture - --output-file ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ccov-all-processing) - else() - add_custom_target( - ccov-all-capture - COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture - --output-file ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ccov-all-processing) - endif() - - # Generates HTML output of all targets for perusal - add_custom_target( - ccov-all - COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} - DEPENDS ccov-all-capture) - - endif() - - add_custom_command( - TARGET ccov-all - POST_BUILD - COMMAND ; - COMMENT - "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." - ) - endif() -endfunction() diff --git a/cmake/nestlog.cmake b/cmake/nestlog.cmake deleted file mode 100644 index 4a1a26ae..00000000 --- a/cmake/nestlog.cmake +++ /dev/null @@ -1,30 +0,0 @@ -# ---------------------------------------------------------------- -# use this in subdirs that compile c++ code -# -macro(xo_include_options target) - # ---------------------------------------------------------------- - # PROJECT_SOURCE_DIR: - # so we can for example write - # #include "nestlog/scope.hpp" - # from anywhere in the project - # PROJECT_BINARY_DIR: - # since generated version file will be in build directory, - # need that build directory to also appear in - # compiler's include path - # - target_include_directories( - ${target} PUBLIC - ${PROJECT_SOURCE_DIR}/include - ${PROJECT_BINARY_DIR} - ) - - # ---------------------------------------------------------------- - # make standard directories for std:: includes explicit - # so that - # (1) they appear in compile_commands.json. - # (2) clangd (run from emacs lsp-mode) can find them - # - if(CMAKE_EXPORT_COMPILE_COMMANDS) - set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) - endif() -endmacro() From 90dcd600c07068fc4162e1068941fae983f38d87 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 00:02:38 -0400 Subject: [PATCH 0238/2693] build: + cmake find_package() support --- CMakeLists.txt | 25 +++++++++++++++++++------ README.md | 3 ++- cmake/randomgenConfig.cmake.in | 4 ++++ 3 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 cmake/randomgenConfig.cmake.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 5653ec33..87383904 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -# using indentlog/CMakeLists.txt as model +# randomgen/CMakeLists.txt cmake_minimum_required(VERSION 3.10) @@ -23,14 +23,14 @@ add_code_coverage() add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) # ---------------------------------------------------------------- -# c++ settings - -set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +# bespoke (usually temporary) c++ settings +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") add_definitions(${PROJECT_CXX_FLAGS}) # ---------------------------------------------------------------- -# default install +# common include paths etc. xo_toplevel_compile_options() @@ -40,7 +40,7 @@ xo_toplevel_compile_options() # set CMAKE_INSTALL_PREFIX to analog of /usr # to use .cmake assistants from /usr/lib/cmake/indentlog # -#find_package(indentlog REQUIRED) +# xo_internal_dependency(..) # ---------------------------------------------------------------- @@ -48,7 +48,20 @@ add_subdirectory(example) #add_subdirectory(utest) # ---------------------------------------------------------------- +# output targets +set(SELF_LIB randomgen) +add_library(${SELF_LIB} INTERFACE) +xo_include_headeronly_options2(${SELF_LIB}) + +# ---------------------------------------------------------------- +# standard install + provide find_package() support + +xo_install_library2(${SELF_LIB}) xo_install_include_tree() +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# install additional components install(TARGETS ex1 DESTINATION bin/randomgen/example) diff --git a/README.md b/README.md index 905f267e..73f13f1b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ $ cd randomgen $ mkdir build $ cd build -$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=$(INSTALL_PREFIX) .. +$ PREFIX=/usr/local # for example +$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=$(PREFIX) -DCMAKE_INSTALL_PREFIX=${PREFIX} .. $ make $ make install ``` diff --git a/cmake/randomgenConfig.cmake.in b/cmake/randomgenConfig.cmake.in new file mode 100644 index 00000000..e66430b0 --- /dev/null +++ b/cmake/randomgenConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/randomgenTargets.cmake") +check_required_components("@PROJECT_NAME@") From f6e0dc2182080f5a528127490105596357711d41 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 13:23:45 -0400 Subject: [PATCH 0239/2693] initial impl --- CMakeLists.txt | 52 + cmake/xo_treeConfig.cmake.in | 5 + include/xo/tree/BplusTree.hpp | 1799 ++++++++++ include/xo/tree/RedBlackTree.hpp | 3158 ++++++++++++++++++ include/xo/tree/bplustree/BplusTreeUtil.hpp | 280 ++ include/xo/tree/bplustree/GenericNode.hpp | 123 + include/xo/tree/bplustree/InternalNode.hpp | 768 +++++ include/xo/tree/bplustree/Iterator.hpp | 355 ++ include/xo/tree/bplustree/IteratorUtil.hpp | 56 + include/xo/tree/bplustree/LeafNode.hpp | 684 ++++ include/xo/tree/bplustree/Lhs.hpp | 68 + include/xo/tree/bplustree/bplustree_tags.hpp | 16 + utest/CMakeLists.txt | 47 + utest/bplustree.cpp | 813 +++++ utest/random_tree_ops.hpp | 450 +++ utest/redblacktree.cpp | 248 ++ utest/tree_utest_main.cpp | 7 + 17 files changed, 8929 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/xo_treeConfig.cmake.in create mode 100644 include/xo/tree/BplusTree.hpp create mode 100644 include/xo/tree/RedBlackTree.hpp create mode 100644 include/xo/tree/bplustree/BplusTreeUtil.hpp create mode 100644 include/xo/tree/bplustree/GenericNode.hpp create mode 100644 include/xo/tree/bplustree/InternalNode.hpp create mode 100644 include/xo/tree/bplustree/Iterator.hpp create mode 100644 include/xo/tree/bplustree/IteratorUtil.hpp create mode 100644 include/xo/tree/bplustree/LeafNode.hpp create mode 100644 include/xo/tree/bplustree/Lhs.hpp create mode 100644 include/xo/tree/bplustree/bplustree_tags.hpp create mode 100644 utest/CMakeLists.txt create mode 100644 utest/bplustree.cpp create mode 100644 utest/random_tree_ops.hpp create mode 100644 utest/redblacktree.cpp create mode 100644 utest/tree_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..2fc26356 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,52 @@ +# xo-tree/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_tree VERSION 0.1) +enable_language(CXX) + +# common XO macros (see github:Rconybea/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() +# enable code coverage for all executables+libraries +# (when configured with -DCODE_COVERAGE=ON) +# +add_code_coverage() +add_code_coverage_all_targets( + EXCLUDE + /nix/store/* + ${PROJECT_SOURCE_DIR}/utest/*) + +# ---------------------------------------------------------------- +# c++ settings + +# sets XO_COMPILE_OPTIONS +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- +# output targets + +add_subdirectory(utest) + +set(SELF_LIB xo_tree) + +add_library(${SELF_LIB} INTERFACE) +xo_include_headeronly_options2(${SELF_LIB}) + +# ---------------------------------------------------------------- +# +xo_install_library2(${PROJECT_NAME}) +xo_install_include_tree() +# (note: ..Targets from xo_install_library2()) +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# input dependencies + +xo_dependency_headeronly(${SELF_LIB} randomgen) + diff --git a/cmake/xo_treeConfig.cmake.in b/cmake/xo_treeConfig.cmake.in new file mode 100644 index 00000000..8fbf8cb5 --- /dev/null +++ b/cmake/xo_treeConfig.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") + diff --git a/include/xo/tree/BplusTree.hpp b/include/xo/tree/BplusTree.hpp new file mode 100644 index 00000000..f58b4317 --- /dev/null +++ b/include/xo/tree/BplusTree.hpp @@ -0,0 +1,1799 @@ +/* @file BplusTree.hpp */ + +/* provides B+ tree with order statistics */ + +/* NOTES: + * - expect optimimum node size to be OS page size. + * + */ + +#pragma once + +//#include "bplustree/BplusTreeNode.hpp" +#include "bplustree/LeafNode.hpp" +#include "bplustree/InternalNode.hpp" +#include "bplustree/Iterator.hpp" +#include "bplustree/Lhs.hpp" +#include "bplustree/bplustree_tags.hpp" +#include "indentlog/scope.hpp" +#include "indentlog/print/tag.hpp" +#include "indentlog/print/pad.hpp" +#include /* for std::unqiue_ptr */ +#include /* for std::max */ +#include /* for std::numeric_limits */ +#include +#include +#include +#if __APPLE__ && __MACH__ +# include +#endif + +namespace xo { + namespace tree { + /* + * +-------------+ + * | BplusTree | +--------------------+ + * | .properties +-----| BplusStdProperties | + * | .n_element | | .branching_factor | + * | .root | | .debug_flag | + * +------+------+ +--------------------+ + * | + * | .root + * | + * +-------------------+ +--------------+ +--------------+ + * | GenericNode | isa | LeafNode | .elt_v[i] | LeafNodeItem | + * | .node_type |<---+----| .elt_v[] +-------------| .kv_pair | + * | .parent | | | | | | + * | .n_elt | | +--------------+ +--------------+ + * | .branching_factor | | + * +-------------------+ | + * | + * | +--------------+ +------------------+ + * | | InternalNode | .elt_v[i] | InternalNodeItem | + * \----| .elt_v[] +-------------| .key | + * | | | .child | + * +--------------+ +------------------+ + * + * Invariants: + * - tree is always balanced -- every path from root to a LeafNode, visits the same number of InternalNodes. + * - all Nodes (both LeafNodes and InternalNodes) satisfy bf/2 <= .n_elt <= bf (where bf = BplusTree.properties.branching_factor) + * Details + * - if InternalNode p has p.elt_v[i].child = q, then q.parent = p + * - GenericNode.branching_factor = BplusTree.properties.branching_factor for all nodes in the same BplusTree + * + * Tree with 0 key/value pairs + * + * +--------------+ + * | BplusTree | + * | .root = null | + * +--------------+ + * + * Tree with [1 .. b] key/value pairs (with b = BplusTree.properties.branching_factor) + * + * +---------------+ + * | BplusTree | + * | .root = node1 | + * +--------+------+ + * | + * node1 | + * +----------------------------------------------+ + * | LeafNode | + * | .parent = null | + * | | + * | .elt_v[0] .elt_v[b-1] | b = BplusTree.properties.branching_factor - 1 + * | +----------------+- ... -+-----------------+ | .elt_v[i].kv_pair.first = i'th key + * | | k0 | v0 | | k(b-1) | v(b-1) | | .elt_v[i].kv_pair.second = i'th value + * | +----------------+- ... -+-----------------+ | + * +----------------------------------------------+ + * + * Tree with [b+1 ..] key/value pairs + * + * +---------------+ + * | BplusTree | + * | .root = node1 | + * +--------+------+ + * | + * node1 | + * +----------------------------------------------+ + * | InternalNode | + * | .parent = null | + * | | + * | .elt_v[0] .elt_v[b-1] | + * | +----------------+- ... -+-----------------+ | .elt_v[i].key = minimum key in subtree .elt_v[i].child + * | | k0 | node2 | | k(b-1) | node(b)| | + * | +----------------+- ... -+-----------------+ | + * | .key .child .key .child | + * +-------------------+-----+--------------------+ + * | | + * | | ... + * | | + * .elt_v[0].child | | .elt_v[1].child + * /--------------------/ \----------------------------\ + * | | + * node2 | node3 | + * +----------------------------------------------+ +-------------------------------------------------+ + * | LeafNode | | LeafNode | + * | .parent = node1 | | .parent = node1 | + * | | | | + * | .elt_v[0] .elt_v[b-1] | | .elt_v[0] .elt_v[b-1] | ..... + * | +----------------+- ... -+-----------------+ | | +--------+--------+- ... -+---------+---------+ | + * | | k0 | v0 | | k(b-1) | v(b-1) | | | | kb | vb | | k(2b-1) | v(2b-1) | | + * | +----------------+- ... -+-----------------+ | | +-----------------+- ... -+---------+---------+ | + * +----------------------------------------------+ +-------------------------------------------------+ + * + * + * Larger trees havedadditional levels comprising InternalNodes. + * + */ + + /* NullReduce: 0-size reduce function (disappears at compile time) */ + template + struct NullReduce; + + struct Machdep { + /* current page size (on a linux system). Probably 4K + * + * (a) need this to be at least large enough to + * hold 3 keys + * (b) note linux page size isn't fixed at compile time + */ + static inline std::size_t get_page_size() { + return ::sysconf(_SC_PAGESIZE); + } + + /* L1 cache line size (on a linux system). Probably 64 bytes */ + static inline std::size_t get_cache_line_size() { + // https://sourceforge.net/p/predef/wiki/OperatingSystems/ +# if __APPLE__ && __MACH__ + std::size_t line_size = 0; + std::size_t sizeof_line_size = sizeof(line_size); + ::sysctlbyname("hw.cachelinesize", + &line_size, &sizeof_line_size, 0, 0); + return line_size; +# else + return ::sysconf(_SC_LEVEL1_DCACHE_LINESIZE); +# endif + } + }; /*Machdep*/ + + /* B+ tree nodes come in several flavors: {root | internal | leaf}: + * + * - root. each B+ tree has exactly one node of this type, + * representing the root of the B+ tree. + * The root node is subject to fewer restrictions than other + * nodes in a B+ tree: + * - can have 2..b elements (where b is branching factor for this B+ tree). + * - can function as tree's one and only leaf node (if tree has <= b items). + * - can function as an internal node (if tree has > b items) + * + * - internal. an internal node has: + * - n child node pointers, subject to ceil(b/2) <= n <= b, + * where b is tree's branching factor + * - n keys. key[j] is the smallest key value in subtree j. + * see InternalNode + * + * - leaf. a leaf node has: + * - n keys, subject to ceil(b/2) <= n <= b, + * where b is tree's branching factor + * - n values. values are stored as pointers. + * - pointer to next leaf node, to streamline inorder traversal + */ + template + struct BplusStdProperties { + public: + using KeyType = Key; + using ValueType = Value; + + public: + BplusStdProperties() = default; + explicit BplusStdProperties(std::size_t bf, bool debug_flag) + : branching_factor_{bf}, debug_flag_{debug_flag} {} + + static constexpr tags::ordinal_tag ordinal_tag_value() { return OrdinalTag; } + static constexpr bool ordinal_enabled() { return OrdinalTag == tags::ordinal_enabled; } + + static constexpr std::size_t c_min_branching_factor = 3; + + /* compute branching factor for given (leaf) node size */ + static constexpr std::size_t branching_factor_for_size(std::size_t z) { + return std::max(c_min_branching_factor, + (z - sizeof(LeafNode)) + / (sizeof(LeafNodeItemPlaceholder))); + } /*branching_factor_for_size*/ + + /* default branching factor. + * attempt to optimize for cache efficiency of 'internal' nodes + * + * minimum branching factor always 3 + */ + static constexpr std::size_t default_branching_factor() { + return branching_factor_for_size(Machdep::get_page_size()); + } + + /* expect this will be min branching factor + * (i.e. smallest allowed LeafNode size likely won't fit in cache line): + * + * - cache line size = 64 bytes + * - leaf node overhead = 56 bytes + * - leaf node item size = 16 bytes + */ + static constexpr std::size_t default_cacheline_branching_factor() { + return branching_factor_for_size(Machdep::get_cache_line_size()); + } + + std::size_t branching_factor() const { return branching_factor_; } + bool debug_flag() const { return debug_flag_; } + + void set_debug_flag(bool x) { debug_flag_ = x; } + + private: + /* branching factor to use for both leaf an inteernal B+ tree nodes */ + std::size_t branching_factor_ = default_branching_factor(); + /* if true enable verbose logging during B+ tree operations */ + bool debug_flag_ = false; + }; /*BplusStdProperties*/ + + template + inline std::ostream & + operator<<(std::ostream & os, + BplusStdProperties const & p) + { + using xo::xtag; + + os << ""; + + return os; + } /*operator<<*/ + + /* B+ tree with order statistics + * + * require: + * - Key is equality comparable, and imposes total ordering on keys. + * - Key, Value, Reduce, Properties are copyable and null-constructible + * - Reduce.value_type = Accumulator + * - Reduce.operator() :: (Accumulator x Key) -> Accumulator + */ + template , + typename Properties = BplusStdProperties> + class BplusTree { + public: + using GenericNodeType = GenericNode; + using InternalNodeType = InternalNode; + using LeafNodeType = LeafNode; + using InternalNodeItemType = InternalNodeItem; + using LeafNodeItemType = LeafNodeItem; + using BpTreeConstLhs = detail::BplusTreeConstLhs>; + using BpTreeUtil = BplusTreeUtil; + + using key_type = Key; + using mapped_type = Value; + using value_type = std::pair; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + // key_compare + // allocator_type + using reference = value_type &; + using const_reference = value_type const &; + // pointer = std::allocator_traits::pointer; + // const_pointer = std::allocator_traits::const_pointer; + using const_iterator = detail::ConstIterator; + // reverse_iterator + // const_reverse_iterator + // value_compare (compares value_type objects by comparing their first elements) + + public: + BplusTree() = default; + explicit BplusTree(Properties const & properties) : properties_{properties} {} + + bool empty() const { return this->n_element_ == 0; } + size_type size() const { return this->n_element_; } + size_type max_size() const { return std::numeric_limits::max(); } + std::size_t branching_factor() const { return this->properties_.branching_factor(); } + + bool debug_flag() const { return this->properties_.debug_flag(); } + void set_debug_flag(bool x) { this->properties_.set_debug_flag(x); } + + /* verify b+ tree invariants. + * if invariants satisfied, return true. + * if not satisfied, + * - throw_flag=true -> throw execption + * - throw_flag=false -> return false; + */ + bool verify_ok(bool throw_flag = true) const { + using xo::scope; + using xo::xtag; + + //scope x("verify_ok"); + //x.log(xtag("n_element", this->n_element_)); + + std::size_t z = 0; + + try { + z = this->verify_helper(throw_flag); + } catch (...) { + if (throw_flag) + throw; + + return false; + } + + //x.log(xtag("z", z)); + + if (z != this->n_element_) { + if (throw_flag) { + std::string err = tostr("BplusTree::verify_ok" + ": bad key count", + xtag("expected", this->n_element_), + xtag("counted", z)); + + throw std::runtime_error(err); + } + + return false; + } + + return true; + } /*verify_ok*/ + + /* cxxx: const iterator + * rxxx: reverse iterator + * crxxx: const reverse iterator + */ + + const_iterator cprebegin() const { return const_iterator::prebegin_aux(this->leafnode_begin_); } + const_iterator cbegin() const { return const_iterator::begin_aux(this->leafnode_begin_); } + const_iterator cend() const { return const_iterator::end_aux(this->leafnode_end_); } + + const_iterator begin() const { return this->cbegin(); } + const_iterator end() const { return this->cend(); } + + const_iterator crprebegin() const { return const_iterator::rprebegin_aux(this->leafnode_end_); } + const_iterator crbegin() const { return const_iterator::rbegin_aux(this->leafnode_end_); } + const_iterator crend() const { return const_iterator::rend_aux(this->leafnode_begin_); } + + const_iterator rbegin() const { return this->crbegin(); } + const_iterator rend() const { return this->crend(); } + + /* find item with key equal to x in this tree. + * success -> return iterator ix with ix->first = x + * failure -> return iterator this->cend() + */ + const_iterator find(Key const & x) const { + FindNodeResult leaffindresult = this->find_leaf_node(x); + LeafNodeType const * leaf = leaffindresult.node(); + + std::pair lub_ix_recd = leaf->find_lub_ix(x); + + if (lub_ix_recd.first) { + return const_iterator(detail::ID_Forward /*dirn*/, + detail::IL_Regular /*loc*/, + leaf, + lub_ix_recd.second - 1); + } else { + return this->cend(); + } + } /*find*/ + + /* find i'th key/value pair (in key order) in this tree. + * + * Require: + * - 0 <= i < .size + */ + const_iterator find_ith(std::size_t i_tree) const { + using xo::tostr; + using xo::xtag; + + if (i_tree >= this->size()) { + throw std::runtime_error(tostr("BplusTree::find_ith: expected index i in range [0..n)", + xtag("i", i_tree), + xtag("n", this->size()))); + } + + GenericNodeType * generic_node = this->root_.get(); + + return BplusTreeUtil::find_ith(generic_node, i_tree, this->cend()); + } /*find_ith*/ + + BpTreeConstLhs at(Key const & k) const { + const_iterator ix = this->find(k); + + if (ix == this->cend()) { + throw std::out_of_range(tostr("BplusTree::at: expected key argument to appear in tree", + xtag("key", k))); + } + + return BpTreeConstLhs(this, ix.item_addr()); + } /*at*/ + + /* e.g. + * BplusTree bptree = ...; + * Key key = ...; + * BplusTree::value_type x = bptree[key]; + */ + BpTreeConstLhs operator[](Key const & k) const { + const_iterator ix = this->find(k); + + return BpTreeConstLhs(this, ix.item_addr()); + } /*operator[]*/ + + void clear() { + this->n_element_ = 0; + this->leafnode_begin_ = nullptr; + this->leafnode_end_ = nullptr; + this->root_.reset(nullptr); + } /*clear*/ + + /* TODO: + * std::pair insert(value_type const & kv_pair); + * + * template + * std::pair insert(P && value) + * + * std::pair insert(value_type && kv_pair); + * + * iterator insert(iterator pos, value_type const & kv_pair); + * iterator insert(const_iterator pos, value_type const & kv_pair); + * + * template + * iterator insert(const_iterator pos, P && value); + * + * iterator insert(const_iterator pos, value_type && value); + * + * template + * void insert(InputIterator lo, InputIterator hi); + * + * void insert(std::initializer_list initlist); + */ + + /* return: true key already existed (tree size increases by 1) + * false if existing key (tree size unchanged) + */ + std::pair insert(std::pair const & kv_pair) { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(this->debug_flag()), + xtag("key", kv_pair.first), + xtag("value", kv_pair.second), + xtag("root", this->root_.get()) + //xtag("nesting", x.nesting_level()) + ); + + log && log(xtag("bptree[before-insert]", (char const *)"...")); + if (log) this->print(std::clog, log.nesting_level()+2); + + std::pair retval; + + if (this->root_) { + NodeType root_type = this->root_->node_type(); + + log && log(xtag("root_type", root_type)); + + switch (root_type) { + case NodeType::leaf: + retval = this->leaf_insert_aux(kv_pair); + break; + case NodeType::internal: + retval = this->internal_insert_aux(kv_pair); + break; + } /*switch*/ + } else { + retval = this->create_root_aux(kv_pair); + } + + log && log(xtag("bptree[after-insert]", (char const *)"...")); + if (log) this->print(std::clog, log.nesting_level() + 2); + + log.end_scope(); + + return retval; + } /*insert*/ + + /* e.g: + * std::map m = ...; + * BplusTree bptree; + * + * bptree.insert(m.begin(), m.end()); + */ + template + void insert(InputIterator lo, InputIterator hi) { + for (InputIterator ix = lo; ix != hi; ++ix) + this->insert(*ix); + } /*insert*/ + + /* return: true if key existed (tree size decreased by 1) + * false if key not found + */ + bool erase(Key const & key) { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(this->debug_flag()), + xtag("key", key), + xtag("root", this->root_.get())); + + log && log(xtag("bptree[before-erase]", (char const *)"...")); + if (log) this->print(std::clog, log.nesting_level()+2); + + bool retval = false; + + if (this->root_) { + NodeType root_type = this->root_->node_type(); + + log && log(xtag("root_type", root_type)); + + switch (root_type) { + case NodeType::leaf: + retval = leaf_erase_aux(key); + break; + case NodeType::internal: + retval = internal_erase_aux(key); + break; + } /*switch*/ + } else { + /* tree empty, certainly doesn't contain key */ + } + + log && log(xtag("bptree[after-erase]", (char const *)"...")); + if (log) this->print(std::clog, log.nesting_level()+2); + + log.end_scope(); + + return retval; + } /*erase*/ + + void print(std::ostream & os, std::int32_t indent = 0) const { + using xo::xtag; + using xo::pad; + + os << pad(indent) << "print_aux(os, this->root_.get(), indent+2); + + os << ">"; + os << std::endl; + } /*print*/ + + private: + /* find leaf node associated with given key, within given subtree + * + * .first: index position of leaf in immediate parent of leaf node. 0 when leaf is also root node. + * .seecond: leaf node + */ + static FindNodeResult find_leaf_node_aux(Key const & key, InternalNodeType * subtree_arg) { + FindNodeResult> findresult(0, subtree_arg); + + while (findresult.node() && (findresult.node()->node_type() == NodeType::internal)) { + findresult = (reinterpret_cast(findresult.node()))->find_child(key); + } + + /* findresult.node().node_type() == NodeType::leaf (if non-null) */ + + if (!findresult.node()) { + assert(false); + return FindNodeResult(); + } + + assert(findresult.node()->node_type() == NodeType::leaf); + + /* subtree.canonical_node_type = leaf */ + return FindNodeResult(findresult.ix(), + reinterpret_cast(findresult.node())); + } /*find_leaf_node_aux*/ + + /* count #of keys present in this b+ tree, by visiting every node; + * but short-circuit if internal inconsistency detected + */ + std::size_t verify_helper(bool throw_flag) const { + using xo::scope; + using xo::xtag; + + //scope x("BplusTree.verify_helper"); + + if (Properties::ordinal_tag_value() == tags::ordinal_enabled) { + /* verify tree size (maintained in each node) matches toplevel tree size) */ + if (this->root_ != nullptr) { + if (this->size() != BplusTreeUtil::get_node_size(this->root_.get())) { + if (throw_flag) { + throw std::runtime_error(tostr("BplusTree::verify_helper" + ": mismatched tree size computation", + xtag("root", this->root_.get()), + xtag("bptree.n_element", this->size()), + xtag("bptree.root.size", logutil::nodesize(this->root_.get())))); + } else { + return -1; + } + } + } + } else { + /* subtree size not maintained; skip test */ + } + + /* verify leafnode iterator endpoints */ + + if (this->root_ == nullptr) { + if (this->leafnode_begin_ != nullptr || this->leafnode_end_ != nullptr) { + if (throw_flag) { + throw std::runtime_error(tostr("BplusTree::verify_helper" + ": expected null .leafnode_begin / .leafnode_end pointers" + " with empty tree", + xtag("root", this->root_.get()), + xtag("leafnode_begin", this->leafnode_begin_), + xtag("leafnode_end", this->leafnode_end_))); + } else { + return -1; + } + } + + return 0; + } else { + auto leftmost_fr = this->root_->find_min_leaf_node(); + auto rightmost_fr = this->root_->find_max_leaf_node(); + + if ((leftmost_fr.node() != this->leafnode_begin_) + || (rightmost_fr.node() != this->leafnode_end_)) + { + if (throw_flag) { + throw std::runtime_error(tostr("BplusTree::verify_helper" + ": expected .leafnode_begin / .leafnode_end pointers" + " to match computed first/last leaf nodes", + xtag("root", this->root_.get()), + xtag("leafnode_begin[stored]", this->leafnode_begin_), + xtag("leafnode_begin[computed]", leftmost_fr.node()), + xtag("leafnode_end[stored]", this->leafnode_end_), + xtag("leafnode_end[computed]", rightmost_fr.node()))); + } else { + return -1; + } + } + } + + return this->root_->verify_helper(nullptr /*parent*/, + false /*!with_lub_flag*/, + Key() /*lub_key*/, + nullptr /*lh_leaf*/, + nullptr /*rh_leaf*/); + } /*verify_helper*/ + + /* find leaf node associated with given key; + * this is the node that would contain target key, if it is present. + */ + FindNodeResult find_leaf_node(Key const & key) { + if (!root_.get()) + return FindNodeResult(); + + switch (root_->node_type()) { + case NodeType::leaf: + return FindNodeResult(0, reinterpret_cast(root_.get())); + case NodeType::internal: + return find_leaf_node_aux(key, reinterpret_cast(root_.get())); + } + + assert(false); + return FindNodeResult(); + } /*find_leaf_node*/ + + FindNodeResult find_leaf_node(Key const & key) const { + FindNodeResult findresult = const_cast(this)->find_leaf_node(key); + + return FindNodeResult(findresult.ix(), findresult.node()); + } /*find_leaf_node*/ + + /* insert helper. + * + * require: + * - root node is NodeType::leaf + * - returns true if new key; false if replace value associated with existing key + */ + std::pair + leaf_insert_aux(std::pair const & kv_pair) { + using xo::scope; + using xo::xtag; + + /* will add/replace key,value pair in existing root (which is a leaf) node */ + + scope log(XO_DEBUG(this->debug_flag()), + xtag("key", kv_pair.first), + xtag("value", kv_pair.second)); + + /* root node is a leaf node: + * - tree has between 1 and b elements (where b = branching factor) + */ + LeafNodeType * leaf = reinterpret_cast(this->root_.get()); + + log && log(xtag("leaf", leaf), + xtag("leaf.n_elt", leaf->n_elt()), + xtag("leaf.bf", leaf->branching_factor())); + + /* .elt_v[] + * + * 0 k n-1 with: n <= b = branching factor + * +---+---+- ... -+---+- ... -+---+---+ k = lub(key) in {e1..en} + * | e1| e2| | ek| | | en| + * +---+---+- ... -+---+- ... -+---+---+ + * + * lub_ix_recd.first: true if key already present in tree. implies lub_ix_recd.second >= 1 + * lub_ix_recd.second: upper bound (strict) index position in .elt_v[] of key + */ + std::pair lub_ix_recd = leaf->find_lub_ix(kv_pair.first); + + log && log(xtag("lub_ix_recd.first", lub_ix_recd.first), + xtag("lub_ix_recd.second", lub_ix_recd.second)); + + if (lub_ix_recd.first) { + leaf->assign_leaf_value(lub_ix_recd.second - 1, kv_pair.second); + + return (std::pair + (const_iterator(detail::ID_Forward /*dirn*/, + detail::IL_Regular /*loc*/, + leaf, + lub_ix_recd.second - 1), + false)); + } + + /* key not present in tree, so will be incrementing tree size */ + + if (leaf->n_elt() == leaf->branching_factor()) { + log && log("split root (leaf) node"); + + /* root node is full: + * 1. split into two leaf nodes + * 2. create new root node (internal instead of leaf) + */ + + ++(this->n_element_); + + std::unique_ptr lower(reinterpret_cast(this->root_.release())); + std::unique_ptr upper(lower->split_leaf_upper()); + + /* insert is made into this leaf */ + LeafNodeType * leaf_node = nullptr; + /* new (key, value) pair placed at this index poseition in leaf_node */ + std::size_t leaf_ix = 0; + + /* note corner case: + * when lub_ix_recd.second == lower->n_elt(), + * could either insert (key, value) as smallest key in upper subtree, or largest key in lower subtree. + * however if we put into upper, then also need to correct .root.elt_v_[1].key; + * slightly simpler to insert into lower subtree. + * + */ + if (lub_ix_recd.second <= lower->n_elt()) { + log && log("insert into new LH leaf node"); + leaf_node = lower.get(); + leaf_ix = lub_ix_recd.second; + } else { + log && log("insert into new RH leaf node"); + leaf_node = upper.get(); + leaf_ix = lub_ix_recd.second - lower->n_elt(); + } + + leaf_node->insert_leaf_item(leaf_ix, + std::move(kv_pair), + this->debug_flag()); + + /* create new root node (now with node-type = internal), + * having two child (leaf) nodes + */ + std::unique_ptr root_2 = InternalNodeType::make_2(std::move(lower), + std::move(upper)); + + /* new root node replaces existing root node */ + this->root_ = std::move(root_2); + + this->post_modify_correct_leafnode_endpoints(); + + return (std::pair + (const_iterator(detail::ID_Forward /*dirn*/, + detail::IL_Regular /*loc*/, + leaf_node, + leaf_ix), + true)); + } else if (leaf->n_elt() < this->properties_.branching_factor()) { + /* 1. key is not already present in b+ tree + * 2. leaf_ix+1 is lub on key; move elements [lub .. n_elt) one step to the right + * hope to move elements leaf_ix+1 .. n_elt-1 to the right, + */ + + /* leaf node has room for one more item */ + ++(this->n_element_); + /* insert in root node, moving items [lub_ix .. .n_elt) one to the right */ + leaf->insert_leaf_item(lub_ix_recd.second, + kv_pair, + this->debug_flag()); + + return (std::pair + (const_iterator(detail::ID_Forward /*dirn*/, + detail::IL_Regular /*loc*/, + leaf, + lub_ix_recd.second), + true)); + } else { + /* impossible! */ + + assert(false); + return (std::pair + (const_iterator(), + false)); + } + } /*leaf_insert_aux*/ + + /* insert helper. + * + * require: + * - root node is NodeType::internal + * + * kv_pair: establish assocation kv_pair.first(=key) -> kv_pair.second(=value) + * return: true if insert performed; false if update on existing key + */ + std::pair + internal_insert_aux(std::pair const & kv_pair) { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(this->debug_flag()), + xtag("key", kv_pair.first), + xtag("value", kv_pair.second)); + + /* root node is an internal node: + * - tree has at least b elements (where b = branching factor) + */ + FindNodeResult leaffindresult = this->find_leaf_node(kv_pair.first); + LeafNodeType * leaf = leaffindresult.node(); + + log && log(xtag("leaf", leaf), + xtag("leaf.n_elt", leaf->n_elt()), + xtag("leaf.bf", leaf->branching_factor())); + + std::pair lub_ix_recd = leaf->find_lub_ix(kv_pair.first); + + log && log(xtag("lub_ix_recd.first", lub_ix_recd.first), + xtag("lub_ix_recd.second", lub_ix_recd.second)); + + if (lub_ix_recd.first) { + /* key already in tree, just updating associated value */ + leaf->assign_leaf_value(lub_ix_recd.second - 1, kv_pair.second); + + return (std::pair + (const_iterator(detail::ID_Forward /*dirn*/, + detail::IL_Regular /*loc*/, + leaf, + lub_ix_recd.second - 1), + false)); + } + + /* key not present in tree, will be incrementing tree size */ + ++(this->n_element_); + + if (leaf->n_elt() < leaf->branching_factor()) { + log && log("insert into existing leaf, since it has room"); + + /* leaf has room for 1 more item */ + leaf->insert_leaf_item(lub_ix_recd.second, + kv_pair, + this->debug_flag()); + + this->post_modify_add_ancestor_size(leaf->parent(), +1); + + /* whenever we insert at first key position, + * need also to update ancestor glb_key values + */ + { + InternalNodeType * ancestor = leaf->parent(); + + while (ancestor && (kv_pair.first < ancestor->glb_key())) { + ancestor->set_glb_key(kv_pair.first); + ancestor = ancestor->parent(); + } + } + + return (std::pair + (const_iterator(detail::ID_Forward /*dirn*/, + detail::IL_Regular /*loc*/, + leaf, + lub_ix_recd.second), + true)); + } + + /* leaf is full. + * 1. split into two half-full leaf nodes + * 2. insert new (key, value) pair into one of the two + * half-full nodes. + * 3. recursively insert entry for new node into parent; + * possibly splitting parent and additional ancestor + * nodes as need be + */ + + std::unique_ptr new_node; + + /* key,value pair will be inserted into this leaf */ + LeafNodeType * leaf_node = nullptr; + /* key,value pair inserted into leaf at this index position */ + std::size_t leaf_ix = 0; + + if (lub_ix_recd.second < leaf->n_elt() / 2) { + /* will insert into lower_leaf */ + std::unique_ptr lower_leaf(leaf->split_leaf_lower()); + + /* lower_leaf holds lower half of leaf's original set of items. + * leaf now holds upper half of leaf's original set of items. + */ + + log && log("split leaf to get lower_leaf", + xtag("lower_leaf", lower_leaf.get()), + xtag("leaf.n_elt", leaf->n_elt()), + xtag("lower_leaf.n_elt", lower_leaf->n_elt())); + + assert(lub_ix_recd.second <= lower_leaf->n_elt()); + + /* this size temporarily excluded from tree */ + std::size_t decr_z = lower_leaf->size(); + + log && log("insert new key into (new) LH leaf"); + lower_leaf->insert_leaf_item(lub_ix_recd.second, + kv_pair, + this->debug_flag()); + + leaf_node = lower_leaf.get(); + leaf_ix = lub_ix_recd.second; + + new_node = std::move(lower_leaf); + + /* new_node may get attached to ree at non-obvious location. + * at this point it is not in tree. + * + * to bookkeep node sizes, decrement now, then increment where new_node is reintroduced + */ + { + InternalNodeType * parent = leaf->parent(); + + BplusTreeUtil::post_modify_sub_ancestor_size(parent, decr_z, this->debug_flag()); + } + + /* however: leaf's glb increased -> + * need to patch state in (at least parent, possibly more) ancestors + */ + { + GenericNodeType * target = leaf; + InternalNodeType * parent = target->parent(); + + while (parent) { + std::size_t ix = parent->locate_child_by_address(target); + + assert(ix != static_cast(-1)); + + InternalNodeItemType & slot = parent->lookup_elt(ix); + + if (slot.key() == target->glb_key()) { + /* done with fixup */ + break; + } + + slot.set_key(target->glb_key()); + + target = parent; + parent = parent->parent(); + } + } + } else { + /* leaf is full: + * + * note that leaf->n_elt() shrinks across this call + * + * before: + * leaf: + * <-- b elements -> + * 0 1 b-1 + * +---+- ... -+---+ + * | e1| | eb| + * +---+- ... -+---+ + * + * after: + * leaf: upper_leaf: + * <-- b/2 elements -> <-- b/2 elements --> + * 0 1 h 0 + * +---+- ... -+---+ +---+- ... -+---+ + * | e1| | eh| |eh'| | eb| with eh'=e(h+1) + * +---+- ... -+---+ +---+- ... -+---+ + * + * note: if b odd, then: + * - leaf gets (b-1)/2 elements, + * - upper_leaf gets (b+1)/2 elements + */ + + /* will insert into upper_leaf */ + std::unique_ptr upper_leaf(leaf->split_leaf_upper()); + + /* leaf now holds lower half of its original set of items; + * upper holds upper half of leaf's original set of items + */ + + log && log("split leaf to get upper_leaf", + xtag("upper_leaf", upper_leaf.get()), + xtag("leaf.n_elt", leaf->n_elt()), + xtag("upper_leaf.n_elt", upper_leaf->n_elt())); + + assert(lub_ix_recd.second >= leaf->n_elt()); + + /* this size temporarily excluded from tree */ + std::size_t decr_z = upper_leaf->size(); + + log && log("insert new key into (new) RH leaf"); + upper_leaf->insert_leaf_item(lub_ix_recd.second - leaf->n_elt(), + kv_pair, + this->debug_flag()); + + leaf_node = upper_leaf.get(); + leaf_ix = lub_ix_recd.second - leaf->n_elt(); + + new_node = std::move(upper_leaf); + + /* new_node may get attached to tree at non-obvious location. + * at this point it is not in tree. + * + * to bookkeep node sizes, decrement now, then increment where new_node is reintroduced + */ + { + InternalNodeType * parent = leaf->parent(); + + BplusTreeUtil::post_modify_sub_ancestor_size(parent, decr_z, this->debug_flag()); + } + + /* leaf's glb unchanged, no glb fixup required here */ + } + + Key new_key = new_node->glb_key(); + std::size_t lub_ix = 0; + + InternalNodeType * ancestor = leaf->parent(); + + while (ancestor) { + /* invariant: need to add new_node to tree somewhere on path to ancestor. + * new_node is a leaf|internal node with already-correct size, + * that isn't yet accounted for in this B+ tree + */ + + lub_ix = ancestor->find_lub_ix(new_key); + + log && log("fixup ancestors", + xtag("new_key", new_key), + xtag("new_node", new_node.get()), + xtag("new_node.size", logutil::nodesize(new_node.get())), + xtag("ancestor", ancestor), + xtag("lub_ix", lub_ix)); + + /* on this iteration, need to introduce (new_key, new_node) to ancestor */ + + if (ancestor->n_elt() < ancestor->branching_factor()) { + /* ordinal_enabled: #of elements in subtree new_node. + * ordinal_disabled: 0 + */ + std::size_t new_z = BplusTreeUtil::get_node_size(new_node.get()); + + log && log("insert into ancestor, since it has room", + xtag("ancestor.size[pre-insert]", logutil::nodesize(ancestor)), + xtag("new_z", logutil::nodesize(new_node.get()))); + + /* room for 1 more child */ + ancestor->insert_node(lub_ix, + std::move(new_node), + this->debug_flag()); + + /* if ordinal_enabled: increase .size on path from root down to and including ancestor + * otherwise no-op + */ + BplusTreeUtil::post_modify_add_ancestor_size(ancestor, new_z, this->debug_flag()); + this->post_modify_correct_ancestor_glb_keys(ancestor); + this->post_modify_correct_leafnode_endpoints(); + + return (std::pair + (const_iterator(detail::ID_Forward /*dirn*/, + detail::IL_Regular /*loc*/, + leaf_node, + leaf_ix), + true)); + } else { + log && log("pre-split (will split ancestor to make room for new node)", + xtag("ancestor", ancestor), + xtag("ancestor.size", logutil::nodesize(ancestor)), + xtag("new_node", new_node.get()), + xtag("new_node.size", logutil::nodesize(new_node.get()))); + + /* no room in ancestor, need to split */ + + std::unique_ptr upper_ancestor(ancestor->split_internal()); + + log && log("post-split", + xtag("ancestor", ancestor), + xtag("ancestor.size", logutil::nodesize(ancestor)), + xtag("upper_ancestor", upper_ancestor.get()), + xtag("upper_ancestor.size", logutil::nodesize(upper_ancestor.get()))); + + /* this size temporarily excluded from tree */ + std::size_t decr_z = BplusTreeUtil::get_node_size(upper_ancestor.get()); + + /* will add back size fom upper_ancestor (w/ +1 for insert), + * once we figure out where to attach it. + * ancestor.size already decreased via ancestor.split_internal() + */ + BplusTreeUtil::post_modify_sub_ancestor_size(ancestor->parent(), decr_z, this->debug_flag()); + + /* ancestor.n_elt reduced to 1/2 value before call to split_internal() */ + + std::size_t new_z = BplusTreeUtil::get_node_size(new_node.get()); + + if (lub_ix <= ancestor->n_elt()) { + log && log("insert into (existing post-split) LH ancestor"); + log && log(xtag("lub_ix", lub_ix), xtag("new_node", new_node.get()), xtag("new_z", new_z)); + + ancestor->insert_node(lub_ix, + std::move(new_node), + this->debug_flag()); + + /* note: updating entire ancestor chain, + * since next iteration will operate on upper_ancestor != ancestor + */ + BplusTreeUtil::post_modify_add_ancestor_size(ancestor, new_z, this->debug_flag()); + + log && log("LH ancestor size", + xtag("ancestor", ancestor), + xtag("ancestor.size", logutil::nodesize(ancestor))); + + /* note next loop iteration will fixup upper_ancestor. + * upper_ancestor != ancestor + */ + this->post_modify_correct_ancestor_glb_keys(ancestor); + } else { + log && log("insert into (new) RH ancestor"); + log && log(xtag("ix", lub_ix - ancestor->n_elt()), + xtag("new_node", new_node.get()), + xtag("new_z", new_z)); + + upper_ancestor->insert_node(lub_ix - ancestor->n_elt(), + std::move(new_node), + this->debug_flag()); + + /* note: deferring update for ancestor's ancestors until next loop iter */ + BplusTreeUtil::node_add_size(upper_ancestor.get(), new_z); + + log && log("upper ancestor size", + xtag("upper_ancestor", ancestor), + xtag("upper_ancestor.size", logutil::nodesize(ancestor))); + + } + + /* setup for next loop iteration + * reminder: upper_ancestor.size was removed from computed treesize, + * will add back on subsequent iteration (when attaching new_node) + */ + new_key = upper_ancestor->glb_key(); + new_node = std::move(upper_ancestor); + ancestor = ancestor->parent(); + } + } + + log && log("root node was split -> create new root, adding one level"); + + /* if control comes here: + * 1. ancestor is null + * 2. root node was full + has been split. . + * root will become LH subtree of new root + * new_node will become RH subtree of new root + * 3. new_node is not present in .root + */ + + log && log(xtag("root.n_elt", this->root_->n_elt()), + xtag("new_node.n_elt", new_node->n_elt())); + + this->root_ = std::move(InternalNodeType::make_2(std::move(this->root_), + std::move(new_node))); + + this->post_modify_correct_leafnode_endpoints(); + + return (std::pair + (const_iterator(detail::ID_Forward /*dirn*/, + detail::IL_Regular /*loc*/, + leaf_node, + leaf_ix), + true)); + } /*internal_insert_aux*/ + + std::pair + create_root_aux(std::pair const & kv_pair) { + /* create root, with one element */ + this->n_element_ = 1; + + std::unique_ptr leaf_node + = LeafNodeType::make(kv_pair, + this->properties_); + + this->leafnode_begin_ = leaf_node.get(); + this->leafnode_end_ = leaf_node.get(); + + std::pair retval + = (std::pair + (const_iterator(detail::ID_Forward /*dirn*/, + detail::IL_Regular /*loc*/, + leaf_node.get(), + 0), + true)); + + this->root_.reset(leaf_node.release()); + + return retval; + } /*create_root_aux*/ + + /* remove helper. + * + * require: + * - root node is NodeType::leaf + * - return true iff key found (in which case #of leaf node items decremented) + */ + bool leaf_erase_aux(Key const & key) { + using xo::scope; + using xo::xtag; + + LeafNodeType * leaf = reinterpret_cast(this->root_.get()); + + scope log(XO_DEBUG(this->debug_flag()), + xtag("leaf", leaf), + xtag("leaf.n_elt", leaf->n_elt()), + xtag("leaf.bf", leaf->branching_factor())); + + /* .elt_v[] + * + * 0 k n-1 with: n <= b = branching factor + * +---+---+- ... -+---+- ... -+---+---+ k = lub(key) in {e1..en} + * | e1| e2| | ek| | | en| + * +---+---+- ... -+---+- ... -+---+---+ + * + * lub_ix_recd.first: true if key already present in tree. implies lub_ix_recd.second >= 1 + * lub_ix_recd.second: upper bound (strict) index position in .elt_v[] of key + */ + std::pair lub_ix_recd = leaf->find_lub_ix(key); + + log && log(xtag("lub_ix_recd.first", lub_ix_recd.first), + xtag("lub_ix_recd.second", lub_ix_recd.second)); + + if (!lub_ix_recd.first) { + /* key is not present in tree --> don't modify anything */ + return false; + } + + /* key is present in tree --> will decrement tree size */ + + if (leaf->n_elt() > 1) { + --(this->n_element_); + + /* reminder: lub_ix_recd.second is strict upper bound */ + leaf->remove_leaf(lub_ix_recd.second - 1, + this->debug_flag()); + + } else { + --(this->n_element_); + + /* removed last node -> tree now empty */ + + this->root_.reset(); + + } + + this->post_modify_correct_leafnode_endpoints(); + + log.end_scope(); + + return true; + } /*leaf_erase_aux*/ + + /* remove helper. + * + * require: + * - root node is NodeType::internal + * - return true iff key found (in which case #of key,value pairs decremented) + */ + bool internal_erase_aux(Key const & key) { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(this->debug_flag()), + xtag("key", key)); + + std::size_t const bf = this->branching_factor(); + + /* root node is an internal node: + * - tree has at least b elements (where b = branching zfactor) + * + * this + * +------+ + * | | + * +------+ + * . + * . + * +------+ + * | i| i = leaffindresult.ix() + * +------+ + * / | \ + * /-------/ | \--------\ + * | | | + * +------+ +------+ +------+ + * | | | | | j | j = lub_ix_recd.second - 1 + * +------+ +------+ +------+ + * leaf + */ + + FindNodeResult leaffindresult = this->find_leaf_node(key); + LeafNodeType * leaf = leaffindresult.node(); + + log && log(xtag("leaf", leaffindresult.node()), + xtag("leaf.n_elt", leaffindresult.node()->n_elt()), + xtag("leaf.loc", leaffindresult.ix()), + xtag("bf", bf)); + + std::pair lub_ix_recd = leaffindresult.node()->find_lub_ix(key); + + log && log(xtag("lub_ix_recd.first", lub_ix_recd.first), + xtag("lub_ix_recd.second", lub_ix_recd.second)); + + if (!lub_ix_recd.first) { + /* key not in tree */ + return false; + } + + /* key present in tree at leaf.elt_v[lub_ix_recd.second - 1] */ + + /* B+ balance invariant is sustained across remove + * + * if glb key changed, then have to propagate up ancestor chain + */ + --(this->n_element_); + + /* reminder: lub_ix_recd.second is strict upper bound */ + leaf->remove_leaf(lub_ix_recd.second - 1, + this->debug_flag()); + + InternalNodeType * parent = leaf->parent(); + + /* whenever we remove at first key position (with strict upper bound index 1), + * then glb key changed, so need also to update ancestor glb_key values + */ + if (lub_ix_recd.second == 1) { + /* glb_key for this leaf node changed (to larger value) */ + log && log("fix glb", + xtag("@", parent), + xtag("old-glb", parent->glb_key()), + xtag("new-glb", leaf->glb_key())); + + /* we dropped smallest key from [leaf] --> correct glb key for leaf in its immediate parent */ + parent->lookup_elt(leaffindresult.ix()).set_key(leaf->glb_key()); + + this->post_modify_correct_ancestor_glb_keys(parent); + } else { + /* removal from position >0 doesn't change glb key + * -> doesn't require ancestor updates + */ + } + + if (2 * leaf->n_elt() >= bf) { + /* after removal, leaf still has acceptable #of children */ + + /* must decrement tree size on path from root down to and including parent */ + this->post_modify_sub_ancestor_size(parent, +1); + + return true; + } else { + /* after removal, leaf will be too small. plan: + * - try redistributing from a neighboring leaf + * - if result too small, then merge with one of neighboring leaves; + * in this case merges may cascade upward to root + * - if root node shrinks to 1 child, that child becomes new root + */ + log && log("leaf too small after remove -> redistribute or shrink tree"); + + std::size_t leaf_ix = leaffindresult.ix(); + /* right_sibling_ix: position of sibling immediately after (leaf_ix, key), in parent */ + std::size_t right_sibling_ix = leaf_ix + 1; + + LeafNodeType * right_sibling = nullptr; + + if (right_sibling_ix < parent->n_elt()) { + /* consider merge with right sibling */ + right_sibling = reinterpret_cast(parent->lookup_elt(right_sibling_ix).child()); + + std::size_t n = leaf->n_elt() + right_sibling->n_elt(); + + if (n >= 2 * ((bf + 1) / 2)) { + /* can redistribute one or more nodes from right_sibling -> leaf + * e.g. + * if bf=3, require 4 nodes between leaf and rh sibling + * if bf=4, also require 4 nodes between leaf and rh sibling. + * + * after redistribution: + * - leaf will have n/2 elements + * - right_sibling will have n - n/2 elements + */ + leaf->append_from_rh_sibling(n/2 - leaf->n_elt(), right_sibling); + + /* glb_key for right sibling changed, need to fix ancestor book-keeping */ + parent->lookup_elt(right_sibling_ix).set_key(right_sibling->glb_key()); + + this->post_modify_sub_ancestor_size(parent, +1); + this->post_modify_correct_ancestor_glb_keys(parent); + + return true; + } else { + log && log("reject redistrib from right sibling, not enough capacity"); + } + } else { + log && log("reject redistrib from right sibling, doesn't exist"); + } + + std::size_t left_sibling_ix = leaf_ix - 1; + LeafNodeType * left_sibling = nullptr; + + if (leaf_ix > 0) { + /* consider redistribution from left sibling */ + left_sibling = reinterpret_cast(parent->lookup_elt(left_sibling_ix).child()); + + std::size_t n = leaf->n_elt() + left_sibling->n_elt(); + + if (n >= 2 * ((bf + 1) / 2)) { + log && log("redistrib from left sibling"); + + std::size_t n_redistrib = n/2 - leaf->n_elt(); + + log && log(xtag("n/2", n/2), + xtag("leaf.n", leaf->n_elt()), + xtag("n_redistrib", n_redistrib)); + + /* can redistribute one or more nodes from left_sibling -> leaf + * after redistribution: + * - leaf will have n/2 elements + * - left_sibling will have n - n/2 elements + */ + leaf->prepend_from_lh_sibling(left_sibling, n_redistrib, this->debug_flag()); + + /* glb key for leaf changed, need to fix ancestor book-keeping */ + parent->lookup_elt(leaf_ix).set_key(leaf->glb_key()); + + this->post_modify_sub_ancestor_size(parent, +1); + this->post_modify_correct_ancestor_glb_keys(parent); + + return true; + } else { + log && log("reject redistib from left sibling, not enough capacity"); + } + } else { + log && log("reject redistrib from left sibling, doesn't exist"); + } + + /* control here + * -> not enough nodes to redistribute from either sibling + * -> must shrink #nodes in tree + */ + + if (right_sibling_ix < parent->n_elt()) { + assert(right_sibling); + + log && log("merge right sibling"); + + /* RH sibling exists -> merge with it (arbitrary choice if leaf_ix > 0) */ + + leaf->append_rh_sibling(right_sibling); + + /* right_sibling is now (effectively) empty, drop from parent; + * also fixup next_leafnode/prev_leafnode links to bypass + */ + parent->remove_node(right_sibling_ix, this->debug_flag()); + + /* note that glb_key for leaf did not change */ + + /* -1 .size on path from root down to and including parent */ + this->post_modify_sub_ancestor_size(parent, +1); + /* since we reduced #of children at parent, it may have fallen below b/2 lower bound */ + this->post_remove_shrink_ancestor_path(parent); + /* since we removed a leaf node, may have invalidated iterator begin/end endpoints */ + this->post_modify_correct_leafnode_endpoints(); + + return true; + } + + if (leaf_ix > 0) { + assert(left_sibling); + + /* LH sibling exists -> merge with it (arbitrary choice if right_sibling_ix < parent.n_elt */ + + left_sibling->append_rh_sibling(leaf); + + /* leaf is now (effectively) empty, drop from parent; + * also fixup next_leafnode/prev_leafnode links to bypass + */ + parent->remove_node(leaf_ix, this->debug_flag()); + + /* note that glb_key for left_sibling did not change */ + + this->post_modify_sub_ancestor_size(parent, +1); + /* since we reduced #of children at parent, it may have fallen below b/2 lower bound */ + this->post_remove_shrink_ancestor_path(parent); + /* since we removed a leaf node, may have invalidated iterator begin/end endpoints */ + this->post_modify_correct_leafnode_endpoints(); + + return true; + } + + /* must have at least one sibling (else prior visit would have shrunk tree height) */ + } + + log.end_scope(); + + assert(false); + return false; + } /*internal_erase_aux*/ + + void post_modify_add_ancestor_size(InternalNodeType * parent, std::size_t incr_z) { + BplusTreeUtil::post_modify_add_ancestor_size(parent, incr_z, this->debug_flag()); + } /*post_modify_add_ancestor_size*/ + + void post_modify_sub_ancestor_size(InternalNodeType * parent, std::size_t decr_z) { + BplusTreeUtil::post_modify_sub_ancestor_size(parent, decr_z, this->debug_flag()); + } /*post_modify_sub_ancestor_size*/ + + void post_modify_correct_ancestor_glb_keys(InternalNodeType * parent) { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(this->debug_flag()), + xtag("parent", parent)); + + InternalNodeType * grandparent = parent->parent(); + + std::size_t i_ancestor = 0;; + while (grandparent) { + log && log(xtag("i_ancestor", i_ancestor), + xtag("grandparent", grandparent)); + + /* find index position of parent subtree, as child of grandparent + * Can only use .find_ix() when key-invariants are satisfied. + * + * Warning: O(bf) call here + */ + std::size_t parent_ix = grandparent->locate_child_by_address(parent); + + log && log(xtag("parent.loc", parent_ix)); + + if (grandparent->lookup_elt(parent_ix).key() == parent->glb_key()) { + log && log("grandparent[parent.loc].key == parent.glb_key --> done"); + break; + } + + log && log("fix glb key in grandparent"); + grandparent->lookup_elt(parent_ix).set_key(parent->glb_key()); + + /* + repeat 1 level up.. */ + parent = grandparent; + grandparent = parent->parent(); + } + } /*post_modify_correct_ancestor_glb_keys*/ + + /* reset .leafnode_begin, .leafnode_end after changing the set of nodes in a b+ tree */ + void post_modify_correct_leafnode_endpoints() { + if (root_) { + this->leafnode_begin_ = root_->find_min_leaf_node().node(); + this->leafnode_end_ = root_->find_max_leaf_node().node(); + } else { + this->leafnode_begin_ = nullptr; + this->leafnode_end_ = nullptr; + } + } /*post_modify_correct_leafnode_endpoints*/ + + void post_remove_shrink_ancestor_path(InternalNodeType * node) { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(this->debug_flag())); + + std::size_t const bf = node->branching_factor(); + + while (node + && (node != this->root_.get())) { + + log && log(xtag("node", node), + xtag("node.n_elt", node->n_elt())); + + if (2 * node->n_elt() >= bf) + break; + + /* node has fewer children than B+ minimum. + * either: + * - redistribute nodes from sibling + * (so that merged node satisfies bf/2 <= n <= bf) + * - merge with sibling + */ + InternalNodeType * parent = node->parent(); + + /* O(bf), but doesn't rely on satisfied key invariants */ + std::size_t node_ix = parent->locate_child_by_address(node); + std::size_t right_sibling_ix = node_ix + 1; + + InternalNodeType * right_sibling = nullptr; + + if (right_sibling_ix < parent->n_elt()) { + /* consider redistributng from right sibling */ + right_sibling = reinterpret_cast(parent->lookup_elt(right_sibling_ix).child()); + + std::size_t n = node->n_elt() + right_sibling->n_elt(); + + if (n >= 2 * ((bf + 1) / 2)) { + log && log("redistribute from right_sibling", + xtag("lh.n", node->n_elt()), + xtag("rh.n", right_sibling->n_elt())); + + /* can redistribute one or more nodes from right_sibling -> node + * + * after redistribution: + * - node will have floor(n/2) elements + * - right_sibling will have ceil(n/2) = n - floor(n/2) elements + */ + node->append_from_rh_sibling(n/2 - node->n_elt(), right_sibling); + + /* glb_key for right sibling changed, need to fixup ancestor book-keeping */ + this->post_modify_correct_ancestor_glb_keys(right_sibling); + + return; + } + } + + std::size_t left_sibling_ix = node_ix - 1; /* but beware underflow when node_ix=0 */ + + InternalNodeType * left_sibling = nullptr; + + if (node_ix > 0) { + /* consider redistributing from left sibling */ + left_sibling = reinterpret_cast(parent->lookup_elt(left_sibling_ix).child()); + + std::size_t n = node->n_elt() + left_sibling->n_elt(); + + if (n >= 2 * ((bf + 1) / 2)) { + log && log("redistribute from left_sibling", + xtag("lh.n", left_sibling->n_elt()), + xtag("rh.n", node->n_elt())); + + /* redistribute one or more nodes from left_sibling -> node */ + node->prepend_from_lh_sibling(left_sibling, + n/2 - node->n_elt(), + this->debug_flag()); + + /* glb_key for node changed, need to fixup ancestor book-keeping */ + this->post_modify_correct_ancestor_glb_keys(node); + + return; + } + } + + log && log("cannot redistribute -> drop a node"); + + /* control here + * -> not enough nodes to redistribute from either sibling + * -> must shrink number of nodes in tree + */ + + if (right_sibling_ix < parent->n_elt()) { + assert(right_sibling); + + /* RH sibling exists -> merge with it */ + + node->append_rh_sibling(right_sibling); + + /* right sibling now empty, drop from parent */ + parent->remove_node(right_sibling_ix, this->debug_flag()); + } else if (node_ix > 0) { + assert(left_sibling); + + /* LH sibling exists -> merge with it */ + + left_sibling->append_rh_sibling(node); + + /* node is now empty, drop from parent */ + parent->remove_node(node_ix, this->debug_flag()); + } + + /* continue tree fixup at parent node */ + node = parent; + } + + /* if node != root: tree shrank successfully, without propgating to root */ + + if ((node == this->root_.get()) && (node->n_elt() == 1)) + { + /* replace root with its single child element; tree height shrinks by one */ + this->root_ = std::move(node->lookup_elt(0).release_child()); + + this->root_->set_parent(nullptr); + } + } /*post_remove_shrink_ancestor_path*/ + + void print_aux(std::ostream & os, + GenericNodeType const * node, + std::uint32_t indent) const + { + using xo::xtag; + + if (node) { + switch(node->node_type()) { + case NodeType::internal: + { + using xo::pad; + + InternalNodeType const * internal = reinterpret_cast(node); + + for (std::uint32_t i=0, n=internal->n_elt(); ilookup_elt(i).child()->n_elt()) + << xtag("treez", logutil::nodesize(internal->lookup_elt(i).child())) + << xtag("glb", internal->lookup_elt(i).key()) + << xtag("@", internal->lookup_elt(i).child()); + + this->print_aux(os, + internal->lookup_elt(i).child(), + indent+1); + } + } + break; + case NodeType::leaf: + { + using xo::pad; + + LeafNodeType const * leaf = reinterpret_cast(node); + + for (std::uint32_t i=0, n=leaf->n_elt(); ilookup_elt(i).key() + << ": " << leaf->lookup_elt(i).value(); + } + } + break; + } + } else { + //os << std::endl; + } + } /*print_aux*/ + + private: + /* tree properties, in particular: branching factor */ + Properties properties_; + + /* #of items in this tree */ + std::size_t n_element_ = 0; + + /* left-most leaf node for inorder traversal */ + LeafNodeType * leafnode_begin_ = nullptr; + /* right-most leaf node for inorder traversal */ + LeafNodeType * leafnode_end_ = nullptr; + + /* tree + * size root depth + * ------------------------- + * 0 nullptr 0 + * 1..b LeafNode 1 + * >b InternalNode >1 + */ + std::unique_ptr root_; + }; /*BplusTree*/ + + } /*namespace tree*/ +} /*namespace xo*/ + +/* end BplusTree.hpp */ diff --git a/include/xo/tree/RedBlackTree.hpp b/include/xo/tree/RedBlackTree.hpp new file mode 100644 index 00000000..e9460b95 --- /dev/null +++ b/include/xo/tree/RedBlackTree.hpp @@ -0,0 +1,3158 @@ +/* @file RedBlackTree.hpp */ + +/* provides red-black tree with order statistics. + */ + +#pragma once + +#include "indentlog/scope.hpp" +#include "indentlog/print/pad.hpp" +#include "indentlog/print/quoted.hpp" +#include +#include +#include +#include +#include +#include + +namespace xo { + namespace tree { + + /* concept for the 'Reduce' argument to RedBlackTree<...> + * + * here: + * T = class implementing reduce feature, e.g. SumReduce<...> + * T::value_type = type for output of reduce function. + * + * Value = value_type for rb-tree that supports ordinal statistics + * + * e.g. + * struct ReduceCountAndSum { + * using value_type = std::pair: + * + * value_type nil() { return value_type(0, 0); } + * value_type operator()(value_type const & acc, int64_t val) + * { return value_type(acc.first + val.first, acc.second + val.second); } + * value_type operator()(value_type const & a1, value_type const & a2) + * { return value_type(a1.first + a2.first, a1.second + a2.second); } + * }; + * + * Reduce.nil() -> nominal reduction i.e. reduce on empty set + * Reduce.leaf(v) -> reduction on set {v} + * + * in general: at some internal node, tree splits set of key/value pairs on some key k1, + * with a left subtree lh, and a right subtree rh. + * + * for a binary tree we want to maintain: + * - r1: reduce applied to collection + * lh + {k1} = reduce(reduce(lh), k1) + * - r2: reduce applied to collection + * lh + {k1} + rh = reduce.combine(r1, reduce(r2)) + * + */ + template + concept ReduceConcept = requires(T r, Value v, typename T::value_type a) { + typename T::value_type; + { r.nil() } -> std::same_as; + { r.leaf(v) } -> std::same_as; + { r(a, v) } -> std::same_as; + { r.combine(a, a) } -> std::same_as; + }; + + /* reduce function that disappears at compile time */ + template + struct NullReduce; + + /* red-black tree with order statistics + * + * require: + * - Key is equality comparable + * - Key, Value, Reduce are copyable and null-constructible + * - Reduce.value_type = Accumulator + * - Reduce.operator() :: (Accumulator x Key) -> Accumulator + * - Reduce.operator() :: (Accumulator x Accumulator) -> Accumulator + */ + template > + class RedBlackTree; + + namespace detail { + enum Color { C_Invalid = -1, C_Black, C_Red, N_Color }; + + enum Direction { D_Invalid = -1, D_Left, D_Right, N_Direction }; + + inline Direction other(Direction d) { + return static_cast(1 - d); + } /*other*/ + + template + class RbTreeUtil; + + /* xo::tree::detail::Node + * + * Require: + * - Key.operator< + * - Key.operator== + * + */ + template + class Node { + public: + using ReducedValue = typename Reduce::value_type; + using ContentsType = std::pair; + using value_type = std::pair; + + public: + Node() = default; + Node(value_type const & kv_pair, + std::pair const & r) + : color_(C_Red), size_(1), contents_{kv_pair}, reduced_(r) {} + Node(value_type && kv_pair, + std::pair && r) + : color_(C_Red), size_(1), + contents_{std::move(kv_pair)}, + reduced_{std::move(r)} {} + + static Node * make_leaf(value_type const & kv_pair, + ReducedValue const & leaf_rv) { + return new Node(kv_pair, + std::pair(leaf_rv, leaf_rv)); + } /*make_leaf*/ + + static Node * make_leaf(value_type && kv_pair, + ReducedValue const & leaf_rv) { + return new Node(kv_pair, + std::pair(leaf_rv, leaf_rv)); + } /*make_leaf*/ + + /* return #of key/vaue pairs in tree rooted at x. */ + static size_t tree_size(Node *x) { + if (x) + return x->size(); + else + return 0; + } /*tree_size*/ + + static bool is_black(Node *x) { + if (x) + return x->is_black(); + else + return true; + } /*is_black*/ + + static bool is_red(Node *x) { + if (x) + return x->is_red(); + else + return false; + } /*is_red*/ + + static Direction child_direction(Node *p, Node *n) { + if (p) { + return p->child_direction(n); + } else { + return D_Invalid; + } + } /*child_direction*/ + + static ReducedValue reduce_aux(Reduce reduce, Node *x) + { + if(x) + return x->reduced2(); + else + return reduce.nil(); + } /*reduce_aux*/ + + /* calculate reduced values for node x. + * does not used x.reduced + */ + static std::pair reduced_pair(Reduce r, Node const * x) + { + if(!x) + assert(false); + + ReducedValue r1 = r(reduce_aux(r, x->left_child()), + x->value()); + ReducedValue r2 = r.combine(r1, + reduce_aux(r, x->right_child())); + return std::pair(r1, r2); + } /*reduced_pair*/ + + /* replace root pointer *pp_root with x; + * set x parent pointer to nil + */ + static void replace_root_reparent(Node *x, Node **pp_root) { + *pp_root = x; + if (x) + x->parent_ = nullptr; + } /*replace_root_reparent*/ + + size_t size() const { return size_; } + /* const access */ + ContentsType const & contents() const { return contents_; } + /* non-const value access. + * + * editorial: would prefer to return + * std::pair & + * here, so that tree[k].first = newk + * prohibited, but std::pair + * is considered unrelated to std::pair, + * so l-value conversion not allowed + */ + ContentsType & contents() { return contents_; } + + Node *parent() const { return parent_; } + Node *child(Direction d) const { return child_v_[d]; } + Node *left_child() const { return child_v_[0]; } + Node *right_child() const { return child_v_[1]; } + ReducedValue const & reduced1() const { return reduced_.first; } + ReducedValue const & reduced2() const { return reduced_.second; } + + /* true if this node has 0 children */ + bool is_leaf() const { + return ((child_v_[0] == nullptr) && (child_v_[1] == nullptr)); + } + + /* identify which child x represents + * Require: + * - x != nullptr + * - x is either this->left_child() or this->right_child() + */ + Direction child_direction(Node *x) { + if (x == this->left_child()) + return D_Left; + else if (x == this->right_child()) + return D_Right; + else + return D_Invalid; + } /*child_direction*/ + + bool is_black() const { return this->color_ == C_Black; } + bool is_red() const { return this->color_ == C_Red; } + + bool is_red_left() const { return is_red(this->left_child()); } + bool is_red_right() const { return is_red(this->right_child()); } + + /* true if this node is red, and either child is red */ + bool is_red_violation() const { + if (this->color_ == C_Red) { + Node *left = this->left_child(); + Node *right = this->right_child(); + + if (left && left->is_red()) + return true; + + if (right && right->is_red()) + return true; + } + + return false; + } /*is_red_violation*/ + + Color color() const { return color_; } + Key const & key() const { return contents_.first; } + Value const & value() const { return contents_.second; } + + /* recalculate size from immediate childrens' sizes + * editor bait: recalc_local_size() + */ + void local_recalc_size(Reduce const & reduce_fn) { + using xo::scope; + using xo::xtag; + + //constexpr char const * c_self = "Node::local_recalc_size"; + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG(c_logging_enabled)); + + this->size_ = (1 + + Node::tree_size(this->left_child()) + + Node::tree_size(this->right_child())); + + /* (note: want reduce applied to all of left subtree) */ + this->reduced_ = Node::reduced_pair(reduce_fn, this); + + log && log("done recalc for key k, value v, reduced r", + xtag("k", this->key()), + xtag("v", this->value()), + xtag("r1", this->reduced1()), + xtag("r2", this->reduced2())); + } /*local_recalc_size*/ + + private: + void assign_color(Color x) { this->color_ = x; } + void assign_size(size_t z) { this->size_ = z; } + + void assign_child_reparent(Direction d, Node *new_x) { + Node *old_x = this->child_v_[d]; + + // trying to fix old_x can be counterproductive, + // since old_x->parent_ may already have been corrected, + // + if (old_x && (old_x->parent_ == this)) + old_x->parent_ = nullptr; + + this->child_v_[d] = new_x; + + if (new_x) { + new_x->parent_ = this; + } + } /*assign_child_reparent*/ + + /* replace child that points to x, with child that points to x_new + * and return direction of the child that was replaced + * + * Require: + * - x is a child of *this + * - x_new is not a child of *this + * + * promise: + * - x is nullptr or x.parent is nullptr + * - x_new is nullptr or x_new.parent is this + */ + Direction replace_child_reparent(Node *x, Node *x_new) { + Direction d = this->child_direction(x); + + if (d == D_Left || d == D_Right) { + this->assign_child_reparent(d, x_new); + return d; + } else { + return D_Invalid; + } + } /*replace_child_reparent*/ + + friend class RbTreeUtil; + friend class xo::tree::RedBlackTree; + + private: + /* red | black */ + Color color_ = C_Red; + /* size of subtree (#of key/value pairs) rooted at this node */ + size_t size_ = 0; + /* .first = key associated with this node + * .second = value associated with this node + * .third = reduced value + */ + ContentsType contents_; + /* accumulator for some binary function of Values. + * must be associative, since value will be produced + * by any testing of calls to Reduce::combine(). + * + * e.g. {a, b, c, d} could be reduced by: + * r(r(a,b), r(c,d)) + * or + * r(a, r(r(b, c), d)) + * etc. + * + * examples: + * - count #of keys + * - sum key values + * + * .reduced.first: reduce applied to all values with keys <= .contents.first + * .reduced.second: reduce applied to all values in this subtree. + */ + std::pair reduced_; + /* pointer to parent node, nullptr iff this is the root node */ + Node *parent_ = nullptr; + /* + * .child_v[0] = left child + * .child_v[1] = right child + * + * invariants: + * - if .child_v[x] non-null, then .child_v[0]->parent = this + * - a red node may not have red children + */ + std::array child_v_ = {nullptr, nullptr}; + }; /*Node*/ + + enum IteratorDirection { + /* ID_Forward. forward iterator + * ID_Reverse. reverse iterator + */ + ID_Forward, + ID_Reverse + }; /*IteratorDirection*/ + + /* specify iterator location relative to Iterator::node. + * using this to make it possible to correctly decrement an + * iterator at RedBlackTree::end(). + * + * IL_BeforeBegin. if non-empty tree, .node is the first node + * in the tree (the one with smallest key), + * and iterator refers to the location + * "one before" that first node. + * IL_Regular. iterator refers to member of the tree + * given by Iterator::node + * IL_AfterEnd. if non-empty tree, .node is the last node + * in the tree (the one with largest key), + * and iterator refers the the location + * "one after" that last node. + */ + enum IteratorLocation { + IL_BeforeBegin, + IL_Regular, + IL_AfterEnd, + }; /*IteratorLocation*/ + + /* require: + * - Reduce::value_type + */ + template + class RbTreeUtil { + public: + using RbNode = Node; + using ReducedValue = typename Reduce::value_type; + using value_type = std::pair; + + public: + /* return #of key/vaue pairs in tree rooted at x. */ + static size_t tree_size(RbNode *x) { + if (x) + return x->size(); + else + return 0; + } /*tree_size*/ + + static bool is_black(RbNode *x) { + if (x) + return x->is_black(); + else + return true; + } /*is_black*/ + + static bool is_red(RbNode *x) { + if (x) + return x->is_red(); + else + return false; + } /*is_red*/ + + /* for every node n in tree, call fn(n, d'). + * d' is the depth of the node n relative to starting point x, + * not counting red nodes. + * make calls in increasing key order (i.e. inorder traversal) + * argument d is the black-height of tree above x + * + * Require: + * - fn(x, d) + */ + template + static void inorder_node_visitor(RbNode const * x, uint32_t d, Fn && fn) { + if (x) { + /* dd: black depth of child subtrees*/ + uint32_t dd = (x->is_black() ? d + 1 : d); + + inorder_node_visitor(x->left_child(), dd, fn); + /* dd includes this node */ + fn(x, dd); + inorder_node_visitor(x->right_child(), dd, fn); + } + } /*inorder_node_visitor*/ + + /* note: RedBlackTree.clear() abuses this to visit-and-delete + * all nodes + */ + template + static void postorder_node_visitor(RbNode const * x, uint32_t d, Fn && fn) { + if (x) { + uint32_t dd = (x->is_black() ? d + 1 : d); + + postorder_node_visitor(x->left_child(), dd, fn); + postorder_node_visitor(x->right_child(), dd, fn); + /* dd includes this node */ + fn(x, dd); + } + } /*postorder_node_visitor*/ + + /* return the i'th inorder node (counting from 0) + * belonging to the subtree rooted at N. + * + * behavior not defined if subtree at N contains less than + * (i + 1) nodes + */ + static RbNode * find_ith(RbNode * N, uint32_t i) { + if(!N) + return nullptr; + + RbNode * L = N->left_child(); + uint32_t n_left = tree_size(L); + + if(i < n_left) + return find_ith(L, i); + else if(i == n_left) + return N; + else if(i < N->size_) + return find_ith(N->right_child(), i - (n_left + 1)); + else + return nullptr; + } /*find_ith*/ + + /* starting from x, traverse only left children + * to find node with a nil left child. + * + * This node has the smallest key in subtree N + */ + static RbNode * find_leftmost(RbNode * N) { + while(N) { + RbNode * S = N->left_child(); + + if(!S) + break; + + N = S; + } + + return N; + } /*find_leftmost*/ + + /* return node containing the next key after N->key_ in the tree + * containing N. This will be either a descendant of N, + * or an ancestor of N. + * returns nil if x.key is the largest key in tree containing x. + */ + static RbNode * next_inorder_node(RbNode * N) { + if(!N) + return nullptr; + + if(N->right_child()) + return find_leftmost(N->right_child()); + + /* N has no right child --> + * successor is the nearest ancestor with a left child + * on path to N + */ + + RbNode * x = N; + + while(x) { + RbNode * P = x->parent(); + + if(P && P->left_child() == x) { + return P; + } + + /* path P..N traverses only right-child pointers) */ + x = P; + } + + /* no ancestor of N with a left child, so N has the largest key + * in the tree + */ + return nullptr; + } /*next_inorder_node*/ + + /* return node containing the key before N->key_ in the tree containing N. + * This will be either a descendant of N, or an ancestor of N + */ + static RbNode * prev_inorder_node(RbNode * N) { + if(!N) + return nullptr; + + if(N->left_child()) + return find_rightmost(N->left_child()); + + /* N has no left child --> + * predecessor is the nearest ancestor with a right child + * on path to N + */ + + RbNode * x = N; + + while(x) { + RbNode * P = x->parent(); + + if(P && (P->right_child() == x)) { + return P; + } + + /* path P..N traverses only left-child pointers */ + x = P; + } + + /* no ancestor of N with a right child, so N has the smallest key + * in tree that containing it. + */ + return nullptr; + } /*prev_inorder_node*/ + + /* compute value of reduce applied to the set K of all keys k[j] in subtree N + * with: + * k[j] <= lub_key if is_closed = true + * k[j] < lub_key if is_closed = false + * return reduce_fn.nil() if K is empty + */ + static ReducedValue reduce_lub(Key const & lub_key, + Reduce const & reduce_fn, + bool is_closed, + RbNode * N) + { + ReducedValue retval = reduce_fn.nil(); + + for (;;) { + if (!N) + return retval; + + if ((N->key() < lub_key) || (is_closed && (N->key() == lub_key))) { + /* all keys k[i] in left subtree of N satisfy k[i] < lub_key + * apply reduce to: + * - left subtree of N + * - N->key() depending on comparison with lub_key + * - any members of right subtree of N, with key < lub_key; + */ + retval = reduce_fn.combine(retval, N->reduced1()); + N = N->right_child(); + } else { + /* all keys k[j] in right subtree of N do NOT satisfy k[j] < + * lub_key, exclude these. also exclude N->key() + */ + N = N->left_child(); + } + } + } /*reduce_lub*/ + + /* find largest key k such that + * reduce({node j in subtree(N)) | j.key <= k}) < p + * + * ^ + * 1 | xxxx + * | xx + * p |....... x + * | x + * | xx . + * | xxxx . + * 0 +----------------> + * ^ + * find_cum_glb(p) + * + * here Key is a sample value, + * Value counts #of samples with that key. + * + * find_cum_glb() computes inverse for a monotonically increasing function, + * if reduce(S) = sum {j.value | j in S}; + * + * if rbtree stores values for a discrete function f: IR -> IR+, + * then x = find_sum_glb(p)->key() inverts the integral of f, i.e. + * computes: + * x + * / + * | + * sup { x: | f(z) dz < y } + * | + * / + * -oo + * + * Require: + * - Reduce behaves like sum: + * must deliver monotonically increasing values + * with increasing key-values. + * + * (for example: if Value is non-negative and Reduce is SumReduce) + */ + static RbNode * find_sum_glb(Reduce const & reduce_fn, + RbNode * N, + typename Reduce::value_type y) { + using xo::scope; + using xo::xtag; + + constexpr char const * c_self = "RbTreeUtil::find_sum_glb"; + constexpr bool c_logging_enabled = false; + scope log(XO_DEBUG(c_logging_enabled)); + + if(!N) { + log && log(c_self, ": return nullptr"); + return nullptr; + } + + typename Reduce::value_type left_sum + = RbNode::reduce_aux(reduce_fn, N->left_child()); + typename Reduce::value_type right_sum + = RbNode::reduce_aux(reduce_fn, N->right_child()); + + log && log("with", + xtag("y", y), + xtag("N.key", N->key()), + xtag("N.value", N->value()), + xtag("N.reduced1", N->reduced1()), + xtag("left_sum", left_sum), + xtag("right_sum", right_sum)); + + if (y <= left_sum) { + return find_sum_glb(reduce_fn, N->left_child(), y); + } else if (y <= N->reduced1() || !N->right_child()) { + log && log("return N"); + /* since N.reduced = reduce(left_sum, N.value, right_sum) */ + return N; + } else { + /* find bound in non-null right subtree */ + return find_sum_glb(reduce_fn, N->right_child(), y - N->reduced1()); + } + } /*find_sum_glb*/ + + /* starting from x, traverse only right children + * to find node with a nil right child + * + * This node has the largest key in subtree N + */ + static RbNode * find_rightmost(RbNode *N) { + while(N) { + RbNode *S = N->right_child(); + + if (!S) + break; + + N = S; + } + + return N; + } /*find_rightmost*/ + + /* find node in x with key k + * return nullptr iff no such node exists. + */ + static RbNode * find(RbNode * x, Key const & k) { + for (;;) { + if (!x) + return nullptr; + + if (k < x->key()) { + /* search in left subtree */ + x = x->left_child(); + } else if (k == x->key()) { + return x; + } else /* k > x->key() */ { + x = x->right_child(); + } + } + } /*find*/ + + /* find greatest lower bound for key k in tree x, + * provided it's tighter than candidate h. + * + * require: + * if h is provided, then x belongs to right subtree of h + * (so any key k' in x satisfies k' > h->key) + * + */ + static RbNode *find_glb_aux(RbNode *x, RbNode *h, Key const &k, + bool is_closed) { + for (;;) { + if (!x) + return h; + + if (x->key() < k) { + /* x.key is a lower bound for k */ + + if (x->right_child() == nullptr) { + /* no tighter lower bounds present in subtree rooted at x */ + + /* x must be better lower bound than h, + * since when h is non-nil we are searching right subtree of h + */ + return x; + } + + /* look for better lower bound in right child */ + h = x; + x = x->right_child(); + continue; + } else if (is_closed && (x->key() == k)) { + /* x.key is exact match */ + return x; + } else { + /* x.key is an upper bound for k. If there's a lower bound, + * it must be in left subtree of x + */ + + /* preserving h */ + x = x->left_child(); + continue; + } + } /*looping over tree nodes*/ + } /*find_glb_aux*/ + + /* find greatest lower bound node for a key, in this subtree + * + * is_open. if true, allow result with N->key = k exactly + * if false, require N->key < k + */ + static RbNode * find_glb(RbNode * x, Key const & k, bool is_closed) { + return find_glb_aux(x, nullptr, k, is_closed); + } /*find_glb*/ + +#ifdef NOT_IN_USE + /* find least upper bound node for a key, in this subtree* + * + * is_open. if true, allow result with N->key = k exactly + * if false, require N->key > k + */ + static RbNode *find_lub(RbNode *x, Key const &k, bool is_closed) { + if (x->key() > k) { + /* x.key is an upper bound for k */ + if (x->left_child() == nullptr) { + /* no tigher upper bound present in subtree rooted at x */ + return x; + } + + RbNode *y = find_lub(x->left_child(), k, is_closed); + + if (y) { + /* found better upper bound in left subtree */ + return y; + } else { + return x; + } + } else if (is_closed && (x->key() == k)) { + return x; + } else { + /* x.key is not an upper bound for k */ + return find_lub(x->right_child(), k, is_closed); + } + } /*find_lub*/ +#endif + + /* perform a tree rotation in direction d at node A. + * + * Require: + * - A is non-nil + * - A->child(other(d)) is non-nil + * + * if direction=D_Left: + * + * G G + * | | + * A B <- retval + * / \ / \ + * R B ==> A T + * / \ / \ + * S T R S + * + * if direction=D_Right: + * + * G G + * | | + * A B <- retval + * / \ / \ + * B R ==> T A + * / \ / \ + * T S S R + */ + static RbNode *rotate(Direction d, RbNode *A, + Reduce const & reduce_fn, + RbNode **pp_root) { + using xo::scope; + using xo::xtag; + + //constexpr char const *c_self = "RbTreeUtil::rotate"; + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG(c_logging_enabled)); + + Direction other_d = other(d); + + RbNode *G = A->parent(); + RbNode *B = A->child(other_d); + //RbNode *R = A->child(d); // not using + RbNode *S = B->child(d); + //RbNode *T = B->child(other_d); // not using + + if (log.enabled()) { + log("rotate-", (d == D_Left) ? "left" : "right", + " at", xtag("A", A), xtag("A.key", A->key()), xtag("B", B), + xtag("B.key", B->key())); + + if (G) { + log("with G", xtag("G", G), + xtag("G.key", G->key())); + // display_aux(D_Invalid /*side*/, G, 0, &lscope); + } else { + log("with A at root"); + // display_aux(D_Invalid /*side*/, A, 0, &lscope); + } + } + + /* note: this will set A's old child B to have null parent ptr */ + A->assign_child_reparent(other_d, S); + A->local_recalc_size(reduce_fn); + + B->assign_child_reparent(d, A); + B->local_recalc_size(reduce_fn); + + if (G) { + G->replace_child_reparent(A, B); + assert(B->parent() == G); + + /* note: G.size not affected by rotation */ + } else { + RbNode::replace_root_reparent(B, pp_root); + } + + return B; + } /*rotate*/ + + /* fixup size in N and all ancestors of N, + * after insert/remove affecting N + */ + static void fixup_ancestor_size(Reduce const & reduce_fn, RbNode *N) { + while (N) { + N->local_recalc_size(reduce_fn); + N = N->parent(); + } + } /*fixup_ancestor_size*/ + + /* rebalance to fix possible red-red violation at node G or G->child(d). + * + * diagrams are for d=D_Left; + * mirror left-to-right to get diagram for d=D_Right + * + * G + * d-> / \ <-other_d + * P U + * / \ + * R S + * + * relative to prevailing black-height h: + * - P at h + * - U at h + * - may have red-red violation between G and P + * + * Require: + * - tree is in RB-shape, except for possible red-red violation + * between {G,P} or {P,R|S} + * Promise: + * - tree is in RB-shape + */ + static void fixup_red_shape(Direction d, RbNode *G, + Reduce const & reduce_fn, + RbNode **pp_root) { + using xo::scope; + using xo::xtag; + using xo::print::ccs; + + //constexpr char const *c_self = "RbTreeUtil::fixup_red_shape"; + constexpr bool c_logging_enabled = false; + constexpr bool c_excessive_verify_enabled = false; + + scope log(XO_DEBUG(c_logging_enabled)); + + RbNode *P = G->child(d); + + for (uint32_t iter = 0;; ++iter) { + if (c_excessive_verify_enabled) + RbTreeUtil::verify_subtree_ok(reduce_fn, G, nullptr /*&black_height*/); + + if (log.enabled()) { + if (G) { + log("consider node G with d-child P", + xtag("iter", iter), xtag("G", G), + xtag("G.col", ccs((G->color() == C_Red) ? "r" : "B")), + xtag("G.key", G->key()), + xtag("d", ccs((d == D_Left) ? "L" : "R")), + xtag("P", P), + xtag("P.col", ccs((P->color() == C_Red) ? "r" : "B")), + xtag("P.key", P->key())); + } else { + log("consider root P", xtag("iter", iter), + xtag("P", P), + xtag("P.col", ccs((P->color() == C_Red) ? "r" : "B")), + xtag("P.key", P->key())); + } + + RbTreeUtil::display_aux(D_Invalid /*side*/, G ? G : P, 0 /*d*/, + &log); + } /*if logging enabled*/ + + if (G && G->is_red_violation()) { + log && log("red-red violation at G - defer"); + + /* need to fix red-red violation at next level up + * + * . (=G') + * | (=d') + * G* (=P') + * d-> / \ <-other-d + * P* U + * / \ + * R S + */ + P = G; + G = G->parent(); + d = RbNode::child_direction(G, P); + + continue; + } + + log && log("check for red violation at P"); + + if (!P->is_red_violation()) { + log && log("red-shape ok at {G,P}"); + + /* RB-shape restored */ + return; + } + + if (!G) { + log && log("make P black to fix red-shape at root"); + + /* special case: P is root of tree. + * can fix red violation by making P black + */ + P->assign_color(C_Black); + return; + } + + Direction other_d = other(d); + + RbNode *R = P->child(d); + RbNode *S = P->child(other_d); + RbNode *U = G->child(other_d); + + if (log.enabled()) { + log("got R,S,U", xtag("R", R), xtag("S", S), + xtag("U", U)); + if (R) { + log("with", + xtag("R.col", ccs(R->color_ == C_Black ? "B" : "r")), + xtag("R.key", R->key())); + } + if (S) { + log("with", + xtag("S.col", ccs(S->color_ == C_Black ? "B" : "r")), + xtag("S.key", S->key())); + } + if (U) { + log("with", + xtag("U.col", ccs(U->color_ == C_Black ? "B" : "r")), + xtag("U.key", U->key())); + } + } + + assert(is_black(G)); + assert(is_red(P)); + assert(is_red(R) || is_red(S)); + + if (RbNode::is_red(U)) { + /* if d=D_Left: + * + * *=red node + * + * . . (=G') + * | | (=d') + * G G* (=P') + * d-> / \ / \ + * P* U* ==> P U + * / \ / \ + * (*)R S(*) (*)R S(*) + * + * (*) exactly one of R or S is red (since we have a red-violation + * at P) + * + * Note: this transformation preserves #of black nodes along path + * from root to each of {T, R, S}, so it preserves the "equal + * black-node path" property + */ + G->assign_color(C_Red); + P->assign_color(C_Black); + U->assign_color(C_Black); + + log && log("fixed red violation at P, retry 1 level higher"); + + /* still need to check for red-violation at G's parent */ + P = G; + G = G->parent(); + d = RbNode::child_direction(G, P); + + continue; + } + + assert(RbNode::is_black(U)); + + if (RbNode::is_red(S)) { + log && log("rotate-", (d == D_Left) ? "left" : "right", + " at P", xtag("P", P), xtag("P.key", P->key()), + xtag("S", S), xtag("S.key", S->key())); + + /* preparatory step: rotate P in d direction if "inner child" + * (S) is red inner-child = right-child of left-parent or vice + * versa + * + * G G + * / \ / \ + * P* U ==> (P'=) S* U + * / \ / \ + * R S* (R'=) P* + * / \ + * R + */ + RbTreeUtil::rotate(d, P, reduce_fn, pp_root); + + if (c_excessive_verify_enabled) + RbTreeUtil::verify_subtree_ok(reduce_fn, S, nullptr /*&black_height*/); + + /* (relabel S->P etc. for merged control flow below) */ + R = P; + P = S; + } + + /* + * G P + * / \ / \ + * P* U ==> R* G* + * / \ / \ + * R* S S U + * + * ok since every path that went through previously-black G + * now goes through newly-black P + */ + P->assign_color(C_Black); + G->assign_color(C_Red); + + log && log("rotate-", + (other_d == D_Left) ? "left" : "right", " at G", + xtag("G", G), xtag("G.key", G->key())); + + RbTreeUtil::rotate(other_d, G, reduce_fn, pp_root); + + if (c_excessive_verify_enabled) { + RbNode *GG = G ? G->parent() : G; + if (!GG) + GG = P; + + if (log.enabled()) { + log("verify subtree at GG", xtag("GG", GG), + xtag("GG.key", GG->key())); + + RbTreeUtil::verify_subtree_ok(reduce_fn, GG, nullptr /*&black_height*/); + RbTreeUtil::display_aux(D_Invalid, GG, 0 /*depth*/, &log); + + log("fixup complete"); + } + } + + return; + } /*walk toward root until red violation fixed*/ + } /*fixup_red_shape*/ + + /* insert key-value pair (key, value) into *pp_root. + * on exit *pp_root contains new tree with (key, value) inserted. + * returns true if node was inserted, false if instead an existing node + * with the same key was replaced. + * + * Require: + * - pp_root is non-nil (*pp_root may be nullptr -> empty tree) + * - *pp_root is in RB-shape + * + * allow_replace_flag. if true, v will replace an existing value + * associated with key k. + * if false, preserve existing value. + * when k already exists in *pp_root. + * + * return pair with: + * - f=true for new node (k did not exist in tree before this call) + * - f=false for existing node (k already in tree before this call) + * - n=node containing key k + */ + static std::pair + insert_aux(value_type const & kv_pair, + bool allow_replace_flag, + Reduce const & reduce_fn, + RbNode ** pp_root) + { + using xo::xtag; + + //XO_SCOPE2(log, true /*debug_flag*/); + + RbNode * N = *pp_root; + + Direction d = D_Invalid; + + while (N) { + if (kv_pair.first == N->key()) { + if(allow_replace_flag) { + /* match on this key already present in tree + * -> just update assoc'd value + */ + N->contents_.second = kv_pair.second; + } + + /* after modifying a node n, must recalculate reductions + * along path [root .. n] + */ + RbTreeUtil::fixup_ancestor_size(reduce_fn, N); + + //log && log(xtag("path", (char const *)"A")); + + /* since we didn't change the set of nodes, + * tree is still in RB-shape, don't need to call fixup_red_shape() + */ + return std::make_pair(false, N); + } + + d = ((kv_pair.first < N->key()) ? D_Left : D_Right); + + /* insert into left subtree somewhere */ + RbNode *C = N->child(d); + + if (!C) + break; + + N = C; + } + + /* invariant: N->child(d) is nil */ + + if (N) { + RbNode * new_node = RbNode::make_leaf(kv_pair, + reduce_fn.leaf(kv_pair.second)); + + N->assign_child_reparent(d, new_node); + + assert(is_red(N->child(d))); + + /* recalculate Node sizes on path [root .. N] */ + RbTreeUtil::fixup_ancestor_size(reduce_fn, N); + /* after adding a node, must rebalance to restore RB-shape */ + RbTreeUtil::fixup_red_shape(d, N, reduce_fn, pp_root); + + //log && log(xtag("path", (char const *)"B")); + + /* note: new_node=N.child(d) is true before call to fixup_red_shape(), + * but not necessarily after + */ + return std::make_pair(true, new_node); + } else { + *pp_root = RbNode::make_leaf(kv_pair, + reduce_fn.leaf(kv_pair.second)); + + /* tree with a single node might as well be black */ + (*pp_root)->assign_color(C_Black); + + //(*pp_root)->local_recalc_size(reduce_fn); + + /* Node.size will be correct for tree, since + * new node is only node in the tree + */ + + //log && log(xtag("path", (char const *)"C")); + + return std::make_pair(true, *pp_root); + } + + } /*insert_aux*/ + + /* remove a black node N with no children. + * this will reduce black-height along path to N + * by 1, so will need to rebalance tree + * + * pp_root. pointer to location of tree root; + * may update with new root + * + * Require: + * - N != nullptr + * - N has no child nodes + * - N->parent() != nullptr + */ + static void remove_black_leaf(RbNode *N, + Reduce const & reduce_fn, + RbNode **pp_root) + { + using xo::scope; + using xo::xtag; + using xo::print::ccs; + + //constexpr char const *c_self = "RbTreeUtil::remove_black_leaf"; + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG(c_logging_enabled)); + + assert(pp_root); + + RbNode *P = N->parent(); + + if (!P) { + /* N was the root node, tree now empty */ + *pp_root = nullptr; + delete N; + return; + } + + /* d: direction in P to immediate child N; + * also sets N.parent to nil + */ + Direction d = P->replace_child_reparent(N, nullptr); + + delete N; + + /* need to delay this assignment until + * we've determined d + */ + N = nullptr; + + /* fixup sizes on path root..P + * subsequent rebalancing rotations will preserve correct .size values + */ + RbTreeUtil::fixup_ancestor_size(reduce_fn, P); + + /* other_d, S, C, D will be assigned by loop below + * + * diagram shown with d=D_Left; mirror left-to-right for d=D_Right + * + * P + * d-> / \ <-other_d + * N S + * / \ + * C D + */ + Direction other_d; + RbNode *S = nullptr; + RbNode *C = nullptr; + RbNode *D = nullptr; + + /* table of outcomes as a function of node color + * + * .=black + * *=red + * x=don't care + * + * #=#of combinations (/16) for P,S,C,D color explained by this row + * + * P S C D case # + * ----------------------- + * . . . . Case(1) 1 + * x * x x Case(3) 8 P,C,D black is forced by RB rules + * * . . . Case(4) 1 + * x . * . Case(5) 2 + * x . x * Case(6) 4 + * -- + * 16 + * + */ + + while (true) { + assert(is_black(N)); /* reminder: nil is black too */ + + /* Invariant: + * - either: + * - N is nil (first iteration only), and + * P->child(d) = nil, or: + * - P is nil and non-nil N is tree root, or: + * - N is an immediate child of P, + * and P->child(d) = N + * - N is black + * - all paths that don't go thru N have prevailing black-height h. + * - paths through N have black-height h-1 + */ + + if (!P) { + /* N is the root node, in which case all paths go through N, + * so black-height is h-1 + */ + *pp_root = N; + return; + } + + other_d = other(d); + S = P->child(other_d); + + /* S can't be nil: since N is non-nil and black, + * it must have a non-nil sibling + */ + assert(S); + + C = S->child(d); + D = S->child(other_d); + + if (log.enabled()) { + log("rebalance at parent P of curtailed subtree N", + xtag("P", P), + xtag("P.col", ccs(P->color() == C_Black ? "B" : "r")), + xtag("P.key", P->key())); + log("with sibling S, nephews C,D", xtag("S", S), + xtag("S.col", ccs(S->color() == C_Black ? "B" : "r")), + xtag("C", C), xtag("D", D)); + } + + if (is_black(P) && is_black(S) && is_black(C) && is_black(D)) { + /* Case(1) */ + + log && log("P,S,C,D all black: mark S red + go up 1 level"); + + /* diagram with d=D_Left: flip left-to-right for d=D_Right + * =black + * *=red + * _=red or black + * + * P + * / \ + * N S + * / \ + * C D + * + * relative to prevailing black-height h: + * - N at h-1 + * - C at h + * - D at h + */ + + S->assign_color(C_Red); + + /* now have: + * + * G (=P') + * | + * P (=N') + * / \ + * N S* + * / \ + * C D + * + * relative to prevailing black-height h: + * - N at h-1 + * - C at h-1 + * - D at h-1 + * + * relabel to one level higher in tree + */ + N = P; + P = P->parent(); + d = RbNode::child_direction(P, N); + + continue; + } else { + break; + } + } /*loop looking for a red node*/ + + if (is_red(S)) { + /* Case(3) */ + + if (log.enabled()) { + log("case 3: S red, P,C,D black -> rotate at P to promote S"); + log("case 3: + make P red instead of S"); + log("case 3: with", + xtag("P", P), + xtag("P.col", ccs(P->color() == C_Black ? "B" : "r")), + xtag("P.key", P->key()), xtag("S", S), + xtag("S.col", ccs(S->color() == C_Black ? "B" : "r")), + xtag("S.key", S->key())); + } + + /* since S is red, {P,C,D} are all black + * + * diagram with d=D_Left: flip left-to-right for d=D_Right + * =black + * *=red + * _=red or black + * + * P + * / \ + * N S* + * / \ + * C D + * + * relative to prevailing black-height h: + * - N at h-1 + * - C at h + * - D at h + */ + + assert(is_black(C)); + assert(is_black(D)); + assert(is_black(P)); + assert(is_black(N)); + + RbTreeUtil::rotate(d, P, reduce_fn, pp_root); + + /* after rotation d at P: + * + * S* + * / \ + * P D + * / \ + * N C + * + * relative to prevailing black-height h: + * - N at h-1 (now goes thru red S) + * - C at H (still goes through black P, red S) + * - D at h-1 (no longer goes thru black P) + */ + + P->assign_color(C_Red); + S->assign_color(C_Black); + + /* after reversing colors of {P,S}: + * + * S + * / \ + * P* D + * / \ + * N C (=S') + * + * relative to prevailing black-height h: + * - N at h-1 (now thru black S, red P instead of red S, black P) + * - C at h (now thru black S, red P instead of red S, black P) + * - D at h (now through black S instead of red S, black P) + */ + + /* now relabel for subsequent cases */ + S = C; + C = S ? S->child(d) : nullptr; + D = S ? S->child(other_d) : nullptr; + } + + assert(is_black(S)); + + if (is_red(P) && is_black(C) && is_black(D)) { + /* Case(4) */ + + if (log.enabled()) { + log("case 4: P red, N,S,C,D black -> recolor and finish"); + log("case 4: with", + xtag("P", P), + xtag("P.col", ccs(P->color() == C_Black ? "B" : "r")), + xtag("P.key", P->key()), xtag("S", S), + xtag("S.col", ccs(S->color() == C_Black ? "B" : "r")), + xtag("S.key", S->key())); + } + + assert(is_black(N)); + + /* diagram with d=D_Left: flip left-to-right for d=D_Right* + * =black + * *=red + * _=red or black + * + * P* + * / \ + * N S + * / \ + * C D + * + * relative to prevailing black-height h: + * - N at h-1 + * - C at h + * - D at h + */ + + P->assign_color(C_Black); + S->assign_color(C_Red); + + /* after making P black, and S red (swapping colors of P,S): + * + * P + * / \ + * N S* + * / \ + * C D + * + * relative to prevailing black-height h: + * - N at h + * - C at h + * - D at h + * + * and RB-shape is restored + */ + return; + } + + assert(is_black(S) && (is_black(P) || is_red(C) || is_red(D))); + + if (is_red(C) && is_black(D)) { + log && log("case 5: C red, S,D black -> rotate at S"); + + /* diagram with d=D_Left; flip left-to-right for d=D_Right + * + * =black + * *=red + * _=red or black + * + * P_ + * / \ + * N S + * / \ + * C* D + * + * relative to prevailing black-height h: + * - N at h-1 + * - C at h + * - D at h + */ + + RbTreeUtil::rotate(other_d, S, reduce_fn, pp_root); + + assert(P->child(other_d) == C); + + /* after other(d) rotation at S: + * + * P_ + * / \ + * N C* + * \ + * S + * \ + * D + * + * relative to prevailing black-height h: + * - N at h-1 + * - C at h-1 (no longer goes thru black S) + * - S at h (now goes thru red C) + * - D at h (now goes thru red C) + */ + + C->assign_color(C_Black); + S->assign_color(C_Red); + + /* after exchanging colors of C,S: + * + * P_ + * / \ + * N C (=S') + * \ + * S* (=D') + * \ + * D + * + * relative to prevailing black-height h: + * - N at h-1 + * - C at h (no longer goes thru black S, but now C black) + * - S at h (no longer red, but now goes thru black C) + * - D at h (now goes thru black C, red S instead of black S) + */ + + /* now relabel to match next and final case */ + D = S; + S = C; + C = nullptr; /* won't be using C past this point */ + + assert(D); + assert(D->is_red()); + + /* fall through to next case */ + } + + if (is_red(D)) { + log && log("case 6: S black, D red -> rotate at P and finish"); + + /* diagram with d=D_Left; flip left-to-right for d=D_Right + * + * Sibling is black, and distant child is red + * + * if N=P->left_child(): + * + * *=red + * _=red or black + * + * P_ + * / \ + * N S + * / \ + * C_ D* + * + * relative to prevailing black-height h: + * - N at h-1 + * - S (+also C,D) at h + */ + + RbTreeUtil::rotate(d, P, reduce_fn, pp_root); + + /* after rotate at P toward d: * + * + * S + * / \ + * P_ D* + * / \ + * N C_ + * + * Now, relative to prevailing black-height h: + * - N at h+1 (paths to N now visit black S) + * - C at h (paths to C still visit P,S) + * - D at: h if P red, + * h-1 if P black + * (paths to D now skip P) + */ + + S->assign_color(P->color()); + P->assign_color(C_Black); + D->assign_color(C_Black); + + /* after recolor: S to old P color, P to black, D to black. + * + * S_ + * / \ + * P D + * / \ + * N C_ + * + * Now, relative to prevailing black-height h: + * - N at h+1 (swapped P, S colors) + * - C at h (paths to C still visit P,S, swapped P,S colors) + * - D at: h if S red (was P red, S black, D red; now S red, D + * black) h if S black (was P black, S black, D red; now S + * black, D black) + * + * RB-shape has been restored + */ + return; + } + } /*remove_black_leaf*/ + + /* remove node with key k from tree rooted at *pp_root. + * on exit *pp_root contains new tree root. + * + * Require: + * - pp_root is non-null. (*pp_root can be null -> tree is empty) + * - *pp_root is in RB-shape + * + * return true if a node was removed; false otherwise. + */ + static bool erase_aux(Key const &k, + Reduce const & reduce_fn, + RbNode **pp_root) { + using xo::scope; + using xo::xtag; + + //constexpr char const *c_self = "RbTreeUtil::erase_aux"; + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG(c_logging_enabled)); + + RbNode *N = *pp_root; + + log && log("enter", xtag("N", N)); + + /* + * here the triangle ascii art indicates a tree structure, + * of arbitrary size + * + * o <- this + * / \ + * o-N-o + * / \ + * X + * / \ + * o---R + */ + + N = RbTreeUtil::find_glb(N, k, true /*is_closed*/); + + if (!N || (N->key() != k)) { + /* no node with .key = k present, so cannot remove it */ + return false; + } + + if (c_logging_enabled) + log && log("got lower bound", xtag("N", N), + xtag("N.key", N->key())); + + /* first step is to simplify problem so that we're removing + * a node with 0 or 1 children. + */ + + RbNode *X = N->left_child(); + + if (X == nullptr) { + /* N has 0 or 1 children */ + ; + } else { + /* R will be 'replacement node' for N */ + RbNode *R = RbTreeUtil::find_rightmost(X); + + /* R->right_child() is nil by definition + * + * copy R's (key + value) into N; + * N now serves as container for information previously + * represented by R. + */ + + N->contents_ = R->contents_; + /* (preserving N->parent_, N->child_v_[]) */ + + /* now relabel N as new R (R'), + * and relabel R as new N (N'). + * Then go to work on reduced problem of deleting N'. + * Problem is redueced since now N' has 0 or 1 child. + * + * (Doesn't matter that N' contains key,values of R, + * since we're going to delete it anyway) + */ + N = R; + /* (preserving R->parent_, R->child_v_[]) */ + + /* o + * / \ + * o-R'o + * / + * X + * / \ + * o---N' + */ + } + + RbNode *P = N->parent(); + + /* N has 0 or 1 children + * + * Implications: + * 1. if N is red, it cannot have red children (by RB rules), + * and it cannot have just 1 black child. + * Therefore red N must have 0 children + * -> can delete N without disturbing RB properties + * 2. if N is black: + * 2.1 if N has 1 child S, then S must be red + * (if S were black, that would require N to have a 2nd child + * to preserve equal black-height for all paths) + * -> replace N with S, repainting S black, in place of + * to-be-reclaimed N + * 1.2 if N is black with 0 children, need to rebalance + */ + + if (N->is_red()) { + if (N->is_leaf()) { + /* replace pointer to N with nil in N's parent. */ + + if (P) { + P->replace_child_reparent(N, nullptr); + RbTreeUtil::fixup_ancestor_size(reduce_fn, P); + } else { + /* N was sole root node; tree will be empty after removing it */ + *pp_root = nullptr; + } + + if (c_logging_enabled) + log && log("delete node", xtag("addr", N)); + delete N; + } else { + assert(false); + + /* control can't come here for RB-tree, + * because a red node can't have red children, or just one black + * child. + */ + } + } else /*N->is_black()*/ { + RbNode *R = N->left_child(); + + if (!R) + R = N->right_child(); + + if (R) { + /* if a black node has one child, that child cannot be black */ + assert(R->is_red()); + + /* replace N with R in N's parent, + * + make R black to preserve black-height + */ + R->assign_color(C_Black); + + if (P) { + P->replace_child_reparent(N, R); + RbTreeUtil::fixup_ancestor_size(reduce_fn, P); + } else { + /* N was root node */ + RbNode::replace_root_reparent(R, pp_root); + } + + if (c_logging_enabled) + log && log("delete node", xtag("addr", N)); + delete N; + } else { + /* N is black with no children, + * may need rebalance here + */ + + if (P) { + RbTreeUtil::remove_black_leaf(N, reduce_fn, pp_root); + } else { + /* N was root node */ + *pp_root = nullptr; + + log && log("delete node", xtag("addr", N)); + delete N; + } + } + } + + return true; + } /*erase_aux*/ + + /* verify that subtree at N is in RB-shape. + * will cover subset of RedBlackTree class invariants: + * + * RB2. if N = P->child(d), then N->parent()=P + * RB3. all paths to leaves have the same black height + * RB4. no red node has a red parent + * RB5. inorder traversal visits keys in monotonically increasing order + * RB6. Node::size reports the size of the subtree reachable from that node + * via child pointers + * RB7. Node::reduced reports the value of + * f(f(L, Node::value), R) + * where: L is reduced-value for left child, + * R is reduced-value for right child + * + * returns the #of nodes in subtree rooted at N. + */ + static size_t verify_subtree_ok(Reduce const & reduce_fn, + RbNode const * N, + int32_t * p_black_height) + { + using xo::scope; + using xo::xtag; + using xo::print::ccs; + + constexpr char const *c_self = "RbTreeUtil::verify_subtree_ok"; + + // scope lscope(c_self); + + /* counts #of nodes in subtree rooted at N */ + size_t i_node = 0; + Key const *last_key = nullptr; + /* inorder node index when establishing black_height */ + size_t i_black_height = 0; + /* establish on first leaf node encountered */ + uint32_t black_height = 0; + + auto verify_fn = [c_self, + &reduce_fn, + &i_node, + &last_key, + &i_black_height, + &black_height] (RbNode const *x, + uint32_t bd) + { + /* RB2. if c=x->child(d), then c->parent()=x */ + + if (x->left_child()) { + XO_EXPECT(x == x->left_child()->parent(), + tostr(c_self, (": expect symmetric child/parent pointers"), + xtag("i", i_node), xtag("node[i]", x), + xtag("key[i]", x->key()), + xtag("child", x->left_child()), + xtag("child.key", x->left_child()->key()), + xtag("child.parent", x->left_child()->parent_))); + } + + if (x->right_child()) { + XO_EXPECT(x == x->right_child()->parent(), + tostr(c_self, ": expect symmetric child/parent pointers", + xtag("i", i_node), + xtag("node[i]", x), + xtag("key[i]", x->key()), + xtag("child", x->right_child()), + xtag("child.key", x->right_child()->key()), + xtag("child.parent", x->right_child()->parent_))); + } + + /* RB3. all nodes have the same black-height */ + + if (x->is_leaf()) { + if (black_height == 0) { + black_height = bd; + } else { + XO_EXPECT(black_height == bd, + tostr(c_self, + ": expect all RB-tree nodes to have the same " + "black-height", + xtag("i1", i_black_height), xtag("i2", i_node), + xtag("blackheight(i1)", black_height), + xtag("blackheight(i2)", bd))); + } + } + + /* RB4. a red node may not have a red parent + * (conversely, a red node may not have a red child) + */ + + RbNode *red_child = + ((x->left_child() && x->left_child()->is_red()) + ? x->left_child() + : ((x->right_child() && x->right_child()->is_red()) + ? x->right_child() + : nullptr)); + + XO_EXPECT( + x->is_red_violation() == false, + tostr(c_self, + ccs(": expect RB-shape tree to have no red violations but " + "red y is child of red x"), + xtag("i", i_node), xtag("x.addr", x), + xtag("x.col", ccs((x->color_ == C_Black) ? "B" : "r")), + xtag("x.key", x->key()), + xtag("y.addr", red_child), + xtag("y.col", ccs((red_child->color_ == C_Black) ? "B" : "r")), + xtag("y.key", red_child->key()))); + + /* RB5. inorder traversal visits nodes in strictly increasing key order */ + + if (last_key) { + XO_EXPECT((*last_key) < x->key(), + tostr(c_self, + ": expect inorder traversal to visit keys" + " in strictly increasing order", + xtag("i", i_node), xtag("key[i-1]", *last_key), + xtag("key[i]", x->key()))); + } + + last_key = &(x->key()); + + /* RB6. Node::size reports the size of the subtree reachable from that + * node by child pointers. + */ + XO_EXPECT(x->size() == (tree_size(x->left_child()) + + 1 + + tree_size(x->right_child())), + tostr(c_self, + ": expect Node::size to be 1 + sum of childrens' size", + xtag("i", i_node), + xtag("key[i]", x->key()), + xtag("left.size", tree_size(x->left_child())), + xtag("right.size", tree_size(x->right_child())))); + + /* RB7. Node::reduced reports the value of + * f(f(L, Node::value), R) + * where: L is reduced-value for left child, + * R is reduced-value for right child + */ + auto reduced_pair + = RbNode::reduced_pair(reduce_fn, x); + + XO_EXPECT(reduce_fn.is_equal + (x->reduced1(), reduced_pair.first), + tostr(c_self, + ": expect Node::reduced to be reduce_fn" + " applied to (.L, .value)", + xtag("node.reduced1", x->reduced1()), + xtag("reduced_pair.first", reduced_pair.first))); + + XO_EXPECT(reduce_fn.is_equal + (x->reduced2(), reduced_pair.second), + tostr(c_self, + ": expect Node::reduced to be reduce_fn" + " applied to (.L, .value, .R)", + xtag("node.reduced2", x->reduced2()), + xtag("reduce2_expr", reduced_pair.second))); + + ++i_node; + }; + + RbTreeUtil::inorder_node_visitor(N, 0 /*d*/, verify_fn); + + if (p_black_height) + *p_black_height = black_height; + + return i_node; + } /*verify_subtree_ok*/ + + /* display tree structure, 1 line per node. + * indent by node depth + d + */ + static void display_aux(Direction side, RbNode const *N, uint32_t d, + xo::scope *p_scope) { + using xo::pad; + using xo::xtag; + using xo::print::ccs; + + if (N) { + p_scope->log(pad(d), + xtag("addr", N), + xtag("par", N->parent()), + xtag("side", ccs((side == D_Left) ? "L" + : (side == D_Right) ? "R" + : "root")), + xtag("col", ccs(N->is_black() ? "B" : "r")), + xtag("key", N->key()), + xtag("value", N->value()), + xtag("wt", N->size()), + xtag("reduced1", N->reduced1()), + xtag("reduced2", N->reduced2())); + display_aux(D_Left, N->left_child(), d + 1, p_scope); + display_aux(D_Right, N->right_child(), d + 1, p_scope); + } + } /*display_aux*/ + + static void display(RbNode const *N, uint32_t d) { + using xo::scope; + + scope log(XO_DEBUG(true /*debug_flag*/)); + + display_aux(D_Invalid, N, d, &log); + } /*display*/ + }; /*RbTreeUtil*/ + + /* xo::tree::detail::RedBlackTreeLhsBase + * + * use for const version of RedBlackTree::operator[]. + * + * Require: RbNode is either + * RedBlackTree::RbNode + * or + * RedBlackTree::RbNode const + */ + template + class RedBlackTreeLhsBase { + public: + using mapped_type = typename RedBlackTree::mapped_type; + using RbUtil = typename RedBlackTree::RbUtil; + + public: + RedBlackTreeLhsBase() = default; + RedBlackTreeLhsBase(RedBlackTree * tree, RbNode * node) + : p_tree_(tree), node_(node) + {} + + operator mapped_type const & () const { + using xo::tostr; + + if (!this->node_) { + throw std::runtime_error + (tostr("rbtree: attempt to use empty lhs object as rvalue")); + } + + return this->node_->contents().second; + } /*operator value_type const &*/ + + protected: + RedBlackTree * p_tree_ = nullptr; + /* invariant: if non-nil, .node belongs to .*p_tree */ + RbNode * node_ = nullptr; + }; /*RedBlackTreeLhsBase*/ + + template + class RedBlackTreeConstLhs : public RedBlackTreeLhsBase + { + public: + RedBlackTreeConstLhs() = default; + RedBlackTreeConstLhs(RedBlackTree const * tree, + typename RedBlackTree::RbNode const * node) + : RedBlackTreeLhsBase(tree, node) {} + }; /*RedBlackTreeConstLhs*/ + + /* xo::tree::detail::RedBlackTreeLhs + * + * use for RedBlackTree::operator[]. + * can't return a regular lvalue, + * because assignment within a Node N invalidates partial sums along + * the path from tree root to N. + * + * instead interpolate instance of this class, that can intercept + * asasignments. + */ + template + class RedBlackTreeLhs : public RedBlackTreeLhsBase + { + public: + using value_type = typename RedBlackTree::value_type; + using key_type = typename RedBlackTree::key_type; + using mapped_type = typename RedBlackTree::mapped_type; + using RbUtil = typename RedBlackTree::RbUtil; + using RbNode = typename RedBlackTree::RbNode; + + public: + RedBlackTreeLhs() = default; + RedBlackTreeLhs(RedBlackTree * tree, typename RedBlackTree::RbNode * node, key_type key) + : RedBlackTreeLhsBase(tree, node), key_(key) {} + + RedBlackTreeLhs & operator=(mapped_type const & v) { + using xo::tostr; + + if(this->p_tree_) { + if(this->node_) { + this->node_->contents().second = v; + + /* after modifying a node n, + * must recalculate reductions along path [root .. n] + */ + RbUtil::fixup_ancestor_size(this->p_tree_->reduce_fn(), + this->node_); + } else { + /* insert (key, v) pair into this tree */ + this->p_tree_->insert(value_type(this->key_, v)); + } + } else { + assert(false); + + throw std::runtime_error + (tostr("rbtree: attempt to apply operator= thru empty lhs object")); + } + + return *this; + } /*operator=*/ + + RedBlackTreeLhs & operator+=(mapped_type const & v) { + using xo::tostr; + + if(this->p_tree_) { + if(this->node_) { + this->node_->contents().second += v; + + /* after modifying value at node n, + * must recalculate order statistics along path [root .. n] + */ + RbUtil::fixup_ancestor_size(this->p_tree_->reduce_fn(), + this->node_); + } else { + /* for form's sake, in case value_type is something unusual */ + mapped_type v2; + v2 += v; + + /* insert (key, v) pair into this tree */ + this->p_tree_->insert(value_type(this->key_, v2)); + } + } else { + assert(false); + + throw std::runtime_error + (tostr("rbtree: attempt to apply operator+= through empty lhs object")); + } + + return *this; + } /*operator+=*/ + + /* TODO: + * - operator-=() + * - operator*=() + * - operator/=() + */ + + private: + /* capture key k used in expression tree[k] + * Invariant: + * - if .node is non-null, then .node.key = key + */ + key_type key_; + }; /*RedBlackTreeLhs*/ + + /* tragically, we can't partially specialize an alias template. + * however we /can/ partially specialize a struct that nests a typealias. + */ + template + struct NodeTypeTraits { using NodeType = void; }; + + template + struct NodeTypeTraits { + using NativeNodeType = Node; + using NodeType = NativeNodeType; + using ContentsType = typename NodeType::ContentsType; + using NodePtrType = NodeType *; + }; + + template + struct NodeTypeTraits { + using NativeNodeType = Node; + using NodeType = NativeNodeType const; + using ContentsType = typename NodeType::ContentsType const; + using NodePtrType = NodeType const *; + }; + + /* xo::tree::detail::IteratorBase + * + * shared between const & and non-const red-black-tree iterators. + * + * editor bait: BaseIterator + */ + template + class IteratorBase { + public: + using RbUtil = RbTreeUtil; + using RbNode = Node; + using Traits = NodeTypeTraits; + using ReducedValue = typename Reduce::value_type; + using RbNativeNodeType = typename Traits::NativeNodeType; + using RbNodePtrType = typename Traits::NodePtrType; + using RbContentsType = typename Traits::ContentsType; + + protected: + IteratorBase() = default; + IteratorBase(IteratorDirection dirn, IteratorLocation loc, RbNodePtrType node) + : dirn_{dirn}, location_{loc}, node_{node} {} + IteratorBase(IteratorBase const & x) = default; + + static IteratorBase prebegin_aux(RbNodePtrType node) { + return IteratorBase(ID_Forward, IL_BeforeBegin, node); + } /*prebegin_aux*/ + + static IteratorBase begin_aux(RbNodePtrType node) { + return IteratorBase(ID_Forward, node ? IL_Regular : IL_AfterEnd, node); + } /*begin_aux*/ + + static IteratorBase end_aux(RbNodePtrType node) { + return IteratorBase(ID_Forward, IL_AfterEnd, node); + } /*end_aux*/ + + static IteratorBase rprebegin_aux(RbNodePtrType node) { + return IteratorBase(ID_Reverse, IL_AfterEnd, node); + } /*rprebegin_aux*/ + + static IteratorBase rbegin_aux(RbNodePtrType node) { + return IteratorBase(ID_Reverse, + (node ? IL_Regular : IL_BeforeBegin), + node); + } /*rbegin_aux*/ + + static IteratorBase rend_aux(RbNodePtrType node) { + return IteratorBase(ID_Reverse, + IL_BeforeBegin, + node); + } /*rend_aux*/ + + public: + IteratorLocation location() const { return location_; } + RbNodePtrType node() const { return node_; } + + ReducedValue const & reduced() const { return node_->reduced(); } + + RbContentsType & operator*() const { + this->check_regular(); + return this->node_->contents(); + } /*operator**/ + + RbContentsType * operator->() const { + return &(this->operator*()); + } + + /* true for "just before beginning" and "just after the end" states. + * false otherwise + */ + bool is_sentinel() const { return (this->location_ != IL_Regular); } + /* true unless iterator is in a sentinel state */ + bool is_dereferenceable() const { return !this->is_sentinel(); } + + /* deferenceable iterators are truth-y; + * sentinel iterators are false-y + */ + operator bool() const { return this->is_dereferenceable(); } + + bool operator==(IteratorBase const & x) const { + return (this->location_ == x.location_) && (this->node_ == x.node_); + } /*operator==*/ + + bool operator!=(IteratorBase const & x) const { + return (this->location_ != x.location_) || (this->node_ != x.node_); + } /*operator!=*/ + + void print(std::ostream & os) const { + using xo::xtag; + + os << ""; + } /*print*/ + + /* pre-increment */ + IteratorBase & operator++() { + return ((this->dirn_ == ID_Forward) + ? this->next_step() + : this->prev_step()); + } /*operator++*/ + + /* pre-decrement */ + IteratorBase & operator--() { + return ((this->dirn_ == ID_Forward) + ? this->prev_step() + : this->next_step()); + } /*operator--*/ + + protected: + void check_regular() const { + using xo::tostr; + + if(this->location_ != IL_Regular) + throw std::runtime_error(tostr("rbtree iterator: cannot deref iterator" + " in non-regular state")); + } /*check_regular*/ + + private: + IteratorBase & next_step() { + switch(this->location_) { + case IL_BeforeBegin: + /* .node is first node in tree */ + this->location_ = IL_Regular; + break; + case IL_Regular: + { + RbNodePtrType next_node + = RbUtil::next_inorder_node(const_cast(this->node_)); + + if(next_node) { + this->node_ = next_node; + } else { + this->location_ = IL_AfterEnd; + } + } + break; + case IL_AfterEnd: + break; + } /*operator++*/ + + return *this; + } /*next_step*/ + + IteratorBase & prev_step() { + switch(this->location_) { + case IL_BeforeBegin: + break; + case IL_Regular: + { + RbNode * prev_node = RbUtil::prev_inorder_node(const_cast(this->node_)); + + if(prev_node) { + this->node_ = prev_node; + } else { + this->location_ = IL_BeforeBegin; + } + } + break; + case IL_AfterEnd: + /* .node is already last node in tree */ + this->location_ = IL_Regular; + break; + } + + return *this; + } /*prev_step*/ + + protected: + /* ID_Forward, ID_Reverse */ + IteratorDirection dirn_ = ID_Forward; + /* IL_BeforeBegin, IL_Regular, IL_AfterEnd */ + IteratorLocation location_ = IL_AfterEnd; + /* location = IL_BeforeBegin: .node is leftmost node in tree + * location = IL_Regular: .node is some node in tree, + * iterator refers to that node. + * location = IL_AfterEnd: .node is rightmost node in tree + */ + RbNodePtrType node_ = nullptr; + }; /*IteratorBase*/ + + /* xo::tree::detail::Iterator + * + * inorder iterator over nodes in a red-black tree. + * invalidated on insert or remove operations on the parent tree. + * + * satisfies the std::bidirectional_iterator concept + */ + template + class Iterator : public IteratorBase { + public: + using iterator_concept = std::bidirectional_iterator_tag; + + using RbIteratorBase = IteratorBase; + using RbNode = typename RbIteratorBase::RbNode; + using RbUtil = typename RbIteratorBase::RbUtil; + using ReducedValue = typename Reduce::value_type; + + public: + Iterator() = default; + Iterator(IteratorDirection dirn, IteratorLocation loc, RbNode * n) + : RbIteratorBase(dirn, loc, n) {} + Iterator(Iterator const & x) = default; + Iterator(RbIteratorBase const & x) : RbIteratorBase(x) {} + Iterator(RbIteratorBase && x) : RbIteratorBase(std::move(x)) {} + + static Iterator begin_aux(RbNode const * n) { return RbIteratorBase::begin_aux(n); } + static Iterator end_aux(RbNode const * n) { return RbIteratorBase::end_aux(n); } + + static Iterator rbegin_aux(RbNode const * n) { return RbIteratorBase::rbegin_aux(n); } + static Iterator rend_aux(RbNode const * n) { return RbIteratorBase::rend_aux(n); } + + /* pre-increment */ + Iterator & operator++() { + RbIteratorBase::operator++(); + return *this; + } /*operator++*/ + + /* post-increment */ + Iterator operator++(int) { + Iterator retval = *this; + + ++(*this); + + return retval; + } /*operator++(int)*/ + + /* pre-decrement */ + Iterator & operator--() { + RbIteratorBase::operator--(); + return *this; + } /*operator--*/ + + /* post-decrement */ + Iterator operator--(int) { + Iterator retval = *this; + + --(*this); + + return retval; + } /*operator--(int)*/ + }; /*Iterator*/ + + /* xo::tree::detail::ConstIterator + * + * inorder iterator over nodes in a red-black tree. + * invalidated on insert or remove operations on the parent tree. + * + * satisfies the std::bidirectional_iterator concept + */ + template + class ConstIterator : public IteratorBase { + public: + using iterator_concept = std::bidirectional_iterator_tag; + + using RbIteratorBase = IteratorBase; + using RbNode = typename RbIteratorBase::RbNode; + using RbUtil = typename RbIteratorBase::RbUtil; + using ReducedValue = typename Reduce::value_type; + + public: + ConstIterator() = default; + ConstIterator(IteratorDirection dirn, IteratorLocation loc, RbNode const * node) + : RbIteratorBase(dirn, loc, node) {} + ConstIterator(ConstIterator const & x) = default; + ConstIterator(RbIteratorBase const & x) : RbIteratorBase(x) {} + ConstIterator(RbIteratorBase && x) : RbIteratorBase(std::move(x)) {} + + static ConstIterator prebegin_aux(RbNode const * n) { return RbIteratorBase::prebegin_aux(n); } + static ConstIterator begin_aux(RbNode const * n) { return RbIteratorBase::begin_aux(n); } + static ConstIterator end_aux(RbNode const * n) { return RbIteratorBase::end_aux(n); } + + static ConstIterator rprebegin_aux(RbNode const * n) { return RbIteratorBase::rprebegin_aux(n); } + static ConstIterator rbegin_aux(RbNode const * n) { return RbIteratorBase::rbegin_aux(n); } + static ConstIterator rend_aux(RbNode const * n) { return RbIteratorBase::rend_aux(n); } + + /* pre-increment */ + ConstIterator & operator++() { + RbIteratorBase::operator++(); + return *this; + } /*operator++*/ + + /* post-increment */ + ConstIterator operator++(int) { + ConstIterator retval = *this; + + ++(*this); + + return retval; + } /*operator++(int)*/ + + /* pre-decrement */ + ConstIterator & operator--() { + RbIteratorBase::operator--(); + return *this; + } /*operator--*/ + + /* post-decrement */ + ConstIterator operator--(int) { + ConstIterator retval = *this; + + --(*this); + + return retval; + } /*operator--(int)*/ + }; /*ConstIterator*/ + } /*namespace detail*/ + + struct null_reduce_value {}; + + /* for null reduce, just have it return empty struct; + * otherwise breaks verification (e.g. verify_subtree_ok() below) + */ + template + struct NullReduce { + static constexpr bool is_null_reduce() { return true; } + static constexpr bool is_monotonic() { return false; } + + /* data type for reduced values */ + using value_type = null_reduce_value; + + value_type nil() const { return value_type(); } + value_type leaf(NodeValue const & /*x*/) const { + return nil(); + } + value_type operator()(value_type /*x*/, + NodeValue const & /*value*/) const { return nil(); } + value_type combine(value_type /*x*/, + value_type /*y*/) const { return nil(); } + bool is_equal(value_type /*x*/, value_type /*y*/) const { return true; } + }; /*NullReduce*/ + + inline std::ostream & operator<<(std::ostream & os, + null_reduce_value /*x*/) + { + os << "{}"; + return os; + } /*operator<<*/ + + /* just counts #of distinct values; + * redundant, same as detail::Node<>::size_. + * providing for completeness' sake + */ + template + class OrdinalReduce { + public: + using value_type = std::size_t; + + public: + static constexpr bool is_monotonic() { return true; } + + value_type nil() const { return 0; } + + value_type leaf(Value const & /*x*/) const { + return 1; + } /*leaf*/ + + value_type operator()(value_type acc, + Value const & /*x*/) const { + /* counts #of values */ + return acc + 1; + } + + value_type combine(value_type x, value_type y) const { return x + y; } + bool is_equal(value_type x, value_type y) const { return x == y; } + }; /*OrdinalReduce*/ + + /* reduction for inverting the integral of a non-negative discrete function + * computes sum of values for each subtree + */ + template + struct SumReduce { + using value_type = Value; + + static constexpr bool is_monotonic() { return true; } + + value_type nil() const { return -std::numeric_limits::infinity(); } + value_type leaf(Value const & x) const { + return x; + } /*leaf*/ + + value_type operator()(value_type reduced, + Value const & x) const { + /* sums tree values */ + if(std::isfinite(reduced)) { + return reduced + x; + } else { + /* omit -oo reduced value from .nil() */ + return x; + } + } /*operator()*/ + + value_type combine(value_type const & x, + value_type const & y) const { + /* omit -oo reduced value from .nil() */ + if(!std::isfinite(x)) + return y; + if(!std::isfinite(y)) + return x; + + return x + y; + } /*combine*/ + + bool is_equal(value_type const & x, value_type const & y) const { return x == y; } + }; /*SumReduce*/ + + /* red-black tree with order statistics + */ + template + class RedBlackTree { + static_assert(ReduceConcept); + //static_assert(requires(Reduce r) { r.nil(); }, "missing .nil() method"); + + public: + using key_type = Key; + using mapped_type = Value; + using value_type = std::pair; + using ReducedValue = typename Reduce::value_type; + using RbTreeLhs = detail::RedBlackTreeLhs>; + using RbTreeConstLhs = detail::RedBlackTreeConstLhs>; + using RbUtil = detail::RbTreeUtil; + using RbNode = detail::Node; + using Direction = detail::Direction; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using iterator = detail::Iterator; + using const_iterator = detail::ConstIterator; + + public: + RedBlackTree() = default; + + bool empty() const { return size_ == 0; } + size_type size() const { return size_; } + size_type max_size() const { return std::numeric_limits::max(); } + Reduce const & reduce_fn() const { return reduce_fn_; } + + /* forward const iterators (canonical names) */ + + /* iterator "one before beginning" */ + const_iterator cprebegin() const { + return const_iterator::prebegin_aux(RbUtil::find_leftmost(this->root_)); + } /*cprebegin*/ + + const_iterator cbegin() const { + return const_iterator::begin_aux(RbUtil::find_leftmost(this->root_)); + } /*begin*/ + + const_iterator cend() const { + return const_iterator::end_aux(RbUtil::find_rightmost(this->root_)); + } /*end*/ + + /* forward const iterators (overloaded names) */ + + const_iterator prebegin() const { return this->cprebegin(); } + const_iterator begin() const { return this->cbegin(); } + const_iterator end() const { return this->cend(); } + + /* forward non-const iterators */ + + iterator prebegin() { + return iterator::prebegin_aux(RbUtil::find_leftmost(this->root_)); + } /*prebegin*/ + + iterator begin() { + return iterator::begin_aux(RbUtil::find_leftmost(this->root_)); + } /*begin*/ + + iterator end() { + return iterator::end_aux(RbUtil::find_rightmost(this->root_)); + } /*end*/ + + /* reverse const iterators (canonical names) */ + + /* reverse-iterator, "one after end" */ + const_iterator crprebegin() const { + return const_iterator::rprebegin_aux(RbUtil::find_rightmost(this->root_)); + } /*crprebegin*/ + + const_iterator crbegin() const { + return const_iterator::rbegin_aux(RbUtil::find_rightmost(this->root_)); + } /*crbegin*/ + + const_iterator crend() const { + return const_iterator::rend_aux(RbUtil::find_leftmost(this->root_)); + } /*crend*/ + + /* reverse const iterators (overloaded names) */ + + const_iterator rprebegin() const { return this->crprebegin(); } + const_iterator rbegin() const { return this->crbegin(); } + const_iterator rend() const { return this->crend(); } + + /* reverse non-const iterators */ + + iterator rprebegin() { + return iterator::rprebegin_aux(RbUtil::find_rightmost(this->root_)); + } /*rprebegin*/ + + iterator rbegin() { + return iterator::rbegin_aux(RbUtil::find_rightmost(this->root_)); + } /*rbegin*/ + + iterator rend() { + return iterator::rend_aux(RbUtil::find_leftmost(this->root_)); + } /*rend*/ + + /* require: + * - .size() > 0 + */ + Key const & min_key() const { return this->cbegin().first; } + /* require: + * - .size() > 0 + */ + Key const & max_key() const { const_iterator ix = this->cend(); --ix; return ix->first; } + + /* visit tree contents in increasing key order + * + * Require: + * - Fn(std::pair const &) + */ + template + void visit_inorder(Fn && fn) { + auto visitor_fn = [&fn](RbNode const * x, uint32_t /*d*/) { fn(x->contents()); }; + + RbUtil::inorder_node_visitor(this->root_, + 0 /*depth -- will be ignored*/, + visitor_fn); + } /*visit_inorder*/ + + /* if i in [0 .. .size], return iterator referring to ith inorder node in tree + * otherwise return this->end() + */ + const_iterator find_ith(uint32_t i) const { + RbNode * node = RbUtil::find_ith(this->root_, i); + + if(node) { + return const_iterator(detail::ID_Forward, detail::IL_Regular, node); + } else { + return this->end(); + } + } /*find_ith*/ + + iterator find_ith(uint32_t i) { + RbNode * node = RbUtil::find_ith(this->root_, i); + + if(node) { + return iterator(detail::IL_Regular, node); + } else { + return this->end(); + } + } /*find_ith*/ + + /* find node with key equal to x in this tree. + * on success, return iterator ix with ix->first = x. + * on failure, return this->end() + */ + const_iterator find(Key const & x) const { + RbNode * node = RbUtil::find(this->root_, x); + + if(node) { + return const_iterator(detail::ID_Forward, detail::IL_Regular, node); + } else { + return this->end(); + } + } /*find*/ + + iterator find(Key const & x) { + RbNode * node = RbUtil::find(this->root_, x); + + if (node) { + return const_iterator(detail::ID_Forward, detail::IL_Regular, node); + } else { + return this->end(); + } + } /*find*/ + + /* find node in tree with largest key k such that: + * k <= x, if is_closed + * k < x, if !is_closed + * + * return iterator to that node. + * + * If no such node exists, return the same value as this->cprebegin(); + * + * This satisfies continuity property: + * if: ix = find_glb(k, is_closed), + * then: ix+1 = find_lub(k, !is_closed) + * + * even when ix.is_dereferenceable() is false + */ + const_iterator find_glb(Key const & k, bool is_closed) const { + RbNode * node = RbUtil::find_glb(this->root_, k, is_closed); + + if (node) { + return const_iterator(detail::ID_Forward, + detail::IL_Regular, + node); + } else { + return this->cprebegin(); + } + } /*find_glb*/ + + const_iterator find_lub(Key const & k, bool is_closed) const { + const_iterator ix = this->find_glb(k, !is_closed); + return ++ix; + } /*find_lub*/ + + /* RbTreeConstLhs provides rvalue-substitute for lookup-only in const RedBlackTree + * instances + */ + RbTreeConstLhs operator[](Key const & k) const + { + RbNode const * node = RbUtil::find(this->root_, k); + + return RbTreeConstLhs(this, node); + } /*operator[]*/ + + /* RbTreeLhs defers assignment, so that rbtree can update values of + * Node::reduce along path from root to Node n with n.key = k + * + * + * Note: + * 1. return value remains valid across subsequent inserts and assignments, + * so this is legal: + * RbTree rbtree = ...; + * auto v = rbtree[key1]; + * + * rbtree[key2] = ...; + * rbtree.insert(key3, value3); + * + * v = ...; + * + * 2. return value is not valid across removes, even of distinct keys, + * so this is ILLEGAL: + * RbTree rbtree = ...; + * auto v = rbtree[key1]; + * + * assert(key1 != key2); + * + * rbtree.remove(key2); + * + * v = ...; // undefined behavior, + * // v.node contents may have been copied and v.node deleted + */ + RbTreeLhs operator[](Key const & k) { + std::pair insert_result + = RbUtil::insert_aux(value_type(k, Value() /*used iff creating new node*/), + false /*allow_replace_flag*/, + this->reduce_fn_, + &(this->root_)); + + return RbTreeLhs(this, insert_result.second, k); + } /*operator[]*/ + + /* compute value of reduce applied to the set K of all keys k[j] in subtree + * N with: + * - k[j] <= lub_key if is_closed = true + * - k[j] < lub_key if is_closed = false + * return reduce_fn.nil() if K is empty + */ + ReducedValue reduce_lub(Key const &lub_key, bool is_closed) const { + return RbUtil::reduce_lub(lub_key, + this->reduce_fn_, + is_closed, + this->root_); + } /*reduce_lub*/ + + /* Provided Reduce computes sum, and we call this rbtree f + * with keys k[i] and values v[i]: + * + * returns iterator pointing to i'th key-value pair {k[i],v[i]} in this tree, + * with reduced value r(i) (i.e. RbNode::reduced1); + * where r(i) is the result of reducing all values v[j] with j<=i + * + * editor bait: invert_integral + */ + const_iterator cfind_sum_glb(ReducedValue const & y) const { + using xo::tostr; + using xo::xtag; + + //char const * c_self = "RedBlackTree::find_sum_glb"; + + RbNode * N = RbUtil::find_sum_glb(this->reduce_fn_, + this->root_, + y); + + if(!N) { + /* for no-lower-bound edge cases, return iterator ix + * pointing to 'before the beginning' of this tree. + * + * will have + * ix.is_deferenceable() == false + * (bool)ix == false + */ + return const_iterator(detail::ID_Forward, + detail::IL_BeforeBegin, + RbUtil::find_leftmost(this->root_)); + } + + return const_iterator(detail::ID_Forward, + detail::IL_Regular, + N); + } /*cfind_sum_glb*/ + + const_iterator find_sum_glb(ReducedValue const & y) const { + return this->cfind_sum_glb(y); + } /*find_sum_glb*/ + + /* non-const version of .cfind_sum_glb() */ + iterator find_sum_glb(ReducedValue const & y) { + const_iterator ix = this->cfind_sum_glb(y); + + return iterator(ix.location(), + const_cast(ix.node())); + } /*find_sum_glb*/ + + void clear() { + auto visitor_fn = [](RbNode const * x, uint32_t /*d*/) { + /* RbUtil.postorder_node_visitor() isn't expecting us to + * alter node, but will not examine it after it's deleted + */ + RbNode * xx = const_cast(x); + + delete xx; + }; + + RbUtil::postorder_node_visitor(this->root_, + 0 /*depth -- ignored by lambda*/, + visitor_fn); + + this->size_ = 0; + this->root_ = nullptr; + } /*clear*/ + + std::pair + insert(std::pair const & kv_pair) { + std::pair insert_result + = RbUtil::insert_aux(kv_pair, + true /*allow_replace_flag*/, + this->reduce_fn_, + &(this->root_)); + + if (insert_result.first) + ++(this->size_); + + return (std::pair + (iterator(detail::ID_Forward, + detail::IL_Regular, + insert_result.second), + insert_result.first)); + } /*insert*/ + + std::pair + insert(std::pair && kv_pair) { + using xo::scope; + using xo::xtag; + + constexpr bool c_logging_enabled = false; + scope log(XO_DEBUG(c_logging_enabled)); + + std::pair insert_result + = RbUtil::insert_aux(std::move(kv_pair), + true /*allow_replace_flag*/, + this->reduce_fn_, + &(this->root_)); + + if (insert_result.first) + ++(this->size_); + + return (std::pair + (iterator(detail::ID_Forward, + detail::IL_Regular, + insert_result.second), + insert_result.first)); + } /*insert*/ + + bool erase(Key const & k) { + bool retval = RbUtil::erase_aux(k, + this->reduce_fn_, + &(this->root_)); + + if (retval) + --(this->size_); + + return retval; + } /*erase*/ + + /* verify class invariants. + * unless implementation is broken, or client manages + * to violate api rules, this will always return true. + * + * RB0. if root node is nil then .size is 0 + * RB1. if root node is non-nil, then root->parent() is nil, + * and .size = root->size + * RB2. if N = P->child(d), then N->parent()=P + * RB3. all paths to leaves have the same black height + * RB4. no red node has a red parent + * RB5. inorder traversal visits keys in monotonically increasing order + * RB6. Node::size reports the size of the subtree reachable from that node + * via child pointers + * RB7. Node::reduced reports the value of + * f(f(L, Node::value), R) + * where: L is reduced-value for left child, + * R is reduced-value for right child + * RB8. RedBlackTree.size() equals the #of nodes in tree + */ + bool verify_ok(bool /*throw_flag_not_implemented*/ = true) const { + using xo::scope; + using xo::tostr; + using xo::xtag; + + constexpr const char *c_self = "RedBlackTree::verify_ok"; + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG(c_logging_enabled)); + + /* RB0. */ + if (root_ == nullptr) { + XO_EXPECT(size_ == 0, tostr(c_self, ": expect .size=0 with null root", + xtag("size", size_))); + } + + /* RB1. */ + if (root_ != nullptr) { + XO_EXPECT(root_->parent_ == nullptr, + tostr(c_self, ": expect root->parent=nullptr", + xtag("parent", root_->parent_))); + XO_EXPECT(root_->size_ == this->size_, + tostr(c_self, ": expect self.size=root.size", + xtag("self.size", size_), + xtag("root.size", root_->size_))); + } + + /* height (counting only black nodes) of tree */ + int32_t black_height = 0; + + /* n_node: #of nodes in this->root_ */ + size_t n_node = RbUtil::verify_subtree_ok(this->reduce_fn_, + this->root_, + &black_height); + + /* RB8. RedBlackTree.size() equals #of nodes in tree */ + XO_EXPECT(n_node == this->size_, + tostr(c_self, ": expect self.size={#of nodes n in tree}", + xtag("self.size", size_), + xtag("n", n_node))); + + if (c_logging_enabled) + log && log(xtag("size", this->size_), + xtag("blackheight", black_height)); + + return true; + } /*verify_ok*/ + + void display() const { RbUtil::display(this->root_, 0); } /*display*/ + + private: + /* #of key/value pairs in this tree */ + size_t size_ = 0; + /* root of red/black tree */ + RbNode * root_ = nullptr; + /* .reduce_fn :: (Accumulator x Key) -> Accumulator */ + Reduce reduce_fn_; + }; /*RedBlackTree*/ + + template + inline std::ostream & + operator<<(std::ostream &os, + RedBlackTree const &tree) + { + tree.display(); + return os; + } /*operator<<*/ + + template + inline std::ostream & + operator<<(std::ostream & os, + detail::IteratorBase const & iter) + { + iter.print(os); + return os; + } /*operator<<*/ + + } /*namespace tree*/ +} /*namespace xo*/ + +/* end RedBlackTree.hpp */ diff --git a/include/xo/tree/bplustree/BplusTreeUtil.hpp b/include/xo/tree/bplustree/BplusTreeUtil.hpp new file mode 100644 index 00000000..f2da0263 --- /dev/null +++ b/include/xo/tree/bplustree/BplusTreeUtil.hpp @@ -0,0 +1,280 @@ +/* @file BplusTreeNode.hpp */ + +#pragma once + +#include "IteratorUtil.hpp" +#include "bplustree_tags.hpp" +#include "indentlog/scope.hpp" +#include "indentlog/print/tag.hpp" +#include // for std::unique_ptr +#include + +namespace xo { + namespace tree { + /* forward decl (see GenericNode.hpp) */ + template + class GenericNode; + /* forward decl (see InternalNode.hpp) */ + template + class InternalNode; + + namespace detail { + /* forward decl (see Iterator.hpp) */ + template + class ConstIterator; + } + + // ----- NodeType ----- + + enum class NodeType { internal, leaf }; + + inline std::string node_type2str(NodeType x) { + switch(x) { + case NodeType::internal: return "internal"; + case NodeType::leaf: return "leaf"; + } + + return "???"; + } /*node_type2str*/ + + inline std::ostream & operator<<(std::ostream & os, NodeType x) { + os << node_type2str(x); + return os; + } /*operator<<*/ + + /* see bplustree/LeafNode.hpp */ + template + struct LeafNode; + + /* see bplustree/InternalNode.hpp */ + template + struct InternalNode; + + // ----- NodeItem + NodeItemPlaceholder ----- + + template + struct NodeItem {}; + + /* struct with same size as NodeItem, but POD + with trivial ctor/dtor */ + template + struct NodeItemPlaceholder { + std::uint8_t mem_v_[sizeof(NodeItem)]; + }; /*NodeItemPlaceholder*/ + + // ----- FindResult ----- + + /* report a node, along with its location (0-based index) within parent. + * use nullptr for .node if item/node not found + * use 0 for .ix if node is root (i.e. has no parent) + * + * expect ConcreteNodeType = LeafNode<..> | InternalNode<..> + */ + template + struct FindNodeResult { + public: + FindNodeResult() = default; + FindNodeResult(FindNodeResult const & x) = default; + FindNodeResult(std::size_t ix, ConcreteNodeType * node) : ix_{ix}, node_{node} {} + + std::size_t ix() const { return ix_; } + ConcreteNodeType * node() const { return node_; } + + private: + /* 0-based index within parent */ + std::size_t ix_ = 0; + /* a B+ tree node */ + ConcreteNodeType * node_ = nullptr; + }; /*FindNodeResult*/ + + template + struct BplusTreeUtil { + public: + using GenericNodeType = GenericNode; + using InternalNodeType = InternalNode; + using LeafNodeType = LeafNode; + using const_iterator = detail::ConstIterator; + + static std::size_t get_node_size(GenericNodeType const * node) { + return node->size(); + } + + /* only implemented for OrdinalTag = ordinal_enabled */ + static void print_node_size(std::ostream & os, GenericNodeType const * node) { + using xo::xtag; + + os << (node ? node->size() : 0UL); + } + + static const_iterator find_ith(GenericNodeType * generic_node, + std::size_t i_tree, + const_iterator cend) { + using xo::xtag; + + if (!generic_node) + return cend; + + std::size_t iter = 0; + + /* 100-level B+ tree won't fit in memory -- would have at least 2^100 nodes! */ + while (iter < 100) { + switch (generic_node->node_type()) { + case NodeType::leaf: + return const_iterator(detail::ID_Forward /*dirn*/, + detail::IL_Regular /*loc*/, + reinterpret_cast(generic_node), + i_tree /*item_ix*/); + case NodeType::internal: + { + /* scan for ith member (counting from 0) */ + + InternalNodeType const * internal_node + = reinterpret_cast(generic_node); + + std::size_t sum_z = 0; + std::size_t z = 0; + + std::size_t i = 0; + std::size_t n = internal_node->n_elt(); + + for (; ilookup_elt(i).child(); + + z = child_node->size(); + + if (i_tree < sum_z + z) { + /* continue search in i'th child of internal_node; + * accounting for the sum_z members in nodes to the left of i_child + */ + generic_node = child_node; + i_tree = i_tree - sum_z; + break; + } + + sum_z += z; + } + + if (i == n) { + throw std::runtime_error(tostr("BplusTree::find_ith: internal index failure", + xtag("i_tree", i_tree), + xtag("last_z", z), + xtag("n", internal_node->n_elt()), + xtag("sum_z", sum_z))); + } + } + break; + } /*switch*/ + + ++iter; + } /*loop over descending internal node path*/ + + throw std::runtime_error(tostr("BplusTree::find_ith: internal loop failure", + xtag("iter", iter))); + + /* impossible! */ + return cend; + } /*find_ith*/ + + static void node_clear_size(InternalNodeType * node) { + node->clear_size(); + } + + static void node_add_size(InternalNodeType * node, std::size_t incr_z) { + node->add_size(incr_z); + } + + static void node_sub_size(InternalNodeType * node, std::size_t decr_z) { + node->sub_size(decr_z); + } + + static void post_modify_add_ancestor_size(InternalNodeType * node, std::size_t incr_z, bool debug_flag) { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(debug_flag)); + + while (node) { + log && log(xtag("node", node), + xtag("old_z", node->size()), + xtag("incr_z", incr_z)); + + node->add_size(incr_z); + + node = node->parent(); + } + } /*post_modify_add_ancestor_size*/ + + static void post_modify_sub_ancestor_size(InternalNodeType * node, std::size_t decr_z, bool debug_flag) { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(debug_flag)); + + while (node) { + log && log(xtag("node", node), + xtag("old_z", node->size()), + xtag("decr_z", decr_z)); + + node->sub_size(decr_z); + + node = node->parent(); + } + } /*post_modify_sub_ancestor_size*/ + }; + + template + struct BplusTreeUtil { + public: + using GenericNodeType = GenericNode; + using InternalNodeType = InternalNode; + using LeafNodeType = LeafNode; + using const_iterator = detail::ConstIterator; + + static std::size_t get_node_size(GenericNodeType const * node) { return 0; } + + static void print_node_size(std::ostream & os, GenericNodeType const * node) { + os << "n/a"; + } + + /* find_ith not implemented without ordinal feature */ + static const_iterator find_ith(GenericNodeType * generic_node, + std::size_t i_tree, + const_iterator cend) { + throw std::runtime_error("BplusTreeUtil::find_ith: not implemented (requires tags::ordinal_enabled)"); + } + + /* per-node size not implemented, so these are no-ops */ + static void node_clear_size(InternalNodeType * node) {} + static void node_add_size(InternalNodeType * node, std::size_t incr_z) {} + static void node_sub_size(InternalNodeType * node, std::size_t decr_z) {} + static void post_modify_add_ancestor_size(InternalNodeType * node, std::size_t incr_z, bool debug_flag) {} + static void post_modify_sub_ancestor_size(InternalNodeType * node, std::size_t decr_z, bool debug_flag) {} + }; + } /*namespace tree*/ +} /*namespace xo*/ + +namespace logutil { + template + struct nodesize { + explicit nodesize(Node const * x) : node_{x} {} + + Node const * node() const { return node_; } + + private: + Node const * node_ = nullptr; + }; /*nodesize*/ + + template + inline std::ostream & operator<<(std::ostream & os, + nodesize const & x) { + xo::tree::BplusTreeUtil::print_node_size(os, x.node()); + return os; + }; +} /*namespace logutil*/ + +/* end BplusTreeUtil.hpp */ diff --git a/include/xo/tree/bplustree/GenericNode.hpp b/include/xo/tree/bplustree/GenericNode.hpp new file mode 100644 index 00000000..aa760c45 --- /dev/null +++ b/include/xo/tree/bplustree/GenericNode.hpp @@ -0,0 +1,123 @@ +/* @file GenericNode.hpp */ + +#pragma once + +#include "BplusTreeUtil.hpp" +#include "bplustree_tags.hpp" +#include // for std::unique_ptr +#include + +namespace xo { + namespace tree { + /* shim so we can partially specialize */ + template + struct GenericNodeBase { + }; /*GenericNodeBase*/ + + template + struct GenericNodeBase { + /* #of items (key-value pairs) in this subtree */ + virtual std::size_t size() const = 0; + }; /*GenericNodeShim*/ + + // ----- GenericNode ----- + // + // base class for LeafNode, InternalNode + + template + class GenericNode : public GenericNodeBase { + public: + using PropertiesType = Properties; + using InternalNodeType = InternalNode; + using LeafNodeType = LeafNode; + + public: + explicit GenericNode(NodeType ntype, std::size_t branching_factor) + : node_type_{ntype}, branching_factor_{branching_factor} {} + virtual ~GenericNode() = default; + + NodeType node_type() const { return node_type_; } + InternalNodeType * parent() const { return parent_; } + std::size_t n_elt() const { return n_elt_; } + std::size_t branching_factor() const { return branching_factor_; } + + void set_parent(InternalNodeType * x) { this->parent_ = x; } + +#ifdef OBSOLETE + /* #of items (key-value pairs) in this subtree */ + virtual std::size_t size() const = 0; +#endif + + virtual Key const & glb_key() const = 0; + /* support methods for BplusTree::verify() + * with_lub. true to use lub_key; false to ignore + * lub_key. if with_lub=true, strict least upper bound key (in B+ tree) for this subtree; + * all keys in this subtree must be strictly less than lub_key. + * ignored when with_lub=false + * lh_leaf. if null, this subtree contains the smallest key in ancestor B+ tree; + * if non-null, lh_leaf's rightmost key is immediate predecessor + * of leftmost key in this subtree + * rh_leaf. if null, this subtree contains the largest key in ancestor B+ tree; + * if non-null, rh_leaf's leftmost key is immediate successor + * of rightmost key in this subtree + */ + virtual std::size_t verify_helper(InternalNodeType const * parent, + bool with_lub, + Key const & lub_key, + LeafNodeType const * lh_leaf, + LeafNodeType const * rh_leaf) const = 0; + virtual void verify_glb_key(Key const & key) const = 0; + FindNodeResult c_find_min_leaf_node() const; + FindNodeResult c_find_max_leaf_node() const; + + virtual FindNodeResult find_min_leaf_node() = 0; + virtual FindNodeResult find_max_leaf_node() = 0; + + /* notification just before permanently removing this node from B+ tree */ + virtual void notify_remove() {} + + private: + /* NodeType::internal | NodeType::leaf */ + NodeType node_type_; + /* pointer to parent node + * invariant: parent has direct pointer to this node, + * except briefly during construction + */ + InternalNodeType * parent_ = nullptr; + + protected: + /* #of non-empty elements (children) of this node + * + * invariant: + * - .elt_v[i].child.ptr is non-null for 0 <= i < .n_elt + * - for (0 < i < .n_elt): + * .elt_v[i-1].key < .elt_v[i].key + * - elt_v[i].key not defined for (i >= .n_elt) + */ + std::size_t n_elt_ = 0; + /* need to store actual branching factor, for LeafNode/InternalNode dtors */ + std::size_t branching_factor_ = 0; + }; /*GenericNode*/ + + /* const version (non-const version below) */ + template + FindNodeResult const> + GenericNode::c_find_min_leaf_node() const { + InternalNode * self = const_cast *>(this); + + return self->find_min_leaf_node(); + } /*c_find_min_leaf_node*/ + + /* const version (non-const version below) */ + template + FindNodeResult const> + GenericNode::c_find_max_leaf_node() const { + InternalNode * self = const_cast *>(this); + + return self->find_max_leaf_node(); + } /*c_find_max_leaf_node*/ + + } /*namespace tree*/ +} /*namespace xo*/ + +/* end GenericNode.hpp */ diff --git a/include/xo/tree/bplustree/InternalNode.hpp b/include/xo/tree/bplustree/InternalNode.hpp new file mode 100644 index 00000000..d4dce90a --- /dev/null +++ b/include/xo/tree/bplustree/InternalNode.hpp @@ -0,0 +1,768 @@ +/* @file InternalNode.hpp */ + +#pragma once + +#include "GenericNode.hpp" +#include "indentlog/scope.hpp" +#include "indentlog/print/tostr.hpp" +#include + +namespace xo { + namespace tree { + // ----- InternalNodeItem ------ + + /* see also: NodeItem */ + template + struct NodeItem { + using GenericNodeType = GenericNode; + + public: + NodeItem() = default; + explicit NodeItem(std::unique_ptr child) + : child_{std::move(child)} { + if (child_) + this->key_ = child_->glb_key(); + } + + Key const & key() const { return key_; } + GenericNodeType * child() const { return child_.get(); } + + std::unique_ptr release_child() { return std::move(child_); } + + void set_key(Key key) { key_ = std::move(key); } + + void notify_remove() { + if (child_) + child_->notify_remove(); + } /*notify_remove*/ + + private: + /* invariant: .key is leftmost key in subtree rooted at .child + * (i.e. greatest lower bound for keys in that subtree) + */ + Key key_; + /* subtree. subtree has minimum key value .key */ + std::unique_ptr child_; + }; /*NodeItem */ + + template + using InternalNodeItem = NodeItem; + + /* struct with same size as InternalNodeItem, but POD + with no ctor/dtor */ + template + using InternalNodeItemPlaceholder = NodeItemPlaceholder; + + /* default implements tags::ordinal_disabled; see partial specialization below for ordinal_enabled */ + template + struct InternalNodeShim : public GenericNode { + public: + using GenericNodeType = GenericNode; + + public: + InternalNodeShim(NodeType ntype, std::size_t branching_factor) : GenericNode{ntype, branching_factor} {} + + protected: + /* not implemented with tags::ordinal_disabled */ + void assign_size(std::size_t z) {} + }; + + template + struct InternalNodeShim : public GenericNode { + public: + using GenericNodeType = GenericNode; + + public: + InternalNodeShim(NodeType ntype, std::size_t branching_factor) : GenericNode{ntype, branching_factor} {} + + void clear_size() { this->size_ = 0; } + void add_size(std::size_t z) { this->size_ += z; } + void sub_size(std::size_t z) { this->size_ -= z; } + + virtual std::size_t size() const override { return size_; } + + protected: + void assign_size(std::size_t z) { this->size_ = z; } + + protected: + std::size_t size_ = 0; + }; /*InternalNodeShim*/ + + /* require: + * - Properties.branching_factor() + */ + template + struct InternalNode : public InternalNodeShim { + public: + using GenericNodeType = GenericNode; + using InternalNodeType = InternalNode; + using LeafNodeType = LeafNode; + using InternalNodeItemPlaceholderType = InternalNodeItemPlaceholder; + using InternalNodeItemType = InternalNodeItem; + + public: + virtual ~InternalNode(); + + /* node size in bytes (increases with branching factor) */ + static std::size_t node_sizeof(std::size_t branching_factor); + + /* use when splitting root node for the first time; + * new root node will be leaf->internal. + * + * require: child_1, child_2 are non-empty + */ + static std::unique_ptr make_2(std::unique_ptr child_1, + std::unique_ptr child_2); + + /* Before: + * + * m = mid_ix + * n = src.n_elt - 1 + * xa @ [m-1] + * xb @ [m] + * xz @ [n-1] + * + * src.elt_v[] + * + * 0 m-1 m n-1 + * +----+-...-+----+----+-...-+----+ + * | x0 | ... | xa | xb | ... | xz | + * +----+-...-+----+----+-...-+----+ + * + * <----------- n items -----------> + * + * After: + * + * src.elt_v[] new_node.elt_v[] + * + * n-m-1 + * 0 m-1 0 v + * +----+-...-+----+ +----+-...-+----+ + * | x0 | ... | xa | | xb | | xz | + * +----+-...-+----+ +----+-...-+----+ + * + * <--- m items ---> <-- n-m items --> + */ + static std::unique_ptr annex(std::size_t mid_ix, + InternalNode * src); + + /* .elt_v[] + * + * 0 k n-1 with: n <= b = branching factor + * +---+---+- ... -+---+- ... -+---+---+ k = lub(key) in {e1..en} + * | e1| e2| | ek| | | en| + * +---+---+- ... -+---+- ... -+---+---+ + * + * retval.first: true if key already present in tree. implies lub_ix_recd.second >= 1 + * retval.second: upper bound (strict) index position in .elt_v[] of key + * + * Cost: O(log(bf)) key comparisons + */ + std::size_t find_lub_ix(Key const & key) const; + + /* warning: requires key is present! */ + std::size_t find_ix(Key const & key) const { return this->find_lub_ix(key) - 1; } + + /* O(bf), but does not rely on key invariants. */ + std::size_t locate_child_by_address(GenericNodeType const * target_child) const; + + InternalNodeItemType & lookup_elt(std::size_t i) { return *(reinterpret_cast(&(elt_v_[i]))); } + + InternalNodeItemType const & lookup_elt(std::size_t i) const { return *(reinterpret_cast(&(elt_v_[i]))); } + + FindNodeResult find_child(Key const & key); + + /* insert node at position ix; moving items starting in .elt_v[ix] one slot to the right */ + void insert_node(std::size_t ix, std::unique_ptr child, bool debug_flag); + + /* remove node at position ix; moving items starting .elt_v[ix+1] one slot to the left; + * if target is a leaf node, also remove from prev_leafnode/next_leafnode list + */ + void remove_node(std::size_t ix, bool debug_flag); + + /* redistribute last n items from left-hand sibling lh to this internal node */ + void prepend_from_lh_sibling(InternalNode * lh, std::size_t n, bool debug_flag); + + /* redistribute first n items from right-hand sibling rh to this internal node */ + void append_from_rh_sibling(std::size_t n, InternalNode * rh); + + void append_rh_sibling(InternalNode * rh) { this->append_from_rh_sibling(rh->n_elt(), rh); } + + /* returns new node with upper half of original element vector (i.e. of this.elt_v[]); + * original updated to retain lower half + */ + std::unique_ptr split_internal(); + + void set_glb_key(Key key) { this->lookup_elt(0).set_key(key); } + + /* memory for InternalNode instances is always created using new[], + * so required to use delete[] to deallocate + */ + void operator delete (void * mem) noexcept { ::operator delete[](mem); } + + // ----- inherited from GenericNode ----- + + virtual Key const & glb_key() const override { return this->lookup_elt(0).key(); } + + virtual std::size_t verify_helper(InternalNode const * parent, + bool with_lub_flag, + Key const & lub_key, + LeafNodeType const * lh_leaf, + LeafNodeType const * rh_leaf) const override; + + virtual void verify_glb_key(Key const & key) const override; + + /* find in subtree_arg the leftmost leaf node (i.e. leaf node with smallest key) */ + virtual FindNodeResult find_min_leaf_node() override; + /* find in subtree_arg the rightmost leaf node (i.e. leaf node with largest key) */ + virtual FindNodeResult find_max_leaf_node() override; + + private: + explicit InternalNode(std::size_t branching_factor); + + private: +#ifdef OBSOLETE + /* total #of elements in this subtree */ + std::size_t size_ = 0; +#endif + /* flexible array; actual size will be .branching_factor(). + * + * .elt_v[i] is created/destroyed as an InternalNodeItemType with non-trivial ctor/dtor. + * we must declare member using POD placeholder to satisfy flexible array rules + * + * invariant: + * - with branching factor b, so range for .elt_v[] is 0 .. b-1: + * - .elt_v[j].child.ptr is null -> {.elt_v[j+1].child.ptr .. .elt_v[b-1].child.ptr} are also null + */ + InternalNodeItemPlaceholderType elt_v_[]; + }; /*InternalNode*/ + + template + InternalNode::~InternalNode() { + /* since we're using flexible array for .elt_v[], need to manually run destructors */ + for (std::size_t i=0, n=this->branching_factor_; ilookup_elt(i).~InternalNodeItemType(); + } + + /* hygiene */ + BplusTreeUtil::node_clear_size(this); + this->n_elt_ = 0; + this->branching_factor_ = 0; + } /*dtor*/ + + template + std::size_t + InternalNode::node_sizeof(std::size_t branching_factor) { + return (sizeof(InternalNode) + + (branching_factor + * sizeof(InternalNodeItemType))); + } /*node_sizeof*/ + + template + std::unique_ptr> + InternalNode::make_2(std::unique_ptr child_1, + std::unique_ptr child_2) { + std::size_t branching_factor = child_1->branching_factor(); + + std::size_t mem_z = node_sizeof(branching_factor); + std::uint8_t * mem = new std::uint8_t[mem_z]; + + assert(child_1->n_elt() > 0); + assert(child_2->n_elt() > 0); + + std::unique_ptr retval(new (mem) InternalNode(branching_factor)); + + child_1->set_parent(retval.get()); + child_2->set_parent(retval.get()); + + retval->assign_size(BplusTreeUtil::get_node_size(child_1.get()) + + BplusTreeUtil::get_node_size(child_2.get())); + retval->n_elt_ = 2; + + retval->lookup_elt(0) = std::move(InternalNodeItemType(std::move(child_1))); + retval->lookup_elt(1) = std::move(InternalNodeItemType(std::move(child_2))); + + return retval; + } /*make_2*/ + + template + std::unique_ptr> + InternalNode::annex(std::size_t mid_ix, + InternalNode * src) + { + std::size_t branching_factor = src->branching_factor(); + + std::size_t mem_z = node_sizeof(branching_factor); + std::uint8_t * mem = new std::uint8_t[mem_z]; + + std::unique_ptr new_node(new (mem) InternalNode(branching_factor)); + + std::size_t hi_ix = src->n_elt(); + + new_node->n_elt_ = hi_ix - mid_ix; + + std::size_t annex_z = 0; + + /* annexing upper-half of *src into new_node */ + for (std::size_t i = 0, n = hi_ix - mid_ix; i < n; ++i) { + InternalNodeItemType & src_slot = src->lookup_elt(mid_ix + i); + InternalNodeItemType & new_slot = new_node->lookup_elt(i); + + annex_z += BplusTreeUtil::get_node_size(src_slot.child()); + + new_slot = std::move(src->lookup_elt(mid_ix + i)); + new_slot.child()->set_parent(new_node.get()); + } + + new_node->assign_size(annex_z); + + /* ordinal_disabled: noop + * ordinal_enabled: bookkeeping for src.size (+ new_node.size, see above) + */ + src->assign_size(BplusTreeUtil::get_node_size(src) - annex_z); + src->n_elt_ = mid_ix; + + return new_node; + } /*annex*/ + + template + std::size_t + InternalNode::find_lub_ix(Key const & key) const { + if (key < this->lookup_elt(0).key()) + return 0; + + std::size_t lo = 0; + std::size_t hi = this->n_elt_; + + while (lo + 1 < hi) { + std::size_t mid = lo + (hi - lo) / 2; + + if (key < this->lookup_elt(mid).key()) + hi = mid; + else + lo = mid; + } + + return hi; + } /*find_lub_ix*/ + + template + std::size_t + InternalNode::locate_child_by_address(GenericNodeType const * target_child) const { + for (std::size_t ix = 0; ix < this->n_elt_; ++ix) { + if (this->lookup_elt(ix).child() == target_child) + return ix; + } + + return static_cast(-1); + } /*locate_child_by_address*/ + + template + FindNodeResult> + InternalNode::find_min_leaf_node() { + FindNodeResult findresult(0, this); + + while (findresult.node() && (findresult.node()->node_type() == NodeType::internal)) { + std::size_t min_ix = 0; + + findresult = FindNodeResult(min_ix, + (reinterpret_cast(findresult.node())) + ->lookup_elt(min_ix /*leftmost child*/).child()); + } + + /* findresult.node()->node_type() == NodeType::leaf (if non-null) */ + + if (!findresult.node()) { + assert(false); + return FindNodeResult(); + } + + assert(findresult.node()->node_type() == NodeType::leaf); + + return FindNodeResult(findresult.ix(), + reinterpret_cast(findresult.node())); + } /*find_min_leaf_node*/ + + template + FindNodeResult> + InternalNode::find_max_leaf_node() { + FindNodeResult findresult(0, this); + + while (findresult.node() && (findresult.node()->node_type() == NodeType::internal)) { + std::size_t max_ix = findresult.node()->n_elt() - 1; + + findresult = FindNodeResult + (max_ix, + (reinterpret_cast(findresult.node())) + ->lookup_elt(max_ix /*rightmost child*/).child()); + } + + /* findresult.node()->node_type() == NodeType::leaf (if non-null) */ + + if (!findresult.node()) { + assert(false); + return FindNodeResult(); + } + + assert(findresult.node()->node_type() == NodeType::leaf); + + return FindNodeResult(findresult.ix(), + reinterpret_cast(findresult.node())); + } /*find_max_leaf_node*/ + + template + FindNodeResult> + InternalNode::find_child(Key const & key) { + std::size_t lub_ix = this->find_lub_ix(key); + + if (lub_ix > 0) + --lub_ix; + + return FindNodeResult(lub_ix, this->lookup_elt(lub_ix).child()); + } /*find_child*/ + + template + void + InternalNode::insert_node(std::size_t ix, std::unique_ptr child, bool debug_flag) + { + using xo::scope; + using xo::tostr; + using xo::xtag; + + scope log(XO_DEBUG(debug_flag), + xtag("self", this), + xtag("n_elt", this->n_elt()), + xtag("bf", this->branching_factor()), + xtag("ix", ix), + xtag("child", child.get())); + + if (this->n_elt_ >= this->branching_factor()) { + assert(false); + throw std::runtime_error(tostr("InternalNode::insert_node: node already full", + xtag("node.n_elt", this->n_elt()), + xtag("branching_factor", this->branching_factor()))); + } + + if (ix > this->n_elt_) { + assert(false); + throw std::runtime_error(tostr("InternalNode::insert_node: insert position out of range", + xtag("ix", ix), + xtag("node.n_elt", this->n_elt()), + xtag("bf", this->branching_factor()))); + } + + std::size_t pos_ix = this->n_elt_; + + while (pos_ix > ix) { + this->lookup_elt(pos_ix) = std::move(this->lookup_elt(pos_ix - 1)); + --pos_ix; + } + + /* WARNING: don't update .size here + * in practice we use .insert_node() when introducing a single new key/value pair; + * when we use .insert_node() we split an existing node, + * and actually just want to increment .size. + * + * We leave this to caller (e.g. BplusTree.internal_insert_aux()) + * because in that context can see the upstream split + */ + // this->size_ += child->n_elt(); + + ++(this->n_elt_); + child->set_parent(this); + this->lookup_elt(ix) = InternalNodeItemType(std::move(child)); + } /*insert_node*/ + + template + void + InternalNode::remove_node(std::size_t ix, bool debug_flag) { + using xo::scope; + using xo::tostr; + using xo::xtag; + + scope log(XO_DEBUG(debug_flag), + xtag("self", this), + xtag("n_elt", this->n_elt()), + xtag("bf", this->branching_factor()), + xtag("ix", ix)); + + if (ix >= this->n_elt_) { + assert(false); + throw std::runtime_error(tostr("InternalNode::remove_node: target position out of range", + xtag("ix", ix), + xtag("node.n_elt", this->n_elt()), + xtag("bf", this->branching_factor()))); + } + + std::size_t pos_ix = ix; + std::size_t end_ix = this->n_elt_ - 1; + + { + InternalNodeItemType & target_item = this->lookup_elt(pos_ix); + + /* WARNING: don't update .size here + * in practice we use .remove_node() when deleting a single new key/value pair; + * when we use .remove_node() we merge existing nodes, + * and actually just want to decrement .size. + * + * We leave this to caller (e.g. BplusTree.internal_remove_aux()) + * because in that context can see the upstream merge + */ + //this->size_ -= target_item.child()->size(); + target_item.notify_remove(); + } + + while (pos_ix < end_ix) { + //scope x1("loop", debug_flag); + //x1(xtag("pos_ix", pos_ix)); + + this->lookup_elt(pos_ix) = std::move(this->lookup_elt(pos_ix + 1)); + ++pos_ix; + } + + --(this->n_elt_); + } /*remove_node*/ + + template + void + InternalNode::prepend_from_lh_sibling(InternalNode * lh, std::size_t n, bool debug_flag) { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(debug_flag), + xtag("@", this), xtag("n", n)); + + if (this->n_elt() + n > this->branching_factor()) { + assert(false); + throw std::runtime_error(tostr("InternalNode.prepend_from_lh_sibling: expected combined #elt <= bf", + xtag("self.n_elt", this->n_elt()), + xtag("n", n), + xtag("bf", this->branching_factor()))); + } + + std::size_t n_lh = lh->n_elt(); + std::size_t n_rh = this->n_elt(); + + /* move elts in *this to the right n steps (starting from the end) */ + for (std::size_t ixp1 = this->n_elt(); ixp1 > 0; --ixp1) { + std::size_t ix = ixp1 - 1; + //x.log("move", xtag("ix", ix), xtag("ix+n", ix+n)); + this->lookup_elt(ix + n) = std::move(this->lookup_elt(ix)); + } + + std::size_t xfer_z = 0; + + /* xfer n elts from upper end of lh, to lower end of *this */ + for (std::size_t ix = 0; ix < n; ++ix) { + //x.log("fill", xtag("ix", ix), xtag("n_lh-n+ix", n_lh - n + ix)); + + InternalNodeItemType & lh_sibling_item = lh->lookup_elt(n_lh - n + ix); + + xfer_z += BplusTreeUtil::get_node_size(lh_sibling_item.child()); + + this->lookup_elt(ix) = std::move(lh_sibling_item); + /* + fixup parent pointer */ + this->lookup_elt(ix).child()->set_parent(this); + } + + BplusTreeUtil::node_add_size(this, xfer_z); + BplusTreeUtil::node_sub_size(lh, xfer_z); + + this->n_elt_ += n; + lh->n_elt_ -= n; + + log && log(xtag("this.glb_key", this->glb_key()), + xtag("this[0].key", this->lookup_elt(0).key())); + + log.end_scope(); + } /*prepend_from_lh_sibling*/ + + template + void + InternalNode::append_from_rh_sibling(std::size_t n, InternalNode * rh) { + using xo::xtag; + + if (this->n_elt() + n > this->branching_factor()) { + assert(false); + throw std::runtime_error(tostr("InternalNode.append_from_rh_sibling: expected combined #elt <= bf", + xtag("self.n_elt", this->n_elt()), + xtag("n", n), + xtag("bf", this->branching_factor()))); + } + + std::size_t n_lh = this->n_elt(); + std::size_t xfer_z = 0; + + for (std::size_t ix = 0; ix < n; ++ix) { + InternalNodeItemType & rh_sibling_item = rh->lookup_elt(ix); + + xfer_z += BplusTreeUtil::get_node_size(rh_sibling_item.child()); + this->lookup_elt(n_lh + ix) = std::move(rh_sibling_item); + /* + fixup parent pointer */ + this->lookup_elt(n_lh + ix).child()->set_parent(this); + } + + BplusTreeUtil::node_add_size(this, xfer_z); + this->n_elt_ += n; + + /* shuffle remaining members of rh sibling n items to the left */ + for (std::size_t ix = 0; ix < rh->n_elt() - n; ++ix) { + rh->lookup_elt(ix) = std::move(rh->lookup_elt(ix + n)); + } + + BplusTreeUtil::node_sub_size(rh, xfer_z); + rh->n_elt_ -= n; + } /*append_from_rh_sibling*/ + + template + std::unique_ptr> + InternalNode::split_internal() { + std::size_t n_elt = this->n_elt_; + std::size_t mid_ix = n_elt / 2; + + return InternalNode::annex(mid_ix, this); + } /*split_internal*/ + + template + std::size_t + InternalNode::verify_helper(InternalNode const * parent, + bool with_lub_flag, + Key const & lub_key, + LeafNodeType const * lh_leaf, + LeafNodeType const * rh_leaf) const + { + using xo::tostr; + using xo::xtag; + + std::size_t retval = 0; + + /* verify immediate parent pointer is correct */ + if (this->parent() != parent) { + throw std::runtime_error(tostr("InternalNode::verify_helper" + ": expected parent pointer to refer to actual parent", + xtag("stored_parent", this->parent()), + xtag("actual_parent", parent))); + } + + std::size_t n = this->n_elt_; + + /* verify all children have same NodeType (either all= internal or all= leaf) */ + NodeType target_child_node_type = NodeType::leaf; + + if (n > 0) + target_child_node_type = this->lookup_elt(0).child()->node_type(); + + LeafNodeType const * prev_lh_leaf = lh_leaf; + + for (std::size_t i=0; i < n; ++i) { + /* check consistent node type */ + NodeType i_nodetype = this->lookup_elt(i).child()->node_type(); + + if ((i > 0) && (i_nodetype != target_child_node_type)) { + throw std::runtime_error(tostr("InternalNode::verify_helper" + ": expected all children to share the same node type", + xtag("i", i), + xtag("elt[0].node_type", target_child_node_type), + xtag("elt[i].node_type", i_nodetype))); + } + + /* nested verify on child subtrees */ + InternalNodeItemType const & i_elt = this->lookup_elt(i); + + LeafNodeType const * next_lh_leaf = ((i+1 < n) + ? this->lookup_elt(i+1).child()->find_min_leaf_node().node() + : rh_leaf); + + retval += i_elt.child()->verify_helper(this, + (i+1 < n) ? true : with_lub_flag, + (i+1 < n) ? this->lookup_elt(i+1).key() : lub_key, + prev_lh_leaf, + next_lh_leaf); + + prev_lh_leaf = i_elt.child()->find_max_leaf_node().node(); + } + + if (Properties::ordinal_tag_value() == tags::ordinal_enabled) { + /* verify stored subtree size is consistent with children's */ + std::size_t sum_z = 0; + + for (std::size_t i=0, n=this->n_elt_; i < n; ++i) { + InternalNodeItemType const & elt = this->lookup_elt(i); + + sum_z += BplusTreeUtil::get_node_size(elt.child()); + } + + std::size_t self_z = BplusTreeUtil::get_node_size(this); + + if (sum_z != self_z) { + throw std::runtime_error(tostr("InternalNode::verify_helper", + ": inconsistent subtree size", + xtag("node", this), + xtag("treez[stored]", self_z), + xtag("treez[computed]", sum_z))); + } + } + + /* verify stored glb_key is correct */ + for (std::size_t i=0, n=this->n_elt_; i < n; ++i) { + InternalNodeItemType const & elt = this->lookup_elt(i); + + elt.child()->verify_glb_key(elt.key()); + } + + /* verify locally stored keys appear in sorted order */ + for (std::size_t i=1; i < n; ++i) { + InternalNodeItemType const & prev = this->lookup_elt(i-1); + InternalNodeItemType const & elt = this->lookup_elt(i); + + if (prev.key() < elt.key()) { + ; + } else { + throw std::runtime_error(tostr("InternalNode::verify_helper" + ": expected local keys in strictly increasing order", + xtag("i", i), + xtag("key(i-1)", prev.key()), + xtag("key(i)", elt.key()))); + } + } + + /* verify highest stored key before parent-supplied upper bound */ + if (with_lub_flag) { + if (this->lookup_elt(n-1).key() < lub_key) { + ; + } else { + throw std::runtime_error(tostr("InternalNode::verify_helper" + ": expected highest local key before parent-supplied lub key", + xtag("n", n), + xtag("key(n-1)", this->lookup_elt(n-1).key()), + xtag("lub_key", lub_key))); + } + } + + return retval; + } /*verify_helper*/ + + template + void + InternalNode::verify_glb_key(Key const & key) const { + InternalNodeItemType const & elt = this->lookup_elt(0); + + elt.child()->verify_glb_key(key); + } /*verify_glb_key*/ + + template + InternalNode::InternalNode(std::size_t branching_factor) + : InternalNodeShim{NodeType::internal, branching_factor} + { + /* must invoke ctor explicitly for each .elt_v[i]. + * compiler doesn't know extent of .elt_v[], since it's a flexible array + */ + for (std::size_t i = 0; i < branching_factor; ++i) { + /* using placement new to force ctor call inside flexible array */ + new (&(this->lookup_elt(i))) InternalNodeItemType(); + } + } /*ctor*/ + + } /*namespace tree*/ +} /*namespace xo*/ + +/* end InternalNode.hpp */ diff --git a/include/xo/tree/bplustree/Iterator.hpp b/include/xo/tree/bplustree/Iterator.hpp new file mode 100644 index 00000000..90f57924 --- /dev/null +++ b/include/xo/tree/bplustree/Iterator.hpp @@ -0,0 +1,355 @@ +/* @file Iterator.hpp */ + +#pragma once + +#include "IteratorUtil.hpp" +#include "LeafNode.hpp" +#include "indentlog/print/tostr.hpp" + +namespace xo { + namespace tree { + namespace detail { + /* TODO: move to tree/IteratorUtil.hpp */ + + /* placeholder - specialize on isConst */ + template + struct NodeTypeTraits { using LeafNodeType = void; }; + + /* non-const node pointer */ + template + struct NodeTypeTraits { + using NativeLeafNodeType = LeafNode; + using LeafNodeType = NativeLeafNodeType; + using NativeContentsType = typename LeafNodeType::ContentsType; + using LeafNodePtrType = LeafNodeType *; + }; + + /* const node pointer */ + template + struct NodeTypeTraits { + using NativeLeafNodeType = LeafNode; + using LeafNodeType = NativeLeafNodeType const; + using NativeContentsType = typename LeafNodeType::ContentsType const; + using LeafNodePtrType = LeafNodeType const *; + }; + + /* shared between const and non-const b+ tree iterators + * + * +------------+ + * |IteratorBase| + * | .dirn | + * | .location | + * | .leafnode | + * | .ix | + * +------------+ + * ^ + * | isa +-------------+ + * +-----------|ConstIterator| + * | | .operator++ | + * | | .operator-- | + * | +-------------+ + * | + * | isa +--------+ + * +-----------|Iterator| + * +--------+ + */ + template + class IteratorBase { + public: + using Traits = NodeTypeTraits; + using BpLeafNodePtrType = typename Traits::LeafNodePtrType; + using BpLeafNodeItemType = typename Traits::LeafNodeType::LeafNodeItemType; + using NativeContentsType = typename Traits::NativeContentsType; + + protected: + IteratorBase() = default; + IteratorBase(IteratorDirection dirn, IteratorLocation loc, BpLeafNodePtrType leaf, std::size_t ix) + : dirn_{dirn}, location_{loc}, leafnode_{leaf}, ix_{ix} {} + IteratorBase(IteratorBase const &) = default; + + static IteratorBase prebegin_aux(BpLeafNodePtrType node) { + return IteratorBase(ID_Forward, IL_BeforeBegin, node, 0 /*ix*/); + } + + static IteratorBase begin_aux(BpLeafNodePtrType node) { + return IteratorBase(ID_Forward, + (node ? IL_Regular : IL_AfterEnd), + node, + 0 /*ix*/); + } + + static IteratorBase end_aux(BpLeafNodePtrType node) { + return IteratorBase(ID_Forward, + IL_AfterEnd, + node, + 0 /*ix*/); + } + + static IteratorBase rprebegin_aux(BpLeafNodePtrType node) { + return IteratorBase(ID_Reverse, + IL_AfterEnd, + node, + 0 /*ix*/); + } + + static IteratorBase rbegin_aux(BpLeafNodePtrType node) { + return IteratorBase(ID_Reverse, + (node ? IL_Regular : IL_BeforeBegin), + node, + (node ? node->n_elt() - 1: 0)); + } + + static IteratorBase rend_aux(BpLeafNodePtrType node) { + return IteratorBase(ID_Reverse, + IL_BeforeBegin, + node, + 0 /*ix*/); + } + + public: + IteratorLocation location() const { return location_; } + BpLeafNodePtrType node() const { return leafnode_; } + BpLeafNodeItemType const * item_addr() const { return &(leafnode_->lookup_elt(this->ix_)); } + + NativeContentsType const & operator*() const { + this->check_regular(); + return this->leafnode_->lookup_elt(this->ix_).kv_pair(); + } /*operator**/ + + NativeContentsType const * operator->() const { + return &(this->operator*()); + } /*operator->*/ + + bool is_sentinel() const { return (this->location_ != IL_Regular); } + bool is_dereferenceable() const { return !this->is_sentinel(); } + + operator bool() const { return this->is_deferenceable(); } + + bool operator==(IteratorBase const & x) const { + return (this->location_ == x.location_) && (this->leafnode_ == x.leafnode_) && (this->ix_ == x.ix_); + } + + bool operator!=(IteratorBase const & x) const { + return (this->location_ != x.location_) || (this->leafnode_ != x.leafnode_) || (this->ix_ != x.ix_); + } + + void print(std::ostream & os) const { + using xo::xtag; + + os << ""; + } /*print*/ + + /* pre-increment */ + IteratorBase & operator++() { + return ((this->dirn_ == ID_Forward) + ? this->next_step() + : this->prev_step()); + } /*operator++*/ + + /* pre-decrement */ + IteratorBase & operator--() { + return ((this->dirn_ == ID_Forward) + ? this->prev_step() + : this->next_step()); + } /*operator--*/ + + private: + IteratorBase & next_step() { + switch(this->location_) { + case IL_BeforeBegin: + /* .leafnode is leftmost node in tree */ + this->location_ = IL_Regular; + break; + case IL_Regular: + { + /* #of elts in node, not #of elts in tree! */ + std::size_t n_elt = this->leafnode_->n_elt(); + + if (this->ix_ + 1 < n_elt) { + ++(this->ix_); + } else if (this->leafnode_->next_leafnode()) { + this->leafnode_ = this->leafnode_->next_leafnode(); + this->ix_ = 0; + } else { + /* preserve .leafnode: + * (a) for == comparison w/ .end() iterator + * (b) so we can iterate backwards from end position + */ + //this->leafnode_ = this->leafnode_->next_leafnode(); + this->location_ = IL_AfterEnd; + this->ix_ = 0; + } + } + break; + case IL_AfterEnd: + break; + } + + return *this; + } /*next_step*/ + + IteratorBase & prev_step() { + switch(this->location_) { + case IL_BeforeBegin: + break; + case IL_Regular: + if (this->ix_ > 0) { + --(this->ix_); + } else if (this->leafnode_->prev_leafnode()) { + this->leafnode_ = this->leafnode_->prev_leafnode(); + this->ix_ = this->leafnode_->n_elt() - 1; + } else /* .ix == 0 && .leafnode.prev_leafnode == nullptr */ { + /* preserve .leafnode: + * (a) for == comparison w/ .prebegin() iterator + * (b) so iterator is reversible; can iterate forwards from prebegin position + */ + this->location_ = IL_BeforeBegin; + } + break; + case IL_AfterEnd: + /* .leafnode is rightmost node in tree */ + this->location_ = IL_Regular; + this->ix_ = this->leafnode_->n_elt() - 1; + break; + } + + return *this; + } /*prev_step*/ + + private: + void check_regular() const { + using xo::tostr; + using xo::xtag; + + if (this->location_ != IL_Regular) { + throw std::runtime_error(tostr("bplustree iterator: cannot deref iterator" + " in sentinel state", + xtag("loc", this->location_))); + } + } /*check_regular*/ + + private: + /* ID_Forward forward iterator + * ID_Reverse reverse iterator + */ + IteratorDirection dirn_ = ID_Forward; + /* IL_BeforeBegin | IL_Regular | IL_AfterEnd + * + * operator++ operator++ + * IL_BeforeBegin --------------> IL_Regular --------------> IL_AfterEnd + * /-> -\ + * | | + * \----------------/ + * operator++ + * + * operator-- operator-- + * IL_BeforeBegin <------------- IL_Regular <-------------- IL_AfterEnd + * /-- <-\ + * | | + * \-----------------/ + * operator-- + * + * + */ + IteratorLocation location_ = IL_AfterEnd; + /* .location .leafnode + * IL_BeforeBegin BplusTree.leafnode_begin (leftmost leaf node) + * IL_Regular any leaf node reachable from BplusTree.leafnode_begin + * (or equivalently from BplusTree.leafnode_end) + * IL_AfterEnd BplusTree.leafnode_end (rightmost leaf node) + */ + BpLeafNodePtrType leafnode_ = nullptr; + /* index position within .leafnode; + * 0 when .location is IL_BeforeBegin | IL_AfterEnd + */ + std::size_t ix_ = 0; + }; /*IteratorBase*/ + + template + class ConstIterator : public IteratorBase { + public: + using iterator_concept = std::bidirectional_iterator_tag; + + using BpIteratorBase = IteratorBase; + using BpLeafNodePtrType = typename BpIteratorBase::BpLeafNodePtrType; + + public: + ConstIterator() = default; + ConstIterator(IteratorDirection dirn, IteratorLocation loc, BpLeafNodePtrType leaf, std::size_t ix) + : IteratorBase(dirn, loc, leaf, ix) {} + ConstIterator(ConstIterator const & x) = default; + ConstIterator(BpIteratorBase const & x) : BpIteratorBase(x) {} + ConstIterator(BpIteratorBase && x) : BpIteratorBase{std::move(x)} {} + + static ConstIterator prebegin_aux(BpLeafNodePtrType leaf) { return BpIteratorBase::prebegin_aux(leaf); } + static ConstIterator begin_aux(BpLeafNodePtrType leaf) { return BpIteratorBase::begin_aux(leaf); } + static ConstIterator end_aux(BpLeafNodePtrType leaf) { return BpIteratorBase::end_aux(leaf); } + + static ConstIterator rprebegin_aux(BpLeafNodePtrType leaf) { return BpIteratorBase::rprebegin_aux(leaf); } + static ConstIterator rbegin_aux(BpLeafNodePtrType leaf) { return BpIteratorBase::rbegin_aux(leaf); } + static ConstIterator rend_aux(BpLeafNodePtrType leaf) { return BpIteratorBase::rend_aux(leaf); } + + /* pre-increment */ + ConstIterator & operator++() { + BpIteratorBase::operator++(); + return *this; + } /*operator++*/ + + /* post-increment */ + ConstIterator operator++(int) { + ConstIterator retval = *this; + + ++(*this); + + return retval; + } /*operator++*/ + + /* pre-decrement */ + ConstIterator & operator--() { + BpIteratorBase::operator--(); + return *this; + } /*operator--*/ + + /* post-decrement */ + ConstIterator operator--(int) { + ConstIterator retval = *this; + + --(*this); + + return retval; + } /*operator--*/ + }; /*ConstIterator*/ + } /*namespace detail*/ + + template + inline std::ostream & + operator<<(std::ostream & os, + detail::IteratorBase const & iter) + { + iter.print(os); + return os; + } /*operator<<*/ + } /*namespace tree*/ +} /*namespace xo*/ + +/* end Iterator.hpp */ diff --git a/include/xo/tree/bplustree/IteratorUtil.hpp b/include/xo/tree/bplustree/IteratorUtil.hpp new file mode 100644 index 00000000..0a0157af --- /dev/null +++ b/include/xo/tree/bplustree/IteratorUtil.hpp @@ -0,0 +1,56 @@ +/* @file IteratorUtil.hpp */ + +#pragma once + +#include + +namespace xo { + namespace tree { + namespace detail { + + enum IteratorDirection { + /* ID_Forward. forward iterator + * ID_Reverse. reverse iterator + */ + ID_Forward, + ID_Reverse + }; /*IteratorDirection*/ + + /* specify iterator location relative to a particular b+ tree node */ + enum IteratorLocation { + /* + * IL_BeforeBegin. if non-empty tree, Iterator.node is the first node + * in the tree (the one with smallest key), + * and iterator refers to the location + * "one before" that first node. + * IL_Regular. iterator refers to member of the tree + * given by Iterator.node + * IL_AfterEnd. if non-empty tree, Iterator.node is the last node + * in the tree (the one with largest key), + * and iterator refers the the location + * "one after" that last node. + */ + IL_BeforeBegin, + IL_Regular, + IL_AfterEnd + }; /*IteratorLocation*/ + + static inline char const * iterator_location_descr(IteratorLocation x) { + switch(x) { + case IL_BeforeBegin: return "before-begin"; + case IL_Regular: return "regular"; + case IL_AfterEnd: return "after-end"; + default: return "???"; + } + } /*iteerator_location_descr*/ + + inline std::ostream & + operator<<(std::ostream & os, IteratorLocation x) { + os << iterator_location_descr(x); + return os; + } /*operator<<*/ + } /*namespace detail*/ + } /*namespace tree*/ +} /*namespace xo*/ + +/* end IteratorUtil.hpp */ diff --git a/include/xo/tree/bplustree/LeafNode.hpp b/include/xo/tree/bplustree/LeafNode.hpp new file mode 100644 index 00000000..419bd63f --- /dev/null +++ b/include/xo/tree/bplustree/LeafNode.hpp @@ -0,0 +1,684 @@ +/* @file LeafNode.hpp */ + +#pragma once + +#include "GenericNode.hpp" +#include "indentlog/scope.hpp" +#include + +namespace xo { + namespace tree { + + // ----- LeafNodeItem ----- + + template + using LeafNodeItem = NodeItem; + + /* - define for symmetry with NodeItem + * - LeafNodeItem doesn't contain a child pointer; + * it belongs inside a leaf mode, which by definition doesn't have children + */ + template + struct NodeItem { + public: + using ContentsType = std::pair; + + public: + NodeItem() = default; + NodeItem(std::pair kv) : kv_pair_{std::move(kv)} {} + + std::pair const & kv_pair() const { return kv_pair_; } + + Key const & key () const { return kv_pair_.first; } + Value const & value() const { return kv_pair_.second; } + + void assign_value(Value x) { kv_pair_.second = std::move(x); } + + private: + /* key+value pair */ + std::pair kv_pair_; + }; /*NodeItem*/ + + /* struct with same size as LeafNodeItem, but POD + with no ctor/dtor */ + template + using LeafNodeItemPlaceholder = NodeItemPlaceholder; + + template + struct LeafNodeShim : public GenericNode + { + LeafNodeShim(NodeType ntype, std::size_t branching_factor) : GenericNode(ntype, branching_factor) {} + + /* ordinal_enabled: LeafNode will provide .size(): inherits+overrides GenericNodeBase.size() */ + }; + + template + struct LeafNodeShim : public GenericNode + { + LeafNodeShim(NodeType ntype, std::size_t branching_factor) : GenericNode(ntype, branching_factor) {} + + /* ordinal_disabled: LeafNode provides LeafNode::size(), but not used */ + + virtual std::size_t size() const = 0; + }; + + // ----- LeafNode ----- + + /* require: + * - Properties.branching_factor() + */ + template + struct LeafNode : public LeafNodeShim { + public: + using GenericNodeType = GenericNode; + using LeafNodeType = LeafNode; + using LeafNodeItemType = LeafNodeItem; + using LeafNodeItemPlaceholderType = LeafNodeItemPlaceholder; + using InternalNodeType = InternalNode; + + using ContentsType = typename LeafNodeItemType::ContentsType; + + public: + virtual ~LeafNode(); + + /* node size in bytes (increases with branching factor) */ + static std::size_t node_sizeof(std::size_t branching_factor); + + /* named ctor idiom. enforce heap allocation + unique_ptr wrapper */ + static std::unique_ptr make(std::pair kv_pair, + Properties const & properties); + + /* create+return new leaf node that contains all the items in *src from position [lo_ix, hi_ix), + * after this operation size of *src is reduced by (hi_ix - lo_ix) + */ + static std::unique_ptr annex(std::size_t lo_ix, + std::size_t hi_ix, + LeafNode * src); + + LeafNode * prev_leafnode() const { return prev_leafnode_; } + LeafNode * next_leafnode() const { return next_leafnode_; } + + /* .first: true if key in tree already + * .second: index position of (strict) least upper bound in .elt_v[] + * if .n_elt, key has no upper bound in this node + */ + std::pair find_lub_ix(Key const & key) const; + + LeafNodeItemType & lookup_elt(std::size_t i) { return *(reinterpret_cast(&(this->elt_v_[i]))); } + + LeafNodeItemType const & lookup_elt(std::size_t i) const { return *(reinterpret_cast(&(this->elt_v_[i]))); } + + void assign_leaf_value(std::size_t elt_ix, Value value) { + assert(elt_ix < this->n_elt_); + + this->lookup_elt(elt_ix).assign_value(std::move(value)); + } /*assign_leaf_value*/ + + /* assign precdeing leaf node (= LH sibling if share same parent) */ + void assign_prev_leafnode(LeafNode * x) { prev_leafnode_ = x; } + void assign_next_leafnode(LeafNode * x) { next_leafnode_ = x; } + + /* insert new leaf at position ix, associating key -> value + * (shuffle existing elements at ix, ix+1.. 1 position to the right) + */ + void insert_leaf_item(std::size_t ix, + std::pair const & kv_pair, + bool debug_flag); + + /* remove key,value pair at position ix */ + void remove_leaf(std::size_t ix, bool debug_flag); + + /* append n items from left-hand sibling, as new left-most elements + * require: combined #of items must be at most b = branching factor + */ + void prepend_from_lh_sibling(LeafNode * lh, std::size_t n, bool debug_flag); + + /* apepnd n items from right-hand sibling, as new right-most elements + * require: combined #of items must be at most b = branching factor + */ + void append_from_rh_sibling(std::size_t n, LeafNode * rh); + + void append_rh_sibling(LeafNode * rh) { this->append_from_rh_sibling(rh->n_elt(), rh); } + + /* returns new leaf with lower half of original element vector; + * original updated to retain upper half + */ + std::unique_ptr split_leaf_lower(); + + /* returns new leaf with upper half of original element vector; + * original updated to retain lower half + */ + std::unique_ptr split_leaf_upper(); + + /* memory for LeafNode instances is always created using new[], + * so required to use delete[] to deallocate + */ + void operator delete (void * mem) noexcept { ::operator delete[](mem); } + + // ----- Inherited from GenericNode ----- + + virtual std::size_t size() const override { return this->n_elt(); } + + virtual Key const & glb_key() const override { return this->lookup_elt(0).key(); } + + virtual std::size_t verify_helper(InternalNodeType const * parent, + bool with_lub_flag, + Key const & lub_key, + LeafNodeType const * lh_leaf, + LeafNodeType const * rh_leaf) const override; + virtual void verify_glb_key(Key const & key) const override; + virtual FindNodeResult find_min_leaf_node() override; + virtual FindNodeResult find_max_leaf_node() override; + + virtual void notify_remove() override; + + private: + explicit LeafNode(std::size_t branching_factor); + + LeafNode(std::pair const & kv_pair, + std::size_t branching_factor); + + void assign_siblings(LeafNode * prev, LeafNode * next); + + private: + /* previous LeafNode in key order, immediately before (all the keys in) this node. + * use to streamline inorder traversal. + */ + LeafNode * prev_leafnode_ = nullptr; + /* next LeafNode in key order, immediately after (all the keys in) this node. + * streamline inorder traversal. + */ + LeafNode * next_leafnode_ = nullptr; + /* flexible array; actual capacity will be Properties.branching_factor(); + * but only members [0 .. n_elt-1] are defined. + * + * actual type of .elt_v[i] is LeafNodeItem; + * need to use POD LeafNodeItemPlaceholder to satisfy flexible-array rules + */ + LeafNodeItemPlaceholderType elt_v_[]; + }; /*LeafNode*/ + + template + LeafNode::~LeafNode() { + /* since we're using flexible array for .elt_v[], need to manually run destructors */ + for (std::size_t i=0, n=this->branching_factor_; ilookup_elt(i).~LeafNodeItemType(); + } + + /* hygiene */ + this->n_elt_ = 0; + this->branching_factor_ = 0; + } /*dtor*/ + + template + std::size_t + LeafNode::node_sizeof(std::size_t branching_factor) { + /* since we're using flexible array for .elt_v[], need to manually account for it's allocated size */ + + return (sizeof(LeafNode) + + (branching_factor + * sizeof(LeafNodeItem))); + } /*node_sizeof*/ + + template + std::unique_ptr> + LeafNode::make(std::pair kv_pair, + Properties const & properties) + { + using xo::scope; + using xo::xtag; + + std::size_t mem_z = node_sizeof(properties.branching_factor()); + /* storage for LeafNode, including storage cost for flexible array LeafNode.elt_v[] */ + std::uint8_t * mem = new std::uint8_t[mem_z]; + +#ifdef NOT_IN_USE + scope x("LeafNode.make"); + x.log(xtag("sizeof(LeafNode)", sizeof(LeafNode)), + xtag("bf", properties.branching_factor()), + xtag("mem_z", mem_z), + xtag("mem", (void *)mem)); +#endif + + return std::unique_ptr(new (mem) LeafNode(std::move(kv_pair), + properties.branching_factor())); + } /*make*/ + + template + std::unique_ptr> + LeafNode::annex(std::size_t lo_ix, + std::size_t hi_ix, + LeafNode * src) + { + using xo::scope; + using xo::xtag; + + std::size_t branching_factor = src->branching_factor(); + + assert(hi_ix >= lo_ix); + assert(hi_ix - lo_ix <= branching_factor); + + std::size_t mem_z = node_sizeof(branching_factor); + std::uint8_t * mem = new std::uint8_t[mem_z]; + +#ifdef NOT_IN_USE + scope x("LeafNode.annex"); + x.log(xtag("sizeof(LeafNode)", sizeof(LeafNode)), + xtag("bf", branching_factor), + xtag("mem_z", mem_z), + xtag("mem", (void *)mem)); +#endif + + std::unique_ptr new_node(new (mem) LeafNode(branching_factor)); + + std::size_t old_n = src->n_elt(); + + new_node->n_elt_ = hi_ix - lo_ix; + + std::size_t n_annex = hi_ix - lo_ix; + + /* annexing from *src into new_node */ + for (std::size_t i = 0; i < n_annex; ++i) { + LeafNodeItemType & new_slot = new_node->lookup_elt(i); + + new_slot = std::move(src->lookup_elt(lo_ix + i)); + } + + /* shuffle over any remaining items in *src starting from hi_ix */ + for (std::size_t i = lo_ix; i + n_annex < old_n; ++i) { + LeafNodeItemType & slot = src->lookup_elt(i); + + slot = std::move(src->lookup_elt(i + n_annex)); + } + + src->n_elt_ = old_n - n_annex; + + if (lo_ix == 0) { + /* new node builds by taking leftmost elements from src + * -> new node becomes src's predecessor + */ + new_node->assign_siblings(src->prev_leafnode(), src); + } else { + /* new node builds by taking rightmost elements from src + * -> new node becomes src's successor + */ + new_node->assign_siblings(src, src->next_leafnode()); + } + + return new_node; + } /*annex*/ + + template + std::pair + LeafNode::find_lub_ix(Key const & key) const { + if (key < this->lookup_elt(0).key()) + return std::make_pair(false, 0); + + /* promise: return value >= 0 */ + + /* .elt_v[0 .. n_elt-1] are maintained in sorted key order */ + std::size_t lo = 0; + std::size_t hi = this->n_elt_; + + while (lo + 1 < hi) { + /* desired child item will be in range [lo, hi) */ + + std::size_t mid = lo + (hi - lo) / 2; + + if (key < this->lookup_elt(mid).key()) + hi = mid; + else + lo = mid; + } + + /* invariant: + * - lo is a valid index: elt_v[lo].kv_pair reflects outcome of most recent call to BplusTree.insert() + * - .elt_v[lo].key <= key + * - if hi<.n_elt, then key < .elt_v[hi].key + */ + bool presence_flag = (key == this->lookup_elt(lo).key()); + + return std::make_pair(presence_flag, hi); + } /*find_lub_ix*/ + + template + void + LeafNode::insert_leaf_item(std::size_t ix, + std::pair const & kv_pair, + bool debug_flag) { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(debug_flag), + xtag("self", this), + xtag("n_elt", this->n_elt()), + xtag("bf", this->branching_factor()), + xtag("ix", ix), + xtag("key", kv_pair.first), + xtag("value", kv_pair.second)); + + if (this->n_elt_ >= this->branching_factor()) { + assert(false); + throw std::runtime_error(tostr("LeafNode::insert_leaf: leaf already full", + xtag("leaf.n_elt", this->n_elt()), + xtag("branching_factor", this->branching_factor()))); + } + + std::size_t pos_ix = this->n_elt_; + + while (pos_ix > ix) { + //scope x1("loop"); + //x1.log(xtag("pos_ix", pos_ix)); + + this->lookup_elt(pos_ix) = std::move(this->lookup_elt(pos_ix - 1)); + --pos_ix; + } + + ++(this->n_elt_); + this->lookup_elt(ix) = LeafNodeItemType(kv_pair); + + log.end_scope(); + } /*insert_leaf*/ + + template + void + LeafNode::remove_leaf(std::size_t ix, bool debug_flag) { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(debug_flag), + xtag("self", this), + xtag("n_elt", this->n_elt()), + xtag("bf", this->branching_factor()), + xtag("ix", ix)); + + if (this->n_elt_ == 0) { + throw std::runtime_error(tostr("LeafNode::remove_leaf: leaf already empty", + xtag("leaf.n_elt", this->n_elt()), + xtag("branching_factor", this->branching_factor()))); + } + + /* TODO: removal action for position pos_ix (maintain reductions) */ + + std::size_t pos_ix = ix; + std::size_t end_ix = this->n_elt_ - 1; + + while (pos_ix < end_ix) { + //scope x1("loop"); + //x1.log(xtag("pos_ix", pos_ix)); + + this->lookup_elt(pos_ix) = std::move(this->lookup_elt(pos_ix + 1)); + ++pos_ix; + } + + --(this->n_elt_); + } /*remove_leaf*/ + + template + void + LeafNode::prepend_from_lh_sibling(LeafNode * lh, std::size_t n, bool debug_flag) { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(debug_flag), + xtag("n", n)); + + if (this->n_elt() + n > this->branching_factor()) { + assert(false); + throw std::runtime_error(tostr("LeafNode.prepend_from_lh_sibling: expected combined #elt <= bf", + xtag("self.n_elt", this->n_elt()), + xtag("n", n), + xtag("bf", this->branching_factor()))); + } + + std::size_t n_lh = lh->n_elt(); + std::size_t n_rh = this->n_elt(); + + /* move elts in *this to the right n steps */ + for (std::size_t ixp1 = this->n_elt(); ixp1 > 0; --ixp1) { + std::size_t ix = ixp1 - 1; + this->lookup_elt(ix + n) = std::move(this->lookup_elt(ix)); + } + + /* xfer n elts from upper end of lh, to lower end of *this */ + for (std::size_t ix = 0; ix < n; ++ix) { + this->lookup_elt(ix) = lh->lookup_elt(n_lh - n + ix); + } + + this->n_elt_ += n; + lh->n_elt_ -= n; + + /* note: since we didn't create/destroy any LeafNodes, + * .prev_leafnode / .next_leafnode pointers are unchanged + */ + + log.end_scope(); + } /*prepend_from_lh_sibling*/ + + template + void + LeafNode::append_from_rh_sibling(std::size_t n, LeafNode * rh) { + using xo::xtag; + + if (this->n_elt() + n > this->branching_factor()) { + assert(false); + throw std::runtime_error(tostr("LeafNode.append_from_rh_sibling: expected combined #elt <= bf", + xtag("self.n_elt", this->n_elt()), + xtag("n", n), + xtag("bf", this->branching_factor()))); + } + + std::size_t n_lh = this->n_elt(); + + for (std::size_t ix = 0; ix < n; ++ix) { + this->lookup_elt(n_lh + ix) = std::move(rh->lookup_elt(ix)); + /* note: leaf items are key,value pairs; + * no parent pointers to fixup (cf InternalNode.append_from_rh_sibling) + */ + } + + this->n_elt_ += n; + + /* shuffle remaining members of rh sibling n items to the left */ + for (std::size_t ix = 0; ix < rh->n_elt() - n; ++ix) { + rh->lookup_elt(ix) = std::move(rh->lookup_elt(ix + n)); + } + + rh->n_elt_ -= n; + + /* note: since we didn't create/destroy any LeafNodes, + * .prev_leafnode / .next_leafnode pointers are unchanged + */ + + } /*append_from_rh_sibling*/ + + template + std::unique_ptr> + LeafNode::split_leaf_lower() { + std::size_t n_elt = this->n_elt_; + std::size_t mid_ix = n_elt / 2; + + return LeafNode::annex(0, mid_ix, this); + } /*split_leaf_lower*/ + + template + std::unique_ptr> + LeafNode::split_leaf_upper() { + std::size_t n_elt = this->n_elt_; + std::size_t mid_ix = n_elt / 2; + + return LeafNode::annex(mid_ix, n_elt, this); + } /*split_leaf_upper*/ + + template + std::size_t + LeafNode::verify_helper(InternalNodeType const * parent, + bool with_lub_flag, + Key const & lub_key, + LeafNodeType const * lh_leaf, + LeafNodeType const * rh_leaf) const { + using xo::xtag; + + /* verify immediate parent pointer is correct */ + if (this->parent() != parent) { + throw std::runtime_error(tostr("LeafNode::verify_helper" + ": expected parent pointer to refer to actual parent", + xtag("stored_parent", this->parent()), + xtag("actual_parent", parent))); + } + + /* verify locally stored keys appear in sorted order */ + std::size_t n = this->n_elt_; + for (std::size_t i=1; i < n; ++i) { + LeafNodeItemType const & prev = this->lookup_elt(i-1); + LeafNodeItemType const & elt = this->lookup_elt(i); + + if (prev.key() < elt.key()) { + ; + } else { + throw std::runtime_error(tostr("LeafNode::verify_helper" + ": expected local keys in strictly increasing order", + xtag("i", i), + xtag("key(i-1)", prev.key()), + xtag("key(i)", elt.key()))); + } + } + + if (with_lub_flag) { + if (this->lookup_elt(n-1).key() < lub_key) { + ; + } else { + throw std::runtime_error(tostr("LeafNode::verify_helper" + ": expected last local key before parent-supplied lub key", + xtag("n", n), + xtag("key(n-1)", this->lookup_elt(n-1).key()), + xtag("lub_key", lub_key))); + } + } + + /* verify next/prev leafnode pointers are consistent */ + if ((lh_leaf && (lh_leaf->next_leafnode() != this)) + || (this->prev_leafnode() != lh_leaf)) + { + throw std::runtime_error(tostr("LeafNode::verify_helper" + ": inconsistent prev/next leaf pointers", + xtag("parent", parent), + xtag("lh_leaf", lh_leaf), + xtag("lh_leaf.next", lh_leaf ? lh_leaf->next_leafnode() : nullptr), + xtag("self", this), + xtag("self.prev", this->prev_leafnode()))); + } + + if ((this->next_leafnode() != rh_leaf) + || (rh_leaf && (rh_leaf->prev_leafnode() != this))) + { + throw std::runtime_error(tostr("LeafNode::verify_helper" + ": inconsistent prev/next leaf pointers", + xtag("parent", parent), + xtag("self", this), + xtag("self.next", this->next_leafnode()), + xtag("rh_leaf", rh_leaf), + xtag("rh_leaf.prev", rh_leaf ? rh_leaf->prev_leafnode() : nullptr))); + } + + return this->n_elt(); + } /*verify_helper*/ + + template + void + LeafNode::verify_glb_key(Key const & key) const { + using xo::xtag; + + LeafNodeItemType const & elt = this->lookup_elt(0); + + if (elt.key() != key) { + throw std::runtime_error(tostr("LeafNode::verify_glb_key" + ": expected stored greatest-lower-bound key to match leftmost leaf's key", + xtag("@", this), + xtag("reported_key", key), + xtag("actual_key", elt.key()))); + } + } /*verify_glb_key*/ + + template + FindNodeResult> + LeafNode::find_min_leaf_node() { + return FindNodeResult>(0, this); + } /*find_min_leaf_node*/ + + template + FindNodeResult> + LeafNode::find_max_leaf_node() { + return FindNodeResult>(0, this); + } /*c_find_max_leaf_node*/ + + template + void + LeafNode::notify_remove() { + if (this->prev_leafnode_) + this->prev_leafnode_->assign_next_leafnode(this->next_leafnode_); + if (this->next_leafnode_) + this->next_leafnode_->assign_prev_leafnode(this->prev_leafnode_); + } /*notify_remove*/ + + template + LeafNode::LeafNode(std::size_t branching_factor) + : LeafNodeShim(NodeType::leaf, branching_factor) + { + /* must call ctor explicitly for each element. + * compiler can't do this for us, b/c it doesn't know size of flexible array + */ + for (std::size_t i = 0, n = branching_factor; i < n; ++i) { + new (&(this->lookup_elt(i))) LeafNodeItemType(); + } + } + + template + LeafNode::LeafNode(std::pair const & kv_pair, + std::size_t branching_factor) + : LeafNodeShim(NodeType::leaf, branching_factor) + { + using xo::scope; + using xo::xtag; + +#ifdef NOT_USING_DEBUG + scope x("LeafNode.ctor"); +#endif + + this->n_elt_ = 1; + /* since .elt_v[] is a flexible array, need to invoke constructors explicitly + * (compiler doesn't know how many elements there are -> can't do it for us + */ + +#ifdef NOT_USING_DEBUG + x.log(xtag("elt[0]", &(this->lookup_elt(0)))); +#endif + + new (&(this->lookup_elt(0))) LeafNodeItemType(kv_pair); + + for (std::size_t i = 1, n = branching_factor; i < n; ++i) { +#ifdef NOT_USING_DEBUG + x.log(xtag("i", i), + xtag("elt[i]", &(this->lookup_elt(i)))); +#endif + + /* using placement-new to invoke ctor explicitly */ + new (&(this->lookup_elt(i))) LeafNodeItemType(); + } + } /*ctor*/ + + template + void + LeafNode::assign_siblings(LeafNode * p, LeafNode * n) { + if (p) + p->assign_next_leafnode(this); + this->prev_leafnode_ = p; + this->next_leafnode_ = n; + if (n) + n->assign_prev_leafnode(this); + } /*assign_siblings*/ + + } /*namespace tree*/ +} /*namespace xo*/ + + +/* end LeafNode.hpp */ diff --git a/include/xo/tree/bplustree/Lhs.hpp b/include/xo/tree/bplustree/Lhs.hpp new file mode 100644 index 00000000..3968a76d --- /dev/null +++ b/include/xo/tree/bplustree/Lhs.hpp @@ -0,0 +1,68 @@ +/* @file Lhs.hpp */ + +#pragma once + +#include + +namespace xo { + namespace tree { + namespace detail { + /* xo::tree::detail::BplusTreeLhsBase + * + * use for {const + non-const} versions of BplusTree::operator[] + * + * Expect: either: + * Tree = BplusTree + * LeafNodeItem = Tree::LeafNodeItemType + * or + * Tree = BplusTree const + * LeafNodeItem = Tree::LeafNodeItemType const + */ + template + class BplusTreeLhsBase { + public: + using mapped_type = typename Tree::mapped_type; + + public: + BplusTreeLhsBase() = default; + BplusTreeLhsBase(Tree * tree, LeafNodeItem const * item) + : p_tree_{tree}, item_{item} {} + + operator mapped_type const & () const { + //using xo::tostr; + + if (!this->item_) { + throw std::runtime_error + ("bptree: attempt to use empty lhs object as rvalue"); + } + + return this->item_->value(); + } + + protected: + Tree * p_tree_ = nullptr; + /* points to key-value pair (interior to a B+ tree LeafNode */ + LeafNodeItem * item_ = nullptr; + }; /*BplusTreeLhsBase*/ + + /* xo::tree::detail::BplusTreeConstLhs + * + * use for const version of BplusTree::operator[] + */ + template + class BplusTreeConstLhs : public BplusTreeLhsBase + { + public: + BplusTreeConstLhs() = default; + BplusTreeConstLhs(BplusTree const * tree, + typename BplusTree::LeafNodeItemType const * item) + : BplusTreeLhsBase(tree, item) {} + }; /*BplusTreeConstLhs*/ + + } /*namespace detail*/ + } /*namespace tree*/ +} /*namespace xo*/ + +/* end Lhs.hpp */ diff --git a/include/xo/tree/bplustree/bplustree_tags.hpp b/include/xo/tree/bplustree/bplustree_tags.hpp new file mode 100644 index 00000000..1f589bee --- /dev/null +++ b/include/xo/tree/bplustree/bplustree_tags.hpp @@ -0,0 +1,16 @@ +/* @file bplustree_tags.hpp */ + +#pragma once + +namespace xo { + namespace tree { + namespace tags { + /* ordinal_enabled: compute ordinal statistics; + * in particular maintain per-node subtree size + */ + enum ordinal_tag { ordinal_enabled, ordinal_disabled }; + } /*tags*/ + } /*namespace tree*/ +} /*namespace xo*/ + +/* end bplustree_tags.hpp */ diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..ea866722 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,47 @@ +# tree/utest/CMakeLists.txt + +# note: tests in this directory use Catch2-provided main +set(SELF_EXE utest.tree) +set(SELF_SOURCE_FILES tree_utest_main.cpp redblacktree.cpp bplustree.cpp) + +add_executable(${SELF_EXE} ${SELF_SOURCE_FILES}) +xo_include_options2(${SELF_EXE}) + +add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) +target_code_coverage(${SELF_EXE} AUTO ALL) + +# ---------------------------------------------------------------- +# internal dependencies: refcnt, ... + +xo_internal_dependency(${SELF_EXE} refcnt) +xo_internal_dependency(${SELF_EXE} indentlog) +xo_dependency_headeronly(${SELF_EXE} randomgen) + +# ---------------------------------------------------------------- +# 3rd part dependency: catch2: + +xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) + +# need this so that catch2/include appears in compile_commands.json, +# on which lsp integration relies. +# +# See also /nix/store/*-catch2-*/lib/cmake/Catch2/ParseAndAddCatchTests.cmake; +# commands here derived from ^ .cmake file +# + +# let's see if xo_external_target_dependency() works for these.. +#find_path(CATCH_INCLUDE_DIR "catch2/catch.hpp") +#target_include_directories(${SELF_UTEST_NAME} PUBLIC ${CATCH_INCLUDE_DIR}) + +## ---------------------------------------------------------------- +## make standard directories for std:: includes explicit +## so that +## (1) they appear in compile_commands.json. +## (2) clangd (run from emacs lsp-mode) can find them +## +#if(CMAKE_EXPORT_COMPILE_COMMANDS) +# set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES +# ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +#endif() + +# end tree/utest/CMakeLists.txt diff --git a/utest/bplustree.cpp b/utest/bplustree.cpp new file mode 100644 index 00000000..5dac55a8 --- /dev/null +++ b/utest/bplustree.cpp @@ -0,0 +1,813 @@ +/* @file bplustree.cpp */ + +#define CATCH_CONFIG_ENABLE_BENCHMARKING + +#include "random_tree_ops.hpp" +#include "xo/tree/BplusTree.hpp" +#include "randomgen/random_seed.hpp" +#include "indentlog/scope.hpp" +#include "catch2/catch.hpp" + +namespace { + using xo::tree::BplusTree; + using xo::tree::BplusStdProperties; + using xo::tree::NullReduce; + using xo::tree::Machdep; + + using xo::rng::Seed; + + using utest::TreeUtil; + + using xo::scope; + using xo::scope_setup; + using xo::xtag; + + using BtreeKey = int; + using BtreeValue = double; + using BtreeProperties = BplusStdProperties; + //using BtreeProperties = BplusStdProperties; + using BpTree = BplusTree, + BtreeProperties>; + + /* random test data (e.g. permutation of integers [0 .. n-1]). + * will do various tree operations using these permutations to control order + * in which keys are presented + */ + struct RandomTestData { + RandomTestData(std::size_t n, + xo::rng::xoshiro256ss * p_rgen); + + std::vector const & u1v() const { return u1v_; } + std::vector const & u2v() const { return u2v_; } + std::vector const & u12_v() const { return u12_v_; } + + private: + /* a set comprising n randomly chosen elements drawn from [0 .. 2n-1]. + * here n = .u1v.size = .u2v.size + */ + std::vector u1v_; + /* complement of .u1v w.r.t. [0 .. 2n-1] */ + std::vector u2v_; + /* .u1v + .u2v */ + std::vector u12_v_; + }; /*RandomTestData*/ + + RandomTestData::RandomTestData(std::size_t n, + xo::rng::xoshiro256ss * p_rgen) + : u1v_(n), u2v_(n), u12_v_(2*n) + { + /* permutation of [0 .. 2n-1] */ + std::vector u(2*n); + + for (std::uint32_t i=0; i<2*n; ++i) + u[i] = i; + std::shuffle(u.begin(), u.end(), *p_rgen); + + u1v_ = std::vector(u.begin(), u.begin() + n); + u2v_ = std::vector(u.begin() + n, u.end()); + u12_v_ = std::move(u); + } /*ctor*/ + + /* representation-independent feature benchmarks for tree algorithms. + * + * +------------------+ + * |AbstractTestParams| + * +------------------+ + * ^ + * | isa +----------------+ + * +------------|StdMapTestParams| benchmark std::map (bogey!) + * | +----------------+ + * | + * | isa +---------------+ + * +------------|BtreeTestParams| benchmark BplusTree + * +---------------+ + */ + struct AbstractTestParams { + virtual ~AbstractTestParams() = default; + /* insert benchmark: + * 1. prime tree by inserting RandomTestData.u1v (random subset comprising n draws from [0 .. 2n-1]) + * 2. measure cost of inserting RandomTestData.u2v (complement of u1v w.r.t [0 .. 2n-1]) + */ + virtual void run_insert_benchmark(RandomTestData const & random_testdata) const = 0; + virtual void run_erase_benchmark(RandomTestData const & random_testdata) const = 0; + virtual void run_lookup_benchmark(RandomTestData const & random_testdata) const = 0; + virtual void run_traverse_benchmark(RandomTestData const & random_testdata) const = 0; + }; + + struct StdMapTestParams : public AbstractTestParams { + StdMapTestParams(char const * name) + : test_name_{name} {} + + /* 1. make map containing keys in random_testdata.u1v. + * 2. during construction, interleave inserts against a temporary map, + * to spoil sequential heap allocation (i.e. simulate fragmentation) + */ + std::map make_random_map1(RandomTestData const & random_testdata) const { + std::map tree; + /* 2nd tree to interfere with locality */ + std::map tree2; + + for (std::uint32_t x : random_testdata.u1v()) { + tree.insert({x, 10*x}); + /* 2nd tree to interfere with locality */ + for (std::uint32_t y = 0; y < 8; ++y) + tree2.insert({8*x+y, 10*8*x+y}); + } + + return tree; + } /*make_random_map1*/ + + /* 1. make map containing keys in both random_testdata.u1v + random_testdata.u2v + * 2. during construction, interleave inserts against a temporary map, + * to spoil sequential heap allocation (i.e. simulate fragmentation) + */ + std::map make_random_map12(RandomTestData const & random_testdata) const { + std::map tree; + /* temporary tree to interfere with locality */ + std::map tree2; + + for (std::uint32_t x : random_testdata.u12_v()) { + tree.insert({x, 10*x}); + /* 2nd tree to interfere with memory locality */ + for (std::uint32_t y = 0; y < 8; ++y) + tree2.insert({8*x+y, 10*8*x+y}); + } + + return tree; + } /*make_random_map12*/ + + virtual void run_insert_benchmark(RandomTestData const & random_testdata) const override; + virtual void run_erase_benchmark(RandomTestData const & random_testdata) const override; + virtual void run_lookup_benchmark(RandomTestData const & random_testdata) const override; + virtual void run_traverse_benchmark(RandomTestData const & random_testdata) const override; + + char const * test_name_ = nullptr; + }; + + void + StdMapTestParams::run_insert_benchmark(RandomTestData const & random_testdata) const + { + /* see also: BtreeTestParams::run_insert_benchmark() */ + + BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock) + { + std::size_t n = random_testdata.u1v().size(); + + std::map tree + = std::move(this->make_random_map1(random_testdata)); + + /* benchmark additional inserts */ + clock.measure([&](int seq) { + std::size_t key = random_testdata.u2v()[seq % n]; + double value = 10 * key; + + tree.insert({key, value}); + return tree.size(); + }); + }; + } /*run_insert_benchmark*/ + + void + StdMapTestParams::run_erase_benchmark(RandomTestData const & random_testdata) const + { + BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock) + { + std::size_t n = random_testdata.u1v().size(); + + std::map tree + = std::move(this->make_random_map12(random_testdata));; + + clock.measure([&](int seq) { + /* catch2 decides how many times to run this lambda, + * in effort to get statistically valid sample. + * + * If it calls lambda n times, then seq will increase from [0 .. n-1] + */ + + std::size_t key = random_testdata.u1v()[seq % n]; + + //std::clog << "i=" << i << std::endl; + tree.erase(key); + + return tree.size(); + }); + }; + } /*run_erase_benchmark*/ + + void + StdMapTestParams::run_lookup_benchmark(RandomTestData const & random_testdata) const + { + BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock) + { + std::size_t n = random_testdata.u1v().size(); + + std::map tree + = std::move(this->make_random_map1(random_testdata)); + + clock.measure([&](int seq) { + /* catch2 decides how many times to run this lambda, + * in effort to get statistically valid sample. + * + * If it calls lambda n times, then seq will increase from [0 .. n-1] + */ + + std::size_t key = random_testdata.u1v()[seq % n]; + + //std::clog << "i=" << i << std::endl; + double value = tree[key]; + + return value; + }); + }; + } /*run_lookup_benchmark*/ + + void + StdMapTestParams::run_traverse_benchmark(RandomTestData const & random_testdata) const + { + BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock) + { + std::size_t n = random_testdata.u1v().size(); + + std::map tree + = std::move(this->make_random_map1(random_testdata)); + + clock.measure([&](int seq) { + /* catch2 decides how many times to run this lambda, + * in effort to get statistically valid sample. + * + * If it calls lambda n times, then seq will increase from [0 .. n-1] + */ + + std::size_t key = random_testdata.u1v()[seq % n]; + + //std::clog << "i=" << i << std::endl; + double value = tree[key]; + + return value; + }); + }; + } /*run_traverse_benchmark*/ + + struct BtreeTestParams : public AbstractTestParams { + BtreeTestParams(char const * name, std::size_t bf, bool debug_flag) + : test_name_{name}, branching_factor_{bf}, debug_flag_{debug_flag} {} + + BpTree make_empty_bptree() const { + BtreeProperties properties(branching_factor_, + debug_flag_); + return BpTree(properties); + } + + /* 1. make b+ tree containing keys in random_testdata.u1v. + * 2. during constructions, interleave inserts against a temporary b+ tree, + * to spoil sequential heap allocation (i.e. simulate fragmentation) + */ + BpTree make_random_bptree1(RandomTestData const & random_testdata) const { + BpTree bptree = this->make_empty_bptree(); + /* 2nd tree, just to spoil memory locality */ + BpTree bptree2 = this->make_empty_bptree(); + + for (std::uint32_t x : random_testdata.u1v()) { + bptree.insert(BpTree::value_type(x, 10 * x)); + /* 2nd tree to interfere with locality */ + for (std::uint32_t y = 0; y < 8; ++y) { + bptree2.insert(BpTree::value_type(8*x+y, 10 * (8*x+y))); + } + } + + return bptree; + } /*make_random_bptree1*/ + + BpTree make_random_bptree12(RandomTestData const & random_testdata) const { + BpTree bptree = this->make_empty_bptree(); + /* 2nd tree, just to spoil memory locality */ + BpTree bptree2 = this->make_empty_bptree(); + + for (std::uint32_t x : random_testdata.u12_v()) { + bptree.insert(BpTree::value_type(x, 10 * x)); + /* 2nd tree to interfere with locality */ + for (std::uint32_t y = 0; y < 8; ++y) { + bptree2.insert(BpTree::value_type(8*x+y, 10 * (8*x+y))); + } + } + + return bptree; + } /*make_random_bptree12*/ + + void run_unit_test(xo::rng::xoshiro256ss * p_rgen) const; + + virtual void run_insert_benchmark(RandomTestData const & random_testdata) const override; + virtual void run_erase_benchmark(RandomTestData const & random_testdata) const override; + virtual void run_lookup_benchmark(RandomTestData const & random_testdata) const override; + virtual void run_traverse_benchmark(RandomTestData const & random_testdata) const override; + + /* test (or benchmark) name -- 1st argument to catch2 TEST_CASE() / BENCHMARK() / SECTION() macro */ + char const * test_name_ = nullptr; + /* exercise B+ tree with this branching factor */ + std::size_t branching_factor_ = 0; + /* for benchmarks only: if true enable verbose logging of B+ tree operations. otherwise not used */ + bool debug_flag_ = false; + }; /*BtreeTestParams*/ + + void + BtreeTestParams::run_unit_test(xo::rng::xoshiro256ss * p_rgen) const + { + std::size_t branching_factor = this->branching_factor_; + + /* perform a series of tests with increasing scale */ + for (std::uint32_t n = 0; n <= 1024;) { + if (n == 0) { + bool ok_flag = false; + + for (std::uint32_t attention = 0; !ok_flag && (attention < 2); ++attention) { + ok_flag = true; + + bool debug_flag = (attention == 1); + + BtreeProperties properties(branching_factor, + debug_flag); + BpTree bptree(properties); + + scope log(XO_DEBUG2(debug_flag, "bptree"), + xtag("vm_page_size", Machdep::get_page_size()), + xtag("branching_factor", bptree.branching_factor()), + xtag("leaf_node_size", sizeof(BpTree::LeafNodeType)), + xtag("internal_node_size", sizeof(BpTree::InternalNodeType))); + + REQUIRE_ORCAPTURE(ok_flag, debug_flag, bptree.size() == 0); + REQUIRE_ORCAPTURE(ok_flag, debug_flag, bptree.verify_ok(true) == true); + + log && log(xtag("size", n)); + + ok_flag &= TreeUtil::check_bidirectional_iterator(0 /*dvalue - not used*/, + debug_flag, + bptree); + + ok_flag &= TreeUtil::test_clear(debug_flag, &bptree); + + log.end_scope(); + } + } else { + /* for each tree size, do multiple trials; + * choosing different pseudorandom key order for each trial + */ + for (std::uint32_t trial = 0; trial < 10; ++trial) { + /* repeated trials with different rng state */ + + bool ok_flag = false; + + for (std::uint32_t attention = 0; !ok_flag && (attention < 2); ++attention) { + ok_flag = true; + + /* attention=0: + * - no logging + * - detect assertion failures, but don't report them to catch + * attention=1: + * - only runs if failure detected with attention=0 + * - full logging + * - report to catch + */ + + bool debug_flag = (attention == 1); + + BtreeProperties properties(branching_factor, + debug_flag); + BpTree bptree(properties); + + scope log(XO_DEBUG2(debug_flag, "bptree"), + xtag("vm_page_size", Machdep::get_page_size()), + xtag("branching_factor", bptree.branching_factor()), + xtag("leaf_node_size", sizeof(BpTree::LeafNodeType)), + xtag("internal_node_size", sizeof(BpTree::InternalNodeType))); + + REQUIRE_ORCAPTURE(ok_flag, debug_flag, bptree.size() == 0); + REQUIRE_ORCAPTURE(ok_flag, debug_flag, bptree.verify_ok(true) == true); + + log && log(xtag("size", n), xtag("trial", trial)); + + /* insert [0..n-1] in random order */ + ok_flag &= TreeUtil::random_inserts(n, debug_flag, p_rgen, &bptree); + + /* verification problem -> print tree */ + log && log(xtag("bptree", (char const *)"...")); + if (log) bptree.print(std::cout, log.nesting_level() + 2); + + try { + REQUIRE_ORCAPTURE(ok_flag, debug_flag, bptree.verify_ok(debug_flag)); + } catch(std::exception & ex) { + log && log(xtag("exception", ex.what())); + } + + if (properties.ordinal_enabled()) { + ok_flag &= TreeUtil::check_ordinal_lookup(0 /*dvalue*/, + debug_flag, + bptree); + } + + /* verify inorder traverse, using iterator api */ + ok_flag &= TreeUtil::check_bidirectional_iterator(0, + debug_flag, + bptree); + + ok_flag &= TreeUtil::random_lookups(debug_flag, + bptree, + p_rgen); + + if (properties.ordinal_enabled()) { + /* paranoid check that iteration / random_lookups didn't somehow disturb tree */ + ok_flag &= TreeUtil::check_ordinal_lookup(0 /*dvalue*/, + debug_flag, + bptree); + } + + /* TODO: + * - check_reduced_sum() + * - check_ordinal_lookup() + * - check_bidirectional_iterator() + * - random_updates() + * - check_ordinal_lookup() + * - check_bidirectional_iterator() + * - check_reduced_sum() + */ + + /* remove [0..n-1] in random order */ + ok_flag &= TreeUtil::random_removes(debug_flag, p_rgen, &bptree); + + /* insert [0..n-1] again, so we can test .clear() */ + ok_flag &= TreeUtil::random_inserts(n, debug_flag, p_rgen, &bptree); + + ok_flag &= TreeUtil::test_clear(debug_flag, &bptree); + + log.end_scope(); + } /*loop over attention value*/ + } /*loop over trial#*/ + } + + if (n == 0) + n = 1; + else + n = 2*n; + } + } /*run_unit_test*/ + + void + BtreeTestParams::run_insert_benchmark(RandomTestData const & random_testdata) const + { + BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock) + { + std::size_t n = random_testdata.u1v().size(); + + BpTree bptree = std::move(this->make_random_bptree1(random_testdata)); + + /* benchmark additional inserts (don't want to benchmark on empty tree) */ + clock.measure([&](int seq) { + /* catch2 decides how many times to run this lambda, + * in effort to get statistically valid sample. + * + * If it calls lambda n times, then seq will increase from [0 .. n-1] + */ + + std::size_t key = random_testdata.u2v()[seq % n]; + double value = 10 * key; + + bptree.insert(BpTree::value_type(key, value)); + + return bptree.size(); + }); + }; + } /*run_insert_benchmark*/ + + void + BtreeTestParams::run_erase_benchmark(RandomTestData const & random_testdata) const + { + BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock) + { + std::size_t n = random_testdata.u1v().size(); + + /* b+ tree with 2n elements */ + BpTree bptree = std::move(this->make_random_bptree12(random_testdata)); + + /* measure time to remove n elements */ + clock.measure([&](int seq) { + /* catch2 decides how many times to run this lambda, + * in effort to get statistically valid sample. + * + * If it calls lambda n times, then seq will increase from [0 .. n-1] + */ + + //std::clog << "i=" << i << std::endl; + bptree.erase(random_testdata.u1v()[seq % n]); + + return bptree.size(); + }); + }; + } /*run_erase_benchmark*/ + + void + BtreeTestParams::run_lookup_benchmark(RandomTestData const & random_testdata) const + { + BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock) + { + std::size_t n = random_testdata.u1v().size(); + + BpTree bptree = std::move(this->make_random_bptree1(random_testdata)); + + /* benchmark random lookups */ + clock.measure([&](int seq) { + /* catch2 decides how many times to run this lambda, + * in effort to get statistically valid sample. + * + * If it calls lambda n times, then seq will increase from [0 .. n-1] + */ + + std::size_t key = random_testdata.u1v()[seq % n]; + + double value = bptree[key]; + + return value; + }); + }; + } /*run_lookup_benchmark*/ + + void + BtreeTestParams::run_traverse_benchmark(RandomTestData const & random_testdata) const + { + BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock) + { + std::size_t n = random_testdata.u1v().size(); + + BpTree bptree = std::move(this->make_random_bptree1(random_testdata)); + + /* benchmark traverse */ + BpTree::const_iterator ix = bptree.begin(); + + clock.measure([&](int seq) { + /* catch2 decides how many times to run this lambda, + * in effort to get statistically valid sample. + * + * If it calls lambda n times, then seq will increase from [0 .. n-1] + */ + + if (seq % n == 0) + ix = bptree.begin(); + + return ix++; + }); + }; + } /*run_traverse_benchmark*/ + + TEST_CASE("bptree", "[bplustree]") { + uint64_t seed = 14950349842636922572UL; + /* can reseed from /dev/random with: */ + //Seed seed; + + auto rgen = xo::rng::xoshiro256ss(seed); + + /* exercise multiple branching factors */ + std::array const params_v + = {{ + BtreeTestParams("bf=4", + 4 /*branching_factor*/, + false /*debug_flag - not used*/), + BtreeTestParams("bf=12", + 12 /*branching_factor*/, + false /*debug_flag - not used*/), + BtreeTestParams("bf=28", + 28 /*branching_factor*/, + false /*debug_flag - not used*/), + BtreeTestParams("bf=60", + 60 /*branching_factor*/, + false /*debug_flag - not used*/) + }}; + + for (std::uint32_t i_pm = 0; i_pm < params_v.size(); ++i_pm) { + SECTION(params_v[i_pm].test_name_) { + params_v[i_pm].run_unit_test(&rgen); + } + } + } /*TEST_CASE(bptree)*/ + + /* to run: + * $ ./utest.tree [!benchmark] + * + * looks like ospage4 (1k nodes) gets best performance + */ + TEST_CASE("bptree-benchmark", "[!benchmark]") { + using BtreeProperties = BplusStdProperties; + + /* 2 cache lines per node (though note that we're not aligning nodes on cacheline boundaries) */ + std::size_t const c_cacheline_branching_factor = 4; // BtreeProperties::default_cacheline_branching_factor(); + std::size_t const c_ospage16_branching_factor = BtreeProperties::branching_factor_for_size(Machdep::get_page_size() / 16); + std::size_t const c_ospage8_branching_factor = BtreeProperties::branching_factor_for_size(Machdep::get_page_size() / 8); + std::size_t const c_ospage4_branching_factor = BtreeProperties::branching_factor_for_size(Machdep::get_page_size() / 4); + std::size_t const c_ospage2_branching_factor = BtreeProperties::branching_factor_for_size(Machdep::get_page_size() / 2); + std::size_t const c_ospage1_branching_factor = BtreeProperties::branching_factor_for_size(Machdep::get_page_size()); + + /* random seed -- we don't need deterministic behavior for benchmarking, unless we encounter internal logic error */ + //std::uint64_t seed = 17372468046414980217UL; + Seed seed; + + auto rgen = xo::rng::xoshiro256ss(seed); + + constexpr bool c_debug_flag = false; + + /* n keys [0 .. n-1] */ + std::uint32_t n = 25000; + + RandomTestData random_testdata(n, &rgen); + +#ifdef OBSOLETE + /* random permutation of [0..n-1] */ + std::vector u(n); + { + for (std::uint32_t i=0; i u2(n); + { + for (std::uint32_t i=0; i, 7> const params_v + = {{ + std::unique_ptr(new StdMapTestParams("std-map-insert")), + std::unique_ptr(new BtreeTestParams("bplus-min-insert", + c_cacheline_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage16-insert", + c_ospage16_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage8-insert", + c_ospage8_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage4-insert", + c_ospage4_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage2-insert", + c_ospage2_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage-insert", + c_ospage1_branching_factor, + false)) + }}; + + /* note: w/cacheline: + * getting 593ms for 10^6 inserts; + * i.e. ~593ns each + * w/ospage: + * getting 188ms for 10^6 inserts; + * i.e. ~188ns each + * (with ospage size 4k -> branching factor 252) + */ + for(std::uint32_t i_bm = 0; i_bm < params_v.size(); ++i_bm) { + params_v[i_bm]->run_insert_benchmark(random_testdata); + } + } + + { + std::array, 7> const params_v + = {{ + std::unique_ptr(new StdMapTestParams("std-map-erase")), + std::unique_ptr(new BtreeTestParams("bplus-min-remove", + c_cacheline_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage16-remove", + c_ospage16_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage8-remove", + c_ospage8_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage4-remove", + c_ospage8_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage2-remove", + c_ospage8_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage1-remove", + c_ospage1_branching_factor, + false)) + }}; + + /* note: cacheline: getting 72us for 10^2 removes; + * i.e. ~7.2ns each + * + * ospage: getting 243us for 10^4 removes; + * i.e. ~24ns each + */ + for (std::uint32_t i_bm = 0; i_bm < params_v.size(); ++i_bm) { + params_v[i_bm]->run_erase_benchmark(random_testdata); + } + } + + { + std::array, 7> const params_v + = {{ + std::unique_ptr(new StdMapTestParams("std-map-lookup")), + std::unique_ptr(new BtreeTestParams("bplus-min-lookup", + c_cacheline_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage16-lookup", + c_ospage16_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage8-lookup", + c_ospage8_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage4-lookup", + c_ospage4_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage2-lookup", + c_ospage2_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage1-lookup", + c_ospage1_branching_factor, + false)) + }}; + + /* note: cacheline: + * getting 850us for 10^4 lookups; + * -> ~85ns each + * ospage: + * getting 585us for 10^4 lookups; + * -> ~58ns each + */ + for (std::uint32_t i_bm = 0; i_bm < params_v.size(); ++i_bm) { + params_v[i_bm]->run_lookup_benchmark(random_testdata); + } + } + + { + std::array, 7> const params_v + = {{ + std::unique_ptr(new StdMapTestParams("std-map-traverse")), + std::unique_ptr(new BtreeTestParams("bplus-min-traverse", + c_cacheline_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage16-traverse", + c_ospage16_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage8-traverse", + c_ospage8_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage4-traverse", + c_ospage4_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage2-traverse", + c_ospage2_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage1-traverse", + c_ospage1_branching_factor, + false)) + }}; + + /* note: cacheline: getting 25us to traverse tree of size 10^4 + * -> ~2.5ns each + * note: ospage: getting 6us to traverse tree of size 10^4 + * -> ~0.6ns each + */ + for (std::uint32_t i_bm = 0; i_bm < params_v.size(); ++i_bm) { + params_v[i_bm]->run_traverse_benchmark(random_testdata); + } + } + + } /*TEST_CASE(bptree-benchmark)*/ +} /*namespace*/ + +/* end bplustree.cpp */ diff --git a/utest/random_tree_ops.hpp b/utest/random_tree_ops.hpp new file mode 100644 index 00000000..b81601f7 --- /dev/null +++ b/utest/random_tree_ops.hpp @@ -0,0 +1,450 @@ +/* @file random_tree_ops.hpp **/ + +#include "randomgen/xoshiro256.hpp" +#include "indentlog/scope.hpp" +#include "indentlog/print/tag.hpp" +#include "indentlog/print/vector.hpp" +#include "catch2/catch.hpp" +#include +#include +#include + +namespace utest { + struct Util { + /* generate vector with integers [0.. n-1] */ + static std::vector vector_upto(std::uint32_t n) { + std::vector u(n); + for (std::uint32_t i = 0; i < n; ++i) + u[i] = i; + + return u; + } /*vector_upto*/ + + static std::map + map_upto(std::uint32_t n) + { + std::map m; + for(std::uint32_t i=0; i + random_permutation(uint32_t n, xo::rng::xoshiro256ss *p_rgen) { + /* vector [0 .. n-1] */ + std::vector u = vector_upto(n); + + /* shuffle to get unpredictable permutation */ + std::shuffle(u.begin(), u.end(), *p_rgen); + + return u; + } /*random_permutation*/ + }; /*Util*/ + +/* note: trivial REQUIRE() call in else branch bc we still want + * catch2 to count assertions when verification succeeds + */ +# define REQUIRE_ORCAPTURE(ok_flag, catch_flag, expr) \ + if (catch_flag) { \ + REQUIRE((expr)); \ + } else { \ + REQUIRE(true); \ + ok_flag &= (expr); \ + } + +# define REQUIRE_ORFAIL(ok_flag, catch_flag, expr) \ + REQUIRE_ORCAPTURE(ok_flag, catch_flag, expr); \ + if (!ok_flag) \ + return ok_flag + + + template + struct TreeUtil : public Util { + static bool + test_clear(bool catch_flag, + Tree * p_tree) + { + bool ok_flag = true; + + REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok()); + + p_tree->clear(); + + REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag)); + REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->empty()); + REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->size() == 0); + + return ok_flag; + } /*test_clear*/ + + /* do n random inserts (taken from *p_rgen) into *p_rbtreẹ + * inserted keys will be distinct values in [0, .., n-1] + */ + static bool + random_inserts(std::uint32_t n, + bool catch_flag, + xo::rng::xoshiro256ss * p_rgen, + Tree * p_tree) + { + using xo::xtag; + + bool ok_flag = true; + + xo::scope log(XO_DEBUG(catch_flag)); + + REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok()); + + /* n keys 0..n-1 */ + std::vector u(n); + for(std::uint32_t i=0; iinsert(typename Tree::value_type(x, 10 * x)); + + REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag)); + + REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.second); + + /* verify: iterator returned by Treẹinsert(), refers to inserted key,value pair */ + log && log(xtag("iter.node", insert_result.first.node())); + REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.first->first == x); + REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.first->second == 10 * x); + + ++i; + } + + REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->size() == n); + + return ok_flag; + } /*random_inserts*/ + + /* do n random removes (taken from *p_rgen) from *p_rbtree; + * assumes *p_rbtree has keys [0 .. n-1] where n=p_rbtreẹsize + */ + static bool + random_removes(bool catch_flag, + xo::rng::xoshiro256ss * p_rgen, + Tree * p_tree) + { + using xo::scope; + using xo::xtag; + + bool ok_flag = true; + + xo::scope log(XO_DEBUG(catch_flag)); + + REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag)); + + uint32_t n = p_tree->size(); + + /* random permutation of keys in *p_tree */ + std::vector u + = random_permutation(n, p_rgen); + + log && log(xtag("remove-order", u)); + + /* will keep track of which keys remain as we move them */ + std::map m = Util::map_upto(n); + + /* remove keys in permutation order */ + std::uint32_t i = 1; + for (std::uint32_t x : u) { + log && log("iter i: removing key from n-node tree", + xtag("i", i), xtag("key", x), xtag("n", n)); + + /* remove x from tracking map m also */ + m.erase(x); + + log && log("remove key :iter ", i, "/", n, xtag("key", x)); + + p_tree->erase(x); + // rbtreẹdisplay(); + REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->size() == n-i); + /* amongst other things, this guarantees that keys in *p_tree + * appear in increasing order + */ + REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag)); + +#ifdef NOT_YET + /* 1. rbtree should now contain all the keys in [0..n-1], + * with u[0]..u[i-1] excluded; this is the same as the + * contents of m. + */ + auto m_ix = m.begin(); + auto m_end_ix = m.end(); + auto visitor_fn = + ([&m_ix, m_end_ix] + (std::pair const & contents) + { + REQUIRE(m_ix != m_end_ix); + REQUIRE(contents.first == m_ix->second); + ++m_ix; + }); + p_tree->visit_inorder(visitor_fn); +#endif + ++i; + } + + REQUIRE_ORFAIL(ok_flag, catch_flag, m.empty()); + REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->size() == 0); + + log.end_scope(); + + return ok_flag; + } /*random_removes*/ + + /* Require: + * - tree has keys [0..n-1], where n=treẹsize() + * - for each key k, associated value is 10*k + */ + static bool + random_lookups(bool catch_flag, + Tree const & tree, + xo::rng::xoshiro256ss * p_rgen) + { + using xo::scope; + using xo::xtag; + + xo::scope log(XO_DEBUG(catch_flag)); + + /* -> false if/when verification fails */ + bool ok_flag = true; + + REQUIRE_ORFAIL(ok_flag, catch_flag, tree.verify_ok(catch_flag)); + + size_t n = tree.size(); + std::vector u + = random_permutation(n, p_rgen); + + /* lookup keys in permutation order */ + std::uint32_t i = 1; + for (std::uint32_t x : u) { + INFO(tostr(xtag("i", i), xtag("n", n), xtag("x", x))); + + REQUIRE_ORFAIL(ok_flag, catch_flag, tree[x] == x*10); + REQUIRE_ORFAIL(ok_flag, catch_flag, tree.verify_ok(catch_flag)); + REQUIRE_ORFAIL(ok_flag, catch_flag, tree.size() == n); + + /* also test treẹfind() */ + auto find_ix = tree.find(x); + + REQUIRE_ORFAIL(ok_flag, catch_flag, find_ix != tree.end()); + REQUIRE_ORFAIL(ok_flag, catch_flag, find_ix->first == x); + REQUIRE_ORFAIL(ok_flag, catch_flag, find_ix->second == x*10); + + ++i; + } + + REQUIRE_ORFAIL(ok_flag, catch_flag, tree.size() == n); + + log.end_scope(); + + return ok_flag; + } /*random_lookups*/ + + /* Require: + * - tree has keys [0..n-1], where n=treẹsize() + * - tree value at key k is dvalue+10*k + */ + static bool + check_ordinal_lookup(std::uint32_t dvalue, + bool catch_flag, + Tree const & tree) + { + using xo::scope; + using xo::xtag; + + /* -> false if/when verification fails */ + bool ok_flag = true; + + xo::scope log(XO_DEBUG(catch_flag)); + + std::size_t const n = tree.size(); + std::size_t i = 0; + + log && log("tree with size n", xtag("n", n)); + + for (std::size_t i=0; ifirst == i)); + REQUIRE_ORFAIL(ok_flag, catch_flag, (ix->second == 10*i + dvalue)); + } + + log.end_scope(); + + return ok_flag; + } /*check_ordinal_lookup*/ + + /* Require: + * - tree has keys [0..n-1], where n=treẹsize() + * - tree values at key k is dvalue+10*k + * + * catch_flag. true -> log to console + interact with catch2 + * false -> verify iteration behavior for return code + */ + static bool + check_bidirectional_iterator(uint32_t dvalue, + bool catch_flag, + Tree const & tree) + { + using xo::scope; + using xo::xtag; + + /* -> false if/when verification fails */ + bool ok_flag = true; + + std::size_t const n = tree.size(); + + xo::scope log(XO_DEBUG(catch_flag)); + + log && log("tree with size n", xtag("n", n)); + + { + std::size_t i = 0; + + auto end_ix = tree.end(); + + log && log(xtag("end_ix", end_ix)); + + auto begin_ix = tree.begin(); + auto ix = begin_ix; + + int last_key = -1; + + while (ix != end_ix) { + log && log("forward loop top", + xtag("i", i), + xtag("ix", ix)); + + REQUIRE_ORFAIL(ok_flag, catch_flag, ix->first == i); + REQUIRE_ORFAIL(ok_flag, catch_flag, ix->second == dvalue + 10*i); + if(i > 0) { + REQUIRE_ORFAIL(ok_flag, catch_flag, ix->first > last_key); + } + last_key = ix->first; + ++i; + ++ix; + + log && log("forward loop bottom", + xtag("last_key", last_key), + xtag("next ix", ix)); + } + + /* should have visited exactly n locations */ + REQUIRE_ORFAIL(ok_flag, catch_flag, i == n); + REQUIRE_ORFAIL(ok_flag, catch_flag, ix == end_ix); + + log && log(xtag("ix", ix), xtag("begin_ix", begin_ix)); + + /* now run iterator backwards, + * starting from "one past the end" + */ + if(ix != begin_ix) { + do { + --i; + --ix; + + log && log("forward backup", + xtag("i", i), + xtag("ix", ix)); + + REQUIRE_ORFAIL(ok_flag, catch_flag, ix.is_dereferenceable()); + + log && log(xtag("ix.first", (*ix).first)); + + REQUIRE_ORFAIL(ok_flag, catch_flag, (*ix).first == i); + } while (ix != begin_ix); + } + + /* should have visited exactly n locations in reverse */ + REQUIRE_ORFAIL(ok_flag, catch_flag, i == 0); + } + + /* ----- reverse iterators ----- */ + + { + std::int64_t i = n - 1; + + auto rbegin_ix = tree.rbegin(); + auto rend_ix = tree.rend(); + + auto rix = rbegin_ix; + + int last_key = -1; + + while (rix != rend_ix) { + log && log("reverse loop top", + xtag("i", i), + xtag("rix", rix)); + + REQUIRE_ORFAIL(ok_flag, catch_flag, rix->first == i); + REQUIRE_ORFAIL(ok_flag, catch_flag, rix->second == dvalue + 10*i); + if (i < n-1) { + REQUIRE_ORFAIL(ok_flag, catch_flag, rix->first < last_key); + } + last_key = rix->first; + --i; + ++rix; + + log && log("reverse loop bottom", + xtag("last_key", last_key), + xtag("next ix", rix)); + } + + /* should have visited exactly n locations */ + REQUIRE_ORFAIL(ok_flag, catch_flag, i == -1); + + log && log(xtag("rbegin_ix", rbegin_ix)); + + /* now run reverse iterator backwrds, + * starting from "one before the beginning" + */ + if (rix != rbegin_ix) { + do { + ++i; + --rix; + + log && log("reverse backup", + xtag("i", i), + xtag("rix", rix), + xtag("rix.first", rix->first)); + + REQUIRE_ORFAIL(ok_flag, catch_flag, (*rix).first == i); + } while (rix != rbegin_ix); + } + + /* should have visited exactly n locations in reversê2 */ + REQUIRE_ORFAIL(ok_flag, catch_flag, i == n - 1); + } + + log.end_scope(); + + return ok_flag; + } /*check_bidirectional_iterator*/ + }; /*TreeUtil*/ +} /*namespace utest*/ + +/* end random_tree_ops.hpp */ diff --git a/utest/redblacktree.cpp b/utest/redblacktree.cpp new file mode 100644 index 00000000..658abf64 --- /dev/null +++ b/utest/redblacktree.cpp @@ -0,0 +1,248 @@ +/* @file redblacktree.cpp */ + +#include "random_tree_ops.hpp" +#include "xo/tree/RedBlackTree.hpp" +#include + +namespace { + using xo::tree::RedBlackTree; + using xo::tree::SumReduce; + using xo::tree::OrdinalReduce; + using xo::tree::NullReduce; + using xo::rng::xoshiro256ss; + + using utest::Util; + using utest::TreeUtil; + + using xo::scope; + using xo::scope_setup; + using xo::xtag; + + //using RbTree = RedBlackTree>; + using RbTree = RedBlackTree>; + +#ifdef OBSOLETE + /* Require: + * - rbtree has keys [0..n-1] where n=rbtree.size(), + * - rbtree value at key k is dvalue+10*k + */ + void + check_ordinal_lookup(uint32_t dvalue, + RbTree const & rbtree) + { + size_t const n = rbtree.size(); + size_t i = 0; + + for(size_t i=0; ifirst == i); + } + } /*check_ordinal_lookup*/ +#endif + + /* check that RedBlackTree<>::find_sum_glb() works as advertised. + * + * partial sums of v[j] for j<=i will be: + * + * (i+1) . i + * 10 . --------- + ((i+1) . dvalue) + * 2 + * + * = (i+1).(5.i + dvalue) + * + * Require: + * - rbtree has keys [0..n-1], where n=rbtree.size() + * - rbtree value at key k is dvalue+10*k + */ + void + check_reduced_sum(uint32_t dvalue, + RbTree const & rbtree) + { + size_t const n = rbtree.size(); + + for(size_t i = 0; i < n; ++i) { + /* compute reduction up to key=i */ + double reduced_upto + = rbtree.reduce_lub(i /*key*/, + true /*is_closed*/); + + double reduced = (i+1) * (5*i + dvalue); + + INFO(tostr(xtag("i", i), xtag("n", n), + xtag("tree.reduced_upto", reduced_upto), + xtag("reduced", reduced), + xtag("dvalue", dvalue))); + + auto glb_ix = rbtree.cfind_sum_glb(reduced); + + REQUIRE(reduced_upto == reduced); + + REQUIRE(glb_ix.is_dereferenceable()); + /* glb_ix is truth-y */ + REQUIRE(glb_ix); + + REQUIRE(glb_ix->first == i); + } + } /*check_reduced_sum*/ + +#ifdef OBSOLETE + /* Require: + * - *p_rbtree has keys [0..n-1], where n=rbtree.size() + * - for each key k, associated value is 10*k + */ + void + random_lookups(RbTree const & rbtree, + xoshiro256ss * p_rgen) + { + REQUIRE(rbtree.verify_ok()); + + size_t n = rbtree.size(); + std::vector u + = Util::random_permutation(n, p_rgen); + + /* lookup keys in permutation order */ + uint32_t i = 1; + for (uint32_t x : u) { + INFO(tostr(xtag("i", i), xtag("n", n), xtag("x", x))); + + REQUIRE(rbtree[x] == x*10); + REQUIRE(rbtree.verify_ok()); + REQUIRE(rbtree.size() == n); + ++i; + } + + REQUIRE(rbtree.size() == n); + } /*random_lookups*/ +#endif + + /* Require: + * - *p_rbtree has keys [0..n-1], where n=rbtree.size() + * - for each key k, associated value is 10*k + * + * Promise: + * - for each key k, associated value is dvalue + 10*k + */ + void + random_updates(uint32_t dvalue, + RbTree * p_rbtree, + xoshiro256ss * p_rgen) + { + REQUIRE(p_rbtree->verify_ok()); + + std::size_t n = p_rbtree->size(); + std::vector u + = Util::random_permutation(n, p_rgen); + + /* update key/value pairs in permutation order */ + uint32_t i = 1; + for (uint32_t x : u) { + REQUIRE((*p_rbtree)[x] == x*10); + + (*p_rbtree)[x] = dvalue + 10*x; + + REQUIRE((*p_rbtree)[x] == dvalue + 10*x); + REQUIRE(p_rbtree->verify_ok()); + /* assignment to existing key does not change tree size */ + REQUIRE(p_rbtree->size() == n); + ++i; + } + + REQUIRE(p_rbtree->size() == n); + } /*random_updates_1*/ + + TEST_CASE("rbtree", "[redblacktree]") { + RbTree rbtree; + + std::uint64_t seed = 14950349842636922572UL; + /* can reseed from /dev/urandom with: */ + //arc4random_buf(&seed, sizeof(seed)); + + auto rgen = xo::rng::xoshiro256ss(seed); + + /* perform a series of tests with increasing scale */ + for(std::uint32_t n=0; n<=1024; ) { + bool ok_flag = false; + + for (std::uint32_t attention = 0; !ok_flag && (attention < 2); ++attention) { + /* attention=0: + * - no logging + * - detect assertion failures, but don't report them to catch2 + * attention=1: + * - only runs if failure detected with attention=0 + * - full logging + * - report to catch + */ + + bool debug_flag = (attention == 1); + + scope log(XO_DEBUG2(debug_flag, "rbtree")); + log && log(xtag("size", n)); + + ok_flag = true; + + if (n == 0) { + /* check iteration on empty tree */ + ok_flag &= TreeUtil::check_bidirectional_iterator(0 /*dvalue - not used*/, + debug_flag, + rbtree); + } else { + /* insert [0..n-1] in random order */ + ok_flag &= TreeUtil::random_inserts(n, debug_flag, &rgen, &rbtree); + + /* TODO: generalize remaining helpers; share with bplustree unit test */ + + /* check iterator traverses [0..n-1] in both directions (using ++ and --) */ + ok_flag &= TreeUtil::check_ordinal_lookup(0 /*dvalue*/, + debug_flag, + rbtree); + /* verify end-to-end iteration */ + ok_flag &= TreeUtil::check_bidirectional_iterator(0, + debug_flag, + rbtree); + /* verify behavior of .reduce_lub(), .find_sum_glb() */ + check_reduced_sum(0, rbtree); + /* verify behavior of read-only variant of operator[] */ + ok_flag &= TreeUtil::random_lookups(debug_flag, + rbtree, + &rgen); + + /* verify that lookups didn't somehow disturb tree contents */ + ok_flag &= TreeUtil::check_ordinal_lookup(0 /*dvalue*/, + debug_flag, + rbtree); + + ok_flag &= TreeUtil::check_bidirectional_iterator(0, + debug_flag, + rbtree); + /* verify update via read/write operator[] */ + random_updates(10000, &rbtree, &rgen); + + /* verify that updates changed tree contents in expected way */ + ok_flag &= TreeUtil::check_ordinal_lookup(10000 /*dvalue*/, + debug_flag, + rbtree); + + /* verify end-to-end iteration */ + ok_flag &= TreeUtil::check_bidirectional_iterator(10000, + debug_flag, + rbtree); + /* verify behavior of .reduce_lub(), .find_sum_glb() */ + check_reduced_sum(10000, rbtree); + /* verify behavior of read/write variant of operator[] */ + ok_flag &= TreeUtil::random_removes(debug_flag, &rgen, &rbtree); + } + + log.end_scope(); + } + + if (n == 0) + n = 1; + else + n = 2*n; + } + } /*TEST_CASE(rbtree)*/ +} /*namespace*/ + +/* end redblacktree.cpp */ diff --git a/utest/tree_utest_main.cpp b/utest/tree_utest_main.cpp new file mode 100644 index 00000000..73ae4bab --- /dev/null +++ b/utest/tree_utest_main.cpp @@ -0,0 +1,7 @@ +/* @file tree_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#define CATCH_CONFIG_ENABLE_BENCHMARKING +#include "catch2/catch.hpp" + +/* end tree_utest_main.cpp */ From 0c76f5f40aa758f4e221e3f3861161af399ac31b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 13:24:09 -0400 Subject: [PATCH 0240/2693] + .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..49f711e2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# typical build directories +build +ccov From 5e16be0b5adeecef371bbc149539e71678576c1d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 13:28:47 -0400 Subject: [PATCH 0241/2693] rename xo-tree -> xo-ordinaltree --- CMakeLists.txt | 9 ++++----- ...treeConfig.cmake.in => xo_ordinaltreeConfig.cmake.in} | 0 include/xo/{tree => ordinaltree}/BplusTree.hpp | 0 include/xo/{tree => ordinaltree}/RedBlackTree.hpp | 0 .../xo/{tree => ordinaltree}/bplustree/BplusTreeUtil.hpp | 0 .../xo/{tree => ordinaltree}/bplustree/GenericNode.hpp | 0 .../xo/{tree => ordinaltree}/bplustree/InternalNode.hpp | 0 include/xo/{tree => ordinaltree}/bplustree/Iterator.hpp | 0 .../xo/{tree => ordinaltree}/bplustree/IteratorUtil.hpp | 0 include/xo/{tree => ordinaltree}/bplustree/LeafNode.hpp | 0 include/xo/{tree => ordinaltree}/bplustree/Lhs.hpp | 0 .../{tree => ordinaltree}/bplustree/bplustree_tags.hpp | 0 utest/bplustree.cpp | 2 +- utest/redblacktree.cpp | 2 +- 14 files changed, 6 insertions(+), 7 deletions(-) rename cmake/{xo_treeConfig.cmake.in => xo_ordinaltreeConfig.cmake.in} (100%) rename include/xo/{tree => ordinaltree}/BplusTree.hpp (100%) rename include/xo/{tree => ordinaltree}/RedBlackTree.hpp (100%) rename include/xo/{tree => ordinaltree}/bplustree/BplusTreeUtil.hpp (100%) rename include/xo/{tree => ordinaltree}/bplustree/GenericNode.hpp (100%) rename include/xo/{tree => ordinaltree}/bplustree/InternalNode.hpp (100%) rename include/xo/{tree => ordinaltree}/bplustree/Iterator.hpp (100%) rename include/xo/{tree => ordinaltree}/bplustree/IteratorUtil.hpp (100%) rename include/xo/{tree => ordinaltree}/bplustree/LeafNode.hpp (100%) rename include/xo/{tree => ordinaltree}/bplustree/Lhs.hpp (100%) rename include/xo/{tree => ordinaltree}/bplustree/bplustree_tags.hpp (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2fc26356..cde16d98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,8 @@ -# xo-tree/CMakeLists.txt +# xo-ordinaltree/CMakeLists.txt cmake_minimum_required(VERSION 3.10) -project(xo_tree VERSION 0.1) +project(xo_ordinaltree VERSION 0.1) enable_language(CXX) # common XO macros (see github:Rconybea/xo-cmake) @@ -15,7 +15,7 @@ include(xo_macros/code-coverage) enable_testing() # enable code coverage for all executables+libraries # (when configured with -DCODE_COVERAGE=ON) -# +# add_code_coverage() add_code_coverage_all_targets( EXCLUDE @@ -33,7 +33,7 @@ xo_toplevel_compile_options() add_subdirectory(utest) -set(SELF_LIB xo_tree) +set(SELF_LIB xo_ordinaltree) add_library(${SELF_LIB} INTERFACE) xo_include_headeronly_options2(${SELF_LIB}) @@ -49,4 +49,3 @@ xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets # input dependencies xo_dependency_headeronly(${SELF_LIB} randomgen) - diff --git a/cmake/xo_treeConfig.cmake.in b/cmake/xo_ordinaltreeConfig.cmake.in similarity index 100% rename from cmake/xo_treeConfig.cmake.in rename to cmake/xo_ordinaltreeConfig.cmake.in diff --git a/include/xo/tree/BplusTree.hpp b/include/xo/ordinaltree/BplusTree.hpp similarity index 100% rename from include/xo/tree/BplusTree.hpp rename to include/xo/ordinaltree/BplusTree.hpp diff --git a/include/xo/tree/RedBlackTree.hpp b/include/xo/ordinaltree/RedBlackTree.hpp similarity index 100% rename from include/xo/tree/RedBlackTree.hpp rename to include/xo/ordinaltree/RedBlackTree.hpp diff --git a/include/xo/tree/bplustree/BplusTreeUtil.hpp b/include/xo/ordinaltree/bplustree/BplusTreeUtil.hpp similarity index 100% rename from include/xo/tree/bplustree/BplusTreeUtil.hpp rename to include/xo/ordinaltree/bplustree/BplusTreeUtil.hpp diff --git a/include/xo/tree/bplustree/GenericNode.hpp b/include/xo/ordinaltree/bplustree/GenericNode.hpp similarity index 100% rename from include/xo/tree/bplustree/GenericNode.hpp rename to include/xo/ordinaltree/bplustree/GenericNode.hpp diff --git a/include/xo/tree/bplustree/InternalNode.hpp b/include/xo/ordinaltree/bplustree/InternalNode.hpp similarity index 100% rename from include/xo/tree/bplustree/InternalNode.hpp rename to include/xo/ordinaltree/bplustree/InternalNode.hpp diff --git a/include/xo/tree/bplustree/Iterator.hpp b/include/xo/ordinaltree/bplustree/Iterator.hpp similarity index 100% rename from include/xo/tree/bplustree/Iterator.hpp rename to include/xo/ordinaltree/bplustree/Iterator.hpp diff --git a/include/xo/tree/bplustree/IteratorUtil.hpp b/include/xo/ordinaltree/bplustree/IteratorUtil.hpp similarity index 100% rename from include/xo/tree/bplustree/IteratorUtil.hpp rename to include/xo/ordinaltree/bplustree/IteratorUtil.hpp diff --git a/include/xo/tree/bplustree/LeafNode.hpp b/include/xo/ordinaltree/bplustree/LeafNode.hpp similarity index 100% rename from include/xo/tree/bplustree/LeafNode.hpp rename to include/xo/ordinaltree/bplustree/LeafNode.hpp diff --git a/include/xo/tree/bplustree/Lhs.hpp b/include/xo/ordinaltree/bplustree/Lhs.hpp similarity index 100% rename from include/xo/tree/bplustree/Lhs.hpp rename to include/xo/ordinaltree/bplustree/Lhs.hpp diff --git a/include/xo/tree/bplustree/bplustree_tags.hpp b/include/xo/ordinaltree/bplustree/bplustree_tags.hpp similarity index 100% rename from include/xo/tree/bplustree/bplustree_tags.hpp rename to include/xo/ordinaltree/bplustree/bplustree_tags.hpp diff --git a/utest/bplustree.cpp b/utest/bplustree.cpp index 5dac55a8..d7ca3aaf 100644 --- a/utest/bplustree.cpp +++ b/utest/bplustree.cpp @@ -3,7 +3,7 @@ #define CATCH_CONFIG_ENABLE_BENCHMARKING #include "random_tree_ops.hpp" -#include "xo/tree/BplusTree.hpp" +#include "xo/ordinaltree/BplusTree.hpp" #include "randomgen/random_seed.hpp" #include "indentlog/scope.hpp" #include "catch2/catch.hpp" diff --git a/utest/redblacktree.cpp b/utest/redblacktree.cpp index 658abf64..f33db805 100644 --- a/utest/redblacktree.cpp +++ b/utest/redblacktree.cpp @@ -1,7 +1,7 @@ /* @file redblacktree.cpp */ #include "random_tree_ops.hpp" -#include "xo/tree/RedBlackTree.hpp" +#include "xo/ordinaltree/RedBlackTree.hpp" #include namespace { From 8c1bbb09356ce896c103cc4da02186da2528d9bf Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 13:32:58 -0400 Subject: [PATCH 0242/2693] xo-cmake: + xo_dependency_headeronly + xo_dependency --- cmake/xo_cxx.cmake | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 66ac7962..84ca488e 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -219,11 +219,22 @@ endmacro() # # dep: name of required dependency, e.g. indentlog # -macro(xo_internal_dependency target dep) +macro(xo_dependency target dep) find_package(${dep} CONFIG REQUIRED) target_link_libraries(${target} PUBLIC ${dep}) endmacro() +macro(xo_internal_dependency target dep) + xo_dependency(target dep) +endmacro() + +# dependency on a header-only library +# +macro(xo_dependency_headeronly target dep) + find_package(${dep} CONFIG REQUIRED) + target_link_libraries(${target} INTERFACE ${dep}) +endmacro() + # dependency on namespaced target # e.g. # add_library(foo ..) or add_executable(foo ...) From ad7ea74f482b26ff4d272761ae4acfcfb0898c01 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 13:35:15 -0400 Subject: [PATCH 0243/2693] bugfix: macro variables in xo_internal_dependency! --- cmake/xo_cxx.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 84ca488e..afdbc443 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -225,7 +225,7 @@ macro(xo_dependency target dep) endmacro() macro(xo_internal_dependency target dep) - xo_dependency(target dep) + xo_dependency(${target} ${dep}) endmacro() # dependency on a header-only library From 58903a6feee5b6e4627aac059177aa686f9461dc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 13:43:04 -0400 Subject: [PATCH 0244/2693] github: provide builder workflow --- .github/workflows/main.yml | 118 +++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..fa2b06e4 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,118 @@ + +name: build xo-ordinaltree + xo dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + +# # ---------------------------------------------------------------- +# +# - name: Clone subsys +# uses: actions/checkout@v3 +# with: +# repository: Rconybea/subsys +# path: repo/subsys +# +# - name: Configure subsys +# # configure cmake for subsys in dedicated build directory. +# run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys +# +# - name: Build subsys +# run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} +# +# - name: Install subsys +# # install into ${{github.workspace}}/local +# run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Configure self (xo-ordinaltree) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_xo_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (xo_ordinaltree) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_xo_ordinaltree --config ${{env.BUILD_TYPE}} + + - name: Test self (xo_ordinaltree) + working-directory: ${{github.workspace}}/build_xo_ordinaltree + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From b33b39e8fb7da923b2657292f2d4e5574a4a78ba Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 13:45:20 -0400 Subject: [PATCH 0245/2693] github: + randomgen dep in workflow --- .github/workflows/main.yml | 46 +++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fa2b06e4..65548a75 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -62,24 +62,24 @@ jobs: # install into ${{github.workspace}}/local run: cmake --install ${{github.workspace}}/build_indentlog -# # ---------------------------------------------------------------- -# -# - name: Clone subsys -# uses: actions/checkout@v3 -# with: -# repository: Rconybea/subsys -# path: repo/subsys -# -# - name: Configure subsys -# # configure cmake for subsys in dedicated build directory. -# run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys -# -# - name: Build subsys -# run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} -# -# - name: Install subsys -# # install into ${{github.workspace}}/local -# run: cmake --install ${{github.workspace}}/build_subsys + # ---------------------------------------------------------------- + + - name: Clone randomgen + uses: actions/checkout@v3 + with: + repository: Rconybea/randomgen + path: repo/randomgen + + - name: Configure randomgen + # configure cmake for randomgen in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/loal repo/randomgen + + - name: Build randomgen + run: cmake --build ${{github.workspace}}/build_randomgen --config ${{env.BUILD_TYPE}} + + - name: Install randomgen + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_randomgen # ---------------------------------------------------------------- @@ -105,14 +105,14 @@ jobs: - name: Configure self (xo-ordinaltree) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build_xo_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - - name: Build self (xo_ordinaltree) + - name: Build self (xo-ordinaltree) # Build your program with the given configuration - run: cmake --build ${{github.workspace}}/build_xo_ordinaltree --config ${{env.BUILD_TYPE}} + run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}} - - name: Test self (xo_ordinaltree) - working-directory: ${{github.workspace}}/build_xo_ordinaltree + - name: Test self (xo-ordinaltree) + working-directory: ${{github.workspace}}/build_ordinaltree # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest -C ${{env.BUILD_TYPE}} From d1f7ae24d26df2ce67ccdb6c75d77aa84d6e47e1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 13:48:10 -0400 Subject: [PATCH 0246/2693] github: need lib/cmake path for ordinaltree --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 65548a75..ae2ccd41 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -72,7 +72,7 @@ jobs: - name: Configure randomgen # configure cmake for randomgen in dedicated build directory. - run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/loal repo/randomgen + run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/loal repo/randomgen - name: Build randomgen run: cmake --build ${{github.workspace}}/build_randomgen --config ${{env.BUILD_TYPE}} From 5b4858e075e73c22391ce1c2a336e21422bfafcc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 13:54:43 -0400 Subject: [PATCH 0247/2693] github: + libbsd-dev dep --- .github/workflows/main.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ae2ccd41..6026c4ba 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,6 +26,10 @@ jobs: # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] run: sudo apt-get install -y catch2 + - name: Install libbsd-dev + # provides arc4random_buf in randomgen + run: sudo apt-get install -y libbsd-dev + # ---------------------------------------------------------------- - name: Clone xo-cmake From a26a6e4656e2ed45be533a7a70c866647a8793d7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 15:50:40 -0400 Subject: [PATCH 0248/2693] random: use get_random() instead of arc4random_buf() for non-bsd builds --- include/randomgen/random_seed.hpp | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/include/randomgen/random_seed.hpp b/include/randomgen/random_seed.hpp index 5273ddeb..016f612f 100644 --- a/include/randomgen/random_seed.hpp +++ b/include/randomgen/random_seed.hpp @@ -1,9 +1,14 @@ /* @file random_seed.hpp */ -#include "indentlog/print/array.hpp" +//#include "indentlog/print/array.hpp" #include #include #include +#ifdef _BSD_SOURCE +# include +#else +# include +#endif namespace xo { namespace rng { @@ -20,10 +25,22 @@ namespace xo { */ template void random_seed(T * p_seed) { +# ifdef _BSD_SOURCE /* NOTE: arc4random_buf() works on darwin/nix; * probably need to do something else on intel linux */ - arc4random_buf(p_seed, sizeof(*p_seed)); + ::arc4random_buf(p_seed, sizeof(*p_seed)); +# else + /* avail flags: GRND_RANDOM | GRND_NONBLOCK */ + while (::getrandom(p_seed, sizeof(*p_seed), 0) == -1) { + if (errno == EINTR) { + /* interrupted by signal, try again */ + continue; + } else { + break; + } + } +# endif } /*random_seed*/ template From d7316a2ef079ff4fc2299dc0230064ace4fa8c52 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 15:56:40 -0400 Subject: [PATCH 0249/2693] github: typo: loal -> local --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6026c4ba..14fe1134 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -76,7 +76,7 @@ jobs: - name: Configure randomgen # configure cmake for randomgen in dedicated build directory. - run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/loal repo/randomgen + run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/randomgen - name: Build randomgen run: cmake --build ${{github.workspace}}/build_randomgen --config ${{env.BUILD_TYPE}} From 9a2fc0605be5b736efc6ab3d545487eae84110c0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 16:01:48 -0400 Subject: [PATCH 0250/2693] randomgen: + print.hpp --- include/randomgen/print.hpp | 7 +++++++ include/randomgen/random_seed.hpp | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 include/randomgen/print.hpp diff --git a/include/randomgen/print.hpp b/include/randomgen/print.hpp new file mode 100644 index 00000000..e9860ab0 --- /dev/null +++ b/include/randomgen/print.hpp @@ -0,0 +1,7 @@ +/* @file print.hpp */ + +#pragma once + +#include "indentlog/print/array.hpp" + +/* end print.hpp */ diff --git a/include/randomgen/random_seed.hpp b/include/randomgen/random_seed.hpp index 016f612f..67246c01 100644 --- a/include/randomgen/random_seed.hpp +++ b/include/randomgen/random_seed.hpp @@ -1,6 +1,5 @@ /* @file random_seed.hpp */ -//#include "indentlog/print/array.hpp" #include #include #include From cfef8198d469e434ac50013018c0b851d554c79e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 16:02:52 -0400 Subject: [PATCH 0251/2693] utest: use randomgen/print.hpp --- utest/bplustree.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/utest/bplustree.cpp b/utest/bplustree.cpp index d7ca3aaf..24115e4b 100644 --- a/utest/bplustree.cpp +++ b/utest/bplustree.cpp @@ -5,6 +5,7 @@ #include "random_tree_ops.hpp" #include "xo/ordinaltree/BplusTree.hpp" #include "randomgen/random_seed.hpp" +#include "randomgen/print.hpp" #include "indentlog/scope.hpp" #include "catch2/catch.hpp" From d148f3d51fcf7f53903e122b531daadffb31cb07 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 16:39:40 -0400 Subject: [PATCH 0252/2693] tidy: remove dup decl --- utest/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 291f6d16..8d142b9b 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -20,7 +20,7 @@ target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) # since version file will be in build directory, need that directory # to also be included in compiler's include path # -xo_include_options2(${SELF_EXECUTABLE_NAME}) +#xo_include_options2(${SELF_EXECUTABLE_NAME}) #target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC # ${PROJECT_SOURCE_DIR} # ${PROJECT_BINARY_DIR}) From c18c848179e10ab770a4c11c2b1a16985bdea70d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 16:39:53 -0400 Subject: [PATCH 0253/2693] tidy: c++ indentation --- include/reflect/TaggedPtr.hpp | 182 +++++++++++++++++----------------- 1 file changed, 90 insertions(+), 92 deletions(-) diff --git a/include/reflect/TaggedPtr.hpp b/include/reflect/TaggedPtr.hpp index 653c8ad7..610cb00f 100644 --- a/include/reflect/TaggedPtr.hpp +++ b/include/reflect/TaggedPtr.hpp @@ -7,119 +7,117 @@ #include namespace xo { -namespace reflect { - class TaggedRcptr; /* see [reflect/TaggedRcptr.hpp] */ + namespace reflect { + class TaggedRcptr; /* see [reflect/TaggedRcptr.hpp] */ - class TaggedPtr { - public: - TaggedPtr(TypeDescr td, void * x) : td_{td}, address_{x} {} + class TaggedPtr { + public: + TaggedPtr(TypeDescr td, void * x) : td_{td}, address_{x} {} - static TaggedPtr universal_null() { return TaggedPtr(nullptr, nullptr); } + static TaggedPtr universal_null() { return TaggedPtr(nullptr, nullptr); } - /* would be clean to put make() here; - * however it leads to cyclic #include paths, - * so put it elsewhere - */ + /* would be clean to put make() here; + * however it leads to cyclic #include paths, + * so put it elsewhere + */ #ifdef NOT_USING - template - static TaggedPtr make(T * x) { return TaggedPtr(Reflect::require(), x); } + template + static TaggedPtr make(T * x) { return TaggedPtr(Reflect::require(), x); } #endif - /* visit an object tree. calls preorder_visit_fn() on tp, - * and all objects reachable directly-or-indirectly from tp. - * will call preorder_visit_fn() multiple times if there are multiple paths - * to a node. - * - * require: no cycles in object graph -- undefined behavior if a cycle is present - */ - template - static void visit_tree_preorder(TaggedPtr tp, Fn && preorder_visit_fn) { - using std::uint32_t; + /* visit an object tree. calls preorder_visit_fn() on tp, + * and all objects reachable directly-or-indirectly from tp. + * will call preorder_visit_fn() multiple times if there are multiple paths + * to a node. + * + * require: no cycles in object graph -- undefined behavior if a cycle is present + */ + template + static void visit_tree_preorder(TaggedPtr tp, Fn && preorder_visit_fn) { + using std::uint32_t; - preorder_visit_fn(tp); + preorder_visit_fn(tp); - for(uint32_t i = 0, n = tp.n_child(); i < n; ++i) { - visit_tree_preorder(tp.get_child(i), preorder_visit_fn); - } - } /*visit_tree_preorder*/ - - /* visit object graph. calls preorder_visit_fn() on tp in depth-first - * order. detects and silently prunes duplicate/cyclic references. - */ - template - static void visit_graph(TaggedPtr tp, Fn && visit_fn) { - std::unordered_set visited_set; + for(uint32_t i = 0, n = tp.n_child(); i < n; ++i) { + visit_tree_preorder(tp.get_child(i), preorder_visit_fn); + } + } /*visit_tree_preorder*/ - visit_graph_aux(tp, visit_fn, &visited_set); - } /*visit_graph*/ + /* visit object graph. calls preorder_visit_fn() on tp in depth-first + * order. detects and silently prunes duplicate/cyclic references. + */ + template + static void visit_graph(TaggedPtr tp, Fn && visit_fn) { + std::unordered_set visited_set; - TypeDescr td() const { return td_; } - void * address() const { return address_; } - - void assign_td(TypeDescr x) { td_ = x; } - void assign_address(void * x) { address_ = x; } + visit_graph_aux(tp, visit_fn, &visited_set); + } /*visit_graph*/ - bool is_universal_null() const { return (td_ == nullptr) && (address_ == nullptr); } - bool is_vector() const { return td_ && td_->is_vector(); } - bool is_struct() const { return td_ && td_->is_struct(); } + TypeDescr td() const { return td_; } + void * address() const { return address_; } + + void assign_td(TypeDescr x) { td_ = x; } + void assign_address(void * x) { address_ = x; } + + bool is_universal_null() const { return (td_ == nullptr) && (address_ == nullptr); } + bool is_vector() const { return td_ && td_->is_vector(); } + bool is_struct() const { return td_ && td_->is_struct(); } - /* returns pointer-to-T, if in fact this tagged pointer is understood - * to refer to a T-instance; otherwise nullptr - */ - template - T * recover_native() const { return this->td_->recover_native(this->address_); } + /* returns pointer-to-T, if in fact this tagged pointer is understood + * to refer to a T-instance; otherwise nullptr + */ + template + T * recover_native() const { return this->td_->recover_native(this->address_); } - uint32_t n_child() const { - return this->td_->n_child(this->address_); - } /*n_child*/ + uint32_t n_child() const { + return this->td_->n_child(this->address_); + } /*n_child*/ - TaggedPtr get_child(uint32_t i) const { - return this->td_->child_tp(i, this->address_); - } /*get_child*/ + TaggedPtr get_child(uint32_t i) const { + return this->td_->child_tp(i, this->address_); + } /*get_child*/ - /* require: - * - .is_struct() is true - */ - std::string const & struct_member_name(uint32_t i) const { - return this->td_->struct_member_name(i); - } + /* require: + * - .is_struct() is true + */ + std::string const & struct_member_name(uint32_t i) const { + return this->td_->struct_member_name(i); + } - private: - template - static void visit_graph_aux(TaggedPtr tp, - Fn && visit_fn, - std::unordered_set * p_visited_set) - { - if (tp.address() == nullptr) - return; - - if (p_visited_set->find(tp.address()) == p_visited_set->end()) { - p_visited_set->insert(tp.address()); + private: + template + static void visit_graph_aux(TaggedPtr tp, + Fn && visit_fn, + std::unordered_set * p_visited_set) + { + if (tp.address() == nullptr) + return; - visit_fn(tp); - - for (uint32_t i = 0, n = tp.n_child(); i < n; ++i) { - visit_graph_aux(tp.get_child(i), visit_fn, p_visited_set); - } - } - } /*visit_graph_aux*/ + if (p_visited_set->find(tp.address()) == p_visited_set->end()) { + p_visited_set->insert(tp.address()); - private: - friend class TaggedRcptr; + visit_fn(tp); - private: - /* describes the actual type stored at *address. - * can be null if .address is null - */ - TypeDescr td_; - /* address with type information preserved at runtime */ - void * address_; - }; /*TaggedPtr*/ + for (uint32_t i = 0, n = tp.n_child(); i < n; ++i) { + visit_graph_aux(tp.get_child(i), visit_fn, p_visited_set); + } + } + } /*visit_graph_aux*/ -} /*namespace reflect*/ + private: + friend class TaggedRcptr; + + private: + /* describes the actual type stored at *address. + * can be null if .address is null + */ + TypeDescr td_; + /* address with type information preserved at runtime */ + void * address_; + }; /*TaggedPtr*/ + + } /*namespace reflect*/ } /*namespace xo*/ /* end TaggedPtr.hpp */ - - From af1d2f3535f8e87bd8befb46c046bb73f2e4440d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 16:40:03 -0400 Subject: [PATCH 0254/2693] bugfix: cmake config template --- cmake/reflectConfig.cmake.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/reflectConfig.cmake.in b/cmake/reflectConfig.cmake.in index e13a2c54..9c15f36a 100644 --- a/cmake/reflectConfig.cmake.in +++ b/cmake/reflectConfig.cmake.in @@ -1,4 +1,4 @@ @PACKAGE_INIT@ -include("${CMAKE_CURRENT_LIST_DIR}/@XO_PROJECT_NAME@Targets.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") From ba95d0a40c91f89a6a49841191aaee34bd0148bb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 16:40:18 -0400 Subject: [PATCH 0255/2693] cosmetic: drop cmake message --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0402062d..40c2c41d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,8 +2,6 @@ cmake_minimum_required(VERSION 3.10) -message(CMAKE_VERSION=${CMAKE_VERSION}) - project(reflect VERSION 0.1) enable_language(CXX) From 6be9037f100b7646dc359003600a4ef6faae04ae Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 16:53:47 -0400 Subject: [PATCH 0256/2693] reflect: insert xo/ subdir into include path --- include/{ => xo}/reflect/CMakeLists.txt | 0 .../{ => xo}/reflect/EstablishTypeDescr.hpp | 38 ++++---- include/{ => xo}/reflect/Metatype.hpp | 0 include/{ => xo}/reflect/Reflect.hpp | 62 ++++++------- include/{ => xo}/reflect/SelfTagging.hpp | 6 +- include/{ => xo}/reflect/StructReflector.hpp | 90 +++++++++---------- include/{ => xo}/reflect/TaggedPtr.hpp | 3 +- include/{ => xo}/reflect/TaggedRcptr.hpp | 50 +++++------ include/{ => xo}/reflect/TypeDescr.hpp | 2 +- include/{ => xo}/reflect/TypeDescrExtra.hpp | 2 +- include/{ => xo}/reflect/TypeDrivenMap.hpp | 0 include/{ => xo}/reflect/atomic/AtomicTdx.hpp | 2 +- include/{ => xo}/reflect/init_reflect.hpp | 0 .../{ => xo}/reflect/pointer/PointerTdx.hpp | 4 +- .../{ => xo}/reflect/struct/StructMember.hpp | 29 +++--- include/{ => xo}/reflect/struct/StructTdx.hpp | 7 +- include/{ => xo}/reflect/vector/VectorTdx.hpp | 34 ++++--- utest/StructReflector.test.cpp | 8 +- utest/StructTdx.test.cpp | 2 +- utest/VectorTdx.test.cpp | 10 +-- 20 files changed, 172 insertions(+), 177 deletions(-) rename include/{ => xo}/reflect/CMakeLists.txt (100%) rename include/{ => xo}/reflect/EstablishTypeDescr.hpp (60%) rename include/{ => xo}/reflect/Metatype.hpp (100%) rename include/{ => xo}/reflect/Reflect.hpp (84%) rename include/{ => xo}/reflect/SelfTagging.hpp (88%) rename include/{ => xo}/reflect/StructReflector.hpp (64%) rename include/{ => xo}/reflect/TaggedPtr.hpp (98%) rename include/{ => xo}/reflect/TaggedRcptr.hpp (67%) rename include/{ => xo}/reflect/TypeDescr.hpp (99%) rename include/{ => xo}/reflect/TypeDescrExtra.hpp (98%) rename include/{ => xo}/reflect/TypeDrivenMap.hpp (100%) rename include/{ => xo}/reflect/atomic/AtomicTdx.hpp (96%) rename include/{ => xo}/reflect/init_reflect.hpp (100%) rename include/{ => xo}/reflect/pointer/PointerTdx.hpp (96%) rename include/{ => xo}/reflect/struct/StructMember.hpp (92%) rename include/{ => xo}/reflect/struct/StructTdx.hpp (95%) rename include/{ => xo}/reflect/vector/VectorTdx.hpp (75%) diff --git a/include/reflect/CMakeLists.txt b/include/xo/reflect/CMakeLists.txt similarity index 100% rename from include/reflect/CMakeLists.txt rename to include/xo/reflect/CMakeLists.txt diff --git a/include/reflect/EstablishTypeDescr.hpp b/include/xo/reflect/EstablishTypeDescr.hpp similarity index 60% rename from include/reflect/EstablishTypeDescr.hpp rename to include/xo/reflect/EstablishTypeDescr.hpp index 2b0e25a7..32e546d1 100644 --- a/include/reflect/EstablishTypeDescr.hpp +++ b/include/xo/reflect/EstablishTypeDescr.hpp @@ -5,8 +5,8 @@ #pragma once -#include "reflect/TypeDescr.hpp" -#include "reflect/TaggedPtr.hpp" +#include "TypeDescr.hpp" +#include "TaggedPtr.hpp" namespace xo { namespace reflect { @@ -26,28 +26,28 @@ namespace xo { template static TypeDescrW establish() { - TypeDescrW td = TypeDescrBase::require(&typeid(T), - type_name(), - nullptr); + TypeDescrW td = TypeDescrBase::require(&typeid(T), + type_name(), + nullptr); #ifdef NOT_USING - std::function to_self_tp; + std::function to_self_tp; - if (std::is_base_of_v) { - /* T is a descendant of SelfTagging (or T = SelfTagging); - * use SelfTagging.self_tp() - */ - to_self_tp = [](void * x) { return reinterpret_cast(x)->self_tp(); }; - } else { - /* T is not a descendant of SelfTagging. - * want to return - */ - to_self_tp = [td](void * x) { return TaggedPtr(td, x); }; - } + if (std::is_base_of_v) { + /* T is a descendant of SelfTagging (or T = SelfTagging); + * use SelfTagging.self_tp() + */ + to_self_tp = [](void * x) { return reinterpret_cast(x)->self_tp(); }; + } else { + /* T is not a descendant of SelfTagging. + * want to return + */ + to_self_tp = [td](void * x) { return TaggedPtr(td, x); }; + } - td->assign_to_self_tp(to_self_tp); + td->assign_to_self_tp(to_self_tp); #endif - return td; + return td; } }; /*EstablishTypeDescr*/ diff --git a/include/reflect/Metatype.hpp b/include/xo/reflect/Metatype.hpp similarity index 100% rename from include/reflect/Metatype.hpp rename to include/xo/reflect/Metatype.hpp diff --git a/include/reflect/Reflect.hpp b/include/xo/reflect/Reflect.hpp similarity index 84% rename from include/reflect/Reflect.hpp rename to include/xo/reflect/Reflect.hpp index 73ef68f3..a6e6fe5e 100644 --- a/include/reflect/Reflect.hpp +++ b/include/xo/reflect/Reflect.hpp @@ -5,12 +5,12 @@ #pragma once -#include "reflect/SelfTagging.hpp" -#include "reflect/EstablishTypeDescr.hpp" -#include "reflect/atomic/AtomicTdx.hpp" -#include "reflect/pointer/PointerTdx.hpp" -#include "reflect/vector/VectorTdx.hpp" -#include "reflect/struct/StructTdx.hpp" +#include "SelfTagging.hpp" +#include "EstablishTypeDescr.hpp" +#include "atomic/AtomicTdx.hpp" +#include "pointer/PointerTdx.hpp" +#include "vector/VectorTdx.hpp" +#include "struct/StructTdx.hpp" #include "refcnt/Refcounted.hpp" #include #include @@ -22,7 +22,7 @@ namespace xo { class EstablishTdx { public: static std::unique_ptr make() { return AtomicTdx::make(); } - }; /*EstablishTdx*/ + }; /*EstablishTdx*/ // ----- xo::ref::rp ----- @@ -34,7 +34,7 @@ namespace xo { }; /*EstablishTdx*/ // ----- std::array ----- - + /* definition provide after decl for Reflect {} below */ template class EstablishTdx> { @@ -73,11 +73,11 @@ namespace xo { class TaggedPtrMaker { public: static TaggedPtr make_tp(SelfTagging * x) { - return x->self_tp(); + return x->self_tp(); } /*make_tp*/ static TaggedRcptr make_rctp(SelfTagging * x) { - return x->self_tp(); + return x->self_tp(); } /*make_rctp*/ }; /*TaggedPtrMaker*/ @@ -113,35 +113,35 @@ namespace xo { * implemented in specialized header (like [reflect/struct/VectorTdx.hpp]) to * refer to reflection info for T without having to pull in all the * headers needed to properly reflect T (like this [reflect/Reflect.hpp]) - * + * */ template static TypeDescrW require() { - TypeDescrW retval_td = EstablishTypeDescr::establish(); + TypeDescrW retval_td = EstablishTypeDescr::establish(); - /* mark TypeDescr for T as complete (even though it isn't quite yet), - * so that when we encounter recursive types, reflection terminates. - * For example consider type resulting from code like - * - * typename T; - * using T = std::vector; - * - */ - if (retval_td->mark_complete()) { - /* control here on 2nd+later calls to require(). - * in principle can immediately short-circuit. - */ - } else { - /* control comes here the first time require() runs */ + /* mark TypeDescr for T as complete (even though it isn't quite yet), + * so that when we encounter recursive types, reflection terminates. + * For example consider type resulting from code like + * + * typename T; + * using T = std::vector; + * + */ + if (retval_td->mark_complete()) { + /* control here on 2nd+later calls to require(). + * in principle can immediately short-circuit. + */ + } else { + /* control comes here the first time require() runs */ - auto final_tdx = EstablishTdx::make(); + auto final_tdx = EstablishTdx::make(); - retval_td->assign_tdextra(std::move(final_tdx)); + retval_td->assign_tdextra(std::move(final_tdx)); - /* also need to require for each child */ - } + /* also need to require for each child */ + } - return retval_td; + return retval_td; } /*require*/ /* Use: diff --git a/include/reflect/SelfTagging.hpp b/include/xo/reflect/SelfTagging.hpp similarity index 88% rename from include/reflect/SelfTagging.hpp rename to include/xo/reflect/SelfTagging.hpp index 29517e33..e188b5c2 100644 --- a/include/reflect/SelfTagging.hpp +++ b/include/xo/reflect/SelfTagging.hpp @@ -5,9 +5,9 @@ #pragma once -#include "refcnt/Refcounted.hpp" -#include "reflect/TypeDescr.hpp" -#include "reflect/TaggedRcptr.hpp" +#include "Refcounted.hpp" +#include "TypeDescr.hpp" +#include "TaggedRcptr.hpp" namespace xo { namespace reflect { diff --git a/include/reflect/StructReflector.hpp b/include/xo/reflect/StructReflector.hpp similarity index 64% rename from include/reflect/StructReflector.hpp rename to include/xo/reflect/StructReflector.hpp index f2237578..750b8669 100644 --- a/include/reflect/StructReflector.hpp +++ b/include/xo/reflect/StructReflector.hpp @@ -2,10 +2,10 @@ #pragma once -#include "reflect/Reflect.hpp" -#include "reflect/TypeDescr.hpp" -#include "reflect/struct/StructMember.hpp" -#include "reflect/struct/StructTdx.hpp" +#include "Reflect.hpp" +#include "TypeDescr.hpp" +#include "struct/StructMember.hpp" +#include "struct/StructTdx.hpp" #include namespace xo { @@ -16,7 +16,7 @@ namespace xo { template struct SelfTagger { static TaggedPtr self_tp(void * object) { - return (reinterpret_cast(object))->self_tp(); + return (reinterpret_cast(object))->self_tp(); } }; @@ -35,7 +35,7 @@ namespace xo { * REFLECT_LITERAL_MEMBER(sr, y_); * * // optional: regardless, reflection will be completed when sr goes out of scope - * sr.require_complete(); + * sr.require_complete(); */ template class StructReflector { @@ -45,68 +45,68 @@ namespace xo { public: StructReflector() : td_{EstablishTypeDescr::establish()} {} ~StructReflector() { - this->require_complete(); + this->require_complete(); } - + bool is_complete() const { return s_reflected_flag; } bool is_incomplete() const { return !s_reflected_flag; } template void reflect_member(std::string const & member_name, - MemberT OwnerT::* member_addr) { + MemberT OwnerT::* member_addr) { - auto accessor - (GeneralStructMemberAccessor::make(member_addr)); + auto accessor + (GeneralStructMemberAccessor::make(member_addr)); - /* used to do this in GeneralStructMemberAccessor<> ctor, - * but that introduces #include cycle - */ - Reflect::require(); + /* used to do this in GeneralStructMemberAccessor<> ctor, + * but that introduces #include cycle + */ + Reflect::require(); - this->member_v_.emplace_back(member_name, std::move(accessor)); + this->member_v_.emplace_back(member_name, std::move(accessor)); } /*reflect_member*/ void require_complete() { - if(!s_reflected_flag) { - s_reflected_flag = true; + if(!s_reflected_flag) { + s_reflected_flag = true; - constexpr bool have_to_self_tp = std::is_base_of_v; + constexpr bool have_to_self_tp = std::is_base_of_v; - /* if self-tagging, can use .self_tp() to get most-derived tagged pointer */ - auto to_self_tp_fn - = ([](void * object) - { - return SelfTagger::self_tp(object); - }); + /* if self-tagging, can use .self_tp() to get most-derived tagged pointer */ + auto to_self_tp_fn + = ([](void * object) + { + return SelfTagger::self_tp(object); + }); - auto tdx = StructTdx::make(std::move(this->member_v_), - have_to_self_tp, - to_self_tp_fn); + auto tdx = StructTdx::make(std::move(this->member_v_), + have_to_self_tp, + to_self_tp_fn); - this->td_->assign_tdextra(std::move(tdx)); - } + this->td_->assign_tdextra(std::move(tdx)); + } } /*complete*/ - + template void adopt_ancestors() { - assert(Reflect::is_reflected()); + assert(Reflect::is_reflected()); - TypeDescr ancestor_td = Reflect::require(); + TypeDescr ancestor_td = Reflect::require(); - /* requires that reflection of AncestorT has completed */ - { - assert(ancestor_td->is_struct()); - assert(ancestor_td->complete_flag()); - } + /* requires that reflection of AncestorT has completed */ + { + assert(ancestor_td->is_struct()); + assert(ancestor_td->complete_flag()); + } - /* for structs, - * we know that object argument to TypeDescr::n_child() is unused - */ - for (uint32_t i = 0, n = ancestor_td->n_child(nullptr); i < n; ++i) { - StructMember const & member = ancestor_td->struct_member(i); + /* for structs, + * we know that object argument to TypeDescr::n_child() is unused + */ + for (uint32_t i = 0, n = ancestor_td->n_child(nullptr); i < n; ++i) { + StructMember const & member = ancestor_td->struct_member(i); - this->member_v_.push_back(member.for_descendant()); - } + this->member_v_.push_back(member.for_descendant()); + } } /*adopt_ancestors*/ private: diff --git a/include/reflect/TaggedPtr.hpp b/include/xo/reflect/TaggedPtr.hpp similarity index 98% rename from include/reflect/TaggedPtr.hpp rename to include/xo/reflect/TaggedPtr.hpp index 610cb00f..7bb520aa 100644 --- a/include/reflect/TaggedPtr.hpp +++ b/include/xo/reflect/TaggedPtr.hpp @@ -2,8 +2,7 @@ #pragma once -#include "reflect/TypeDescr.hpp" -//#include "reflect/EstablishTypeDescr.hpp" +#include "TypeDescr.hpp" #include namespace xo { diff --git a/include/reflect/TaggedRcptr.hpp b/include/xo/reflect/TaggedRcptr.hpp similarity index 67% rename from include/reflect/TaggedRcptr.hpp rename to include/xo/reflect/TaggedRcptr.hpp index 9ca8b15f..e31cffb3 100644 --- a/include/reflect/TaggedRcptr.hpp +++ b/include/xo/reflect/TaggedRcptr.hpp @@ -5,7 +5,7 @@ #pragma once -#include "reflect/TaggedPtr.hpp" +#include "TaggedPtr.hpp" // causes #include cycle, reflect/Reflect.hpp includes this header //#include "reflect/Reflect.hpp" #include "refcnt/Refcounted.hpp" @@ -20,22 +20,22 @@ namespace xo { class TaggedRcptr : public TaggedPtr { public: using Refcount = ref::Refcount; - + public: TaggedRcptr(TypeDescr td, Refcount * x) : TaggedPtr(td, x) { - ref::intrusive_ptr_add_ref(x); + ref::intrusive_ptr_add_ref(x); } TaggedRcptr(TaggedRcptr const & x) : TaggedPtr(x) { - ref::intrusive_ptr_add_ref(x.rc_address()); + ref::intrusive_ptr_add_ref(x.rc_address()); } TaggedRcptr(TaggedRcptr && x) : TaggedPtr(std::move(x)) { - /* since we're moving from x, need to make sure x.dtor - * doesn't decrement refcount - */ - x.assign_address(nullptr); + /* since we're moving from x, need to make sure x.dtor + * doesn't decrement refcount + */ + x.assign_address(nullptr); } ~TaggedRcptr() { - ref::intrusive_ptr_release(this->rc_address()); + ref::intrusive_ptr_release(this->rc_address()); } /* causes #include cycle, see [reflect/Reflect.hpp] */ @@ -46,37 +46,37 @@ namespace xo { #endif Refcount * rc_address() const { - return reinterpret_cast(this->address()); + return reinterpret_cast(this->address()); } /*rc_address*/ TaggedRcptr & operator=(TaggedRcptr const & rhs) { - Refcount * x = rhs.rc_address(); - Refcount * old = this->rc_address(); + Refcount * x = rhs.rc_address(); + Refcount * old = this->rc_address(); - TaggedPtr::operator=(rhs); + TaggedPtr::operator=(rhs); - if (x != old) { - intrusive_ptr_release(old); - intrusive_ptr_add_ref(x); - } + if (x != old) { + intrusive_ptr_release(old); + intrusive_ptr_add_ref(x); + } - return *this; + return *this; } /*operator=*/ TaggedRcptr & operator=(TaggedRcptr && rhs) { - /* swap pointers + type descriptions; - * then don't need to touch refcounts - */ - std::swap(this->td_, rhs.td_); - std::swap(this->address_, rhs.address_); + /* swap pointers + type descriptions; + * then don't need to touch refcounts + */ + std::swap(this->td_, rhs.td_); + std::swap(this->address_, rhs.address_); - return *this; + return *this; } /*operator=*/ void display(std::ostream & os) const; std::string display_string() const; }; /*TaggedRcptr*/ - + inline std::ostream & operator<<(std::ostream & os, TaggedRcptr const & x) { x.display(os); return os; diff --git a/include/reflect/TypeDescr.hpp b/include/xo/reflect/TypeDescr.hpp similarity index 99% rename from include/reflect/TypeDescr.hpp rename to include/xo/reflect/TypeDescr.hpp index 08614071..da59c007 100644 --- a/include/reflect/TypeDescr.hpp +++ b/include/xo/reflect/TypeDescr.hpp @@ -3,7 +3,7 @@ #pragma once //#include "reflect/atomic/AtomicTdx.hpp" -#include "reflect/TypeDescrExtra.hpp" +#include "TypeDescrExtra.hpp" #include "cxxutil/demangle.hpp" #include #include diff --git a/include/reflect/TypeDescrExtra.hpp b/include/xo/reflect/TypeDescrExtra.hpp similarity index 98% rename from include/reflect/TypeDescrExtra.hpp rename to include/xo/reflect/TypeDescrExtra.hpp index 2d2be7fa..824cd5d4 100644 --- a/include/reflect/TypeDescrExtra.hpp +++ b/include/xo/reflect/TypeDescrExtra.hpp @@ -2,7 +2,7 @@ #pragma once -#include "reflect/Metatype.hpp" +#include "Metatype.hpp" #include /* note: this file #include'd into TypeDescr.hpp */ #include diff --git a/include/reflect/TypeDrivenMap.hpp b/include/xo/reflect/TypeDrivenMap.hpp similarity index 100% rename from include/reflect/TypeDrivenMap.hpp rename to include/xo/reflect/TypeDrivenMap.hpp diff --git a/include/reflect/atomic/AtomicTdx.hpp b/include/xo/reflect/atomic/AtomicTdx.hpp similarity index 96% rename from include/reflect/atomic/AtomicTdx.hpp rename to include/xo/reflect/atomic/AtomicTdx.hpp index 7b2e042d..98e10cfe 100644 --- a/include/reflect/atomic/AtomicTdx.hpp +++ b/include/xo/reflect/atomic/AtomicTdx.hpp @@ -2,7 +2,7 @@ #pragma once -#include "reflect/TypeDescrExtra.hpp" +#include "xo/reflect/TypeDescrExtra.hpp" //#include "reflect/TaggedPtr.hpp" #include diff --git a/include/reflect/init_reflect.hpp b/include/xo/reflect/init_reflect.hpp similarity index 100% rename from include/reflect/init_reflect.hpp rename to include/xo/reflect/init_reflect.hpp diff --git a/include/reflect/pointer/PointerTdx.hpp b/include/xo/reflect/pointer/PointerTdx.hpp similarity index 96% rename from include/reflect/pointer/PointerTdx.hpp rename to include/xo/reflect/pointer/PointerTdx.hpp index d2d3b868..327456cf 100644 --- a/include/reflect/pointer/PointerTdx.hpp +++ b/include/xo/reflect/pointer/PointerTdx.hpp @@ -5,8 +5,8 @@ #pragma once -#include "reflect/TypeDescrExtra.hpp" -#include "reflect/EstablishTypeDescr.hpp" +#include "xo/reflect/TypeDescrExtra.hpp" +#include "xo/reflect/EstablishTypeDescr.hpp" #include "indentlog/scope.hpp" namespace xo { diff --git a/include/reflect/struct/StructMember.hpp b/include/xo/reflect/struct/StructMember.hpp similarity index 92% rename from include/reflect/struct/StructMember.hpp rename to include/xo/reflect/struct/StructMember.hpp index eaddf3b3..3c7bab62 100644 --- a/include/reflect/struct/StructMember.hpp +++ b/include/xo/reflect/struct/StructMember.hpp @@ -2,10 +2,9 @@ #pragma once -#include "reflect/TypeDescr.hpp" -#include "reflect/EstablishTypeDescr.hpp" -//#include "reflect/Reflect.hpp" -#include "reflect/TaggedPtr.hpp" +#include "xo/reflect/TypeDescr.hpp" +#include "xo/reflect/EstablishTypeDescr.hpp" +#include "xo/reflect/TaggedPtr.hpp" #include #include @@ -14,7 +13,7 @@ namespace reflect { class AbstractStructMemberAccessor { public: virtual ~AbstractStructMemberAccessor() = default; - + /* get tagged pointer referring to this member of the object at *struct_addr */ TaggedPtr member_tp(void * struct_addr) const; @@ -30,7 +29,7 @@ namespace reflect { * .member_td() => Reflect::require(); */ virtual TypeDescr member_td() const = 0; - + /* get address of a particular member, given parent address */ virtual void * address(void * struct_addr) const = 0; @@ -63,7 +62,7 @@ namespace reflect { public: GeneralStructMemberAccessor(Memptr memptr) : member_td_{EstablishTypeDescr::establish()}, - memptr_{memptr} {} + memptr_{memptr} {} GeneralStructMemberAccessor(GeneralStructMemberAccessor const & x) = default; virtual ~GeneralStructMemberAccessor() = default; @@ -78,7 +77,7 @@ namespace reflect { return &(owner_addr->*memptr_); } /*address_impl*/ - + // ----- Inherited from AbstractStructMemberAccessor ----- #ifdef OBSOLETE @@ -102,7 +101,7 @@ namespace reflect { virtual std::unique_ptr clone() const override { return std::unique_ptr - (new GeneralStructMemberAccessor(*this)); + (new GeneralStructMemberAccessor(*this)); } /*clone*/ private: @@ -142,7 +141,7 @@ namespace reflect { static std::unique_ptr adopt(std::unique_ptr ancestor_accessor) { return std::unique_ptr - (new AncestorStructMemberAccessor(std::move(ancestor_accessor))); + (new AncestorStructMemberAccessor(std::move(ancestor_accessor))); } /*adopt*/ void * address_impl(StructT * self_addr) const { @@ -171,7 +170,7 @@ namespace reflect { virtual std::unique_ptr clone() const override { return std::unique_ptr - (new AncestorStructMemberAccessor(std::move(this->ancestor_accessor_->clone()))); + (new AncestorStructMemberAccessor(std::move(this->ancestor_accessor_->clone()))); } /*clone*/ private: @@ -186,11 +185,11 @@ namespace reflect { public: StructMember() = default; StructMember(std::string const & name, - std::unique_ptr accessor) + std::unique_ptr accessor) : member_name_{name}, accessor_{std::move(accessor)} {} StructMember(StructMember && x) : member_name_{std::move(x.member_name_)}, - accessor_{std::move(x.accessor_)} {} + accessor_{std::move(x.accessor_)} {} static StructMember null(); @@ -210,8 +209,8 @@ namespace reflect { assert(EstablishTypeDescr::establish() == this->get_struct_td()); return StructMember(this->member_name(), - std::move(AncestorStructMemberAccessor::adopt - (std::move(this->accessor_->clone())))); + std::move(AncestorStructMemberAccessor::adopt + (std::move(this->accessor_->clone())))); } /*for_descendant*/ StructMember & operator=(StructMember && x) { diff --git a/include/reflect/struct/StructTdx.hpp b/include/xo/reflect/struct/StructTdx.hpp similarity index 95% rename from include/reflect/struct/StructTdx.hpp rename to include/xo/reflect/struct/StructTdx.hpp index 5e170ef7..594de2d5 100644 --- a/include/reflect/struct/StructTdx.hpp +++ b/include/xo/reflect/struct/StructTdx.hpp @@ -2,9 +2,10 @@ #pragma once -#include "reflect/TypeDescrExtra.hpp" -#include "reflect/TaggedPtr.hpp" -#include "reflect/struct/StructMember.hpp" +#include "xo/reflect/TypeDescrExtra.hpp" +#include "xo/reflect/TaggedPtr.hpp" +#include "StructMember.hpp" +//#include "xo/reflect/struct/StructMember.hpp" #include #include #include diff --git a/include/reflect/vector/VectorTdx.hpp b/include/xo/reflect/vector/VectorTdx.hpp similarity index 75% rename from include/reflect/vector/VectorTdx.hpp rename to include/xo/reflect/vector/VectorTdx.hpp index 4f3309c2..944c451c 100644 --- a/include/reflect/vector/VectorTdx.hpp +++ b/include/xo/reflect/vector/VectorTdx.hpp @@ -5,12 +5,8 @@ #pragma once -#include "reflect/TypeDescrExtra.hpp" -//#include "reflect/TaggedPtr.hpp" -#include "reflect/EstablishTypeDescr.hpp" -//#include "reflect/TaggedPtr.hpp" -//#include -//#include +#include "xo/reflect/TypeDescrExtra.hpp" +#include "xo/reflect/EstablishTypeDescr.hpp" namespace xo { namespace reflect { @@ -42,19 +38,19 @@ namespace xo { using target_t = VectorT; static std::unique_ptr make() { - return std::unique_ptr(new StlVectorTdx()); + return std::unique_ptr(new StlVectorTdx()); } /*make*/ virtual uint32_t n_child(void * object) const override { - target_t * vec = reinterpret_cast(object); + target_t * vec = reinterpret_cast(object); - return vec->size(); + return vec->size(); } /*n_child*/ virtual TaggedPtr child_tp(uint32_t i, void * object) const override { - target_t * vec = reinterpret_cast(object); + target_t * vec = reinterpret_cast(object); - return establish_most_derived_tp(&((*vec)[i])); + return establish_most_derived_tp(&((*vec)[i])); } /*child_tp*/ }; /*StlVectorTdx*/ @@ -66,9 +62,9 @@ namespace xo { template using StdArrayTdx = StlVectorTdx>; - + // ----- std::vector ----- - + /* coordinates with EstablishTdx>::make() * see [reflect/Reflect.hpp] */ @@ -76,21 +72,21 @@ namespace xo { class StdVectorTdx : public VectorTdx { public: using target_t = std::vector; - + static std::unique_ptr make() { - return std::unique_ptr(new StdVectorTdx()); + return std::unique_ptr(new StdVectorTdx()); } /*make*/ virtual uint32_t n_child(void * object) const override { - target_t * vec = reinterpret_cast(object); + target_t * vec = reinterpret_cast(object); - return vec->size(); + return vec->size(); } /*n_child*/ virtual TaggedPtr child_tp(uint32_t i, void * object) const override { - target_t * vec = reinterpret_cast(object); + target_t * vec = reinterpret_cast(object); - return establish_most_derived_tp(&((*vec)[i])); + return establish_most_derived_tp(&((*vec)[i])); } }; /*StdVectorTdx*/ } /*namespace reflect*/ diff --git a/utest/StructReflector.test.cpp b/utest/StructReflector.test.cpp index 5bf87ade..4ab0772d 100644 --- a/utest/StructReflector.test.cpp +++ b/utest/StructReflector.test.cpp @@ -3,12 +3,12 @@ * author: Roland Conybeare, Aug 2022 */ -#include "reflect/Reflect.hpp" -#include "reflect/StructReflector.hpp" +#include "xo/reflect/Reflect.hpp" +#include "xo/reflect/StructReflector.hpp" #include #define STRINGIFY(x) #x - + namespace xo { using xo::reflect::Reflect; using xo::reflect::TaggedPtr; @@ -129,7 +129,7 @@ namespace xo { REQUIRE(tp.get_child(2).td() == Reflect::require()); REQUIRE(tp.get_child(2).address() == &(recd1.z_)); - + REQUIRE(tp.get_child(3).is_universal_null()); REQUIRE(tp.get_child(3).td() == nullptr); REQUIRE(tp.get_child(3).address() == nullptr); diff --git a/utest/StructTdx.test.cpp b/utest/StructTdx.test.cpp index c20e2139..cb845e18 100644 --- a/utest/StructTdx.test.cpp +++ b/utest/StructTdx.test.cpp @@ -3,7 +3,7 @@ * author: Roland Conybeare, Aug 2022 */ -#include "reflect/Reflect.hpp" +#include "xo/reflect/Reflect.hpp" #include namespace xo { diff --git a/utest/VectorTdx.test.cpp b/utest/VectorTdx.test.cpp index 3836b4f7..00f30a48 100644 --- a/utest/VectorTdx.test.cpp +++ b/utest/VectorTdx.test.cpp @@ -3,7 +3,7 @@ * author: Roland Conybeare, Aug 2022 */ -#include "reflect/Reflect.hpp" +#include "xo/reflect/Reflect.hpp" #include namespace xo { @@ -81,7 +81,7 @@ namespace xo { REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); REQUIRE(tp0.recover_native() == &(v[0])); REQUIRE(tp0.n_child() == 0); - + TaggedPtr tp1 = tp.get_child(1); REQUIRE(tp1.td()->complete_flag()); @@ -91,7 +91,7 @@ namespace xo { REQUIRE(tp1.td()->metatype() == Metatype::mt_atomic); REQUIRE(tp1.recover_native() == &(v[1])); REQUIRE(tp1.n_child() == 0); - } /*TEST(std-vector-reflect-two)*/ + } /*TEST(std-vector-reflect-two)*/ // ----- std::array ----- @@ -163,7 +163,7 @@ namespace xo { REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); REQUIRE(tp0.recover_native() == &(v[0])); REQUIRE(tp0.n_child() == 0); - + TaggedPtr tp1 = tp.get_child(1); REQUIRE(tp1.td()->complete_flag()); @@ -173,7 +173,7 @@ namespace xo { REQUIRE(tp1.td()->metatype() == Metatype::mt_atomic); REQUIRE(tp1.recover_native() == &(v[1])); REQUIRE(tp1.n_child() == 0); - } /*TEST(std-array-reflect-two)*/ + } /*TEST(std-array-reflect-two)*/ } /*namespace ut*/ } /*namespace xo*/ From 08d4ceeb329ffacffba13b8fb19b95b7d3b1f090 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 16:54:25 -0400 Subject: [PATCH 0257/2693] provide for xo/ subdir in include path --- cmake/xo_cxx.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index afdbc443..e540bc75 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -62,7 +62,8 @@ macro(xo_include_headeronly_options2 target) ${target} INTERFACE $ # e.g. for #include "indentlog/scope.hpp" $ - $ # e.g. for #include "Refcounted.hpp" in refcnt/src + $ # e.g. for #include "Refcounted.hpp" in refcnt/src when ${target}=refcnt [DEPRECATED] + $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect $ $ # e.g. for generated .hpp files ) @@ -123,7 +124,8 @@ macro(xo_include_options2 target) ${target} PUBLIC $ # e.g. for #include "indentlog/scope.hpp" $ - $ # e.g. for #include "Refcounted.hpp" in refcnt/src + $ # e.g. for #include "Refcounted.hpp" in refcnt/src [DEPRECATED] + $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect $ $ # e.g. for generated .hpp files ) From 035c187cd1503ce004b795a9d54af4568485dfc4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 16:59:19 -0400 Subject: [PATCH 0258/2693] cmake: remove not-working guards on CMAKE_EXPORT_COMPILE_COMMANDS --- cmake/xo_cxx.cmake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index e540bc75..7eaaaff9 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -37,9 +37,9 @@ macro(xo_toplevel_compile_options) # writes ${PROJECT_BINARY_DIR}/compile_commands.json; # (symlink from toplevel git dir to tell LSP how to build) # - if(NOT DEFINED CMAKE_EXPORT_COMPILE_COMMANDS) - set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") - endif() + # note: trying to protect this with if(NOT DEFINED ..) is /not/ effective + # + set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") if(NOT CMAKE_INSTALL_RPATH) set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib CACHE STRING From c8280cb8c4e00d0c0973d245726b1c31ed18cd25 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 17:05:08 -0400 Subject: [PATCH 0259/2693] document compile_commands.json symlink + .gitignore --- .gitignore | 2 ++ README.md | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6637741b..eff45bd2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .cache # typical cmake build directory (source-tree-nephew) build*/* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json diff --git a/README.md b/README.md index 0cd50f02..3c5d34be 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ $ cd reflect $ mkdir build $ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer $ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. $ make $ make install @@ -12,8 +13,14 @@ $ make install ### build for unit test coverage ``` -$ cd refcnt +$ cd xo-reflect $ mkdir build-ccov $ cd build-ccov $ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. ``` + +### LSP support +``` +$ cd xo-reflect +$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree +``` From 9718be824b6270f445ec79f62c6dfc89ba1dde9c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 17:05:28 -0400 Subject: [PATCH 0260/2693] cosmetic: indentation --- include/xo/reflect/Reflect.hpp | 352 +++++++++++++++---------------- include/xo/reflect/TypeDescr.hpp | 119 ++++++----- 2 files changed, 235 insertions(+), 236 deletions(-) diff --git a/include/xo/reflect/Reflect.hpp b/include/xo/reflect/Reflect.hpp index a6e6fe5e..478078ad 100644 --- a/include/xo/reflect/Reflect.hpp +++ b/include/xo/reflect/Reflect.hpp @@ -17,219 +17,219 @@ #include // for std::pair<> namespace xo { - namespace reflect { - template - class EstablishTdx { - public: - static std::unique_ptr make() { return AtomicTdx::make(); } - }; /*EstablishTdx*/ + namespace reflect { + template + class EstablishTdx { + public: + static std::unique_ptr make() { return AtomicTdx::make(); } + }; /*EstablishTdx*/ - // ----- xo::ref::rp ----- + // ----- xo::ref::rp ----- - /* definition provide after decl for Reflect {} below */ - template - class EstablishTdx> { - public: - static std::unique_ptr make(); - }; /*EstablishTdx*/ + /* definition provide after decl for Reflect {} below */ + template + class EstablishTdx> { + public: + static std::unique_ptr make(); + }; /*EstablishTdx*/ - // ----- std::array ----- + // ----- std::array ----- - /* definition provide after decl for Reflect {} below */ - template - class EstablishTdx> { - public: - static std::unique_ptr make(); - }; /*EstablishTdx*/ + /* definition provide after decl for Reflect {} below */ + template + class EstablishTdx> { + public: + static std::unique_ptr make(); + }; /*EstablishTdx*/ - // ----- std::vector ----- + // ----- std::vector ----- - /* definition provide after decl for Reflect {} below */ - template - class EstablishTdx> { - public: - static std::unique_ptr make(); - }; /*EstablishTdx*/ + /* definition provide after decl for Reflect {} below */ + template + class EstablishTdx> { + public: + static std::unique_ptr make(); + }; /*EstablishTdx*/ - // ----- std::pair ----- + // ----- std::pair ----- - /* definition provide after decl for Reflect {} below */ - template - class EstablishTdx> { - public: - static std::unique_ptr make(); - }; /*EstablishTdx*/ + /* definition provide after decl for Reflect {} below */ + template + class EstablishTdx> { + public: + static std::unique_ptr make(); + }; /*EstablishTdx*/ - // ----- MakeTagged ----- + // ----- MakeTagged ----- - template - class TaggedPtrMaker { - public: - static TaggedPtr make_tp(T * x); - static TaggedRcptr make_rctp(T * x); - }; + template + class TaggedPtrMaker { + public: + static TaggedPtr make_tp(T * x); + static TaggedRcptr make_rctp(T * x); + }; - template<> - class TaggedPtrMaker { - public: - static TaggedPtr make_tp(SelfTagging * x) { - return x->self_tp(); - } /*make_tp*/ + template<> + class TaggedPtrMaker { + public: + static TaggedPtr make_tp(SelfTagging * x) { + return x->self_tp(); + } /*make_tp*/ - static TaggedRcptr make_rctp(SelfTagging * x) { - return x->self_tp(); - } /*make_rctp*/ - }; /*TaggedPtrMaker*/ + static TaggedRcptr make_rctp(SelfTagging * x) { + return x->self_tp(); + } /*make_rctp*/ + }; /*TaggedPtrMaker*/ - // ----- Reflect ----- + // ----- Reflect ----- - class Reflect { - public: - /* Use: - * using mytype = ...; - * if (Reflect::is_reflected()) { ... } - */ - template - static bool is_reflected() { return TypeDescrBase::is_reflected(&typeid(T)); } + class Reflect { + public: + /* Use: + * using mytype = ...; + * if (Reflect::is_reflected()) { ... } + */ + template + static bool is_reflected() { return TypeDescrBase::is_reflected(&typeid(T)); } - /* Use: - * using mytype = ...; - * TypeDescrW td = Reflect::require(); - * - * Note: - * To avoid cyclic header dependencies - * (between EstablishTypeDescr.hpp <-> {vector/VectorTdx.hpp etc.}, - * we use a 2-stage setup process: - * - * 1. EstablishTypeDescr::establish() creates a TypeDescr* object - * with lowest-common-denominator .tdextra AtomicTdx. - * (see [reflect/EstablishTypeDescr.hpp]) - * - * 2. Reflect::require() upgrades .tdextra to suitable implementation - * depending on T; this means also need to visit reflection info - * (TypeDescr objects) for nested types to upgrade them too. - * - * This allows template-fu for a compound type (like std::vector), - * implemented in specialized header (like [reflect/struct/VectorTdx.hpp]) to - * refer to reflection info for T without having to pull in all the - * headers needed to properly reflect T (like this [reflect/Reflect.hpp]) - * - */ - template - static TypeDescrW require() { - TypeDescrW retval_td = EstablishTypeDescr::establish(); + /* Use: + * using mytype = ...; + * TypeDescrW td = Reflect::require(); + * + * Note: + * To avoid cyclic header dependencies + * (between EstablishTypeDescr.hpp <-> {vector/VectorTdx.hpp etc.}, + * we use a 2-stage setup process: + * + * 1. EstablishTypeDescr::establish() creates a TypeDescr* object + * with lowest-common-denominator .tdextra AtomicTdx. + * (see [reflect/EstablishTypeDescr.hpp]) + * + * 2. Reflect::require() upgrades .tdextra to suitable implementation + * depending on T; this means also need to visit reflection info + * (TypeDescr objects) for nested types to upgrade them too. + * + * This allows template-fu for a compound type (like std::vector), + * implemented in specialized header (like [reflect/struct/VectorTdx.hpp]) to + * refer to reflection info for T without having to pull in all the + * headers needed to properly reflect T (like this [reflect/Reflect.hpp]) + * + */ + template + static TypeDescrW require() { + TypeDescrW retval_td = EstablishTypeDescr::establish(); - /* mark TypeDescr for T as complete (even though it isn't quite yet), - * so that when we encounter recursive types, reflection terminates. - * For example consider type resulting from code like - * - * typename T; - * using T = std::vector; - * - */ - if (retval_td->mark_complete()) { - /* control here on 2nd+later calls to require(). - * in principle can immediately short-circuit. - */ - } else { - /* control comes here the first time require() runs */ + /* mark TypeDescr for T as complete (even though it isn't quite yet), + * so that when we encounter recursive types, reflection terminates. + * For example consider type resulting from code like + * + * typename T; + * using T = std::vector; + * + */ + if (retval_td->mark_complete()) { + /* control here on 2nd+later calls to require(). + * in principle can immediately short-circuit. + */ + } else { + /* control comes here the first time require() runs */ - auto final_tdx = EstablishTdx::make(); + auto final_tdx = EstablishTdx::make(); - retval_td->assign_tdextra(std::move(final_tdx)); + retval_td->assign_tdextra(std::move(final_tdx)); - /* also need to require for each child */ - } + /* also need to require for each child */ + } - return retval_td; - } /*require*/ + return retval_td; + } /*require*/ - /* Use: - * T * xyz = ...; - * TaggedPtr xyz_tp = Reflect::make_tp(xyz); - */ - template - static TaggedPtr make_tp(T * x) { return TaggedPtrMaker::make_tp(x); } + /* Use: + * T * xyz = ...; + * TaggedPtr xyz_tp = Reflect::make_tp(xyz); + */ + template + static TaggedPtr make_tp(T * x) { return TaggedPtrMaker::make_tp(x); } - template - static TaggedRcptr make_rctp(T * x) { return TaggedPtrMaker::make_rctp(x); } - }; /*Reflect*/ + template + static TaggedRcptr make_rctp(T * x) { return TaggedPtrMaker::make_rctp(x); } + }; /*Reflect*/ - // ----- MakeTagged ----- + // ----- MakeTagged ----- - template - TaggedPtr - TaggedPtrMaker::make_tp(T * x) { - return TaggedPtr(Reflect::require(), x); - } /*make_tp*/ + template + TaggedPtr + TaggedPtrMaker::make_tp(T * x) { + return TaggedPtr(Reflect::require(), x); + } /*make_tp*/ - template - TaggedRcptr - TaggedPtrMaker::make_rctp(T * x) { - return TaggedRcptr(Reflect::require(), x); - } /*make_rctp*/ + template + TaggedRcptr + TaggedPtrMaker::make_rctp(T * x) { + return TaggedRcptr(Reflect::require(), x); + } /*make_rctp*/ - // ----- xo::ref::rp ----- + // ----- xo::ref::rp ----- - /* declared above before - * class Reflect { .. } - */ - template - std::unique_ptr - EstablishTdx>::make() { - /* need to ensure Object is property reflected. - * - * In practice must be a class type, since has to store refcount - * + supply assoc'd incr/decr methods - */ - Reflect::require(); + /* declared above before + * class Reflect { .. } + */ + template + std::unique_ptr + EstablishTdx>::make() { + /* need to ensure Object is property reflected. + * + * In practice must be a class type, since has to store refcount + * + supply assoc'd incr/decr methods + */ + Reflect::require(); - return RefPointerTdx>::make(); - } /*make*/ + return RefPointerTdx>::make(); + } /*make*/ - // ----- std::array ----- + // ----- std::array ----- - /* declared above before - * class Reflect { .. } - */ - template - std::unique_ptr - EstablishTdx>::make() { - /* need to ensure Element is properly reflected */ - Reflect::require(); + /* declared above before + * class Reflect { .. } + */ + template + std::unique_ptr + EstablishTdx>::make() { + /* need to ensure Element is properly reflected */ + Reflect::require(); - return StdArrayTdx::make(); - } /*make*/ + return StdArrayTdx::make(); + } /*make*/ - // ----- std::vector ----- + // ----- std::vector ----- - /* declared above before - * class Reflect { .. } - */ - template - std::unique_ptr - EstablishTdx>::make() { - /* need to ensure Element is properly reflected */ - Reflect::require(); + /* declared above before + * class Reflect { .. } + */ + template + std::unique_ptr + EstablishTdx>::make() { + /* need to ensure Element is properly reflected */ + Reflect::require(); - return StdVectorTdx::make(); - } /*make*/ + return StdVectorTdx::make(); + } /*make*/ - // ----- std::pair ----- + // ----- std::pair ----- - /* declared above before - * class Reflect { .. } - */ - template - std::unique_ptr - EstablishTdx>::make() { - /* need to ensure Lhs, Rhs are properly reflected */ - Reflect::require(); - Reflect::require(); + /* declared above before + * class Reflect { .. } + */ + template + std::unique_ptr + EstablishTdx>::make() { + /* need to ensure Lhs, Rhs are properly reflected */ + Reflect::require(); + Reflect::require(); - return StructTdx::pair(); - } /*make*/ - } /*namespace reflect*/ + return StructTdx::pair(); + } /*make*/ + } /*namespace reflect*/ } /*namespace xo*/ /* end Reflect.hpp */ diff --git a/include/xo/reflect/TypeDescr.hpp b/include/xo/reflect/TypeDescr.hpp index da59c007..142a3287 100644 --- a/include/xo/reflect/TypeDescr.hpp +++ b/include/xo/reflect/TypeDescr.hpp @@ -2,7 +2,6 @@ #pragma once -//#include "reflect/atomic/AtomicTdx.hpp" #include "TypeDescrExtra.hpp" #include "cxxutil/demangle.hpp" #include @@ -16,80 +15,80 @@ #include namespace xo { - namespace reflect { - class TaggedPtr; /* see [reflect/TaggedPtr.hpp] */ + namespace reflect { + class TaggedPtr; /* see [reflect/TaggedPtr.hpp] */ - /* A reflected type is a type for which we keep information around at runtime - * Assign reflected types unique (within an executable) ids, - * allocating consecutively, starting from 1. - * Reserve 0 as a sentinel - */ - class TypeId { - public: - /* allocate a new TypeId value. - * promise: - * - retval.id() > 0 - */ - static TypeId allocate() { return TypeId(s_next_id++); } + /* A reflected type is a type for which we keep information around at runtime + * Assign reflected types unique (within an executable) ids, + * allocating consecutively, starting from 1. + * Reserve 0 as a sentinel + */ + class TypeId { + public: + /* allocate a new TypeId value. + * promise: + * - retval.id() > 0 + */ + static TypeId allocate() { return TypeId(s_next_id++); } - std::uint32_t id() const { return id_; } + std::uint32_t id() const { return id_; } - private: - explicit TypeId(std::uint32_t id) : id_{id} {} + private: + explicit TypeId(std::uint32_t id) : id_{id} {} - private: - static std::uint32_t s_next_id; + private: + static std::uint32_t s_next_id; - /* unique index# for this type. - * 0 reserved for sentinel - */ - std::uint32_t id_ = 0; - }; /*TypeId*/ + /* unique index# for this type. + * 0 reserved for sentinel + */ + std::uint32_t id_ = 0; + }; /*TypeId*/ - inline std::ostream & - operator<<(std::ostream & os, TypeId x) { - os << x.id(); - return os; - } /*operator<<*/ + inline std::ostream & + operator<<(std::ostream & os, TypeId x) { + os << x.id(); + return os; + } /*operator<<*/ - /* runtime description of a struct/class instance variable */ - class StructMember; + /* runtime description of a struct/class instance variable */ + class StructMember; - class TypeDescrBase; + class TypeDescrBase; - using TypeDescr = TypeDescrBase const *; - using TypeDescrW = TypeDescrBase *; + using TypeDescr = TypeDescrBase const *; + using TypeDescrW = TypeDescrBase *; - /* convenience wrapper for a std::type_info pointer. - * works properly with pybind11, since python doens't encounter - * native type_info pointer, it won't try to delete it. - */ - class TypeInfoRef { - public: - explicit TypeInfoRef(std::type_info const * tinfo) : tinfo_{tinfo} {} - TypeInfoRef(TypeInfoRef const & x) = default; + /* convenience wrapper for a std::type_info pointer. + * works properly with pybind11, since python doens't encounter + * native type_info pointer, it won't try to delete it. + */ + class TypeInfoRef { + public: + explicit TypeInfoRef(std::type_info const * tinfo) : tinfo_{tinfo} {} + TypeInfoRef(TypeInfoRef const & x) = default; - /* use: - * TypeInfoRef tinfo = TypeInfoRef::make(); - */ - template - TypeInfoRef make() { return TypeInfoRef(&typeid(T)); } + /* use: + * TypeInfoRef tinfo = TypeInfoRef::make(); + */ + template + TypeInfoRef make() { return TypeInfoRef(&typeid(T)); } - std::size_t hash_code() const { return this->tinfo_->hash_code(); } - char const * impl_name() const { return this->tinfo_->name(); } + std::size_t hash_code() const { return this->tinfo_->hash_code(); } + char const * impl_name() const { return this->tinfo_->name(); } - static bool is_equal(TypeInfoRef x, TypeInfoRef y) noexcept { - if (x.hash_code() != y.hash_code()) - return false; + static bool is_equal(TypeInfoRef x, TypeInfoRef y) noexcept { + if (x.hash_code() != y.hash_code()) + return false; - return ::strcmp(x.impl_name(), y.impl_name()) == 0; - } /*is_equal*/ + return ::strcmp(x.impl_name(), y.impl_name()) == 0; + } /*is_equal*/ - private: - /* native type_info object for encapsulated type */ - std::type_info const * tinfo_ = nullptr; - }; /*TypeInfoRef*/ - } /*namespace reflect*/ + private: + /* native type_info object for encapsulated type */ + std::type_info const * tinfo_ = nullptr; + }; /*TypeInfoRef*/ + } /*namespace reflect*/ } /*namespace xo*/ namespace std { From 61ddb8140e4a79f728a51bec9589d85b08328031 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:02:22 -0400 Subject: [PATCH 0261/2693] refcnt: update after inserting xo into include path --- include/xo/reflect/Reflect.hpp | 2 +- include/xo/reflect/SelfTagging.hpp | 2 +- include/xo/reflect/TaggedRcptr.hpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/xo/reflect/Reflect.hpp b/include/xo/reflect/Reflect.hpp index 478078ad..89a75374 100644 --- a/include/xo/reflect/Reflect.hpp +++ b/include/xo/reflect/Reflect.hpp @@ -11,7 +11,7 @@ #include "pointer/PointerTdx.hpp" #include "vector/VectorTdx.hpp" #include "struct/StructTdx.hpp" -#include "refcnt/Refcounted.hpp" +#include "xo/refcnt/Refcounted.hpp" #include #include #include // for std::pair<> diff --git a/include/xo/reflect/SelfTagging.hpp b/include/xo/reflect/SelfTagging.hpp index e188b5c2..b41d4ce3 100644 --- a/include/xo/reflect/SelfTagging.hpp +++ b/include/xo/reflect/SelfTagging.hpp @@ -5,7 +5,7 @@ #pragma once -#include "Refcounted.hpp" +#include "xo/refcnt/Refcounted.hpp" #include "TypeDescr.hpp" #include "TaggedRcptr.hpp" diff --git a/include/xo/reflect/TaggedRcptr.hpp b/include/xo/reflect/TaggedRcptr.hpp index e31cffb3..3e06af64 100644 --- a/include/xo/reflect/TaggedRcptr.hpp +++ b/include/xo/reflect/TaggedRcptr.hpp @@ -8,7 +8,7 @@ #include "TaggedPtr.hpp" // causes #include cycle, reflect/Reflect.hpp includes this header //#include "reflect/Reflect.hpp" -#include "refcnt/Refcounted.hpp" +#include "xo/refcnt/Refcounted.hpp" namespace xo { namespace reflect { From d480174107a7a2c89b761c9c7a7a483055dd9656 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:03:21 -0400 Subject: [PATCH 0262/2693] refcnt: insert xo/ into include path --- include/{ => xo}/cxxutil/demangle.hpp | 0 include/{ => xo}/refcnt/Displayable.hpp | 2 +- include/{ => xo}/refcnt/Refcounted.hpp | 0 include/{ => xo}/refcnt/Unowned.hpp | 0 src/Displayable.cpp | 2 +- utest/intrusive_ptr.test.cpp | 2 +- 6 files changed, 3 insertions(+), 3 deletions(-) rename include/{ => xo}/cxxutil/demangle.hpp (100%) rename include/{ => xo}/refcnt/Displayable.hpp (95%) rename include/{ => xo}/refcnt/Refcounted.hpp (100%) rename include/{ => xo}/refcnt/Unowned.hpp (100%) diff --git a/include/cxxutil/demangle.hpp b/include/xo/cxxutil/demangle.hpp similarity index 100% rename from include/cxxutil/demangle.hpp rename to include/xo/cxxutil/demangle.hpp diff --git a/include/refcnt/Displayable.hpp b/include/xo/refcnt/Displayable.hpp similarity index 95% rename from include/refcnt/Displayable.hpp rename to include/xo/refcnt/Displayable.hpp index 74708573..5a184667 100644 --- a/include/refcnt/Displayable.hpp +++ b/include/xo/refcnt/Displayable.hpp @@ -2,7 +2,7 @@ #pragma once -#include "refcnt/Refcounted.hpp" +#include "Refcounted.hpp" namespace xo { namespace ref { diff --git a/include/refcnt/Refcounted.hpp b/include/xo/refcnt/Refcounted.hpp similarity index 100% rename from include/refcnt/Refcounted.hpp rename to include/xo/refcnt/Refcounted.hpp diff --git a/include/refcnt/Unowned.hpp b/include/xo/refcnt/Unowned.hpp similarity index 100% rename from include/refcnt/Unowned.hpp rename to include/xo/refcnt/Unowned.hpp diff --git a/src/Displayable.cpp b/src/Displayable.cpp index b8793ad3..ea839841 100644 --- a/src/Displayable.cpp +++ b/src/Displayable.cpp @@ -1,6 +1,6 @@ /* @file Displayable.cpp */ -#include "refcnt/Displayable.hpp" +#include "Displayable.hpp" namespace xo { using xo::tostr; diff --git a/utest/intrusive_ptr.test.cpp b/utest/intrusive_ptr.test.cpp index d8d756e2..f9d1f212 100644 --- a/utest/intrusive_ptr.test.cpp +++ b/utest/intrusive_ptr.test.cpp @@ -1,6 +1,6 @@ /* @file intrusive_ptr.test.cpp */ -#include "refcnt/Refcounted.hpp" +#include "Refcounted.hpp" #include "indentlog/scope.hpp" #include "catch2/catch.hpp" #include From 2b195c47f7a63f6ff5bfc3ead4eb4999ec9d7d50 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:09:21 -0400 Subject: [PATCH 0263/2693] subsys: insert xo/ into include path --- .gitignore | 2 ++ include/{ => xo}/subsys/Subsystem.hpp | 0 2 files changed, 2 insertions(+) create mode 100644 .gitignore rename include/{ => xo}/subsys/Subsystem.hpp (100%) diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..d6bd1a25 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# typical build location +build diff --git a/include/subsys/Subsystem.hpp b/include/xo/subsys/Subsystem.hpp similarity index 100% rename from include/subsys/Subsystem.hpp rename to include/xo/subsys/Subsystem.hpp From f5c60e299b6743eae84342da41184435583d5da3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:09:41 -0400 Subject: [PATCH 0264/2693] xo-reflect: update subsys include location --- include/xo/reflect/init_reflect.hpp | 2 +- src/reflect/init_reflect.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/xo/reflect/init_reflect.hpp b/include/xo/reflect/init_reflect.hpp index 00506a25..b6365632 100644 --- a/include/xo/reflect/init_reflect.hpp +++ b/include/xo/reflect/init_reflect.hpp @@ -5,7 +5,7 @@ #pragma once -#include "subsys/Subsystem.hpp" +#include "xo/subsys/Subsystem.hpp" namespace xo { /* tag to represent the reflect/ subsystem within ordered initialization */ diff --git a/src/reflect/init_reflect.cpp b/src/reflect/init_reflect.cpp index 20dd1441..d95bf2e3 100644 --- a/src/reflect/init_reflect.cpp +++ b/src/reflect/init_reflect.cpp @@ -4,7 +4,7 @@ */ #include "init_reflect.hpp" -#include "subsys/Subsystem.hpp" +#include "xo/subsys/Subsystem.hpp" namespace xo { void From 243e9573c7601e01d236ad855d76263e7cb88a81 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:21:58 -0400 Subject: [PATCH 0265/2693] randomgen: inserted xo/ into include path --- utest/bplustree.cpp | 4 ++-- utest/random_tree_ops.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/utest/bplustree.cpp b/utest/bplustree.cpp index 24115e4b..2fe68d83 100644 --- a/utest/bplustree.cpp +++ b/utest/bplustree.cpp @@ -4,8 +4,8 @@ #include "random_tree_ops.hpp" #include "xo/ordinaltree/BplusTree.hpp" -#include "randomgen/random_seed.hpp" -#include "randomgen/print.hpp" +#include "xo/randomgen/random_seed.hpp" +#include "xo/randomgen/print.hpp" #include "indentlog/scope.hpp" #include "catch2/catch.hpp" diff --git a/utest/random_tree_ops.hpp b/utest/random_tree_ops.hpp index b81601f7..890202f7 100644 --- a/utest/random_tree_ops.hpp +++ b/utest/random_tree_ops.hpp @@ -1,6 +1,6 @@ /* @file random_tree_ops.hpp **/ -#include "randomgen/xoshiro256.hpp" +#include "xo/randomgen/xoshiro256.hpp" #include "indentlog/scope.hpp" #include "indentlog/print/tag.hpp" #include "indentlog/print/vector.hpp" From b7b80ffe7502309e96f6977aac84907088619493 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:22:27 -0400 Subject: [PATCH 0266/2693] .gitignore: + .ccache --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 49f711e2..9e716afc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +# lsp keeps state here +.cache # typical build directories build ccov From 1e62d358dc9c17e5c8a2d13d4bf785f138def827 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:22:44 -0400 Subject: [PATCH 0267/2693] + README.md --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..41625b26 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# ordinal tree library + +### build + install +``` +$ cd xo-ordinaltree +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` + +### build for unit test coverage +``` +$ cd xo-ordinaltree +$ mkdir build-ccov +$ cd build-ccov +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + +### LSP support +``` +$ cd xo-ordinaltree +$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree +``` From c6a258eb3cffe52d61bd9ab08b713050b0c9ab5b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:30:10 -0400 Subject: [PATCH 0268/2693] indentlog: insert xo/ into include path --- example/ex1/ex1.cpp | 6 +++++- example/ex2/ex2.cpp | 2 +- example/ex3/ex3.cpp | 2 +- example/ex4/ex4.cpp | 2 +- include/{ => xo}/indentlog/log_config.hpp | 0 include/{ => xo}/indentlog/log_level.hpp | 0 include/{ => xo}/indentlog/log_state.hpp | 0 include/{ => xo}/indentlog/log_streambuf.hpp | 0 include/{ => xo}/indentlog/print/array.hpp | 0 include/{ => xo}/indentlog/print/code_location.hpp | 0 include/{ => xo}/indentlog/print/color.hpp | 0 include/{ => xo}/indentlog/print/concat.hpp | 0 include/{ => xo}/indentlog/print/filename.hpp | 0 include/{ => xo}/indentlog/print/fixed.hpp | 0 include/{ => xo}/indentlog/print/function.hpp | 0 include/{ => xo}/indentlog/print/pad.hpp | 0 include/{ => xo}/indentlog/print/printer.hpp | 0 include/{ => xo}/indentlog/print/quoted.hpp | 0 include/{ => xo}/indentlog/print/quoted_char.hpp | 0 include/{ => xo}/indentlog/print/tag.hpp | 0 include/{ => xo}/indentlog/print/tag_config.hpp | 0 include/{ => xo}/indentlog/print/time.hpp | 2 +- include/{ => xo}/indentlog/print/tostr.hpp | 0 include/{ => xo}/indentlog/print/vector.hpp | 0 include/{ => xo}/indentlog/scope.hpp | 0 include/{ => xo}/indentlog/timeutil/timeutil.hpp | 0 utest/array.test.cpp | 4 ++-- utest/code_location.test.cpp | 6 +++--- utest/filename.test.cpp | 4 ++-- utest/fixed.test.cpp | 4 ++-- utest/function.test.cpp | 4 ++-- utest/quoted.test.cpp | 4 ++-- utest/tag.test.cpp | 6 +++--- utest/timeutil.test.cpp | 4 ++-- utest/vector.test.cpp | 4 ++-- 35 files changed, 29 insertions(+), 25 deletions(-) rename include/{ => xo}/indentlog/log_config.hpp (100%) rename include/{ => xo}/indentlog/log_level.hpp (100%) rename include/{ => xo}/indentlog/log_state.hpp (100%) rename include/{ => xo}/indentlog/log_streambuf.hpp (100%) rename include/{ => xo}/indentlog/print/array.hpp (100%) rename include/{ => xo}/indentlog/print/code_location.hpp (100%) rename include/{ => xo}/indentlog/print/color.hpp (100%) rename include/{ => xo}/indentlog/print/concat.hpp (100%) rename include/{ => xo}/indentlog/print/filename.hpp (100%) rename include/{ => xo}/indentlog/print/fixed.hpp (100%) rename include/{ => xo}/indentlog/print/function.hpp (100%) rename include/{ => xo}/indentlog/print/pad.hpp (100%) rename include/{ => xo}/indentlog/print/printer.hpp (100%) rename include/{ => xo}/indentlog/print/quoted.hpp (100%) rename include/{ => xo}/indentlog/print/quoted_char.hpp (100%) rename include/{ => xo}/indentlog/print/tag.hpp (100%) rename include/{ => xo}/indentlog/print/tag_config.hpp (100%) rename include/{ => xo}/indentlog/print/time.hpp (98%) rename include/{ => xo}/indentlog/print/tostr.hpp (100%) rename include/{ => xo}/indentlog/print/vector.hpp (100%) rename include/{ => xo}/indentlog/scope.hpp (100%) rename include/{ => xo}/indentlog/timeutil/timeutil.hpp (100%) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 91e321e2..b791aee0 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -1,4 +1,6 @@ -#include "indentlog/scope.hpp" +/* ex1.cpp */ + +#include "xo/indentlog/scope.hpp" using namespace xo; @@ -16,3 +18,5 @@ int main(int argc, char ** argv) { outer(123); } + +/* end ex1.cpp */ diff --git a/example/ex2/ex2.cpp b/example/ex2/ex2.cpp index f329b9d4..4ccfaae2 100644 --- a/example/ex2/ex2.cpp +++ b/example/ex2/ex2.cpp @@ -1,6 +1,6 @@ /* examples ex2/ex2.cpp */ -#include "indentlog/scope.hpp" +#include "xo/indentlog/scope.hpp" using namespace xo; diff --git a/example/ex3/ex3.cpp b/example/ex3/ex3.cpp index 2a09d2d7..0fc0fcc2 100644 --- a/example/ex3/ex3.cpp +++ b/example/ex3/ex3.cpp @@ -1,6 +1,6 @@ /* examples ex3/ex3.cpp */ -#include "indentlog/scope.hpp" +#include "xo/indentlog/scope.hpp" using namespace xo; diff --git a/example/ex4/ex4.cpp b/example/ex4/ex4.cpp index d59e83c1..01a141c8 100644 --- a/example/ex4/ex4.cpp +++ b/example/ex4/ex4.cpp @@ -1,6 +1,6 @@ /* @file ex4.cpp */ -#include "indentlog/scope.hpp" +#include "xo/indentlog/scope.hpp" using namespace xo; diff --git a/include/indentlog/log_config.hpp b/include/xo/indentlog/log_config.hpp similarity index 100% rename from include/indentlog/log_config.hpp rename to include/xo/indentlog/log_config.hpp diff --git a/include/indentlog/log_level.hpp b/include/xo/indentlog/log_level.hpp similarity index 100% rename from include/indentlog/log_level.hpp rename to include/xo/indentlog/log_level.hpp diff --git a/include/indentlog/log_state.hpp b/include/xo/indentlog/log_state.hpp similarity index 100% rename from include/indentlog/log_state.hpp rename to include/xo/indentlog/log_state.hpp diff --git a/include/indentlog/log_streambuf.hpp b/include/xo/indentlog/log_streambuf.hpp similarity index 100% rename from include/indentlog/log_streambuf.hpp rename to include/xo/indentlog/log_streambuf.hpp diff --git a/include/indentlog/print/array.hpp b/include/xo/indentlog/print/array.hpp similarity index 100% rename from include/indentlog/print/array.hpp rename to include/xo/indentlog/print/array.hpp diff --git a/include/indentlog/print/code_location.hpp b/include/xo/indentlog/print/code_location.hpp similarity index 100% rename from include/indentlog/print/code_location.hpp rename to include/xo/indentlog/print/code_location.hpp diff --git a/include/indentlog/print/color.hpp b/include/xo/indentlog/print/color.hpp similarity index 100% rename from include/indentlog/print/color.hpp rename to include/xo/indentlog/print/color.hpp diff --git a/include/indentlog/print/concat.hpp b/include/xo/indentlog/print/concat.hpp similarity index 100% rename from include/indentlog/print/concat.hpp rename to include/xo/indentlog/print/concat.hpp diff --git a/include/indentlog/print/filename.hpp b/include/xo/indentlog/print/filename.hpp similarity index 100% rename from include/indentlog/print/filename.hpp rename to include/xo/indentlog/print/filename.hpp diff --git a/include/indentlog/print/fixed.hpp b/include/xo/indentlog/print/fixed.hpp similarity index 100% rename from include/indentlog/print/fixed.hpp rename to include/xo/indentlog/print/fixed.hpp diff --git a/include/indentlog/print/function.hpp b/include/xo/indentlog/print/function.hpp similarity index 100% rename from include/indentlog/print/function.hpp rename to include/xo/indentlog/print/function.hpp diff --git a/include/indentlog/print/pad.hpp b/include/xo/indentlog/print/pad.hpp similarity index 100% rename from include/indentlog/print/pad.hpp rename to include/xo/indentlog/print/pad.hpp diff --git a/include/indentlog/print/printer.hpp b/include/xo/indentlog/print/printer.hpp similarity index 100% rename from include/indentlog/print/printer.hpp rename to include/xo/indentlog/print/printer.hpp diff --git a/include/indentlog/print/quoted.hpp b/include/xo/indentlog/print/quoted.hpp similarity index 100% rename from include/indentlog/print/quoted.hpp rename to include/xo/indentlog/print/quoted.hpp diff --git a/include/indentlog/print/quoted_char.hpp b/include/xo/indentlog/print/quoted_char.hpp similarity index 100% rename from include/indentlog/print/quoted_char.hpp rename to include/xo/indentlog/print/quoted_char.hpp diff --git a/include/indentlog/print/tag.hpp b/include/xo/indentlog/print/tag.hpp similarity index 100% rename from include/indentlog/print/tag.hpp rename to include/xo/indentlog/print/tag.hpp diff --git a/include/indentlog/print/tag_config.hpp b/include/xo/indentlog/print/tag_config.hpp similarity index 100% rename from include/indentlog/print/tag_config.hpp rename to include/xo/indentlog/print/tag_config.hpp diff --git a/include/indentlog/print/time.hpp b/include/xo/indentlog/print/time.hpp similarity index 98% rename from include/indentlog/print/time.hpp rename to include/xo/indentlog/print/time.hpp index 275f43da..28a1a687 100644 --- a/include/indentlog/print/time.hpp +++ b/include/xo/indentlog/print/time.hpp @@ -2,7 +2,7 @@ #pragma once -#include "indentlog/timeutil/timeutil.hpp" +#include "xo/indentlog/timeutil/timeutil.hpp" namespace xo { namespace time { diff --git a/include/indentlog/print/tostr.hpp b/include/xo/indentlog/print/tostr.hpp similarity index 100% rename from include/indentlog/print/tostr.hpp rename to include/xo/indentlog/print/tostr.hpp diff --git a/include/indentlog/print/vector.hpp b/include/xo/indentlog/print/vector.hpp similarity index 100% rename from include/indentlog/print/vector.hpp rename to include/xo/indentlog/print/vector.hpp diff --git a/include/indentlog/scope.hpp b/include/xo/indentlog/scope.hpp similarity index 100% rename from include/indentlog/scope.hpp rename to include/xo/indentlog/scope.hpp diff --git a/include/indentlog/timeutil/timeutil.hpp b/include/xo/indentlog/timeutil/timeutil.hpp similarity index 100% rename from include/indentlog/timeutil/timeutil.hpp rename to include/xo/indentlog/timeutil/timeutil.hpp diff --git a/utest/array.test.cpp b/utest/array.test.cpp index 4ba8b83a..e916802d 100644 --- a/utest/array.test.cpp +++ b/utest/array.test.cpp @@ -1,7 +1,7 @@ /* @file array.test.cpp */ -#include "indentlog/print/array.hpp" /* overload operator<< for std::array */ -#include "indentlog/print/tag.hpp" +#include "xo/indentlog/print/array.hpp" /* overload operator<< for std::array */ +#include "xo/indentlog/print/tag.hpp" #include #include diff --git a/utest/code_location.test.cpp b/utest/code_location.test.cpp index 64f1a0c4..7be0eb6a 100644 --- a/utest/code_location.test.cpp +++ b/utest/code_location.test.cpp @@ -1,8 +1,8 @@ /* @file code_location.test.cpp */ -#include "indentlog/print/code_location.hpp" -#include "indentlog/print/color.hpp" -#include "indentlog/print/tag.hpp" +#include "xo/indentlog/print/code_location.hpp" +#include "xo/indentlog/print/color.hpp" +#include "xo/indentlog/print/tag.hpp" #include #include diff --git a/utest/filename.test.cpp b/utest/filename.test.cpp index ebff42b4..a47a5ab0 100644 --- a/utest/filename.test.cpp +++ b/utest/filename.test.cpp @@ -1,7 +1,7 @@ /* @file filename.test.cpp */ -#include "indentlog/print/filename.hpp" -#include "indentlog/print/tag.hpp" +#include "xo/indentlog/print/filename.hpp" +#include "xo/indentlog/print/tag.hpp" #include #include diff --git a/utest/fixed.test.cpp b/utest/fixed.test.cpp index c2b7146b..d42ffc63 100644 --- a/utest/fixed.test.cpp +++ b/utest/fixed.test.cpp @@ -1,7 +1,7 @@ /* @file fixed.test.cpp */ -#include "indentlog/print/fixed.hpp" -#include "indentlog/print/tag.hpp" +#include "xo/indentlog/print/fixed.hpp" +#include "xo/indentlog/print/tag.hpp" #include #include diff --git a/utest/function.test.cpp b/utest/function.test.cpp index bd71bd35..2a21b077 100644 --- a/utest/function.test.cpp +++ b/utest/function.test.cpp @@ -1,7 +1,7 @@ /* @file function.test.cpp */ -#include "indentlog/print/function.hpp" -#include "indentlog/print/tag.hpp" +#include "xo/indentlog/print/function.hpp" +#include "xo/indentlog/print/tag.hpp" #include #include diff --git a/utest/quoted.test.cpp b/utest/quoted.test.cpp index 35583d4d..e90c195b 100644 --- a/utest/quoted.test.cpp +++ b/utest/quoted.test.cpp @@ -1,7 +1,7 @@ /* @file fixed.test.cpp */ -#include "indentlog/print/quoted.hpp" -#include "indentlog/print/tag.hpp" +#include "xo/indentlog/print/quoted.hpp" +#include "xo/indentlog/print/tag.hpp" #include #include diff --git a/utest/tag.test.cpp b/utest/tag.test.cpp index 8ecabb8d..4b2d939e 100644 --- a/utest/tag.test.cpp +++ b/utest/tag.test.cpp @@ -1,8 +1,8 @@ /* @file tag.test.cpp */ -#include "indentlog/print/tag.hpp" -#include "indentlog/print/vector.hpp" -#include "indentlog/print/concat.hpp" +#include "xo/indentlog/print/tag.hpp" +#include "xo/indentlog/print/vector.hpp" +#include "xo/indentlog/print/concat.hpp" #include #include diff --git a/utest/timeutil.test.cpp b/utest/timeutil.test.cpp index 816c92da..fb50ca63 100644 --- a/utest/timeutil.test.cpp +++ b/utest/timeutil.test.cpp @@ -1,7 +1,7 @@ /* @file timeutil.test.cpp */ -#include "indentlog/timeutil/timeutil.hpp" -#include "indentlog/print/tag.hpp" +#include "xo/indentlog/timeutil/timeutil.hpp" +#include "xo/indentlog/print/tag.hpp" #include #include diff --git a/utest/vector.test.cpp b/utest/vector.test.cpp index 8b4f1f4c..eaa1b4ea 100644 --- a/utest/vector.test.cpp +++ b/utest/vector.test.cpp @@ -1,7 +1,7 @@ /* @file vector.test.cpp */ -#include "indentlog/print/vector.hpp" /* overload operator<< for std::vector */ -#include "indentlog/print/tag.hpp" +#include "xo/indentlog/print/vector.hpp" /* overload operator<< for std::vector */ +#include "xo/indentlog/print/tag.hpp" #include #include From aaba8f05080816839bc322d8ff32629a306dd78c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:33:48 -0400 Subject: [PATCH 0269/2693] drop include path --- cmake/xo_cxx.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 7eaaaff9..da93eccb 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -64,7 +64,7 @@ macro(xo_include_headeronly_options2 target) $ $ # e.g. for #include "Refcounted.hpp" in refcnt/src when ${target}=refcnt [DEPRECATED] $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect - $ +# $ $ # e.g. for generated .hpp files ) @@ -126,7 +126,7 @@ macro(xo_include_options2 target) $ $ # e.g. for #include "Refcounted.hpp" in refcnt/src [DEPRECATED] $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect - $ +# $ $ # e.g. for generated .hpp files ) From 00fae469e02e42ed9c9e3f9b5aa1f4527d789cf2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:41:45 -0400 Subject: [PATCH 0270/2693] randomgen: fixing include paths for xo/ insertion --- example/ex1/ex1.cpp | 4 +--- example/ex2/ex2.cpp | 4 ++-- include/{ => xo}/randomgen/engine_concept.hpp | 0 include/{ => xo}/randomgen/print.hpp | 0 include/{ => xo}/randomgen/random_seed.hpp | 0 include/{ => xo}/randomgen/xoshiro256.hpp | 0 6 files changed, 3 insertions(+), 5 deletions(-) rename include/{ => xo}/randomgen/engine_concept.hpp (100%) rename include/{ => xo}/randomgen/print.hpp (100%) rename include/{ => xo}/randomgen/random_seed.hpp (100%) rename include/{ => xo}/randomgen/xoshiro256.hpp (100%) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 8c908fa5..0db92be4 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -1,10 +1,8 @@ /* @file ex1.cpp */ -#include "randomgen/xoshiro256.hpp" +#include "xo/randomgen/xoshiro256.hpp" #include #include -//#include -//#include using namespace xo; using namespace xo::rng; diff --git a/example/ex2/ex2.cpp b/example/ex2/ex2.cpp index c77e06ff..949b65dc 100644 --- a/example/ex2/ex2.cpp +++ b/example/ex2/ex2.cpp @@ -1,7 +1,7 @@ /* @file ex2.cpp */ -#include "randomgen/xoshiro256.hpp" -#include "randomgen/random_seed.hpp" +#include "xo/randomgen/xoshiro256.hpp" +#include "xo/randomgen/random_seed.hpp" using namespace xo; using namespace xo::rng; diff --git a/include/randomgen/engine_concept.hpp b/include/xo/randomgen/engine_concept.hpp similarity index 100% rename from include/randomgen/engine_concept.hpp rename to include/xo/randomgen/engine_concept.hpp diff --git a/include/randomgen/print.hpp b/include/xo/randomgen/print.hpp similarity index 100% rename from include/randomgen/print.hpp rename to include/xo/randomgen/print.hpp diff --git a/include/randomgen/random_seed.hpp b/include/xo/randomgen/random_seed.hpp similarity index 100% rename from include/randomgen/random_seed.hpp rename to include/xo/randomgen/random_seed.hpp diff --git a/include/randomgen/xoshiro256.hpp b/include/xo/randomgen/xoshiro256.hpp similarity index 100% rename from include/randomgen/xoshiro256.hpp rename to include/xo/randomgen/xoshiro256.hpp From 5b558ce086ccbb4861e914a676dc8212cb3042f0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:42:32 -0400 Subject: [PATCH 0271/2693] .gitignore: + compile_commands.json --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 69e11d47..e90fd723 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .cache # typical build directory build +# compile_commands.json: symlink to build directory, should be created manually +compile_commands.json From 8ba04142eaec5de963c208979c42f05fa830d7b0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:43:00 -0400 Subject: [PATCH 0272/2693] build fix: account for xo/ insertion into include path --- README.md | 18 ++++++++++++------ include/xo/refcnt/Refcounted.hpp | 4 ++-- utest/intrusive_ptr.test.cpp | 2 +- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index bae1fb2c..0b53dc81 100644 --- a/README.md +++ b/README.md @@ -17,13 +17,13 @@ see [github/Rconybea/indentlog](https://github.com/Rconybea/indentlog) ### copy `refcnt` repository locally ``` $ git clone git@github.com:rconybea/refcnt.git -$ ls -d refcnt -refcnt +$ ls -d xo-refcnt +xo-refcnt ``` ### build + install ``` -$ cd refcnt +$ cd xo-refcnt $ mkdir build $ cd build $ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. @@ -39,22 +39,28 @@ $ git clone git@github.com:rconybea/xo-nix.git $ ls -d xo-nix xo-nix $ cd xo-nix -$ nix-build -A refcnt +$ nix-build -A xo-refcnt ``` ### build for unit test coverage ``` -$ cd refcnt +$ cd xo-refcnt $ mkdir ccov $ cd ccov $ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. ``` +### LSP support +``` +$ cd xo-refcnt +$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree +``` + ## Examples ### 1 ``` -#include "refcnt/Refcounted.hpp" +#include "xo/refcnt/Refcounted.hpp" using xo::ref::Refcounted; diff --git a/include/xo/refcnt/Refcounted.hpp b/include/xo/refcnt/Refcounted.hpp index dc069cf5..3db621a1 100644 --- a/include/xo/refcnt/Refcounted.hpp +++ b/include/xo/refcnt/Refcounted.hpp @@ -2,8 +2,8 @@ #pragma once -#include "indentlog/scope.hpp" -#include "cxxutil/demangle.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/cxxutil/demangle.hpp" //#include #include diff --git a/utest/intrusive_ptr.test.cpp b/utest/intrusive_ptr.test.cpp index f9d1f212..bc45d38c 100644 --- a/utest/intrusive_ptr.test.cpp +++ b/utest/intrusive_ptr.test.cpp @@ -1,7 +1,7 @@ /* @file intrusive_ptr.test.cpp */ #include "Refcounted.hpp" -#include "indentlog/scope.hpp" +#include "xo/indentlog/scope.hpp" #include "catch2/catch.hpp" #include #include From 1b3be7e7335d8c5d607656f75cd5a026963831d1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:43:49 -0400 Subject: [PATCH 0273/2693] build: account for xo/ insertion into include path --- include/xo/reflect/TypeDescr.hpp | 2 +- include/xo/reflect/pointer/PointerTdx.hpp | 2 +- src/reflect/TaggedRcptr.cpp | 2 +- src/reflect/TypeDescr.cpp | 2 +- src/reflect/struct/StructMember.cpp | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/xo/reflect/TypeDescr.hpp b/include/xo/reflect/TypeDescr.hpp index 142a3287..d908a77b 100644 --- a/include/xo/reflect/TypeDescr.hpp +++ b/include/xo/reflect/TypeDescr.hpp @@ -3,7 +3,7 @@ #pragma once #include "TypeDescrExtra.hpp" -#include "cxxutil/demangle.hpp" +#include "xo/cxxutil/demangle.hpp" #include #include #include diff --git a/include/xo/reflect/pointer/PointerTdx.hpp b/include/xo/reflect/pointer/PointerTdx.hpp index 327456cf..037d3ee4 100644 --- a/include/xo/reflect/pointer/PointerTdx.hpp +++ b/include/xo/reflect/pointer/PointerTdx.hpp @@ -7,7 +7,7 @@ #include "xo/reflect/TypeDescrExtra.hpp" #include "xo/reflect/EstablishTypeDescr.hpp" -#include "indentlog/scope.hpp" +#include "xo/indentlog/scope.hpp" namespace xo { namespace reflect { diff --git a/src/reflect/TaggedRcptr.cpp b/src/reflect/TaggedRcptr.cpp index cc461e0c..8d68a60b 100644 --- a/src/reflect/TaggedRcptr.cpp +++ b/src/reflect/TaggedRcptr.cpp @@ -4,7 +4,7 @@ */ #include "TaggedRcptr.hpp" -#include "indentlog/print/tag.hpp" +#include "xo/indentlog/print/tag.hpp" namespace xo { using xo::xtag; diff --git a/src/reflect/TypeDescr.cpp b/src/reflect/TypeDescr.cpp index 564fec14..93a0bfc1 100644 --- a/src/reflect/TypeDescr.cpp +++ b/src/reflect/TypeDescr.cpp @@ -4,7 +4,7 @@ #include "TaggedPtr.hpp" #include "TypeDescrExtra.hpp" #include "atomic/AtomicTdx.hpp" -#include "indentlog/scope.hpp" +#include "xo/indentlog/scope.hpp" namespace xo { using xo::scope; diff --git a/src/reflect/struct/StructMember.cpp b/src/reflect/struct/StructMember.cpp index 59d69056..7f27af8c 100644 --- a/src/reflect/struct/StructMember.cpp +++ b/src/reflect/struct/StructMember.cpp @@ -4,7 +4,7 @@ */ #include "struct/StructMember.hpp" -#include "indentlog/scope.hpp" +#include "xo/indentlog/scope.hpp" #include namespace xo { From ebfbd40afc752d160ae23c1b424094f14ec66d77 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 19:12:24 -0400 Subject: [PATCH 0274/2693] bugfix: include part must supply xo/ --- include/xo/subsys/Subsystem.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/subsys/Subsystem.hpp b/include/xo/subsys/Subsystem.hpp index b9a41e18..ece72db6 100644 --- a/include/xo/subsys/Subsystem.hpp +++ b/include/xo/subsys/Subsystem.hpp @@ -5,7 +5,7 @@ #pragma once -#include "indentlog/scope.hpp" +#include "xo/indentlog/scope.hpp" #include #include #include From 14ee09a76b45a9baf64a1de9941ae5028e612e8f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 19:22:04 -0400 Subject: [PATCH 0275/2693] build: update include paths to include xo/ --- include/xo/ordinaltree/BplusTree.hpp | 6 +++--- include/xo/ordinaltree/RedBlackTree.hpp | 6 +++--- include/xo/ordinaltree/bplustree/BplusTreeUtil.hpp | 4 ++-- include/xo/ordinaltree/bplustree/InternalNode.hpp | 4 ++-- include/xo/ordinaltree/bplustree/Iterator.hpp | 2 +- include/xo/ordinaltree/bplustree/LeafNode.hpp | 2 +- utest/bplustree.cpp | 2 +- utest/random_tree_ops.hpp | 6 +++--- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/include/xo/ordinaltree/BplusTree.hpp b/include/xo/ordinaltree/BplusTree.hpp index f58b4317..7810a031 100644 --- a/include/xo/ordinaltree/BplusTree.hpp +++ b/include/xo/ordinaltree/BplusTree.hpp @@ -15,9 +15,9 @@ #include "bplustree/Iterator.hpp" #include "bplustree/Lhs.hpp" #include "bplustree/bplustree_tags.hpp" -#include "indentlog/scope.hpp" -#include "indentlog/print/tag.hpp" -#include "indentlog/print/pad.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/tag.hpp" +#include "xo/indentlog/print/pad.hpp" #include /* for std::unqiue_ptr */ #include /* for std::max */ #include /* for std::numeric_limits */ diff --git a/include/xo/ordinaltree/RedBlackTree.hpp b/include/xo/ordinaltree/RedBlackTree.hpp index e9460b95..7a3ea1e4 100644 --- a/include/xo/ordinaltree/RedBlackTree.hpp +++ b/include/xo/ordinaltree/RedBlackTree.hpp @@ -5,9 +5,9 @@ #pragma once -#include "indentlog/scope.hpp" -#include "indentlog/print/pad.hpp" -#include "indentlog/print/quoted.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/pad.hpp" +#include "xo/indentlog/print/quoted.hpp" #include #include #include diff --git a/include/xo/ordinaltree/bplustree/BplusTreeUtil.hpp b/include/xo/ordinaltree/bplustree/BplusTreeUtil.hpp index f2da0263..61c91e95 100644 --- a/include/xo/ordinaltree/bplustree/BplusTreeUtil.hpp +++ b/include/xo/ordinaltree/bplustree/BplusTreeUtil.hpp @@ -4,8 +4,8 @@ #include "IteratorUtil.hpp" #include "bplustree_tags.hpp" -#include "indentlog/scope.hpp" -#include "indentlog/print/tag.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/tag.hpp" #include // for std::unique_ptr #include diff --git a/include/xo/ordinaltree/bplustree/InternalNode.hpp b/include/xo/ordinaltree/bplustree/InternalNode.hpp index d4dce90a..9ef4b7dd 100644 --- a/include/xo/ordinaltree/bplustree/InternalNode.hpp +++ b/include/xo/ordinaltree/bplustree/InternalNode.hpp @@ -3,8 +3,8 @@ #pragma once #include "GenericNode.hpp" -#include "indentlog/scope.hpp" -#include "indentlog/print/tostr.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/tostr.hpp" #include namespace xo { diff --git a/include/xo/ordinaltree/bplustree/Iterator.hpp b/include/xo/ordinaltree/bplustree/Iterator.hpp index 90f57924..ed82f8d8 100644 --- a/include/xo/ordinaltree/bplustree/Iterator.hpp +++ b/include/xo/ordinaltree/bplustree/Iterator.hpp @@ -4,7 +4,7 @@ #include "IteratorUtil.hpp" #include "LeafNode.hpp" -#include "indentlog/print/tostr.hpp" +#include "xo/indentlog/print/tostr.hpp" namespace xo { namespace tree { diff --git a/include/xo/ordinaltree/bplustree/LeafNode.hpp b/include/xo/ordinaltree/bplustree/LeafNode.hpp index 419bd63f..bcb43469 100644 --- a/include/xo/ordinaltree/bplustree/LeafNode.hpp +++ b/include/xo/ordinaltree/bplustree/LeafNode.hpp @@ -3,7 +3,7 @@ #pragma once #include "GenericNode.hpp" -#include "indentlog/scope.hpp" +#include "xo/indentlog/scope.hpp" #include namespace xo { diff --git a/utest/bplustree.cpp b/utest/bplustree.cpp index 2fe68d83..a38fd545 100644 --- a/utest/bplustree.cpp +++ b/utest/bplustree.cpp @@ -6,7 +6,7 @@ #include "xo/ordinaltree/BplusTree.hpp" #include "xo/randomgen/random_seed.hpp" #include "xo/randomgen/print.hpp" -#include "indentlog/scope.hpp" +#include "xo/indentlog/scope.hpp" #include "catch2/catch.hpp" namespace { diff --git a/utest/random_tree_ops.hpp b/utest/random_tree_ops.hpp index 890202f7..0c6d898f 100644 --- a/utest/random_tree_ops.hpp +++ b/utest/random_tree_ops.hpp @@ -1,9 +1,9 @@ /* @file random_tree_ops.hpp **/ #include "xo/randomgen/xoshiro256.hpp" -#include "indentlog/scope.hpp" -#include "indentlog/print/tag.hpp" -#include "indentlog/print/vector.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/tag.hpp" +#include "xo/indentlog/print/vector.hpp" #include "catch2/catch.hpp" #include #include From b3d4a1276efcfa9b555161b38dc7db44747e05f6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 19:27:24 -0400 Subject: [PATCH 0276/2693] build: fix include path to insert xo/ dir --- include/xo/randomgen/print.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/randomgen/print.hpp b/include/xo/randomgen/print.hpp index e9860ab0..d5b0a992 100644 --- a/include/xo/randomgen/print.hpp +++ b/include/xo/randomgen/print.hpp @@ -2,6 +2,6 @@ #pragma once -#include "indentlog/print/array.hpp" +#include "xo/indentlog/print/array.hpp" /* end print.hpp */ From e6e659bc0563ed08726216ac13929aa164488d67 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 20:14:06 -0400 Subject: [PATCH 0277/2693] + xo_pybind11_link_flags() + xo_pybind11_library() --- cmake/xo_cxx.cmake | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index da93eccb..37867d59 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -263,4 +263,46 @@ macro(xo_self_dependency target dep) endmacro() # ---------------------------------------------------------------- +# need this when linking pybind11-generated libraries # +macro(xo_pybind11_link_flags) + # see: + # 1. FAQ Build Issues Q2 + # 2. CMAKE_SHARED_LINKER_FLAGS in src/CMakeLists.txt + # 3. pybind11 cmake support, somewhere like + # [path/to/pybind11-2.9.2/ + # lib/python3.9-pybind11-2.9.2/ + # lib/python3.9/site-packages/ + # pybind11/share/cmake/ + # pybind11/pybind11Common.cmake] + # + set_property( + TARGET pybind11::python_link_helper + APPEND + PROPERTY INTERFACE_LINK_OPTIONS "$<$:LINKER:-flat_namespace>" + ) +endmacro() + +# ---------------------------------------------------------------- +# use this for a subdir that builds a python library using pybind11 +# +# expecting the following +# 1. a directory pyfoo/ -> library pyfoo +# 2. pyfoo/pyfoo.hpp.in -> pyfoo/pyfoo.hpp +# +macro(xo_pybind11_library target source_files) + configure_file(${target}.hpp.in ${target}.hpp) + + # find_package(Python..) finds python in + # /Library/Frameworks/Python.framework/... + # but we want to use python from nix + # + #find_package(Python COMPONENTS Interpreter Development REQUIRED) + # + + find_package(pybind11) + pybind11_add_module(${target} MODULE ${source_files}) + xo_pybind11_link_flags() + # use xo_install_library2() instead + #install(TARGETS ${target} DESTINATION lib) +endmacro() From 515f632cb59b459d53abc06972f9c0c7ebf67318 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 23:10:40 -0400 Subject: [PATCH 0278/2693] initial commit (kitbashed from kalman project) --- CMakeLists.txt | 50 +++++++++++++++++++++++++++++ README.md | 26 ++++++++++++++++ cmake/xo_pyreflectConfig.cmake.in | 4 +++ src/pyreflect/CMakeLists.txt | 11 +++++++ src/pyreflect/pyreflect.cpp | 52 +++++++++++++++++++++++++++++++ src/pyreflect/pyreflect.hpp.in | 25 +++++++++++++++ 6 files changed, 168 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/xo_pyreflectConfig.cmake.in create mode 100644 src/pyreflect/CMakeLists.txt create mode 100644 src/pyreflect/pyreflect.cpp create mode 100644 src/pyreflect/pyreflect.hpp.in diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..9c372fd5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,50 @@ +# xo-pyreflect/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_pyreflect VERSION 0.1) +enable_language(CXX) + +# common XO cmake macros (see github.com:Rconybea/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() +# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) +add_code_coverage() +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) + +# ---------------------------------------------------------------- +# c++ settings (usually temporary) + +set(PROJECT_CXX_FLAGS "") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- +# sources + +# note: library=pyreflect (not xo_pyreflect) -> establishes pyreflectTargets +add_subdirectory(src/pyreflect) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} pyreflectTargets) + +# ---------------------------------------------------------------- +# install .hpp files + +#xo_install_include_tree() diff --git a/README.md b/README.md new file mode 100644 index 00000000..c020ad6e --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# python bindings for c++ reflection library (xo-reflect) + +### build + install +``` +$ cd xo-pyreflect +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer, e.g. ~/local +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` + +### build for unit test coverage +``` +$ cd xo-pyreflect +$ mkdir build-ccov +$ cd build-ccov +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + +### LSP support +``` +$ cd xo-pyreflect +$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree +``` diff --git a/cmake/xo_pyreflectConfig.cmake.in b/cmake/xo_pyreflectConfig.cmake.in new file mode 100644 index 00000000..9c15f36a --- /dev/null +++ b/cmake/xo_pyreflectConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/src/pyreflect/CMakeLists.txt b/src/pyreflect/CMakeLists.txt new file mode 100644 index 00000000..9b63978f --- /dev/null +++ b/src/pyreflect/CMakeLists.txt @@ -0,0 +1,11 @@ +# xo_pyreflect/CMakeLists.txt + +set(SELF_LIB pyreflect) +set(SELF_SRCS pyreflect.cpp) + +# ---------------------------------------------------------------- +# pybind11 dep + +xo_pybind11_library(${SELF_LIB} ${SELF_SRCS}) + +xo_dependency(${SELF_LIB} reflect) diff --git a/src/pyreflect/pyreflect.cpp b/src/pyreflect/pyreflect.cpp new file mode 100644 index 00000000..3a9f81bb --- /dev/null +++ b/src/pyreflect/pyreflect.cpp @@ -0,0 +1,52 @@ +/* @file pyreflect.cpp */ + +// note: need pyreflect/ here bc pyreflect.hpp is generated, located in build directory +#include "src/pyreflect/pyreflect.hpp" +#include "xo/reflect/TypeDescr.hpp" +#include "xo/reflect/TaggedRcptr.hpp" +#include "xo/reflect/SelfTagging.hpp" +//#include "time/Time.hpp" +//#include "xo/pyutil/pytime.hpp" +#include "xo/pyutil/pyutil.hpp" +//#include +//#include +//#include +//#include + +namespace xo { + using xo::time::utc_nanos; + using xo::ref::unowned_ptr; + using xo::ref::rp; + namespace py = pybind11; + + namespace reflect { + PYBIND11_MODULE(PYREFLECT_MODULE_NAME(), m) { + + /* note: possibly move this to pytime/ if/when we provide it */ + //py::class_(m, "utc_nanos"); + + //py::class_(m, "TypeDescr"); + py::class_>(m, "TypeDescr") + .def_static("print_reflected_types", + [](){ TypeDescrBase::print_reflected_types(std::cout); }) + .def_property_readonly("canonical_name", &TypeDescrBase::canonical_name) + .def("__repr__", &TypeDescrBase::display_string); + + /* note: this means python will use + * std::unique_ptr + * when it encounters a TaggedRcptr instance. + * Maintains refcount at cost of 2nd level of indirection. + */ + py::class_(m, "TaggedRcptr") + .def_property_readonly("td", &TaggedPtr::td) + .def("__repr__", &TaggedRcptr::display_string); + + py::class_>(m, "SelfTagging") + .def("self_tp", &SelfTagging::self_tp); + + } /*pyreflect*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end pyreflect.cpp */ diff --git a/src/pyreflect/pyreflect.hpp.in b/src/pyreflect/pyreflect.hpp.in new file mode 100644 index 00000000..62b64a25 --- /dev/null +++ b/src/pyreflect/pyreflect.hpp.in @@ -0,0 +1,25 @@ +/* @file pyreflect.hpp + * + * automatically generated from src/xo_pyreflect/pyreflect.hpp.in + * see src/xo_pyreflect/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(PYREFLECT_MODULE_NAME(), m) { ... } + */ +#define PYREFLECT_MODULE_NAME() @SELF_LIB@ + +/* example: + * py::module_::import(PYREFLECT_MODULE_NAME_STR) + */ +#define PYREFLECT_MODULE_NAME_STR "@SELF_LIB@" + +/* example: + * PYREFLECT_IMPORT_MODULE() + * replaces + * py::module_::import("pyreflect") + */ +#define PYREFLECT_IMPORT_MODULE() py::module_::import("@SELF_LIB@") + +/* end pyreflect.hpp */ From c12a3ec726d04a311ec39cd84bc9fd4210438f5b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 23:13:27 -0400 Subject: [PATCH 0279/2693] + .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..6abfb0de --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# lsp keeps state here +.cache +# typical build directory +build From a467f151bcaa9e6e3a1b6de1b2bbac279d9b19a6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 23:41:16 -0400 Subject: [PATCH 0280/2693] .gitignore: + compile_commands.json --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 6abfb0de..f52f1311 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .cache # typical build directory build +# lsp: symlink to file in build directory (established manually) +compile_commands.json From 415c81b1814140b0cf1774a846f16f6f2cf59ed3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 23:41:26 -0400 Subject: [PATCH 0281/2693] README: formatting + example --- README.md | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c020ad6e..b0500c48 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,52 @@ # python bindings for c++ reflection library (xo-reflect) -### build + install +## build + install ``` $ cd xo-pyreflect $ mkdir build $ cd build $ INSTALL_PREFIX=/usr/local # or wherever you prefer, e.g. ~/local -$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. $ make $ make install ``` -### build for unit test coverage +## build for unit test coverage ``` $ cd xo-pyreflect $ mkdir build-ccov $ cd build-ccov -$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCODE_COVERAGE=ON \ + -DCMAKE_BUILD_TYPE=Debug .. ``` -### LSP support +## LSP (language server) support + +LSP looks for compile commands in the root of the source tree; +while Cmake creates them in the root of its build directory. + ``` $ cd xo-pyreflect -$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree +$ ln -s build/compile_commands.json # supply compile commands to LSP ``` + +## Examples + +Assumes `xo-pyreflect` installed to `~/local2/lib` + +``` +PYTHONPATH=~/local2/lib python +>>> import pyreflect +>>> dir(pyreflect) +['SelfTagging', 'TaggedRcptr', 'TypeDescr', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__'] +>>> pyreflect.TypeDescr.print_reflected_types() + +``` + +Not /immediately/ interesting: no reflected types in `pyreflect` itself From cd6bd92e3f6393fdb9512d9af4a370e58cabe507 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 23:52:45 -0400 Subject: [PATCH 0282/2693] xo-cmake: tweak exported header dirs --- cmake/xo_cxx.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 37867d59..9b14025f 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -60,11 +60,11 @@ macro(xo_include_headeronly_options2 target) # target_include_directories( ${target} INTERFACE - $ # e.g. for #include "indentlog/scope.hpp" $ + $ + $ # e.g. for #include "indentlog/scope.hpp" $ # e.g. for #include "Refcounted.hpp" in refcnt/src when ${target}=refcnt [DEPRECATED] $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect -# $ $ # e.g. for generated .hpp files ) From bfc7828b0ab53fe8342992980699f5cff435b903 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 23:53:04 -0400 Subject: [PATCH 0283/2693] xo-cmake: bugfix: PUBLIC not INTERFACE in xo_dependency_headeronly() --- cmake/xo_cxx.cmake | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 9b14025f..5c4c3d6a 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -234,7 +234,12 @@ endmacro() # macro(xo_dependency_headeronly target dep) find_package(${dep} CONFIG REQUIRED) - target_link_libraries(${target} INTERFACE ${dep}) + # PUBLIC here is important -- it's needed so that include directories that are required by ${dep}, + # will be included in compilation of ${target}. + # + # INTERFACE doesn't make this happen; for a header-only library, it should be supplied to the add_library() macro + # + target_link_libraries(${target} PUBLIC ${dep}) endmacro() # dependency on namespaced target From 8247f9b56f77ee4519d54dab6934d2195d8d2b00 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 23:53:28 -0400 Subject: [PATCH 0284/2693] pybind11: hack to set library directory (won't work with nix) --- cmake/xo_cxx.cmake | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 5c4c3d6a..3ee07ddb 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -284,8 +284,24 @@ macro(xo_pybind11_link_flags) set_property( TARGET pybind11::python_link_helper APPEND - PROPERTY INTERFACE_LINK_OPTIONS "$<$:LINKER:-flat_namespace>" - ) + PROPERTY + INTERFACE_LINK_OPTIONS "$<$:LINKER:-flat_namespace>") + + # looks like pybind11_add_module() tries to link transitive deps + # of libs mentioned in xo_dependency() -- perhaps for link-time optimization? + # + # For example when linking libpyreflect, get link line with -lrefcnt + # (presumably since cmake knows that libreflect.so depends on librefcnt.so); + # this triggers error, since link doesn't know where to find librefcnt.so + # + # To workaround, add ${CMAKE_INSTALL_PREFIX}/lib to the link line. + # + # WARNING: expect this not to work in a hermetic nix build! + # + set_property(TARGET pybind11::python_link_helper + APPEND + PROPERTY + INTERFACE_LINK_OPTIONS "-L${CMAKE_INSTALL_PREFIX}/lib") endmacro() # ---------------------------------------------------------------- From cb74a35334240b31d48afd29cf85afdb4191668b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 23:53:56 -0400 Subject: [PATCH 0285/2693] xo-cmake: pybind11 install tweaks --- cmake/xo_cxx.cmake | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 3ee07ddb..1c323823 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -322,8 +322,13 @@ macro(xo_pybind11_library target source_files) # find_package(pybind11) + + # this only works if one source file, right? pybind11_add_module(${target} MODULE ${source_files}) + xo_pybind11_link_flags() + xo_include_options2(${target}) # use xo_install_library2() instead #install(TARGETS ${target} DESTINATION lib) + xo_install_library2(${target}) endmacro() From 7d1e2f39029b400cafa91a1eda62ce9525eb877a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 23:54:42 -0400 Subject: [PATCH 0286/2693] drop cmake-config-mediated indentlog dep (will see if need later) --- cmake/reflectConfig.cmake.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmake/reflectConfig.cmake.in b/cmake/reflectConfig.cmake.in index 9c15f36a..e98de9c1 100644 --- a/cmake/reflectConfig.cmake.in +++ b/cmake/reflectConfig.cmake.in @@ -1,4 +1,6 @@ @PACKAGE_INIT@ +#include(CMakeFindDependencyMacro) +#find_dependency(indentlog) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") From 0c73b5417b908697629be986ba45513c1e832646 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 23:55:13 -0400 Subject: [PATCH 0287/2693] reflect: tidy indentlog dep --- src/reflect/CMakeLists.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index 6056ebca..f8d84446 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -6,10 +6,11 @@ set(SELF_SOURCE_FILES TypeDescr.cpp TypeDescrExtra.cpp TaggedRcptr.cpp atomic/At xo_add_shared_library(${SELF_LIBRARY_NAME} ${PROJECT_VERSION} 1 ${SELF_SOURCE_FILES}) # ---------------------------------------------------------------- -# dependencies: logutil, ... +# dependencies: indentlog, ... -xo_internal_dependency(${SELF_LIBRARY_NAME} indentlog) -xo_internal_dependency(${SELF_LIBRARY_NAME} refcnt) +# note: changes here must coordinate with cmake/reflectConfig.cmake.in +xo_dependency_headeronly(${SELF_LIBRARY_NAME} indentlog) +xo_dependency(${SELF_LIBRARY_NAME} refcnt) # ---------------------------------------------------------------- # 3rd party dependency: boost: From 5f22bca0dc92a1abe3754eadaaa47f50afe2e9e4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 23:56:16 -0400 Subject: [PATCH 0288/2693] refcnt: build tweaks --- src/CMakeLists.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f8b65942..8a05a143 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,5 @@ -set(SELF_LIBRARY_NAME refcnt) -set(SELF_SOURCE_FILES Refcounted.cpp Displayable.cpp) - -xo_add_shared_library(${SELF_LIBRARY_NAME} ${PROJECT_VERSION} 1 ${SELF_SOURCE_FILES}) -xo_internal_dependency(${SELF_LIBRARY_NAME} indentlog) +set(SELF_LIB refcnt) +set(SELF_SRCS Refcounted.cpp Displayable.cpp) +xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_dependency_headeronly(${SELF_LIB} indentlog) From f25389cda2da75ba225d1a663d30dea9660af31b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 7 Oct 2023 00:02:05 -0400 Subject: [PATCH 0289/2693] initial commit --- CMakeLists.txt | 67 ++++++++++++++++++++++++++++++++++ README.md | 13 +++++++ cmake/xo_pyutilConfig.cmake.in | 4 ++ include/xo/pyutil/pyutil.hpp | 30 +++++++++++++++ 4 files changed, 114 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/xo_pyutilConfig.cmake.in create mode 100644 include/xo/pyutil/pyutil.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..ea3f4237 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,67 @@ +# pyutil/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_pyutil VERSION 0.1) +enable_language(CXX) + +# common XO cmake macros (see github:Rconybea/xo-cmake) +include(xo_macros/xo_cxx) +#include(xo_macros/code-coverage) # very little to unit test here + +# ---------------------------------------------------------------- +# unit test setup + +#enable_testing() +## enable code coverage for all executables+libraries +## (when configured with -DCODE_COVERAGE=ON) +## +#add_code_coverage() +#add_code_coverage_all_targets( +# EXCLUDE +# /nix/store/* +# ${PROJECT_SOURCE_DIR}/utest/*) + +# ---------------------------------------------------------------- +# bespoke (usually temporary) c++ settings + +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- +# c++ settings + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- +# external dependencies +# +# set CMAKE_INSTALL_PREFIX to analog of /usr +# to use .cmake assistants from /usr/lib/cmake/indentlog +# +# xo_dependency(..) + +# ---------------------------------------------------------------- + +#add_subdirectory(example) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# output targets + +set(SELF_LIB xo_pyutil) +add_library(${SELF_LIB} INTERFACE) +xo_include_headeronly_options2(${SELF_LIB}) + +# ---------------------------------------------------------------- +# standard install + provide find_package() support + +xo_install_library2(${SELF_LIB}) +xo_install_include_tree() +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# install any additional components + +#install(TARGETS ex1 DESTINATION bin/${PROJECT_NAME}/example) diff --git a/README.md b/README.md new file mode 100644 index 00000000..d9bbae39 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# pybind11 utilities for XO projects + +# to build + install locally + +``` +$ cd xo-pyutil +$ mkdir build +$ cd build +$ PREFIX=/usr/local # for example +$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=$(PREFIX) -DCMAKE_INSTALL_PREFIX=${PREFIX} .. +$ make +$ make install +``` diff --git a/cmake/xo_pyutilConfig.cmake.in b/cmake/xo_pyutilConfig.cmake.in new file mode 100644 index 00000000..9c15f36a --- /dev/null +++ b/cmake/xo_pyutilConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/pyutil/pyutil.hpp b/include/xo/pyutil/pyutil.hpp new file mode 100644 index 00000000..a450f5d2 --- /dev/null +++ b/include/xo/pyutil/pyutil.hpp @@ -0,0 +1,30 @@ +/* @file pyutil.hpp + * + * utility stuff to be used across multiple pybind11 .cpp files + */ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" +#include "xo/refcnt/Unowned.hpp" +#include + +/* xo::ref::intrusive_ptr is an intrusively-reference-counted pointer. + * always safe to create one from a T* p + * (since refcount is directly accessible from p) + * + * Need declaration like this before any pybind11 bindings + * that expose an object of types like + * (a) intrusive_ptr or + * (b) T * / T const * / T & / T const & to python. + * If this were not done, pybind11 would by default use unique_ptr> + * (ok but inefficient) or unique_ptr (fatal!) + */ +PYBIND11_DECLARE_HOLDER_TYPE(T, xo::ref::intrusive_ptr, true); + +/* xo::ref::unowned_ptr is an unmanaged pointer. + * use this for immortal objects that pybind11 must not delete. + */ +PYBIND11_DECLARE_HOLDER_TYPE(T, xo::ref::unowned_ptr, true); + +/* end pyutil.hpp */ From 863f87db7df9802516ff550f3d5c3d29c15dfd16 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 7 Oct 2023 00:16:07 -0400 Subject: [PATCH 0290/2693] xo-cmake: unwind mistake, revert to INTERFACE for headeronly dep --- cmake/xo_cxx.cmake | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 1c323823..150c665f 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -234,12 +234,17 @@ endmacro() # macro(xo_dependency_headeronly target dep) find_package(${dep} CONFIG REQUIRED) - # PUBLIC here is important -- it's needed so that include directories that are required by ${dep}, - # will be included in compilation of ${target}. + # Conflict here between PUBLIC and INTERFACE # - # INTERFACE doesn't make this happen; for a header-only library, it should be supplied to the add_library() macro + # PUBLIC ensures that include directories required by ${dep} will also be included in compilation of ${target}; + # we generally want this # - target_link_libraries(${target} PUBLIC ${dep}) + # INTERFACE mandatory when depending on a header-only library (created with add_library(foo INTERFACE)). + # otherwise get error: + # INTERFACE library can only be used with the INTERFACE keyword of + # target_link_libraries + # + target_link_libraries(${target} INTERFACE ${dep}) endmacro() # dependency on namespaced target From 0bc65c87271d91a83781599e20283355ff005b37 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 7 Oct 2023 00:31:35 -0400 Subject: [PATCH 0291/2693] experiment: try attaching dirs with PUBLIC keyword --- cmake/xo_cxx.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 150c665f..740e0833 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -59,7 +59,7 @@ macro(xo_include_headeronly_options2 target) # compiler's include path # target_include_directories( - ${target} INTERFACE + ${target} PUBLIC $ $ $ # e.g. for #include "indentlog/scope.hpp" From 7e80de41aa4f766fd3e2b0f6694f6fb1d9740f80 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 7 Oct 2023 00:34:56 -0400 Subject: [PATCH 0292/2693] revert failed experiment --- cmake/xo_cxx.cmake | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 740e0833..e82d21d1 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -58,8 +58,11 @@ macro(xo_include_headeronly_options2 target) # need that build directory to also appear in # compiler's include path # + # NOTE: using INTERFACE here is mandatory. Otherwise get error: + # target_include_directories may only set INTERFACE properties on INTERFACE targets + # target_include_directories( - ${target} PUBLIC + ${target} INTERFACE $ $ $ # e.g. for #include "indentlog/scope.hpp" From 5ad75dec28c6b6bedbbd9ddb9be953a3688271a4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 7 Oct 2023 00:45:02 -0400 Subject: [PATCH 0293/2693] bugfix: workaround apparent cmake bug --- cmake/xo_cxx.cmake | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index e82d21d1..7be27f62 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -240,14 +240,23 @@ macro(xo_dependency_headeronly target dep) # Conflict here between PUBLIC and INTERFACE # # PUBLIC ensures that include directories required by ${dep} will also be included in compilation of ${target}; - # we generally want this + # i.e. will appear in property ${target}.INCLUDE_DIRECTORIES # # INTERFACE mandatory when depending on a header-only library (created with add_library(foo INTERFACE)). # otherwise get error: # INTERFACE library can only be used with the INTERFACE keyword of # target_link_libraries + # Unfortunately target_link_libraries() does not copy dependent's INTERFACE_INCLUDE_DIRECTORIES property + # (at least asof cmake 3.25.3). Dependent's INCLUDE_DIRECTORIES property will be empty, since it's header-only. + # + # Workaround by copying property explicity, which we do below # target_link_libraries(${target} INTERFACE ${dep}) + + get_target_property(xo_dependency_headeronly__tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES) + set_property( + TARGET ${target} + APPEND PROPERTY INCLUDE_DIRECTORIES ${xo_dependency_headeronly__tmp}) endmacro() # dependency on namespaced target From ac5534d8e2717e4241b9e66ba4ef42dcf373ce15 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 7 Oct 2023 16:03:39 -0400 Subject: [PATCH 0294/2693] build: use new xo_pybind11_dependency() --- README.md | 2 +- src/pyreflect/CMakeLists.txt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b0500c48..434a80a5 100644 --- a/README.md +++ b/README.md @@ -49,4 +49,4 @@ PYTHONPATH=~/local2/lib python ``` -Not /immediately/ interesting: no reflected types in `pyreflect` itself +Not _immediately_ interesting: no reflected types in `pyreflect` itself diff --git a/src/pyreflect/CMakeLists.txt b/src/pyreflect/CMakeLists.txt index 9b63978f..0579d761 100644 --- a/src/pyreflect/CMakeLists.txt +++ b/src/pyreflect/CMakeLists.txt @@ -8,4 +8,5 @@ set(SELF_SRCS pyreflect.cpp) xo_pybind11_library(${SELF_LIB} ${SELF_SRCS}) -xo_dependency(${SELF_LIB} reflect) +xo_pybind11_dependency(${SELF_LIB} reflect) + From 2ccb974fbfec85b28e4efe423db77b9bd935170f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 7 Oct 2023 16:07:06 -0400 Subject: [PATCH 0295/2693] xo-cmake: + xo_pybind11_dependency() --- cmake/xo_cxx.cmake | 53 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 7be27f62..09a2fb22 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -341,11 +341,60 @@ macro(xo_pybind11_library target source_files) find_package(pybind11) # this only works if one source file, right? + # + # 6oct2023 + # Having trouble at link time with this. + # Getting broken link for nix, because link line lists short library nicknames + # (e.g. -lfoo) for transitive deps. nix link needs to be given the directory + # in which libfoo.so resides, so we need to ensure full path + # + # - source files: + # -fPIC -fvisibility=hidden -flto -fno-fat-lto-objects + # - library: + # -fPIC -flto + # (transitive closure of library deps for lto?) + # pybind11_add_module(${target} MODULE ${source_files}) xo_pybind11_link_flags() xo_include_options2(${target}) - # use xo_install_library2() instead - #install(TARGETS ${target} DESTINATION lib) xo_install_library2(${target}) endmacro() + +# ---------------------------------------------------------------- +# use this for a dependency of a pybind11 library, +# e.g. that was introduced by xo_pybind11_library() +# +# Working around the following problem (cmake 3.25.3, pybind11 2.10.4).N +# if: +# 1. we have pybind11 library pyfoo, depending on c++ native library foo. +# 2. foo depends on other libraries foodep1, foodep2; +# assume also that foodep2 is header-only +# +# if we write: +# # CMakeLists.txt +# pybind11_add_module(pyfoo MODULE pyfoo.cpp) +# find_package(foo CONFIG_REQUIRED) +# target_link_libraries(pyfoo PUBLIC foo) +# +# get compile instructions like: +# g++ -o pyfoo.cpython-311-x86_64-linux-gnu.so path/to/pyfoo.cpp.o path/to/libfoo.so.x.y -lfoodep1 -lfoodep2 +# +# 1. This is broken for foodep2, since there no libfoodep2.so exists +# 2. Also broken for nix build, because directory containing libfoodep1.so doesn't appear on the compile line. +# (It's likely possible to extract this from the .cmake package in lib/cmake/foo/fooTargets.cmake, +# but I don't know how to do that yet) +# +# workaround here is to suppress these secondary dependencies. +# This assumes: +# 1. secondary dependencies are all in shared libraries (not needed on link line) +# 2. (maybe?) primary dependency libfoo.so is sufficient to satisfy g++ +# -- conceivably true if libfoo.so has RUNPATH etc. +# +macro(xo_pybind11_dependency target dep) + find_package(${dep} CONFIG REQUIRED) + # clobber secondary dependencies, as discussed above + set_property(TARGET ${dep} PROPERTY INTERFACE_LINK_LIBRARIES "") + # now that secondary deps are gone, attach to target pybind11 library + xo_dependency(${target} ${dep}) +endmacro() From 46ff8f0b41d45d92a49d3f69abb5dba8cc27c008 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 8 Oct 2023 13:50:08 -0400 Subject: [PATCH 0296/2693] cmake: + xo_hreaderonly_dependency() to fix confusion --- cmake/xo_cxx.cmake | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 09a2fb22..f0e2e29b 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -229,13 +229,9 @@ macro(xo_dependency target dep) target_link_libraries(${target} PUBLIC ${dep}) endmacro() -macro(xo_internal_dependency target dep) - xo_dependency(${target} ${dep}) -endmacro() - -# dependency on a header-only library +# dependency of a header-only library on another header-only library # -macro(xo_dependency_headeronly target dep) +macro(xo_headeronly_dependency target dep) find_package(${dep} CONFIG REQUIRED) # Conflict here between PUBLIC and INTERFACE # @@ -253,10 +249,10 @@ macro(xo_dependency_headeronly target dep) # target_link_libraries(${target} INTERFACE ${dep}) - get_target_property(xo_dependency_headeronly__tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES) - set_property( - TARGET ${target} - APPEND PROPERTY INCLUDE_DIRECTORIES ${xo_dependency_headeronly__tmp}) +# get_target_property(xo_dependency_headeronly__tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES) +# set_property( +# TARGET ${target} +# APPEND PROPERTY INCLUDE_DIRECTORIES ${xo_dependency_headeronly__tmp}) endmacro() # dependency on namespaced target @@ -396,5 +392,6 @@ macro(xo_pybind11_dependency target dep) # clobber secondary dependencies, as discussed above set_property(TARGET ${dep} PROPERTY INTERFACE_LINK_LIBRARIES "") # now that secondary deps are gone, attach to target pybind11 library - xo_dependency(${target} ${dep}) + # skip xo_dependency() here, that would repeat the find_package() expansion + target_link_libraries(${target} PUBLIC ${dep}) endmacro() From 50cf27b6a21b254f1fbdc92bf34537c0aa5d119b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 8 Oct 2023 13:51:14 -0400 Subject: [PATCH 0297/2693] bugfix: xo/ in include path --- include/xo/subsys/Subsystem.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/subsys/Subsystem.hpp b/include/xo/subsys/Subsystem.hpp index b9a41e18..ece72db6 100644 --- a/include/xo/subsys/Subsystem.hpp +++ b/include/xo/subsys/Subsystem.hpp @@ -5,7 +5,7 @@ #pragma once -#include "indentlog/scope.hpp" +#include "xo/indentlog/scope.hpp" #include #include #include From 5019c9402474883a71f77740df6fc979d057c397 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 8 Oct 2023 13:51:39 -0400 Subject: [PATCH 0298/2693] build nit: xo_internal_dependency() -> xo_dependency() --- CMakeLists.txt | 2 +- example/ex2/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 87383904..c5413d7a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,7 @@ xo_toplevel_compile_options() # set CMAKE_INSTALL_PREFIX to analog of /usr # to use .cmake assistants from /usr/lib/cmake/indentlog # -# xo_internal_dependency(..) +# xo_dependency(..) # ---------------------------------------------------------------- diff --git a/example/ex2/CMakeLists.txt b/example/ex2/CMakeLists.txt index ee6687f2..78016d45 100644 --- a/example/ex2/CMakeLists.txt +++ b/example/ex2/CMakeLists.txt @@ -1,3 +1,3 @@ add_executable(ex2 ex2.cpp) xo_include_options2(ex2) -xo_internal_dependency(ex2 indentlog) +xo_dependency(ex2 indentlog) From f05068a45b2e15be7b438b44921caf47fe04873d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 8 Oct 2023 13:52:02 -0400 Subject: [PATCH 0299/2693] bugfix: xo/ in include path --- include/xo/randomgen/print.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/randomgen/print.hpp b/include/xo/randomgen/print.hpp index e9860ab0..d5b0a992 100644 --- a/include/xo/randomgen/print.hpp +++ b/include/xo/randomgen/print.hpp @@ -2,6 +2,6 @@ #pragma once -#include "indentlog/print/array.hpp" +#include "xo/indentlog/print/array.hpp" /* end print.hpp */ From 19fe68dc0e873b0e6a5eb8e757fcca2b5d0becfe Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 8 Oct 2023 13:54:55 -0400 Subject: [PATCH 0300/2693] + .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..49f711e2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# typical build directories +build +ccov From c494a44f990310bfe6034791c12cc9c5edef8cfe Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 8 Oct 2023 13:55:45 -0400 Subject: [PATCH 0301/2693] build: xo_dependency_headeronly() -> xo_dependency() --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8a05a143..9d35b21d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,4 +2,4 @@ set(SELF_LIB refcnt) set(SELF_SRCS Refcounted.cpp Displayable.cpp) xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) -xo_dependency_headeronly(${SELF_LIB} indentlog) +xo_dependency(${SELF_LIB} indentlog) From 58b7f567e9a5bf772c317171bfd4b73972841ceb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 8 Oct 2023 13:56:20 -0400 Subject: [PATCH 0302/2693] build: xo_dependency_headeronly() -> xo_dependency() --- src/reflect/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index f8d84446..e22a2c4c 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -9,7 +9,7 @@ xo_add_shared_library(${SELF_LIBRARY_NAME} ${PROJECT_VERSION} 1 ${SELF_SOURCE_FI # dependencies: indentlog, ... # note: changes here must coordinate with cmake/reflectConfig.cmake.in -xo_dependency_headeronly(${SELF_LIBRARY_NAME} indentlog) +xo_dependency(${SELF_LIBRARY_NAME} indentlog) xo_dependency(${SELF_LIBRARY_NAME} refcnt) # ---------------------------------------------------------------- From 5da396c8c3f85811f1c7f078a3c1f20beb72e334 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 8 Oct 2023 14:05:42 -0400 Subject: [PATCH 0303/2693] build: xo_dependency_headeronly() -> xo_headeronly_dependency() --- CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cde16d98..94bd08fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,4 +48,6 @@ xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets # ---------------------------------------------------------------- # input dependencies -xo_dependency_headeronly(${SELF_LIB} randomgen) +# xo-ordinaltree is also header-only +# +xo_headeronly_dependency(${SELF_LIB} randomgen) From e1bbb4261c21a2fcf4bc9c70dc76d8538cec7e61 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 8 Oct 2023 14:10:38 -0400 Subject: [PATCH 0304/2693] buld: simplify: just need xo_dependency() --- utest/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index ea866722..26b370b4 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -13,9 +13,9 @@ target_code_coverage(${SELF_EXE} AUTO ALL) # ---------------------------------------------------------------- # internal dependencies: refcnt, ... -xo_internal_dependency(${SELF_EXE} refcnt) -xo_internal_dependency(${SELF_EXE} indentlog) -xo_dependency_headeronly(${SELF_EXE} randomgen) +xo_dependency(${SELF_EXE} refcnt) +xo_dependency(${SELF_EXE} indentlog) +xo_dependency(${SELF_EXE} randomgen) # ---------------------------------------------------------------- # 3rd part dependency: catch2: From 4daef0555000f41b8485a765ac85adfe216c9714 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 8 Oct 2023 14:21:40 -0400 Subject: [PATCH 0305/2693] build: + testing (even though no tests) for consistency --- CMakeLists.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ea3f4237..c7e32668 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,20 +7,20 @@ enable_language(CXX) # common XO cmake macros (see github:Rconybea/xo-cmake) include(xo_macros/xo_cxx) -#include(xo_macros/code-coverage) # very little to unit test here +include(xo_macros/code-coverage) # very little to unit test here # ---------------------------------------------------------------- # unit test setup -#enable_testing() +enable_testing() ## enable code coverage for all executables+libraries ## (when configured with -DCODE_COVERAGE=ON) ## -#add_code_coverage() -#add_code_coverage_all_targets( -# EXCLUDE -# /nix/store/* -# ${PROJECT_SOURCE_DIR}/utest/*) +add_code_coverage() +add_code_coverage_all_targets( + EXCLUDE + /nix/store/* + ${PROJECT_SOURCE_DIR}/utest/*) # ---------------------------------------------------------------- # bespoke (usually temporary) c++ settings From effbbf22d9f4238147274befbe1116a288cc535e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 13:49:08 -0400 Subject: [PATCH 0306/2693] initial implementation --- CMakeLists.txt | 52 ++++ README.md | 26 ++ cmake/printjsonConfig.cmake.in | 6 + include/xo/printjson/JsonPrinter.hpp | 99 ++++++ include/xo/printjson/PrintJson.hpp | 143 +++++++++ include/xo/printjson/init_printjson.hpp | 29 ++ src/printjson/CMakeLists.txt | 13 + src/printjson/PrintJson.cpp | 398 ++++++++++++++++++++++++ src/printjson/init_printjson.cpp | 32 ++ 9 files changed, 798 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/printjsonConfig.cmake.in create mode 100644 include/xo/printjson/JsonPrinter.hpp create mode 100644 include/xo/printjson/PrintJson.hpp create mode 100644 include/xo/printjson/init_printjson.hpp create mode 100644 src/printjson/CMakeLists.txt create mode 100644 src/printjson/PrintJson.cpp create mode 100644 src/printjson/init_printjson.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..028f4c5e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,52 @@ +# printjson/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(printjson VERSION 0.1) +enable_language(CXX) + +# common XO cmake macros (see proj/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() +# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) +add_code_coverage() +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) + +# ---------------------------------------------------------------- +# c++ settings + +# one-time project-specific c++ flags. usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- + +add_subdirectory(src/printjson) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support for printjson customers + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# install .hpp files + +xo_install_include_tree() + +# end CMakeLists.txt diff --git a/README.md b/README.md new file mode 100644 index 00000000..ab049018 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# printjson library + +### build + install +``` +$ cd xo-printjson +$ mkdir build +$ cd build +$ PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} .. +$ make +$ make install +``` + +### build for unit test coverage +``` +$ cd xo-printjson +$ mkdir ccov +$ cd ccov +$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + +### LSP support +``` +$ cd xo-printjson +$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree +``` diff --git a/cmake/printjsonConfig.cmake.in b/cmake/printjsonConfig.cmake.in new file mode 100644 index 00000000..c7d8974c --- /dev/null +++ b/cmake/printjsonConfig.cmake.in @@ -0,0 +1,6 @@ +@PACKAGE_INIT@ + +#include(CMakeFindDependencyMacro) +#find_dependency(refcnt) +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/printjson/JsonPrinter.hpp b/include/xo/printjson/JsonPrinter.hpp new file mode 100644 index 00000000..c1b8b62a --- /dev/null +++ b/include/xo/printjson/JsonPrinter.hpp @@ -0,0 +1,99 @@ +/* @file JsonPrinter.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "xo/reflect/Reflect.hpp" +#include "xo/reflect/TypeDrivenMap.hpp" +#include "xo/reflect/TaggedPtr.hpp" +//#include +#include + +namespace xo { + namespace json { + class PrintJson; + + class JsonPrinter { + public: + using Reflect = xo::reflect::Reflect; + using TaggedPtr = xo::reflect::TaggedPtr; + using TypeDescr = xo::reflect::TypeDescr; + using TypeId = xo::reflect::TypeId; + + public: + JsonPrinter(PrintJson const * pjson) : pjson_{pjson} {} + virtual ~JsonPrinter() = default; + + PrintJson const * pjson() const { return pjson_; } + + /* print tagged pointer in json format */ + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const = 0; + + void report_internal_type_consistency_error(TypeDescr td1, + TypeDescr td2, + std::ostream * p_os) const; + + /* convenience method for derived printers. + * retrieves contents of tp as a T*, complains to *p_os if that fails. + * + * (Failure would occur if printer for type T was instead installed + * for some unrelated type U) + */ + template + T * check_recover_native(TaggedPtr tp, std::ostream * p_os) const { + T * x = tp.recover_native(); + + if (!x) { + this->report_internal_type_consistency_error(Reflect::require(), + tp.td(), + p_os); + } + + return x; + } /*check_recover_native*/ + + void assign_pjson(PrintJson const * pjson) { + assert(this->pjson_ == nullptr || this->pjson_ == pjson); + this->pjson_ = pjson; + } /*assign_pjson*/ + + private: + /* a json printers is installed into one PrintJson instance; + * capture address of that instance at install time + */ + PrintJson const * pjson_ = nullptr; + }; /*JsonPrinter*/ + + /* AsStringJsonPrinter + * prints a T-instance by using operator<< and surrounding in quotes. + * + * e.g: + * T & x = ..; + * std::ostream * p_os = ..; + * + * *p_os << "\"" << x << "\"" + * + */ + template + class AsStringJsonPrinter : public JsonPrinter { + public: + AsStringJsonPrinter(PrintJson const * pjson) : JsonPrinter(pjson) {} + + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override + { + T * x = this->check_recover_native(tp, p_os); + + if(x) { + *p_os << "\"" << *x << "\""; + } + } /*print_json*/ + }; /*AsStringJsonPrinter*/ + } /*namespace json*/ +} /*namespace xo*/ + + +/* end JsonPrinter.hpp */ diff --git a/include/xo/printjson/PrintJson.hpp b/include/xo/printjson/PrintJson.hpp new file mode 100644 index 00000000..c0ddcf55 --- /dev/null +++ b/include/xo/printjson/PrintJson.hpp @@ -0,0 +1,143 @@ +/* @file JsonPrinter.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "JsonPrinter.hpp" +#include "xo/reflect/TypeDrivenMap.hpp" +#include "xo/reflect/SelfTagging.hpp" +#include +#include + +namespace xo { + namespace json { + class PrintJson : public reflect::SelfTagging { + public: + using Reflect = xo::reflect::Reflect; + using TypeDrivenMap = xo::reflect::TypeDrivenMap>; + using SelfTagging = xo::reflect::SelfTagging; + using TaggedPtr = xo::reflect::TaggedPtr; + using TaggedRcptr = xo::reflect::TaggedRcptr; + using TypeDescr = xo::reflect::TypeDescr; + using TypeId = xo::reflect::TypeId; + + public: + PrintJson(); + ~PrintJson() = default; + + template + void print(T const & x_arg, std::ostream * p_os) const { + T * x = const_cast(&x_arg); + + this->print_tp(Reflect::make_tp(x), p_os); + } /*print*/ + + /* print object tp on stream *p_os, in JSON format; + */ + void print_tp(TaggedPtr tp, std::ostream * p_os) const; + + /* convenience -- shorthand for + * .print(obj->self_tp(), p_os) + */ + void print_obj(ref::rp const & obj, std::ostream * p_os) const; + + void provide_printer(TypeId id, std::unique_ptr p) { + *(printer_map_.require(id)) = std::move(p); + } + + void provide_printer(TypeDescr td, std::unique_ptr p) { + this->provide_printer(td->id(), std::move(p)); + } + + /* write json representation for tp on *p_os */ + void print_aux(TaggedPtr tp, std::ostream * p_os) const; + + // ----- inherited from SelfTagging ----- + + virtual TaggedRcptr self_tp(); + + private: + /* provide printers for common basic types */ + void provide_std_printers(); + + private: + /* map contains specialized printers for specific c++ types */ + TypeDrivenMap printer_map_; + }; /*PrintJson*/ + + /* Using singleton here to collect type-specific json printers, + * collected during program initialization. + * + * Could relabel as PrintJsonInitContext if desired + */ + class PrintJsonSingleton { + public: + static ref::rp instance(); + + private: + /* we don't need this to be stored as pointer. + * memory burned if unused will be one empty std::vector<> + */ + static ref::rp s_instance; + }; /*PrintJsonSingleton*/ + + } /*namespace json*/ + +#ifdef NOT_USING + namespace print { + using PrintJson = xo::json::PrintJson; + + /* stream inserter for printing a T-instance in json format */ + template + class jsonp_impl { + public: + jsonp_impl(T const & x, PrintJson const * pjson) : value_(x), pjson_{pjson} {} + //jsonp_impl(T const & x, PrintJson const * pjson) : value_{x}, pjson_{pjson} {} + //jsonp_impl(T && x, PrintJson const * pjson) : value_(std::move(x)), pjson_{pjson} {} + + void print(std::ostream & os) const { + using xo::reflect::Reflect; + + this->pjson_->print_tp(Reflect::make_tp(&value_), &os); + } /*print*/ + + private: + /* value, to be printed, in json format */ + T value_; + /* json printer (bc we don't care for singletons) */ + PrintJson const * pjson_ = nullptr; + }; /*jsonp_impl*/ + + template + inline + std::ostream & operator<<(std::ostream & os, jsonp_impl const & x) { + x.print(os); + return os; + } /*operator<<*/ + + /* writing out std::forward behavior for completeness' sake: + * + * 1. call jsonp(x) with rvalue std::string x, then: + * - T will be deduced to [std::string] + * (in particular: _not_ std::string &, std::string const &, std::string &&) + * - rvalue std::string passed to jsonp_impl ctor + * + * 2a. call jsonp(x) with std::string & x, then: + * - T deduced to [std::string &] + * - std::string & passed to jsonp_impl ctor + * + * 2b. call jsonp(x) with std::string const & x, then: + * - T deduced to [std::string const &] + * - std::string const & passed to jsonp_impl ctor + */ + template + auto jsonp(T && x, PrintJson const * pjson) { + return jsonp_impl(std::forward(x), pjson); + } /*jsonp*/ + } /*namespace print*/ +#endif +} /*namespace xo*/ + +/* end JsonPrinter.hpp */ diff --git a/include/xo/printjson/init_printjson.hpp b/include/xo/printjson/init_printjson.hpp new file mode 100644 index 00000000..f2b06a19 --- /dev/null +++ b/include/xo/printjson/init_printjson.hpp @@ -0,0 +1,29 @@ +/* file init_printjson.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "xo/subsys/Subsystem.hpp" + +namespace xo { + /* tag to represent the printjson/ subsystem within ordered initialization */ + enum S_printjson_tag {}; + + /* Use: + * // anywhere, to declare printjson dependency e.g. at file scope + * InitEvidence s_evidence = InitSubsys::require(); + * + * // from main(), though can resort to module initialization in a pybind11 library + * Subsystem::initialize_all(); + */ + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; +} /*namespace xo*/ + + +/* end init_printjson.hpp */ diff --git a/src/printjson/CMakeLists.txt b/src/printjson/CMakeLists.txt new file mode 100644 index 00000000..cf191d02 --- /dev/null +++ b/src/printjson/CMakeLists.txt @@ -0,0 +1,13 @@ +# xo-printjson/src/printjson/CMakeLists.txt + +set(SELF_LIB printjson) +set(SELF_SRCS PrintJson.cpp init_printjson.cpp) + +xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) + +# ---------------------------------------------------------------- +# dependencies: indentlog, ... + +xo_dependency(${SELF_LIB} reflect) + +# end CMakeLists.txt diff --git a/src/printjson/PrintJson.cpp b/src/printjson/PrintJson.cpp new file mode 100644 index 00000000..1ac048b6 --- /dev/null +++ b/src/printjson/PrintJson.cpp @@ -0,0 +1,398 @@ +/* file JsonPrinter.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "PrintJson.hpp" +//#include "time/Time.hpp" +#include "xo/reflect/TypeDescr.hpp" +#include "xo/indentlog/print/tag.hpp" +#include + +namespace xo { + using xo::time::utc_nanos; + using xo::reflect::Metatype; + using xo::reflect::Reflect; + using xo::reflect::SelfTagging; + using xo::reflect::TypeDescr; + using xo::reflect::TaggedPtr; + using xo::reflect::TaggedRcptr; + using xo::time::iso8601; + using xo::ref::rp; + using xo::xtag; + + namespace json { + TaggedRcptr + PrintJson::self_tp() + { + return Reflect::make_rctp(this); + //return TaggedRcptr::make(this); + } /*self_tp*/ + + void + JsonPrinter::report_internal_type_consistency_error(TypeDescr td1, + TypeDescr td2, + std::ostream * p_os) const + { + *p_os << "canonical_name()) + << xtag("S", td2->canonical_name()) + << ">"; + } /*report_internal_type_consistency_error*/ + + namespace { + /* this will be used when TaggedPtr refers to a pointer-like value, + * e.g. + * xo::ref::rp + */ + void + print_generic_pointer(PrintJson const & print_json, + TaggedPtr tp, + std::ostream * p_os) + { + /* e.g. if + * xo::ref::rp opt = ...; + * then expect to print just as we would for + * VanillaOption & opt = ...; + * if pointer is null, will print {} + */ + + if (tp.n_child()) { + print_json.print_aux(tp.get_child(0), p_os); + } else { + /* note: this can be distinguished from a bona fide struct, + * b/c it doesn't supply the _name_ member + */ + *p_os << "{}"; + } + } /*print_generic_pointer*/ + + /* this will be used when TaggedPtr refers to a vector-like value, + * e.g. + * std::vector + * std::array + */ + void + print_generic_vector(PrintJson const & print_json, + TaggedPtr tp, + std::ostream * p_os) + { + /* e.g. if + * std::array v{1, 2, 3}; + * + * then expect to print + * [1.0, 2.0, 3.0] + */ + + *p_os << "["; + + for (uint32_t i = 0, n = tp.n_child(); i < n; ++i) { + if (i > 0) + *p_os << ", "; + + print_json.print_aux(tp.get_child(i), p_os); + } + + *p_os << "]"; + } /*print_generic_vector*/ + + /* this will be used when TaggedPtr is understood to refer to a struct-like value. + */ + void + print_generic_struct(PrintJson const & print_json, + TaggedPtr tp, + std::ostream * p_os) + { + /* e.g. if + * struct Foo { int x_; double y_; }; + * Foo foo{1, 1.4142}; + * + * then expect to print + * {"_name_": "Foo", "x": 1, "y": 1.4142} + * + * note that python json parser requires property names in double quotes + */ + + *p_os << "{"; + + *p_os << "\"_name_\": \"" << tp.td()->short_name() << "\""; + + for (uint32_t i = 0, n = tp.n_child(); i < n; ++i) { + *p_os << ", \"" << tp.struct_member_name(i) << "\": "; + + print_json.print_aux(tp.get_child(i), p_os); + } + + *p_os << "}"; + } /*print_generic_struct*/ + + } /*namespace*/ + + void + PrintJson::print_aux(TaggedPtr tp, + std::ostream * p_os) const + { + if (tp.td()) { + TypeId id = tp.td()->id(); + + std::unique_ptr const * printer + = this->printer_map_.lookup(id); + + if (printer && *printer) { + (*printer)->print_json(tp, p_os); + } else { + /* if no special-case printer, apply generic printing behavior */ + switch (tp.td()->metatype()) { + case Metatype::mt_pointer: + print_generic_pointer(*this, tp, p_os); + return; + case Metatype::mt_vector: + print_generic_vector(*this, tp, p_os); + return; + case Metatype::mt_struct: + print_generic_struct(*this, tp, p_os); + return; + case Metatype::mt_invalid: + case Metatype::mt_atomic: + break; + } + + (*p_os) << "canonical_name()) + << xtag("metatype", tp.td()->metatype()) + << ">"; + } + } else { + (*p_os) << ""; + } + } /*print_aux*/ + + void + PrintJson::print_tp(TaggedPtr tp, + std::ostream * p_os) const + { + this->print_aux(tp, p_os); + //*p_os << std::ends; + } /*print*/ + + void + PrintJson::print_obj(rp const & obj, std::ostream * p_os) const + { + assert(obj.get()); + + this->print_tp(obj->self_tp(), p_os); + } /*print_obj*/ + + class JsonPrinter_bool : public JsonPrinter { + public: + JsonPrinter_bool(PrintJson const * pjson) : JsonPrinter(pjson) {} + + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override { + bool * x = this->check_recover_native(tp, p_os); + + if (x) { + /* json boolean format is lower case true/false. + * (note that this conflicts with python True/False, achtung!) + */ + *p_os << (*x ? "true" : "false"); + } + } /*print_json*/ + }; /*JsonPrinter_bool*/ + + namespace { + void + provide_bool_printer(PrintJson * p_json) + { + std::unique_ptr printer(new JsonPrinter_bool(p_json)); + + p_json->provide_printer(Reflect::require(), + std::move(printer)); + } /*provide_bool_printer*/ + } /*namespace*/ + + template + class JsonPrinter_integer : public JsonPrinter { + public: + JsonPrinter_integer(PrintJson const * pjson) : JsonPrinter(pjson) {} + + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override { + T * x = tp.recover_native(); + + if (x) { + *p_os << *x; + } else { + report_internal_type_consistency_error(Reflect::require(), + tp.td(), + p_os); + } + } /*print_json*/ + }; /*JsonPrinter_integer*/ + + namespace { + template + void + provide_integer_printer(PrintJson * p_json) + { + std::unique_ptr printer(new JsonPrinter_integer(p_json)); + + p_json->provide_printer(Reflect::require(), std::move(printer)); + } /*provide_integer_printer*/ + } + + template + class JsonPrinter_floatingpoint : public JsonPrinter { + public: + JsonPrinter_floatingpoint(PrintJson const * pjson) : JsonPrinter(pjson) {} + + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override + { + T * x = tp.recover_native(); + + if (x) { + if (std::isfinite(*x)) { + *p_os << *x; + } else { + /* special cases. + * use javascript-friendly format + * + * Note non-finite floating-point values are not representable in + * standard json (?!#), though it's a standard extension + */ + + if (std::isnan(*x)) + *p_os << "NaN"; + else if (*x > 0.0) + *p_os << "Infinity"; + else + *p_os << "-Infinity"; + } + } else { + report_internal_type_consistency_error(Reflect::require(), + tp.td(), + p_os); + } + } /*print_json*/ + }; /*JsonPrinter_floatingpoint*/ + + namespace { + template + void + provide_floatingpoint_printer(PrintJson * p_json) + { + std::unique_ptr printer(new JsonPrinter_floatingpoint(p_json)); + + p_json->provide_printer(Reflect::require(), std::move(printer)); + } /*provide_floatingpoint_printer*/ + } /*namespace*/ + + template + class JsonPrinter_string : public JsonPrinter { + public: + JsonPrinter_string(PrintJson const * pjson) : JsonPrinter(pjson) {} + + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override { + T * x = tp.recover_native(); + + if (x) { + /* TODO: escapes special characters */ + *p_os << *x; + } else { + report_internal_type_consistency_error(Reflect::require(), + tp.td(), + p_os); + } + } /*print_json*/ + }; /*JsonPrinter_string*/ + + namespace { + template + void + provide_string_printer(PrintJson * p_json) + { + std::unique_ptr printer(new JsonPrinter_string(p_json)); + + p_json->provide_printer(Reflect::require(), std::move(printer)); + } /*provide_string_printer*/ + } /*namespace */ + + class JsonPrinter_utc_nanos : public JsonPrinter { + public: + JsonPrinter_utc_nanos(PrintJson * pjson) : JsonPrinter(pjson) {} + + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override { + utc_nanos * x = tp.recover_native(); + + if (x) { + /* format like + * "2012-04-23T18:25:43.511Z" + * since that's what javascript uses + */ + *p_os << "\"" << iso8601(*x) << "\""; + } else { + report_internal_type_consistency_error(Reflect::require(), + tp.td(), + p_os); + } + } /*print_json*/ + }; /*JsonPrinter_utc_nanos*/ + + namespace { + void + provide_utc_nanos_printer(PrintJson * p_json) + { + std::unique_ptr printer(new JsonPrinter_utc_nanos(p_json)); + + p_json->provide_printer(Reflect::require(), + std::move(printer)); + } /*provide_utc_nanos_printer*/ + } /*namespace*/ + + PrintJson::PrintJson() { + this->provide_std_printers(); + } /*ctor*/ + + /* provide printers for common basic types */ + void + PrintJson::provide_std_printers() + { + provide_bool_printer(this); + + provide_integer_printer(this); + provide_integer_printer(this); + provide_integer_printer(this); + provide_integer_printer(this); + provide_integer_printer(this); + provide_integer_printer(this); + + provide_floatingpoint_printer(this); + provide_floatingpoint_printer(this); + + provide_string_printer(this); + provide_string_printer(this); + provide_string_printer(this); + provide_string_printer(this); + + provide_utc_nanos_printer(this); + } /*provide_std_printers*/ + + rp + PrintJsonSingleton::s_instance; + + rp + PrintJsonSingleton::instance() + { + if (!s_instance) + s_instance = new PrintJson(); + + return s_instance; + } /*instance*/ + + } /*namespace json*/ +} /*namespace xo*/ + +/* end JsonPrinter.cpp */ diff --git a/src/printjson/init_printjson.cpp b/src/printjson/init_printjson.cpp new file mode 100644 index 00000000..6bbaf01b --- /dev/null +++ b/src/printjson/init_printjson.cpp @@ -0,0 +1,32 @@ +/* file init_printjson.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "init_printjson.hpp" +#include "xo/reflect/init_reflect.hpp" +#include "xo/subsys/Subsystem.hpp" + +namespace xo { + void + InitSubsys::init() + { + /* placeholder -- expecting there to be non-trivial content soon */ + } /*init*/ + + InitEvidence + InitSubsys::require() + { + InitEvidence retval; + + /* subsystem dependencies for printjson/ */ + retval ^= InitSubsys::require(); + + /* printjson/'s own initialization code */ + retval ^= Subsystem::provide("printjson", &init); + + return retval; + } /*require*/ +} /*namespace xo*/ + +/* end init_printjson.cpp */ From 25380b8da7b83bd0ab7dbd379d67b703e0e00baa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 13:51:34 -0400 Subject: [PATCH 0307/2693] + .gitignore --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..2323db6f --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# lsp keeps state here +.cache +# typical build directories +build +ccov +# compile_commands.json symlink -> build/compile_commands.json should be created manually +compile_commands.json From 5840dffe50e5b29022e9455f3068db8c058638e1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 13:55:12 -0400 Subject: [PATCH 0308/2693] github: + workflow (build on base ubuntu) --- .github/workflows/main.yml | 136 +++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..c11fc4ee --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,136 @@ +name: build on ubuntu base platform for xo-printjson + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + # configure cmake for subsys in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/reflect + + - name: Configure reflect + # configure cmake for reflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reflect + + - name: Build reflect + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} + + - name: Install reflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reflect + + # ---------------------------------------------------------------- + + - name: Configure self (printjson) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_printjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (printjson) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_printjson --config ${{env.BUILD_TYPE}} + + - name: Test self (printjson) + working-directory: ${{github.workspace}}/build_printjson + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From 26dffce47026e740564cca6ac01ff23f23b6c3a2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 14:00:54 -0400 Subject: [PATCH 0309/2693] build: bugfix: support transitive deps in find_package helper --- cmake/reflectConfig.cmake.in | 6 ++++-- src/reflect/CMakeLists.txt | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cmake/reflectConfig.cmake.in b/cmake/reflectConfig.cmake.in index e98de9c1..ce449a35 100644 --- a/cmake/reflectConfig.cmake.in +++ b/cmake/reflectConfig.cmake.in @@ -1,6 +1,8 @@ @PACKAGE_INIT@ -#include(CMakeFindDependencyMacro) -#find_dependency(indentlog) +include(CMakeFindDependencyMacro) +find_dependency(refcnt) +find_dependency(indentlog) +find_dependency(subsys) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index e22a2c4c..27485780 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -8,9 +8,9 @@ xo_add_shared_library(${SELF_LIBRARY_NAME} ${PROJECT_VERSION} 1 ${SELF_SOURCE_FI # ---------------------------------------------------------------- # dependencies: indentlog, ... -# note: changes here must coordinate with cmake/reflectConfig.cmake.in -xo_dependency(${SELF_LIBRARY_NAME} indentlog) xo_dependency(${SELF_LIBRARY_NAME} refcnt) +xo_dependency(${SELF_LIBRARY_NAME} indentlog) +xo_dependency(${SELF_LIBRARY_NAME} subsys) # ---------------------------------------------------------------- # 3rd party dependency: boost: From d0ccff195f49712ded3bc015b6619ba8ef685f2d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 14:03:10 -0400 Subject: [PATCH 0310/2693] bugfix: include path + reindent --- include/xo/reflect/TypeDrivenMap.hpp | 56 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/include/xo/reflect/TypeDrivenMap.hpp b/include/xo/reflect/TypeDrivenMap.hpp index c38c8739..3eb42660 100644 --- a/include/xo/reflect/TypeDrivenMap.hpp +++ b/include/xo/reflect/TypeDrivenMap.hpp @@ -5,42 +5,42 @@ #pragma once -#include "reflect/TypeDescr.hpp" +#include "TypeDescr.hpp" #include namespace xo { - namespace reflect { - /* represents a map :: TypeId -> Value */ - template - class TypeDrivenMap { - public: - Value const * lookup(TypeId id) const { return this->lookup_slot(id); } + namespace reflect { + /* represents a map :: TypeId -> Value */ + template + class TypeDrivenMap { + public: + Value const * lookup(TypeId id) const { return this->lookup_slot(id); } - Value * require(TypeId id) { return this->require_slot(id); } - Value * require(TypeDescr td) { return this->require_slot(td->id()); } - - private: - Value const * lookup_slot(TypeId id) const { - if (this->contents_v_.size() <= id.id()) - return nullptr; + Value * require(TypeId id) { return this->require_slot(id); } + Value * require(TypeDescr td) { return this->require_slot(td->id()); } - return &(this->contents_v_[id.id()]); - } /*lookup_slot*/ + private: + Value const * lookup_slot(TypeId id) const { + if (this->contents_v_.size() <= id.id()) + return nullptr; - Value * require_slot(TypeId id) { - if (this->contents_v_.size() <= id.id()) - this->contents_v_.resize(id.id() + 1); + return &(this->contents_v_[id.id()]); + } /*lookup_slot*/ - return &(this->contents_v_[id.id()]); - } /*require_slot*/ + Value * require_slot(TypeId id) { + if (this->contents_v_.size() <= id.id()) + this->contents_v_.resize(id.id() + 1); - private: - /* since TypeId/s are unique, compact sequence numbers, - * can efficiently store mapping to Values using a vector indexed by TypeId - */ - std::vector contents_v_; - }; /*TypeDrivenMap*/ - } /*namespace reflect*/ + return &(this->contents_v_[id.id()]); + } /*require_slot*/ + + private: + /* since TypeId/s are unique, compact sequence numbers, + * can efficiently store mapping to Values using a vector indexed by TypeId + */ + std::vector contents_v_; + }; /*TypeDrivenMap*/ + } /*namespace reflect*/ } /*namespace xo*/ From a228728445752cd907e4c3b6c82673424df449e9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 14:53:12 -0400 Subject: [PATCH 0311/2693] printjson: indentation in .hpp --- include/xo/printjson/JsonPrinter.hpp | 136 +++++++++++++-------------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/include/xo/printjson/JsonPrinter.hpp b/include/xo/printjson/JsonPrinter.hpp index c1b8b62a..2be405c0 100644 --- a/include/xo/printjson/JsonPrinter.hpp +++ b/include/xo/printjson/JsonPrinter.hpp @@ -12,87 +12,87 @@ #include namespace xo { - namespace json { - class PrintJson; + namespace json { + class PrintJson; - class JsonPrinter { - public: - using Reflect = xo::reflect::Reflect; - using TaggedPtr = xo::reflect::TaggedPtr; - using TypeDescr = xo::reflect::TypeDescr; - using TypeId = xo::reflect::TypeId; + class JsonPrinter { + public: + using Reflect = xo::reflect::Reflect; + using TaggedPtr = xo::reflect::TaggedPtr; + using TypeDescr = xo::reflect::TypeDescr; + using TypeId = xo::reflect::TypeId; - public: - JsonPrinter(PrintJson const * pjson) : pjson_{pjson} {} - virtual ~JsonPrinter() = default; + public: + JsonPrinter(PrintJson const * pjson) : pjson_{pjson} {} + virtual ~JsonPrinter() = default; - PrintJson const * pjson() const { return pjson_; } + PrintJson const * pjson() const { return pjson_; } - /* print tagged pointer in json format */ - virtual void print_json(TaggedPtr tp, - std::ostream * p_os) const = 0; + /* print tagged pointer in json format */ + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const = 0; - void report_internal_type_consistency_error(TypeDescr td1, - TypeDescr td2, - std::ostream * p_os) const; + void report_internal_type_consistency_error(TypeDescr td1, + TypeDescr td2, + std::ostream * p_os) const; - /* convenience method for derived printers. - * retrieves contents of tp as a T*, complains to *p_os if that fails. - * - * (Failure would occur if printer for type T was instead installed - * for some unrelated type U) - */ - template - T * check_recover_native(TaggedPtr tp, std::ostream * p_os) const { - T * x = tp.recover_native(); + /* convenience method for derived printers. + * retrieves contents of tp as a T*, complains to *p_os if that fails. + * + * (Failure would occur if printer for type T was instead installed + * for some unrelated type U) + */ + template + T * check_recover_native(TaggedPtr tp, std::ostream * p_os) const { + T * x = tp.recover_native(); - if (!x) { - this->report_internal_type_consistency_error(Reflect::require(), - tp.td(), - p_os); - } + if (!x) { + this->report_internal_type_consistency_error(Reflect::require(), + tp.td(), + p_os); + } - return x; - } /*check_recover_native*/ + return x; + } /*check_recover_native*/ - void assign_pjson(PrintJson const * pjson) { - assert(this->pjson_ == nullptr || this->pjson_ == pjson); - this->pjson_ = pjson; - } /*assign_pjson*/ + void assign_pjson(PrintJson const * pjson) { + assert(this->pjson_ == nullptr || this->pjson_ == pjson); + this->pjson_ = pjson; + } /*assign_pjson*/ - private: - /* a json printers is installed into one PrintJson instance; - * capture address of that instance at install time - */ - PrintJson const * pjson_ = nullptr; - }; /*JsonPrinter*/ + private: + /* a json printers is installed into one PrintJson instance; + * capture address of that instance at install time + */ + PrintJson const * pjson_ = nullptr; + }; /*JsonPrinter*/ - /* AsStringJsonPrinter - * prints a T-instance by using operator<< and surrounding in quotes. - * - * e.g: - * T & x = ..; - * std::ostream * p_os = ..; - * - * *p_os << "\"" << x << "\"" - * - */ - template - class AsStringJsonPrinter : public JsonPrinter { - public: - AsStringJsonPrinter(PrintJson const * pjson) : JsonPrinter(pjson) {} + /* AsStringJsonPrinter + * prints a T-instance by using operator<< and surrounding in quotes. + * + * e.g: + * T & x = ..; + * std::ostream * p_os = ..; + * + * *p_os << "\"" << x << "\"" + * + */ + template + class AsStringJsonPrinter : public JsonPrinter { + public: + AsStringJsonPrinter(PrintJson const * pjson) : JsonPrinter(pjson) {} - virtual void print_json(TaggedPtr tp, - std::ostream * p_os) const override - { - T * x = this->check_recover_native(tp, p_os); + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override + { + T * x = this->check_recover_native(tp, p_os); - if(x) { - *p_os << "\"" << *x << "\""; - } - } /*print_json*/ - }; /*AsStringJsonPrinter*/ - } /*namespace json*/ + if(x) { + *p_os << "\"" << *x << "\""; + } + } /*print_json*/ + }; /*AsStringJsonPrinter*/ + } /*namespace json*/ } /*namespace xo*/ From 3b00641d17b79ef4ed2c8617cf7fe9aa4fc0608e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 14:53:51 -0400 Subject: [PATCH 0312/2693] init_printjson: always create singleton --- src/printjson/init_printjson.cpp | 34 ++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/printjson/init_printjson.cpp b/src/printjson/init_printjson.cpp index 6bbaf01b..893ffaf3 100644 --- a/src/printjson/init_printjson.cpp +++ b/src/printjson/init_printjson.cpp @@ -4,29 +4,33 @@ */ #include "init_printjson.hpp" +#include "PrintJson.hpp" #include "xo/reflect/init_reflect.hpp" #include "xo/subsys/Subsystem.hpp" namespace xo { - void - InitSubsys::init() - { - /* placeholder -- expecting there to be non-trivial content soon */ - } /*init*/ + using xo::json::PrintJsonSingleton; - InitEvidence - InitSubsys::require() - { - InitEvidence retval; + void + InitSubsys::init() + { + /* create singleton */ + PrintJsonSingleton::instance(); + } /*init*/ - /* subsystem dependencies for printjson/ */ - retval ^= InitSubsys::require(); + InitEvidence + InitSubsys::require() + { + InitEvidence retval; - /* printjson/'s own initialization code */ - retval ^= Subsystem::provide("printjson", &init); + /* subsystem dependencies for printjson/ */ + retval ^= InitSubsys::require(); - return retval; - } /*require*/ + /* printjson/'s own initialization code */ + retval ^= Subsystem::provide("printjson", &init); + + return retval; + } /*require*/ } /*namespace xo*/ /* end init_printjson.cpp */ From 0f4f874587b1530fe7d30b6247d6068322646db5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 14:54:17 -0400 Subject: [PATCH 0313/2693] printjson: indentation in .hpp --- include/xo/printjson/init_printjson.hpp | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/include/xo/printjson/init_printjson.hpp b/include/xo/printjson/init_printjson.hpp index f2b06a19..dbad03f8 100644 --- a/include/xo/printjson/init_printjson.hpp +++ b/include/xo/printjson/init_printjson.hpp @@ -8,21 +8,21 @@ #include "xo/subsys/Subsystem.hpp" namespace xo { - /* tag to represent the printjson/ subsystem within ordered initialization */ - enum S_printjson_tag {}; + /* tag to represent the printjson/ subsystem within ordered initialization */ + enum S_printjson_tag {}; - /* Use: - * // anywhere, to declare printjson dependency e.g. at file scope - * InitEvidence s_evidence = InitSubsys::require(); - * - * // from main(), though can resort to module initialization in a pybind11 library - * Subsystem::initialize_all(); - */ - template<> - struct InitSubsys { - static void init(); - static InitEvidence require(); - }; + /* Use: + * // anywhere, to declare printjson dependency e.g. at file scope + * InitEvidence s_evidence = InitSubsys::require(); + * + * // from main(), though can resort to module initialization in a pybind11 library + * Subsystem::initialize_all(); + */ + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; } /*namespace xo*/ From 45a0675436c7512398a0750b158d0a1907e6cf27 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 14:54:39 -0400 Subject: [PATCH 0314/2693] printjson: add printer for nested TaggedPtr's --- src/printjson/PrintJson.cpp | 45 ++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/printjson/PrintJson.cpp b/src/printjson/PrintJson.cpp index 1ac048b6..7df2d5b3 100644 --- a/src/printjson/PrintJson.cpp +++ b/src/printjson/PrintJson.cpp @@ -17,6 +17,7 @@ namespace xo { using xo::reflect::TypeDescr; using xo::reflect::TaggedPtr; using xo::reflect::TaggedRcptr; + using xo::print::quoted; using xo::time::iso8601; using xo::ref::rp; using xo::xtag; @@ -183,6 +184,46 @@ namespace xo { this->print_tp(obj->self_tp(), p_os); } /*print_obj*/ + /* Consider: + * TaggedPtr tp = ...; + * std::ostream * p_os = ...; + * + * PrintJson * pjson = PrintJsonSingleton::instance(); + * + * // print json representation, depending on runtime type of tp's target + * pjson->print_tp(tp, p_os); + * + * // can also use .print(), relying on JsonPrinter_TaggedPtr + * // .print() will next original TaggedPtr in another; + * // this shim unwinds that + * // + * pjson->print(tp, p_os); + */ + class JsonPrinter_TaggedPtr : public JsonPrinter { + public: + JsonPrinter_TaggedPtr(PrintJson const * pjson) : JsonPrinter(pjson) {} + + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override { + TaggedPtr * x = this->check_recover_native(tp, p_os); + + if (x) { + this->pjson()->print_tp(*x, p_os); + } + } /*print_json*/ + }; /*JsonPrinter_TaggedPtr*/ + + namespace { + void + provide_tagged_ptr_printer(PrintJson * p_json) + { + std::unique_ptr printer(new JsonPrinter_TaggedPtr(p_json)); + + p_json->provide_printer(Reflect::require(), + std::move(printer)); + } /*provide_tagged_ptr_printer*/ + } /*namespace*/ + class JsonPrinter_bool : public JsonPrinter { public: JsonPrinter_bool(PrintJson const * pjson) : JsonPrinter(pjson) {} @@ -299,7 +340,7 @@ namespace xo { if (x) { /* TODO: escapes special characters */ - *p_os << *x; + *p_os << quoted(*x); } else { report_internal_type_consistency_error(Reflect::require(), tp.td(), @@ -360,6 +401,8 @@ namespace xo { void PrintJson::provide_std_printers() { + provide_tagged_ptr_printer(this); + provide_bool_printer(this); provide_integer_printer(this); From 0a26fa9911f085f22d6aee1dfc49bc9d6ed4314a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 14:55:08 -0400 Subject: [PATCH 0315/2693] printjson: fix+ reinstate unit test --- CMakeLists.txt | 2 +- utest/CMakeLists.txt | 58 +++++++++++++++ utest/CMakeLists.txt.safe | 55 +++++++++++++++ utest/PrintJson.test.cpp | 124 +++++++++++++++++++++++++++++++++ utest/printjson_utest_main.cpp | 6 ++ 5 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 utest/CMakeLists.txt create mode 100644 utest/CMakeLists.txt.safe create mode 100644 utest/PrintJson.test.cpp create mode 100644 utest/printjson_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 028f4c5e..af42e3e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ xo_toplevel_compile_options() # ---------------------------------------------------------------- add_subdirectory(src/printjson) -#add_subdirectory(utest) +add_subdirectory(utest) # ---------------------------------------------------------------- # provide find_package() support for printjson customers diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..0b2177e8 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,58 @@ +# build unittest printjson/utest + +set(SELF_EXE utest.printjson) +set(SELF_SRCS printjson_utest_main.cpp PrintJson.test.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) +target_code_coverage(${SELF_EXE} AUTO ALL) + +# ---------------------------------------------------------------- +# generic project dependency + +## PROJECT_SOURCE_DIR: +## so we can for example write +## #include "indentlog/scope.hpp" +## from anywhere in the project +## PROJECT_BINARY_DIR: +## since version file will be in build directory, need that directory +## to also be included in compiler's include path +## +#target_include_directories(${SELF_EXE} PUBLIC +# ${PROJECT_SOURCE_DIR} +# ${PROJECT_BINARY_DIR}) + +# ---------------------------------------------------------------- +# dependencies on this codebase + +xo_self_dependency(${SELF_EXE} printjson) + +# ---------------------------------------------------------------- +# dependencies on other codebases + +xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) + +# need this so that catch2/include appears in compile_commands.json, +# on which lsp integration relies. +# +# See also /nix/store/*-catch2-*/lib/cmake/Catch2/ParseAndAddCatchTests.cmake; +# commands here derived from ^ .cmake file +# +#find_path(CATCH_INCLUDE_DIR "catch2/catch.hpp") +#target_include_directories(${SELF_EXE} PUBLIC ${CATCH_INCLUDE_DIR}) + +# supplied from xo_include_options2() +## ---------------------------------------------------------------- +## make standard directories for std:: includes explicit +## so that +## (1) they appear in compile_commands.json. +## (2) clangd (run from emacs lsp-mode) can find them +## +#if(CMAKE_EXPORT_COMPILE_COMMANDS) +# set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES +# ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +#endif() + +# end CMakeLists.txt diff --git a/utest/CMakeLists.txt.safe b/utest/CMakeLists.txt.safe new file mode 100644 index 00000000..60200a81 --- /dev/null +++ b/utest/CMakeLists.txt.safe @@ -0,0 +1,55 @@ +# build unittest printjson/utest + +set(SELF_EXECUTABLE_NAME utest.printjson) +set(SELF_SOURCE_FILES printjson_utest_main.cpp PrintJson.test.cpp) + +add_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) + +add_test(NAME ${SELF_EXECUTABLE_NAME} COMMAND ${SELF_EXECUTABLE_NAME}) + +# ---------------------------------------------------------------- +# generic project dependency + +# PROJECT_SOURCE_DIR: +# so we can for example write +# #include "indentlog/scope.hpp" +# from anywhere in the project +# PROJECT_BINARY_DIR: +# since version file will be in build directory, need that directory +# to also be included in compiler's include path +# +target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC + ${PROJECT_SOURCE_DIR} + ${PROJECT_BINARY_DIR}) + +# ---------------------------------------------------------------- +# internal dependencies: logutil, ... + +target_link_libraries(${SELF_EXECUTABLE_NAME} PUBLIC printjson) + +# ---------------------------------------------------------------- +# 3rd part dependency: catch2: + +find_package(Catch2 2 REQUIRED) + +# need this so that catch2/include appears in compile_commands.json, +# on which lsp integration relies. +# +# See also /nix/store/*-catch2-*/lib/cmake/Catch2/ParseAndAddCatchTests.cmake; +# commands here derived from ^ .cmake file +# +find_path(CATCH_INCLUDE_DIR "catch2/catch.hpp") +target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC ${CATCH_INCLUDE_DIR}) + +# ---------------------------------------------------------------- +# make standard directories for std:: includes explicit +# so that +# (1) they appear in compile_commands.json. +# (2) clangd (run from emacs lsp-mode) can find them +# +if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES + ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +endif() + +# end CMakeLists.txt diff --git a/utest/PrintJson.test.cpp b/utest/PrintJson.test.cpp new file mode 100644 index 00000000..51259e23 --- /dev/null +++ b/utest/PrintJson.test.cpp @@ -0,0 +1,124 @@ +/* file PrintJson.test.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "xo/printjson/PrintJson.hpp" +#include "xo/printjson/init_printjson.hpp" +#include "xo/reflect/Reflect.hpp" +#include "xo/reflect/StructReflector.hpp" +#include +#include +#include + +//#define STRINGIFY(x) #x + +namespace xo { + using xo::json::PrintJson; + using xo::reflect::Reflect; + using xo::reflect::StructReflector; + using xo::reflect::TaggedPtr; + + namespace ut { + InitEvidence s_init_evidence = InitSubsys::require(); + + namespace { + struct TestStruct0 {}; + } + + TEST_CASE("print-json-empty-struct", "[printjson]") { + INFO(tag("s_init_evidence", s_init_evidence)); + + StructReflector sr; + + sr.require_complete(); + + TestStruct0 recd0; + + PrintJson print_json; + + TaggedPtr tp = Reflect::make_tp(&recd0); + + std::stringstream ss; + + print_json.print(tp, &ss); + + REQUIRE(ss.str() == std::string("{\"_name_\": \"TestStruct0\"}")); + } /*TEST_CASE(print-json-empty-struct)*/ + + namespace { + struct TestStruct1 { + std::int16_t i16_; std::uint16_t u16_; + std::int32_t i32_; std::uint32_t u32_; + std::int64_t i64_; std::uint64_t u64_; + float f32_; double f64_; + std::string s_; + }; + } + + TEST_CASE("print-json-s1", "[printjson]") { + INFO(tag("s_init_evidence", s_init_evidence)); + + StructReflector sr; + { + REFLECT_MEMBER(sr, i16); + REFLECT_MEMBER(sr, u16); + REFLECT_MEMBER(sr, i32); + REFLECT_MEMBER(sr, u32); + REFLECT_MEMBER(sr, i64); + REFLECT_MEMBER(sr, u64); + REFLECT_MEMBER(sr, f32); + REFLECT_MEMBER(sr, f64); + REFLECT_MEMBER(sr, s); + + sr.require_complete(); + } + + TestStruct1 recd1{-1, 2, -3, 4, -5, 6, 1.23f, 4.56, "hello, world"}; + + PrintJson print_json; + + TaggedPtr tp = Reflect::make_tp(&recd1); + + std::stringstream ss; + + print_json.print(tp, &ss); + + REQUIRE(ss.str() == std::string("{\"_name_\": \"TestStruct1\"" + ", \"i16\": -1" + ", \"u16\": 2" + ", \"i32\": -3" + ", \"u32\": 4" + ", \"i64\": -5" + ", \"u64\": 6" + ", \"f32\": 1.23" + ", \"f64\": 4.56" + ", \"s\": \"hello, world\"}")); + } /*TEST_CASE(print-json-s1)*/ + + TEST_CASE("print-json-v1", "[printjson]") { + INFO(tag("s_init_evidence", s_init_evidence)); + + std::vector v1{1, 2, 3}; + + PrintJson print_json; + + TaggedPtr tp = Reflect::make_tp(&v1); + + std::stringstream ss; + + print_json.print(tp, &ss); + + REQUIRE(ss.str() == std::string("[1, 2, 3]")); + } /*TEST_CASE(print-json-v1)*/ + + /* also see tests: + * [option_util/utest/Px2.test.cpp] + * [option_util/utest/Size2.test.cpp] + * [option_util/utest/PxSize2.test.cpp] + */ + } /*namespace ut */ +} /*namespace xo*/ + + +/* end StructReflector.test.cpp */ diff --git a/utest/printjson_utest_main.cpp b/utest/printjson_utest_main.cpp new file mode 100644 index 00000000..e7c11b66 --- /dev/null +++ b/utest/printjson_utest_main.cpp @@ -0,0 +1,6 @@ +/* file printjson_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end printjson_utest_main.cpp */ From ccb934e2b5fffd2d02d22ed7ce992310b3da3bd5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 16:53:37 -0400 Subject: [PATCH 0316/2693] cosmetic: indentation in init_reflect.hpp --- include/xo/reflect/init_reflect.hpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/include/xo/reflect/init_reflect.hpp b/include/xo/reflect/init_reflect.hpp index b6365632..5f69eadd 100644 --- a/include/xo/reflect/init_reflect.hpp +++ b/include/xo/reflect/init_reflect.hpp @@ -8,14 +8,14 @@ #include "xo/subsys/Subsystem.hpp" namespace xo { - /* tag to represent the reflect/ subsystem within ordered initialization */ - enum S_reflect_tag {}; + /* tag to represent the reflect/ subsystem within ordered initialization */ + enum S_reflect_tag {}; - template<> - struct InitSubsys { - static void init(); - static InitEvidence require(); - }; + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; } /*namespace xo*/ From 6da0ceb35d88a326b567ab40c5206eeb5289602c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 16:57:05 -0400 Subject: [PATCH 0317/2693] subsys: + operator<< for InitEvidence --- include/xo/subsys/Subsystem.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/xo/subsys/Subsystem.hpp b/include/xo/subsys/Subsystem.hpp index ece72db6..3cc6012a 100644 --- a/include/xo/subsys/Subsystem.hpp +++ b/include/xo/subsys/Subsystem.hpp @@ -6,6 +6,7 @@ #pragma once #include "xo/indentlog/scope.hpp" +#include #include #include #include @@ -57,6 +58,12 @@ namespace xo { std::uint64_t evidence_ = 0; }; /*InitEvidence*/ + inline std::ostream & + operator<<(std::ostream & os, InitEvidence x) { + os << ""; + return os; + } /*operator<<*/ + /* Goals: * 1. provide for code that must run once (and only once) * to initialize subsystems From 487f516a3fd513e92fa75a338e13994626d7090f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 12:32:34 -0400 Subject: [PATCH 0318/2693] initial implementation --- CMakeLists.txt | 48 +++++ cmake/callbackConfig.cmake.in | 6 + include/xo/callback/CallbackSet.hpp | 312 ++++++++++++++++++++++++++++ src/callback/CMakeLists.txt | 14 ++ src/callback/CallbackSet.cpp | 22 ++ 5 files changed, 402 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/callbackConfig.cmake.in create mode 100644 include/xo/callback/CallbackSet.hpp create mode 100644 src/callback/CMakeLists.txt create mode 100644 src/callback/CallbackSet.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..ada7743d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,48 @@ +# callback/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(callback VERSION 0.1) +enable_language(CXX) + +# common XO cmake macros (see proj/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() +# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) +add_code_coverage() +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) + +# ---------------------------------------------------------------- +# c++ settings + +# PROJECT_CXX_FLAGS: bespoke for this project - usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- +# sources + +add_subdirectory(src/callback) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support to customers + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# end CMakeLists.txt diff --git a/cmake/callbackConfig.cmake.in b/cmake/callbackConfig.cmake.in new file mode 100644 index 00000000..f7176f38 --- /dev/null +++ b/cmake/callbackConfig.cmake.in @@ -0,0 +1,6 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(refcnt) +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/callback/CallbackSet.hpp b/include/xo/callback/CallbackSet.hpp new file mode 100644 index 00000000..2abb2c04 --- /dev/null +++ b/include/xo/callback/CallbackSet.hpp @@ -0,0 +1,312 @@ +/* @file CallbackSet.hpp */ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" +//#include "indentlog/scope.hpp" +//#include "indentlog/print/tag.hpp" +#include +#include + +namespace xo { + namespace fn { + /* identifies a particular callback in a CallbackSet (see below). + * an unique id is created: + * CallbackSetImpl cbset = ...; + * CallbackId cb_id = cbset.add_callback(..); + * + * can use id to remove callback later: + * cbset.remove_callback(cb_id); + */ + class CallbackId { + public: + CallbackId() = default; + explicit CallbackId(uint32_t id) : id_{id} {} + + /* generate a globally-unique id (not threadsafe) */ + static CallbackId generate(); + + uint32_t id() const { return id_; } + + private: + uint32_t id_ = 0; + }; /*CallbackId*/ + + inline bool operator==(CallbackId lhs, CallbackId rhs) { return lhs.id() == rhs.id(); } + inline bool operator!=(CallbackId lhs, CallbackId rhs) { return lhs.id() != rhs.id(); } + + /* queue add/remove callback instructions encountered during callback + * execution, to avoid invalidating vector iterator. + * + */ + template + struct ReentrantCbsetCmd { + enum CbsetCmdEnum { AddCallback, RemoveCallback }; + + ReentrantCbsetCmd() = default; + ReentrantCbsetCmd(CbsetCmdEnum cmd, CallbackId id, Fn const & fn) + : cmd_{cmd}, id_{id}, fn_{fn} {} + + static ReentrantCbsetCmd add(CallbackId id, Fn const & fn) { + return ReentrantCbsetCmd{AddCallback, id, fn}; + } /*add*/ + + static ReentrantCbsetCmd remove(CallbackId id) { + return ReentrantCbsetCmd{RemoveCallback, id, Fn()}; + } /*remove*/ + + bool is_add() const { return cmd_ == AddCallback; } + bool is_remove() const { return cmd_ == RemoveCallback; } + CallbackId id() const { return id_; } + Fn const & fn() const { return fn_; } + + private: + /* AddCallback: deferred CallbackSet::add_callback(.fn) + * RemoveCallback: deferred CallbackSet::remove_callback(.fn) + */ + CbsetCmdEnum cmd_ = AddCallback; + CallbackId id_; + Fn fn_; + }; /*ReentrantCbsetCmd*/ + + /* record for remembering a single callback. + * callbacks are given unique ids so they can be removed later + */ + template + struct CbRecd { + CbRecd(CallbackId id, Fn const & fn) : id_{id}, fn_{fn} {} + + CallbackId id_; + Fn fn_; + }; /*CbRecd*/ + + /* If Fnptr is a type such that this works: + * Fnptr fn = ...; + * using Fn = Fnptr::destination_type; + * Fn * native_fn = fn.get(); + * (native_fn->*member_fn)(args ...); + * + * then + * CallbackSet cbset = ...; + * cbset.invoke(&Fn::member_fn, args...) + * + * calls + * (cb->*member_fn)(args...) + * + * for each callback cb in this set. + * + * In addition, calls hook methods: + * cb->notify_add_callback() + * cb->notify_remove_callback() + * when adding/removing callback. + * + * Require: + * - can invoke (Fnptr->*member_fn)(...) + * + * implementation is reentrant: running callbacks can safely make + * add/remove calls on the cbset that invoked them. + * + * not threadsafe. + */ + template + class CallbackSetImpl { + public: + using callback_type = typename Fn::element_type; + //using scope = xo::scope; + + public: + CallbackSetImpl() = default; + + /* support for range iterators */ + typename std::vector>::const_iterator begin() const { return cb_v_.begin(); } + typename std::vector>::const_iterator end() const { return cb_v_.end(); } + + /* invoke callbacks registered with this callback set */ + template + void invoke(void (callback_type::* member_fn)(Sn... args), Tn&&... args) { + this->cb_running_ = true; + + try { + for(CbRecd const & cb_recd : this->cb_v_) { + callback_type * native_cb = cb_recd.fn_.get(); + + /* clang11 doesn't like + * cb->*member_fn + * when cb-> is overloaded + */ + (native_cb->*member_fn)(args...); + } + + this->make_deferred_changes(); + } catch(...) { + this->make_deferred_changes(); + throw; + } + } /*operator()*/ + + /* call fn(cb) for each callback present in this set */ + void visit_callbacks(std::function fn) const { + CallbackSetImpl * self = const_cast(this); + + self->cb_running_ = true; + + try { + for(Fn const & cb : this->cb_v_) + fn(cb); + + this->make_deferred_changes(); + } catch(...) { + this->make_deferred_changes(); + throw; + } + } /*visit_callbacks*/ + + /* add callback target_fn to this callback set. + * reentrant + */ + CallbackId add_callback(Fn const & target_fn) { + CallbackId id = CallbackId::generate(); + + if(this->cb_running_) { + /* defer until callback execution completes */ + this->reentrant_cmd_v_.push_back(ReentrantCbsetCmd::add(id, target_fn)); + } else { +#ifdef NOT_USING + constexpr bool c_debug_enabled_flag = false; + scope lscope(reflect::type_name(), + "::add_callback", c_debug_enabled_flag); + + if (c_debug_enabled_flag) { + lscope.log("before appending .cb_v[]", + xo::xtag("target_fn", (void*)target_fn.get()), + xo::xtag("target_fn.refcnt", + target_fn->reference_counter())); + } +#endif + + this->cb_v_.push_back(CbRecd(id, target_fn)); + +#ifdef NOT_USING + if (c_debug_enabled_flag) { + lscope.log("after appending .cb_v[]", + xo::xtag("target_fn", (void *)target_fn.get()), + xo::xtag("target_fn.refcnt", + target_fn->reference_counter())); + } +#endif + } + + return id; + } /*add_callback*/ + + void remove_callback(CallbackId id) { + if(this->cb_running_) { + /* defer until callback execution completes */ + this->reentrant_cmd_v_.push_back(ReentrantCbsetCmd::remove(id)); + } else { + this->remove_callback_impl(id); + } + + } /*remove_callback*/ + +#ifdef NOT_USING + /* remove callback target_fn from this callback set. + * noop if callback is not present + */ + void remove_callback(Fn const & target_fn) { + if(this->cb_running_) { + /* defer until callback execution completes */ + this->reentrant_cmd_v_.push_back(ReentrantCbsetCmd::remove(target_fn)); + } else { + this->remove_callback_impl(target_fn); + } + } /*remove_callback*/ +#endif + + private: + /* apply deferred changes to .cb_v[] */ + void make_deferred_changes() { + this->cb_running_ = false; + + std::vector> cmd_v; + std::swap(cmd_v, this->reentrant_cmd_v_); + + for(ReentrantCbsetCmd const & cmd : cmd_v) { + if(cmd.is_add()) { + this->cb_v_.push_back(CbRecd(cmd.id(), cmd.fn())); + + cmd.fn()->notify_add_callback(); + } else if(cmd.is_remove()) { + this->remove_callback_impl(cmd.id()); + } + } + } /*make_deferred_changes*/ + + void remove_callback_impl(CallbackId target_id) { + for (auto ix = this->cb_v_.begin(); ix != this->cb_v_.end(); ++ix) { + if (ix->id_ == target_id) { + Fn target_fn = ix->fn_; + + this->cb_v_.erase(ix); + + target_fn->notify_remove_callback(); + break; + } + } + } /*remove_callback_impl*/ + +#ifdef NOT_USING + void remove_callback_impl(Fn const & target_fn) { + auto ix = std::find(this->cb_v_.begin(), this->cb_v_.end(), target_fn); + + if(ix != this->cb_v_.end()) + this->cb_v_.erase(ix); + + target_fn->notify_remove_callback(); + } /*remove_callback_impl*/ +#endif + + private: + bool cb_running_ = false; + /* collection of callback functions */ + std::vector> cb_v_; + /* when a callback registered with *this, while running, + * attempts to add/remove a callback to/from this set + * (including removing itself), + * must defer until all callbacks have executed. + * remember deferred instructions here. + */ + std::vector> reentrant_cmd_v_; + }; /*CallbackSetImpl*/ + + template + using RpCallbackSet = CallbackSetImpl>; + + /* like RpCallbackSet, + * but also provides overload(s) for operator()(..) + */ + template + class NotifyCallbackSet : public RpCallbackSet { + public: + NotifyCallbackSet(MemberFn fn) + : privileged_member_fn_{fn} {} + + template + void operator()(Tn&&... args) { + this->invoke(this->privileged_member_fn_, args...); + } /*operator()*/ + + private: + /* implements operator()(...) */ + MemberFn privileged_member_fn_; + }; /*NotifyCallbackSet*/ + + template + inline NotifyCallbackSet + make_notify_cbset(Sret (NativeFn::* member_fn)(Sn...)) { + return NotifyCallbackSet(member_fn); + } /*make_notify_cbset*/ + } /*namespace fn*/ +} /*namespace xo*/ + +/* end CallbackSet.hpp */ diff --git a/src/callback/CMakeLists.txt b/src/callback/CMakeLists.txt new file mode 100644 index 00000000..58dc3de5 --- /dev/null +++ b/src/callback/CMakeLists.txt @@ -0,0 +1,14 @@ +# callback/CMakeLists.txt + +set(SELF_LIB callback) +set(SELF_SRCS CallbackSet.cpp) + +# reminder: can't be header-only library, because depends on non-header-only refcnt +xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) + +# ---------------------------------------------------------------- +# external dependencies: + +xo_dependency(${SELF_LIB} refcnt) + +# end CMakeLists.txt diff --git a/src/callback/CallbackSet.cpp b/src/callback/CallbackSet.cpp new file mode 100644 index 00000000..881f57f8 --- /dev/null +++ b/src/callback/CallbackSet.cpp @@ -0,0 +1,22 @@ +/* file CallbackSet.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "CallbackSet.hpp" + +namespace xo { + namespace fn { + CallbackId + CallbackId::generate() + { + static CallbackId s_last_id; + + s_last_id = CallbackId(s_last_id.id() + 1); + + return s_last_id; + } /*generate*/ + } /*namespace fn*/ +} /*namespace xo*/ + +/* end CallbackSet.cpp */ From e4f54cb2f75a973df28f9a22864e76a2c1fbfe8a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 12:33:30 -0400 Subject: [PATCH 0319/2693] + .gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..9e716afc --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# lsp keeps state here +.cache +# typical build directories +build +ccov From 9644d6b966f6640823cd34f28bf9ca7385ecaedb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 12:44:14 -0400 Subject: [PATCH 0320/2693] + README.md --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..fcfa8e8a --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# callback-set with reentrant invocation + +Reentrant: +1. A callback can modify parent callback-set (for example to remove itself), + even while being invoked. +2. Any such re-entrant operations are deferred until callback invocation completes. + +# build + install +``` +$ cd xo-callback +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer, e.g. ~/local +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +# build for unit test coverage +``` +$ cd xo-callback +$ mkdir build-ccov +$ cd build-ccov +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCODE_COVERAGE=ON \ + -DCMAKE_BUILD_TYPE=Debug .. +``` + +# LSP (language server) support + +LSP looks for compile commands in the root of the source tree; +cmake creates them in the root of its build directory. +``` +$ cd xo-callback +$ ln -s build/compile_commands.json +``` From 437f943e37160972cae569d1d0b5e5b6b830728b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 12:59:54 -0400 Subject: [PATCH 0321/2693] cosmetic: comment adjustments --- include/xo/callback/CallbackSet.hpp | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/include/xo/callback/CallbackSet.hpp b/include/xo/callback/CallbackSet.hpp index 2abb2c04..05f6c5fa 100644 --- a/include/xo/callback/CallbackSet.hpp +++ b/include/xo/callback/CallbackSet.hpp @@ -82,7 +82,7 @@ namespace xo { /* If Fnptr is a type such that this works: * Fnptr fn = ...; - * using Fn = Fnptr::destination_type; + * using Fn = Fnptr::element_type; * Fn * native_fn = fn.get(); * (native_fn->*member_fn)(args ...); * @@ -101,6 +101,8 @@ namespace xo { * when adding/removing callback. * * Require: + * - Fnptr::element_type + * - Fnptr::get() -> Fnptr::element_type const * * - can invoke (Fnptr->*member_fn)(...) * * implementation is reentrant: running callbacks can safely make @@ -130,7 +132,7 @@ namespace xo { for(CbRecd const & cb_recd : this->cb_v_) { callback_type * native_cb = cb_recd.fn_.get(); - /* clang11 doesn't like + /* clang11 doesn't like (with cb=cb_recd.fn_) * cb->*member_fn * when cb-> is overloaded */ @@ -255,17 +257,6 @@ namespace xo { } } /*remove_callback_impl*/ -#ifdef NOT_USING - void remove_callback_impl(Fn const & target_fn) { - auto ix = std::find(this->cb_v_.begin(), this->cb_v_.end(), target_fn); - - if(ix != this->cb_v_.end()) - this->cb_v_.erase(ix); - - target_fn->notify_remove_callback(); - } /*remove_callback_impl*/ -#endif - private: bool cb_running_ = false; /* collection of callback functions */ @@ -297,7 +288,7 @@ namespace xo { } /*operator()*/ private: - /* implements operator()(...) */ + /* implements NotifyCallbackSet's operator()(...) */ MemberFn privileged_member_fn_; }; /*NotifyCallbackSet*/ From d4c486abf13bd5d1fcc3b22c8bab9113241ce3a5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 13:00:15 -0400 Subject: [PATCH 0322/2693] cosmetic: comment --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ada7743d..1735ddc8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ include(xo_macros/xo_cxx) include(xo_macros/code-coverage) # ---------------------------------------------------------------- -# unit test setup +# unit test setup (for consistency with other xo libraries. no unit tests as of 10oct2023) enable_testing() # activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) From 75ee64909e9aa20e66cf4924c77faf368536151c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 13:00:28 -0400 Subject: [PATCH 0323/2693] github: workflow --- .github/workflows/main.yml | 100 +++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..0971d8de --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,100 @@ +# This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml +name: CMake on a single platform + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Configure self (callback) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_callback -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (callback) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_callback --config ${{env.BUILD_TYPE}} + + - name: Test self (callback) + working-directory: ${{github.workspace}}/build_callback + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From 4d2cc526585116aed8a9a90cf948cad0085d6490 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 13:03:45 -0400 Subject: [PATCH 0324/2693] refcnt: + indentlog dep in generated cmake config --- cmake/refcntConfig.cmake.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmake/refcntConfig.cmake.in b/cmake/refcntConfig.cmake.in index 9c15f36a..5b38fa74 100644 --- a/cmake/refcntConfig.cmake.in +++ b/cmake/refcntConfig.cmake.in @@ -1,4 +1,6 @@ @PACKAGE_INIT@ +include(CMakeFindDependencyMacro) +find_dependency(indentlog) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") From 5b8f2b6d7458e5e8e7614ce0167cff331f0867d8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 13:07:04 -0400 Subject: [PATCH 0325/2693] cosmetic: comments --- include/xo/callback/CallbackSet.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/xo/callback/CallbackSet.hpp b/include/xo/callback/CallbackSet.hpp index 05f6c5fa..c15aeb34 100644 --- a/include/xo/callback/CallbackSet.hpp +++ b/include/xo/callback/CallbackSet.hpp @@ -65,7 +65,9 @@ namespace xo { * RemoveCallback: deferred CallbackSet::remove_callback(.fn) */ CbsetCmdEnum cmd_ = AddCallback; + /* operate on callback with this id */ CallbackId id_; + /* callback function to add/remove */ Fn fn_; }; /*ReentrantCbsetCmd*/ From 532d48529f708234d3aac178ab4dc3d51b684147 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 15:20:32 -0400 Subject: [PATCH 0326/2693] initial implementation --- CMakeLists.txt | 51 +++ README.md | 42 ++ cmake/reactorConfig.cmake.in | 13 + include/xo/reactor/AbstractEventProcessor.hpp | 50 +++ include/xo/reactor/AbstractSink.hpp | 71 ++++ include/xo/reactor/AbstractSource.hpp | 94 +++++ include/xo/reactor/DirectSource.hpp | 19 + include/xo/reactor/DirectSourcePtr.hpp | 25 ++ include/xo/reactor/EventSource.hpp | 25 ++ include/xo/reactor/EventStore.hpp | 317 ++++++++++++++++ include/xo/reactor/EventTimeFn.hpp | 38 ++ include/xo/reactor/HeapReducer.hpp | 72 ++++ include/xo/reactor/LastReducer.hpp | 154 ++++++++ include/xo/reactor/PollingReactor.hpp | 44 +++ include/xo/reactor/PolyAdapterSink.hpp | 92 +++++ include/xo/reactor/Reactor.hpp | 63 +++ include/xo/reactor/ReactorSource.hpp | 130 +++++++ include/xo/reactor/Reducer.hpp | 33 ++ include/xo/reactor/SecondarySource.hpp | 359 ++++++++++++++++++ include/xo/reactor/Sink.hpp | 222 +++++++++++ include/xo/reactor/init_reactor.hpp | 20 + src/reactor/AbstractEventProcessor.cpp | 93 +++++ src/reactor/AbstractSource.cpp | 84 ++++ src/reactor/CMakeLists.txt | 21 + src/reactor/PollingReactor.cpp | 88 +++++ src/reactor/Reactor.cpp | 26 ++ src/reactor/ReactorSource.cpp | 34 ++ src/reactor/Sink.cpp | 18 + src/reactor/init_reactor.cpp | 31 ++ 29 files changed, 2329 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/reactorConfig.cmake.in create mode 100644 include/xo/reactor/AbstractEventProcessor.hpp create mode 100644 include/xo/reactor/AbstractSink.hpp create mode 100644 include/xo/reactor/AbstractSource.hpp create mode 100644 include/xo/reactor/DirectSource.hpp create mode 100644 include/xo/reactor/DirectSourcePtr.hpp create mode 100644 include/xo/reactor/EventSource.hpp create mode 100644 include/xo/reactor/EventStore.hpp create mode 100644 include/xo/reactor/EventTimeFn.hpp create mode 100644 include/xo/reactor/HeapReducer.hpp create mode 100644 include/xo/reactor/LastReducer.hpp create mode 100644 include/xo/reactor/PollingReactor.hpp create mode 100644 include/xo/reactor/PolyAdapterSink.hpp create mode 100644 include/xo/reactor/Reactor.hpp create mode 100644 include/xo/reactor/ReactorSource.hpp create mode 100644 include/xo/reactor/Reducer.hpp create mode 100644 include/xo/reactor/SecondarySource.hpp create mode 100644 include/xo/reactor/Sink.hpp create mode 100644 include/xo/reactor/init_reactor.hpp create mode 100644 src/reactor/AbstractEventProcessor.cpp create mode 100644 src/reactor/AbstractSource.cpp create mode 100644 src/reactor/CMakeLists.txt create mode 100644 src/reactor/PollingReactor.cpp create mode 100644 src/reactor/Reactor.cpp create mode 100644 src/reactor/ReactorSource.cpp create mode 100644 src/reactor/Sink.cpp create mode 100644 src/reactor/init_reactor.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..9bbe8050 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,51 @@ +# xo-reactor/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(reactor VERSION 0.1) +enable_language(CXX) + +# common XO cmake macros (see proj/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() +# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) +add_code_coverage() +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) + +# ---------------------------------------------------------------- +# c++ settings + +# one-time project-specific c++ flags. usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- + +add_subdirectory(src/reactor) + +# ---------------------------------------------------------------- +# provide find_pacakge() support for reactor customers + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# install .hpp files + +xo_install_include_tree() + +# end CMakeLists.txt diff --git a/README.md b/README.md new file mode 100644 index 00000000..02b7e7a2 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# reactor library + +in-memory queuing system + +# dependencies + +build+install these first + +- xo-reflect [github.com/Rconybea/xo-reflect] +- xo-callback [github.com/Rconybea/xo-callback] + +# build + install + +# build +``` +$ cd reactor +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +# build for unit test coverage +``` +$ cd xo-reactor +$ mkdir ccov +$ cd ccov +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + +# LSP support + +LSP looks for compile commands in the root of the source tree; +cmake creates them in the root of its build directory. + +``` +$ cd xo-reactor +$ ln -s build/compile_commands.json +``` diff --git a/cmake/reactorConfig.cmake.in b/cmake/reactorConfig.cmake.in new file mode 100644 index 00000000..5456d16b --- /dev/null +++ b/cmake/reactorConfig.cmake.in @@ -0,0 +1,13 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# note: changes to find_dependency() calls here +# must coordinate with xo_dependency() calls +# in xo-reactor/src/reactor/CMakeLists.txt +# +find_dependency(reflect) +find_dependency(callback) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/reactor/AbstractEventProcessor.hpp b/include/xo/reactor/AbstractEventProcessor.hpp new file mode 100644 index 00000000..820ab0ae --- /dev/null +++ b/include/xo/reactor/AbstractEventProcessor.hpp @@ -0,0 +1,50 @@ +/* @file AbstractEventProcessor.hpp */ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" +#include +#include +#include + +namespace xo { + namespace reactor { + /* common base class for {AbstractSource, AbstractSink}. + * An event processor can be: + * 1. an event source (inherits AbstractSource) + * 2. an event sink (inherits AbstractSink) + * 3. both source+sink (inherits both) + */ + class AbstractEventProcessor : virtual public ref::Refcount { + public: + /* reporting name for this source. ideally unique, but not required */ + virtual std::string const & name() const = 0; + /* set .name */ + virtual void set_name(std::string const & x) = 0; + + /* find all event processors ep reachable from x (i.e. downstream from x). + * report each such ep exactly once + */ + static std::vector> map_network(ref::rp const & x); + + /* visit direct downstream consumers c[i] of this event processor. + * call ep(c[i]) for each such consumer. + */ + virtual void visit_direct_consumers(std::function ep)> const & fn) = 0; + + /* write representation to stream */ + virtual void display(std::ostream & os) const = 0; + /* human-readable string identifying this source */ + virtual std::string display_string() const; + }; /*AbstractEventProcessor*/ + + inline std::ostream & + operator<<(std::ostream & os, AbstractEventProcessor const & src) { + src.display(os); + return os; + } /*operator<<*/ + + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end AbstractEventProcessor.hpp */ diff --git a/include/xo/reactor/AbstractSink.hpp b/include/xo/reactor/AbstractSink.hpp new file mode 100644 index 00000000..e855eda8 --- /dev/null +++ b/include/xo/reactor/AbstractSink.hpp @@ -0,0 +1,71 @@ +/* @file AbstractSink.hpp */ + +#pragma once + +#include "AbstractSource.hpp" +#include "xo/reflect/TaggedPtr.hpp" +#include "xo/reflect/TypeDescr.hpp" +//#include "time/Time.hpp" +#include "xo/indentlog/print/tag.hpp" +#include "xo/cxxutil/demangle.hpp" +#include + +namespace xo { + namespace reactor { + /* an event consumer. + * note that event representation is not specified, + * this helps avoid mandating a type hierarchy for events + */ + class AbstractSink : public virtual AbstractEventProcessor { + public: + using TypeDescr = reflect::TypeDescr; + using TaggedPtr = reflect::TaggedPtr; + + public: + virtual ~AbstractSink() = default; + + /* if true: sources may produce events of any reflected type. + * sink will accept such events using .notify_ev_tp() + * for example see web_util/WebsocketSink + * + * if false (common): souce is expected to to produce events of + * a single type, specified by .sink_ev_type() + * .notify_ev_tp() will downcast to that type. + * for example see reactor/Sink1 + * + * polymorphic sinks pay for runtime polymorphism + * (since WebsocketSink sends events in json format this is + * expected to be negligible compared to message formatting) + */ + virtual bool allow_polymorphic_source() const = 0; + + /* identify datatype for items expected by this sink */ + virtual TypeDescr sink_ev_type() const = 0; + + /* true iff this sink accepts volatile events. + * volatile events are events that may be modified + * or destroyed after being delivered to this sink. + * + * For example KalmanFilterSvc accepts volatile events, + * but EventStore requires non-volatile events. + */ + virtual bool allow_volatile_source() const = 0; + + /* counts lifetime #of incoming events for this sink */ + virtual uint32_t n_in_ev() const = 0; + + /* attach an input source. + * typically this means calling src.add_callback() + * with a function thats calls a .notify_xxx() method + * on this Sink + */ + virtual void attach_source(ref::rp const & src) = 0; + + /* accept incoming event, given by tagged pointer */ + virtual void notify_ev_tp(TaggedPtr const & ev_tp) = 0; + }; /*AbstractSink*/ + + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end AbstractSink.hpp */ diff --git a/include/xo/reactor/AbstractSource.hpp b/include/xo/reactor/AbstractSource.hpp new file mode 100644 index 00000000..ed31d04f --- /dev/null +++ b/include/xo/reactor/AbstractSource.hpp @@ -0,0 +1,94 @@ +/* @file AbstractSource.hpp */ + +#pragma once + +#include "AbstractEventProcessor.hpp" +#include "xo/reflect/TypeDescr.hpp" +#include "xo/callback/CallbackSet.hpp" +#include "xo/refcnt/Refcounted.hpp" +#include + +namespace xo { + namespace web { class StreamEndpointDescr; } + + namespace reactor { + class AbstractSink; + + template + class Sink1; + + /* abstract api for a source of events. + * Event representation is left open: Sources and Sinks + * need to have compatible event representations, + * and coordination is left to such (Source, Sink) pairs. + * + * See ReactorSource, for example + * + * Typically a Source will have one or more .add_callback() + * methods, for listening to source events + */ + class AbstractSource : public virtual AbstractEventProcessor { + public: + using StreamEndpointDescr = web::StreamEndpointDescr; + using TypeDescr = reflect::TypeDescr; + using CallbackId = fn::CallbackId; + + public: + /* identify datatype for items delivered by this source */ + virtual TypeDescr source_ev_type() const = 0; + + /* if true: event objects (see .source_ev_type()) + * maybe overwritten between callbacks. + * A sink that wants to capture events + * (e.g. EventStore<>) will need to deep-copy them + * if false: event objects are preserved between callbacks. + */ + virtual bool is_volatile() const = 0; + + /* counts #of outbound events ready for delivery, + * but not yet sent */ + virtual uint32_t n_queued_out_ev() const = 0; + /* counts lifetime #of events delivered. + * see also AbstractSink.n_in_ev + */ + virtual uint32_t n_out_ev() const = 0; + + /* if true, simulator will report interaction with this source */ + virtual bool debug_sim_flag() const = 0; + /* set .trace_sim_flag */ + virtual void set_debug_sim_flag(bool x) = 0; + + virtual CallbackId attach_sink(ref::rp const & sink) = 0; + virtual void detach_sink(CallbackId id) = 0; + + /* endpoint for a websocket subscriber; + * subscriber delivers events produced by this source + */ + StreamEndpointDescr stream_endpoint_descr(std::string const & url_prefix); + + /* typically expect events to be delivered using a reactor or simulator. + * (for example see reactor/Reactor, simulator/Simulator); + * reactor allocates cpu, and controls event ordering across sources + * when there are multiple sources. + * + * However, also possible for user code to invoke .deliver_one() directly. + * Beware, may get unpredictable results if attempt to do this on a source + * that's also attached to a reactor. + */ + virtual std::uint64_t deliver_one() = 0; + + /* convenience: call .deliver_one() n times, return sum of results */ + std::uint64_t deliver_n(uint64_t n); + + /* convenience: call .deliver_one() until it returns 0 + * (beware of inexhaustible sources!) + */ + std::uint64_t deliver_all(); + }; /*AbstractSource*/ + + using AbstractSourcePtr = ref::rp; + + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end AbstractSource.hpp */ diff --git a/include/xo/reactor/DirectSource.hpp b/include/xo/reactor/DirectSource.hpp new file mode 100644 index 00000000..03710281 --- /dev/null +++ b/include/xo/reactor/DirectSource.hpp @@ -0,0 +1,19 @@ +/* @file DirectSource.hpp */ + +#pragma once + +#include "time/Time.hpp" +#include "reactor/Sink.hpp" +#include "reactor/EventSource.hpp" +#include "reactor/HeapReducer.hpp" +//#include "reactor/LastReducer.hpp" +#include "reactor/Reactor.hpp" +#include "callback/CallbackSet.hpp" + +namespace xo { + namespace reactor { + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end DirectSource.hpp */ + diff --git a/include/xo/reactor/DirectSourcePtr.hpp b/include/xo/reactor/DirectSourcePtr.hpp new file mode 100644 index 00000000..db9c7734 --- /dev/null +++ b/include/xo/reactor/DirectSourcePtr.hpp @@ -0,0 +1,25 @@ +/* @file DirectSourcePtr.hpp */ + +#pragma once + +#include "reactor/SecondarySource.hpp" +#include "reactor/LastReducer.hpp" +#include "reactor/EventTimeFn.hpp" + +namespace xo { + namespace reactor { + template + using DirectSource = SecondarySource>>; + + /* use when Event is ref::rp for some T */ + template + using DirectSourcePtr = SecondarySource>>; + + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end DirectSourcePtr.hpp */ diff --git a/include/xo/reactor/EventSource.hpp b/include/xo/reactor/EventSource.hpp new file mode 100644 index 00000000..a60297f7 --- /dev/null +++ b/include/xo/reactor/EventSource.hpp @@ -0,0 +1,25 @@ +/* @file EventSource.hpp */ + +#pragma once + +#include "reactor/ReactorSource.hpp" +#include "callback/CallbackSet.hpp" + +namespace xo { + namespace reactor { + template + class EventSource : public ReactorSource { + public: + using CallbackId = fn::CallbackId; + + public: + virtual CallbackId add_callback(ref::rp const & cb) = 0; + virtual void remove_callback(CallbackId id) = 0; + }; /*EventSource*/ + + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end EventSource.hpp */ diff --git a/include/xo/reactor/EventStore.hpp b/include/xo/reactor/EventStore.hpp new file mode 100644 index 00000000..62ab7f9a --- /dev/null +++ b/include/xo/reactor/EventStore.hpp @@ -0,0 +1,317 @@ +/* @file EventStore.hpp */ + +#pragma once + +#include "reactor/Reducer.hpp" +#include "reactor/EventTimeFn.hpp" +#include "reactor/Sink.hpp" +#include "web_util/HttpEndpointDescr.hpp" +#include "printjson/PrintJson.hpp" +#include "reflect/Reflect.hpp" +#include "tree/RedBlackTree.hpp" + +namespace xo { + namespace reactor { + + /* abstract event store api */ + class AbstractEventStore : virtual public ref::Refcount { + public: + using PrintJson = xo::json::PrintJson; + using TaggedPtr = xo::reflect::TaggedPtr; + using HttpEndpointDescr = xo::web::HttpEndpointDescr; + using Alist = xo::web::Alist; + + public: + /* true iff .size() == 0 */ + virtual bool empty() const = 0; + + /* #of events currently held in this store */ + virtual std::uint32_t size() const = 0; + + /* TODO: + * 1. TaggedGptr = discriminated union of + * a. TaggedRcptr (i.e. refcounted semantics) + * b. Unique (i.e. unique_ptr semantics) + * c. Exptr (i.e. unowned_ptr semantics) + * d. compact (special-case -- value fits in pointer) + * will need mpl copy/assign stuff for TaggedUnique + * 2. provide .last_n(), .last_dt() + */ + + virtual void http_snapshot(ref::rp const & pjson, + std::ostream * p_os) const = 0; + + /* http endpoint; generates http output for this eventstore */ + virtual HttpEndpointDescr http_endpoint_descr(ref::rp const & pjson, + std::string const & url_prefix) const { + + /* important that lambda contains its own rp; + * reference to stack will not do + */ + ref::rp pjson_rp = pjson; + + auto http_fn = ([this, pjson_rp] + (std::string const & /*uri*/, + Alist const & /*alist*/, + std::ostream * p_os) + { + /* WARNING: race condition here, + * given webserver runs from a separate thread + */ + + this->http_snapshot(pjson_rp, p_os); + }); + + return HttpEndpointDescr(url_prefix + "/snap", http_fn); + } /*http_endpoint_descr*/ + + virtual void clear() = 0; + + virtual void insert_tp(TaggedPtr const & ev_tp) = 0; + }; /*AbstractEventStore*/ + + /* in-memory storage for a set of events. + * + * Require: + * - Event is null-constructible + * - Event is copyable + * - EventTimeFn :: Event -> utc_nanos + * + * inheritance + * ref::Refcount + * ^ + * isa + * | + * reactor::AbstractEventProcessor + req .visit_direct_consumers() + * ^ + * isa + * | + * reactor::AbstractSink + req .sink_ev_type(), .notify_ev() etc. + * ^ + * isa + * | + * reactor::Sink1 + .attach_source(), .sink_ev_type(), + * ^ req .notify_ev() etc + * | + * isa + * | + * reactor::SinkEndpoint + impl .visit_direct_consumers() + * ^ + * isa + * | + * reactor::StructEventStore + .last_n() .last_dt() etc. + */ + template + class EventStoreImpl : public SinkEndpoint, + public AbstractEventStore, + ReducerBase + { + static_assert(EventTimeConcept); + + public: + using utc_nanos = xo::time::utc_nanos; + using nanos = xo::time::nanos; + using EventTree = xo::tree::RedBlackTree>; + using PrintJson = xo::json::PrintJson; + using Alist = xo::web::Alist; + using HttpEndpointDescr = xo::web::HttpEndpointDescr; + + static ref::rp make() { return new EventStoreImpl(); } + + /* visit most recent n events in this store. + * returns #of events actually visited + * + * if events visited are e1 .. en, then: + * (1) en is the most recent recorded event + * (.event_tm(en) is .tree.max_key()) + * (2) there are no events between e(i) and e(i+1) + * (i.e. visit does not skip over any events) + * (3) if v < n, then v = .size(), + * where v is the #of events visited + * + * require: + * - Fn :: (Event -> ) + */ + template + std::uint32_t visit_last_n(std::uint32_t n, Fn && fn) const { + std::uint32_t z = this->size(); + std::uint32_t lo = ((n >= z) ? 0 : z - n); + + typename EventTree::const_iterator lo_ix = this->tree_.find_ith(lo); + typename EventTree::const_iterator hi_ix = this->tree_.cend(); + + return this->visit_range(lo_ix, hi_ix, fn); + } /*visit_last_n*/ + + /* visit suffix of events sufficient to cover interval of length dt. + * visit events in increasing timestamp order. + * + * if events visited are e1 .. en, then: + * (1) en is the most recent recorded event + * (.event_tm(en) is .tree.max_key()) + * (2) there are no events between e(i) and e(i+1) + * (i.e. visit does not skip over any events) + * (3) if .event_tm(en) - .event_tm(e1) < dt, + * then e1 is the earliest recorded event + * (.event_tm(e1) is .tree.min_key()) + * (4) if .event_tm(en) - .event_tm(e1) > dt, + * then (.event_tm(en) - .event_tm(e2)) < dt + * + * |<---------- dt ----------->| + * ^ ^ ^ + * e1 e2 en + */ + template + std::uint32_t visit_last_dt(nanos dt, Fn && fn) const { + if (tree_.empty()) + return 0; + + /* tree not empty -> has max key */ + utc_nanos tn = this->tree_.max_key(); + utc_nanos tk = tn - dt; + + typename EventTree::const_iterator lo_ix = this->tree_.find_glb(tk, true /*closed*/); + typename EventTree::const_iterator hi_ix = this->tree_.end(); + + return this->visit_range(lo_ix, hi_ix, fn); + } /*visit_last_dt*/ + + std::vector last_n(std::uint32_t n) const { + std::vector retval; + + auto fn = [&retval](Event const &ev) { retval.push_back(ev); }; + + this->visit_last_n(n, fn); + + return retval; + } /*last_n*/ + + std::vector last_dt(nanos dt) const { + std::vector retval; + + auto fn = [&retval](Event const &ev) { retval.push_back(ev); }; + + this->visit_last_dt(dt, fn); + + return retval; + } /*last_dt*/ + + void insert(Event const & ev) { this->tree_.insert(typename EventTree::value_type(this->event_tm(ev), ev)); } + + // ----- Inherited from AbstractEventStore ----- + + virtual bool empty() const override { return tree_.empty(); } + virtual std::uint32_t size() const override { return tree_.size(); } + + /* write http snapshot of current state to *p_os */ + virtual void http_snapshot(ref::rp const & pjson, std::ostream * p_os) const override { + using xo::reflect::Reflect; + + /* visit last 100 events; + * write them to *p_os in increasing time order + */ + auto ev_v = this->last_n(100); + + pjson->print_tp(Reflect::make_tp(&ev_v), p_os); + } /*http_snapshot*/ + + virtual void clear() override { this->tree_.clear(); } + + virtual void insert_tp(TaggedPtr const & ev_tp) override { + using xo::xtag; + + Event * p_ev = ev_tp.recover_native(); + + if (p_ev) { + this->insert(*p_ev); + } else { + throw std::runtime_error(tostr("StructEventStore::insert_tp" + ": unable to convert ev_tp to Event", + xtag("ev_tp.type", ev_tp.td()->canonical_name()), + xtag("Event", reflect::type_name()))); + } + } /*insert_tp*/ + + // ----- Inherited from AbstractSink ----- + + virtual uint32_t n_in_ev() const override { return n_in_ev_; } + virtual bool allow_volatile_source() const override { return false; } + virtual void notify_ev(Event const & ev) override { + ++(this->n_in_ev_); + this->insert(ev); + } + + // ----- Inherited from AbstractSource ----- + + virtual void display(std::ostream & os) const override { + using xo::xtag; + + os << "name()) + << xtag("n_in_ev", this->n_in_ev()) + << ">"; + } /*display*/ + + // ----- Inherited from AbstractEventProcessor ----- + + virtual std::string const & name() const override { return name_; } + virtual void set_name(std::string const & x) override { name_ = x; } + + private: + EventStoreImpl() = default; + + template + std::uint32_t visit_range(typename EventTree::const_iterator lo_ix, + typename EventTree::const_iterator hi_ix, + Fn && fn) const { + std::uint32_t n = 0; + for (; lo_ix != hi_ix; ++lo_ix, ++n) { + fn(lo_ix->second); + } + + return n; + } /*visit_range*/ + + private: + /* reporting name for this store */ + std::string name_; + /* fetches per-event timestamp */ + EventTimeFn event_tm_fn_; + /* counts lifetime #of incoming events (see .notify_ev()) */ + uint32_t n_in_ev_ = 0; + /* events stored here */ + EventTree tree_; + }; /*EventStoreImpl*/ + + template + using StructEventStore = EventStoreImpl>; + + template + using PtrEventStore = EventStoreImpl>; + + /* Require: + * EventTimeConcept> + */ + template + class SinkToEventStore : public SinkEndpoint { + public: + using EventStore = StructEventStore; + + public: + SinkToEventStore() = default; + + virtual void notify_ev(T const & ev) override { + store_.insert(ev); + } /*notify_ev*/ + + private: + /* stash remembered events (all of them!) here */ + EventStore store_; + }; /*SinkToEventStore*/ + + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end EventStore.hpp */ diff --git a/include/xo/reactor/EventTimeFn.hpp b/include/xo/reactor/EventTimeFn.hpp new file mode 100644 index 00000000..28c36d0b --- /dev/null +++ b/include/xo/reactor/EventTimeFn.hpp @@ -0,0 +1,38 @@ +/* @file EventTimeFn.hpp */ + +#pragma once + +#include "time/Time.hpp" +#include + +namespace xo { + namespace reactor { + template + concept EventTimeConcept = requires(EventTimeFn etfn, Event ev) { + { etfn(ev) } -> std::same_as; + }; + + template + class StructEventTimeFn { + public: + using event_t = Event; + using utc_nanos = xo::time::utc_nanos; + + public: + utc_nanos operator()(Event const & ev) const { return ev.tm(); } + }; /*StructEventTimeFn*/ + + template + class PtrEventTimeFn { + public: + using event_t = Event; + using utc_nanos = xo::time::utc_nanos; + + public: + utc_nanos operator()(Event const & ev) const { return ev->tm(); } + }; /*PtrEventTimeFn*/ + + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end EventTimeFn.hpp */ diff --git a/include/xo/reactor/HeapReducer.hpp b/include/xo/reactor/HeapReducer.hpp new file mode 100644 index 00000000..5355e056 --- /dev/null +++ b/include/xo/reactor/HeapReducer.hpp @@ -0,0 +1,72 @@ +/* @file HeapReducer.hpp */ + +#pragma once + +#include "reactor/Reducer.hpp" + +namespace xo { + namespace reactor { + /* collect incoming events in a heap, + * ordered by timestamp. + * output events in increasing timestamp order. + * Information preserving in all other respects + * + * Require: + * - Event is null-constructible + * - Event is copyable + * - EventTimeFn :: Event -> utc_nanos + */ + template> + class HeapReducer : public ReducerBase { + public: + using utc_nanos = xo::time::utc_nanos; + public: + HeapReducer() = default; + HeapReducer(EventTimeFn const & evtfn) : ReducerBase(evtfn) {} + + bool is_empty() const { return this->event_heap_.empty(); } + /* require: .is_empty() = false */ + utc_nanos next_tm() const { return this->event_tm(this->event_heap_.front()); } + /* #of events stored in this reducer */ + uint32_t n_event() const { return this->event_heap_.size(); } + + Event const & last_annexed_ev() const { return this->annexed_ev_; } + + void include_event(Event const & ev) { + this->event_heap_.push_back(ev); + std::push_heap(this->event_heap_.begin(), + this->event_heap_.end(), + std::greater()); + } /*include_event*/ + + void include_event(Event && ev) { + this->event_heap_.push_back(std::move(ev)); + std::push_heap(this->event_heap_.begin(), + this->event_heap_.end(), + std::greater()); + } /*include_event*/ + + Event & annex_one() { + this->annexed_ev_ = this->event_heap_.front(); + std::pop_heap(this->event_heap_.begin(), + this->event_heap_.end(), + std::greater()); + this->event_heap_.pop_back(); + + return this->annexed_ev_; + } /*annex_one*/ + + // ----- Inherited from ReducerBase ----- + + // utc_nanos event_tm(Event const & x); + + private: + /* queued Events, in increasing timestamp order */ + std::vector event_heap_; + /* annexed event, removed from .event_heap */ + Event annexed_ev_; + }; /*HeapReducer*/ + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end HeapReducer.hpp */ diff --git a/include/xo/reactor/LastReducer.hpp b/include/xo/reactor/LastReducer.hpp new file mode 100644 index 00000000..86a5913c --- /dev/null +++ b/include/xo/reactor/LastReducer.hpp @@ -0,0 +1,154 @@ +/* @file LastReducer.hpp */ + +#pragma once + +#include "reactor/Reducer.hpp" +#include + +namespace xo { + namespace reactor { + /* implementation record used in LastReducer. + * LastReducer (see below) remembers a single event, + * + will be updated on successive calls to + * LastReducer.include_event() + * + * need to remember the _first_ (& therefore earliest) + * event timestamp in such a wave, since that establishes when simulator + * should deliver the event -- even if event is subsequently + * overwritten. + * + * once event is delivered, timestamp can reset + * + * otherwise if upstream producer sends events with + * future timestamps, can get indefinite postponement + * with simulation clock failing to catch up to event time. + * + */ + + template + class EventRecd { + public: + using utc_nanos = xo::time::utc_nanos; + + public: + EventRecd() = default; + EventRecd(utc_nanos tm, Event ev) : trigger_tm_{tm}, ev_{ev} {} + EventRecd(utc_nanos tm, Event && ev) : trigger_tm_{tm}, ev_{std::move(ev)} {} + + public: + /* if sim, deliver event when simulation clock reaches + * .trigger_tm; .trigger_tm can be earlier than .ev time + */ + utc_nanos trigger_tm_; + /* event to deliver */ + Event ev_; + }; + + /* reducer that just remembers the last event + * + * Require: + * - Event is null-contructible + * - Event is copyable + * + * LastReducer provides reentrancy support. This support doesn't operate + * if Event copy is not deep, e.g. for Event = rpn + * + * .include_event() + * /-------\ -----------------> /------\ + * | empty | | full | + * \-------/ <----------------- \------/ + * . .annex_one() . + * . . + * .is_empty()=true .is_empty()=false + */ + template> + class LastReducer : public ReducerBase { + public: + using utc_nanos = xo::time::utc_nanos; + + public: + LastReducer() = default; + LastReducer(EventTimeFn const & evtfn) : ReducerBase(evtfn) {} + + bool is_empty() const { return empty_flag_; } + /* require: .is_empty() = false */ + utc_nanos next_tm() const { + return this->last_ev_[this->last_ix_].trigger_tm_; + //return this->event_tm(this->last_ev_[this->last_ix_]); + } + /* #of events stored in this reducer (0 or 1) */ + uint32_t n_event() const { return this->empty_flag_ ? 0 : 1; } + + Event const & last_annexed_ev() const { + return this->last_ev_[1 - this->last_ix_].ev_; + } + + EventRecd & include_event_aux(Event const & ev) { + EventRecd & evr + = this->last_ev_[this->last_ix_]; + + if (this->empty_flag_) { + /* evr.trigger_tm will be preserved across + * successive calls to .include_event(); + * until .annex_one() + */ + evr.trigger_tm_ = this->event_tm(ev); + + this->empty_flag_ = false; + } + + return evr; + } /*include_event_aux*/ + + void include_event(Event const & ev) { + EventRecd & evr + = this->include_event_aux(ev); + + evr.ev_ = ev; + } /*include_event*/ + + void include_event(Event && ev) { + EventRecd & evr + = this->include_event_aux(ev); + + evr.ev_ = std::move(ev); + } /*include_event*/ + + Event & annex_one() { + std::uint32_t annexed_ix = this->last_ix_; + + /* since .empty_flag is true, + * next call to .include_event_aux() will + * capture new timestamp + */ + this->empty_flag_ = true; + this->last_ix_ = (1 - this->last_ix_); + + return this->last_ev_[annexed_ix].ev_; + } /*annex_one*/ + + // ----- Inherited from ReducerBase ----- + + //utc_nanos event_tm(Event const & ev) const { return this->event_tm_fn_(ev); } + + private: + /* true when reducer contains 0 queued events, + * not counting any annexed event + */ + bool empty_flag_ = true; + + /* .last_ev[.last_ix] updated by .include_event() + */ + std::uint32_t last_ix_ = 0; + /* remember two events + * (a) a single queued event (updated by .include_event()) + * (b) a single removed event (reported by .annex_one()) + * + * roles of .last_ev[0], .last_ev[1] reverse each time .annex_one() runs + */ + std::array, 2> last_ev_; + }; /*LastReducer*/ + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end LastReducer.hpp */ diff --git a/include/xo/reactor/PollingReactor.hpp b/include/xo/reactor/PollingReactor.hpp new file mode 100644 index 00000000..7dbad4ab --- /dev/null +++ b/include/xo/reactor/PollingReactor.hpp @@ -0,0 +1,44 @@ +/* @file PollingReactor.hpp */ + +#pragma once + +#include "Reactor.hpp" +#include "ReactorSource.hpp" +#include +#include + +namespace xo { + namespace reactor { + /* reactor that runs by polling an ordered set of sources */ + class PollingReactor : public Reactor { + public: + PollingReactor() = default; + + // ----- inherited from Reactor ----- + + virtual bool add_source(ref::brw src) override; + virtual bool remove_source(ref::brw src) override; + virtual std::uint64_t run_one() override; + + private: + /* find non-empty source, starting from .source_v_[start_ix], + * wrapping around to .source_v_[start_ix - 1]. + * + * return index of first available non-empty source, + * or -1 if all sources are empty + */ + std::int64_t find_nonempty_source(std::size_t start_ix); + + private: + /* next source to poll will be .source_v_[.next_ix_] */ + std::size_t next_ix_ = 0; + + /* ordered set of sources (see reactor::Source) + * reactor will poll sources in round-robin order + */ + std::vector source_v_; + }; /*PollingReactor*/ + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end PollingReactor.hpp */ diff --git a/include/xo/reactor/PolyAdapterSink.hpp b/include/xo/reactor/PolyAdapterSink.hpp new file mode 100644 index 00000000..e8f68eb9 --- /dev/null +++ b/include/xo/reactor/PolyAdapterSink.hpp @@ -0,0 +1,92 @@ +/* file PolyAdapterSink.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "Sink.hpp" +#include "xo/reflect/Reflect.hpp" + +namespace xo { + namespace reactor { + /* adapter between a source that delivers a particular event type T, + * and a sink that accepts arbitrarily-typed events via .notify_ev_tp() + * Use this to connect to a polymorphic sink. + * + * Require: + * - .poly_sink.allow_polymorphic_source() + * (ofc. otherwise no point in using PolyAdapterSink) + * - .poly_sink.allow_volatile_source() + * need this bc will be wrapping event with TaggedPtr, + * which doesn't manage event lifetime + */ + template + class PolyAdapterSink : public reactor::Sink1 { + public: + using Reflect = reflect::Reflect; + using TaggedPtr = reflect::TaggedPtr; + + public: + /* named ctor idiom */ + static ref::rp make(ref::rp poly_sink) { + //xo::scope lscope("PolyAdapterSink::make"); + + ref::rp retval(new PolyAdapterSink(poly_sink)); + + //lscope.log("adapter", (void*)retval.get()); + + return retval; + } /*make*/ + + // ----- Inherited from Sink1 ----- + + virtual void notify_ev(T const & ev) override { + //xo::scope lscope("PolyAdapterSink::notify_ev"); + //lscope.log(xo::xtag("ev", ev)); + + TaggedPtr ev_tp = Reflect::make_tp(const_cast(&ev)); + + this->notify_ev_tp(ev_tp); + } /*notify_ev*/ + + // ----- Inherited from AbstractSink ----- + + virtual bool allow_volatile_source() const override { return true; } + virtual uint32_t n_in_ev() const override { return this->poly_sink_->n_in_ev(); } + /* note: ok to do this, however if expecting to use this entry point, + * maybe don't need to interpose PolyAdapterSink ahead of .poly_sink + */ + virtual void notify_ev_tp(TaggedPtr const & ev_tp) override { + //xo::scope lscope("PolyAdapterSink::notify_ev_tp"); + + return this->poly_sink_->notify_ev_tp(ev_tp); + } + + // ----- Inherited from AbstractEventProcessor ----- + + virtual std::string const & name() const override { return this->poly_sink_->name(); } + virtual void set_name(std::string const & x) override { this->poly_sink_->set_name(x); } + virtual void visit_direct_consumers(std::function ep)> const & fn) override { + this->poly_sink_->visit_direct_consumers(fn); + } + virtual void display(std::ostream & os) const override { + using xo::xtag; + os << "()) + << xtag("poly", this->poly_sink_) + << ">"; + } /*display*/ + + private: + PolyAdapterSink(ref::rp poly_sink) : poly_sink_{std::move(poly_sink)} {} + + private: + /* mandate: .poly_sink.allow_polymorphic_source() is true */ + ref::rp poly_sink_; + }; /*PolyAdapterSink*/ + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end PolyAdapterSink.hpp */ diff --git a/include/xo/reactor/Reactor.hpp b/include/xo/reactor/Reactor.hpp new file mode 100644 index 00000000..c56a590c --- /dev/null +++ b/include/xo/reactor/Reactor.hpp @@ -0,0 +1,63 @@ +/* @file Reactor.hpp */ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" +#include + +namespace xo { + namespace reactor { + class ReactorSource; + + /* abtract api for a reactor: + * something that arranges to have work done on a set of Sources. + */ + class Reactor : public ref::Refcount { + public: + virtual ~Reactor() = default; + + /* add source src to this reactor. + * on success, invoke src.notify_reactor_add(this) + * + * returns true if source added; false if already present + */ + virtual bool add_source(ref::brw src) = 0; + + /* remove source src from this reactor. + * source must previously have been added by + * .add_source(src). + * + * on success, invoke src.notify_reactor_remove(this) + * + * returns true if source removed; false if not present + */ + virtual bool remove_source(ref::brw src) = 0; + + /* notification when non-primed source (source with no known events) + * becomes primed (source with at least one event) + */ + virtual void notify_source_primed(ref::brw src) = 0; + + /* dispatch one reactor event, borrowing the calling thread + * amount of work this represents is Source/Sink specific. + * + * returns #of events dispatched (0 or 1) + */ + virtual std::uint64_t run_one() = 0; + + /* borrow calling thread to dispatch reactor events. + * if n is -1, run indefinitely + * otherwise dispatch up to n events. + * n = 0 is a noop + */ + void run_n(int32_t n); + + /* borrow calling thread to run indefinitely. + * suitable implementation for dedicated reactor threads + */ + void run() { this->run_n(-1); } + }; /*Reactor*/ + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end Reactor.hpp */ diff --git a/include/xo/reactor/ReactorSource.hpp b/include/xo/reactor/ReactorSource.hpp new file mode 100644 index 00000000..92f275d4 --- /dev/null +++ b/include/xo/reactor/ReactorSource.hpp @@ -0,0 +1,130 @@ +/* @file ReactorSource.hpp */ + +#pragma once + +#include "AbstractSource.hpp" +//#include "time/Time.hpp" +#include + +namespace xo { + namespace reactor { + class Reactor; + + /* abstract api for a source of events. + * Event representation is left open: Sources and Sinks + * need to have compatible event representations, + * and coordination is left to such (Source, Sink) pairs. + * + * Source->Sink activity may be expected to be mediated by a reactor, + * that implements the Reactor api. + * + * At any time, A Source can be associated with at most one reactor. + * Sources are informed of Reactor<->Source association being + * formed/broken by the + * .notify_reactor_add(), .notify_reactor_remove() + * methods + * + * The source api intends also to provide for simulation. + * There introduces two simulation-specific methods: + * .sim_current_tm() + * .sim_advance_until() + * + * A non-simulation source can implement these as calls to + * .online_current_tm(), .online_advance_until() respectively + * .online_current_tm() aborts since an online source is never exhausted + * .online_advance_until() is a no-op that returns 0 + * + * Loop for consuming from a primary simulation source: + * + * brw s = ...; + * while(!s->is_exhausted()) + * s->deliver_one(); + * + * Secondary sources (sources that depend on other sources) can be + * in a state where they don't know their next event, in which case: + * + * s->is_notprimed() == true + */ + class ReactorSource : public AbstractSource { + public: + using utc_nanos = xo::time::utc_nanos; + + public: + virtual ~ReactorSource() = default; + + /* true if source is currently empty (has 0 events to deliver) */ + virtual bool is_empty() const = 0; + bool is_nonempty() const { return !this->is_empty(); } + + /* true when source knows its next event + * A source that isn't primed is also excluded from simulation + * heap until it becomes primed. + * This make feasible simulation sources that + * depend on other simulation sources + */ + virtual bool is_primed() const { return !this->is_empty(); } + virtual bool is_notprimed() const { return this->is_empty(); } + + /* if true, this source has no events, and will never publish more events + * - for sim, return true for a standalone source that has replayed all events + * - for rt, set during orderly + */ + virtual bool is_exhausted() const = 0; + + /* if this is a simulation source and .is_exhausted is false: + * returns next event time; more precisely, no events exist prior to + * this time. + * + * if sim, and .is_primed = true, + * returns timestamp of next event + */ + virtual utc_nanos sim_current_tm() const = 0; + + /* promise: + * - .current_tm() > tm || .is_notprimed() || .is_exhausted() = true + * - if replay_flag is true, then any events between previous .current_tm() + * and new .current_tm() will have been published + * + * returns #of events delivered. + * does not count events that were skipped, so always returns 0 if + * replay_flag is false + */ + virtual std::uint64_t sim_advance_until(utc_nanos tm, bool replay_flag) = 0; + + /* informs source when it's added to a reactor + + * (see Reactor.add_source()) + */ + virtual void notify_reactor_add(Reactor * /*reactor*/) {} + + /* informs source when it's removed from a reactor + * (see Reactor.remove_source()) + */ + virtual void notify_reactor_remove(Reactor * /*reactor*/) {} + + // ----- Inherited from AbstractSource ----- + + /* deliver one event to attached sink + * interpretation of 'one event' is source-specific; + * could be a collapsed or batched event in practice. + * + * no-op if source is empty. + * + * if sim, promise: + * - new .current_tm >= old .current_tm() || .is_notprimed() || .is_exhausted() + * + * returns #of events delivered. Must be 0 or 1 in this context + */ + virtual std::uint64_t deliver_one() override = 0; + + protected: + /* default implementations for online sources */ + utc_nanos online_current_tm() const; + uint64_t online_advance_until(utc_nanos tm, bool replay_flag); + }; /*ReactorSource*/ + + using ReactorSourcePtr = ref::rp; + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end ReactorSource.hpp */ diff --git a/include/xo/reactor/Reducer.hpp b/include/xo/reactor/Reducer.hpp new file mode 100644 index 00000000..907d9d04 --- /dev/null +++ b/include/xo/reactor/Reducer.hpp @@ -0,0 +1,33 @@ +/* @file Reducer.hpp */ + +#pragma once + +#include "reactor/EventTimeFn.hpp" + +namespace xo { + namespace reactor { + /* LastReducer, HeapReducer inherit ReducerBase */ + template + class ReducerBase { + static_assert(EventTimeConcept); + + public: + using utc_nanos = xo::time::utc_nanos; + + public: + ReducerBase() = default; + ReducerBase(EventTimeFn const & evtfn) : event_tm_fn_{evtfn} {} + + utc_nanos event_tm(Event const & ev) const { return this->event_tm_fn_(ev); } + + private: + /* Event ev = ...; + * .event_tm_fn(ev) -> utc_nanos + * reports event time associated with ev + */ + EventTimeFn event_tm_fn_; + }; /*ReducerBase*/ + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end Reducer.hpp */ diff --git a/include/xo/reactor/SecondarySource.hpp b/include/xo/reactor/SecondarySource.hpp new file mode 100644 index 00000000..398a17bb --- /dev/null +++ b/include/xo/reactor/SecondarySource.hpp @@ -0,0 +1,359 @@ +/* @file SecondarySource.hpp */ + +#pragma once + +#include "time/Time.hpp" +#include "reactor/Sink.hpp" +#include "reactor/DirectSource.hpp" +#include "reactor/Reactor.hpp" +#include "callback/CallbackSet.hpp" +#include "reflect/demangle.hpp" +#include + +namespace xo { + namespace reactor { + /* A passive event source. + * Can use as backend publisher when implementating another + * event processor. + */ + template> + class SecondarySource : public EventSource> { + public: + using EventSink = Sink1; + template + using RpCallbackSet = fn::RpCallbackSet; + using CallbackId = fn::CallbackId; + using TypeDescr = xo::reflect::TypeDescr; + using utc_nanos = xo::time::utc_nanos; + + public: + ~SecondarySource() = default; + + static ref::rp make() { return new SecondarySource(); } + + /* last event delivered from this source -- + * i.e. event in most recent call to .deliver_one_aux() + */ + Event const & last_annexed_ev() const { return this->reducer_.last_annexed_ev(); } + + void notify_upstream_exhausted() { this->upstream_exhausted_ = true; } + + /* make event available to reactor, by adding to internal reducer */ + void notify_secondary_event(Event const & ev) { + /* test if ev is priming, update .current_tm */ + bool is_priming = this->preprocess_secondary_event(ev); + + this->reducer_.include_event(ev); + + this->postprocess_secondary_event(is_priming); + } /*notify_secondary_event*/ + + void notify_secondary_event(Event && ev) { + bool is_priming = this->preprocess_secondary_event(ev); + + this->reducer_.include_event(ev); + + this->postprocess_secondary_event(is_priming); + } /*notify_secondary_event*/ + + template + void notify_secondary_event_v(T const & v) { + using xo::scope; + using xo::xtag; + + if (v.empty()) + return; + + scope log(XO_DEBUG(this->debug_sim_flag_)); + + log && log(xtag("name", this->name())); + + if (this->upstream_exhausted_) { + throw std::runtime_error("SecondarySource::notify_secondary_event_v" + ": not allowed after upstream exhausted"); + } + + uint32_t n_ev = 0; + + for (Event const & ev : v) { + utc_nanos evtm = this->reducer_.event_tm(ev); + + if (this->current_tm_ < evtm) + this->current_tm_ = evtm; + + ++n_ev; + } + + log && log(xtag("T", reflect::type_name()), + xtag("n_ev", n_ev)); + + if (n_ev > 0) { + /* if reducer is empty when .notify_secondary_event_v() begins, + * then reactor/simulator needs to be notified that source is no longer empty + */ + bool is_priming = this->reducer_.is_empty(); + + for (Event const & ev : v) + this->reducer_.include_event(ev); + + Reactor * reactor = this->parent_reactor_; + + if (reactor) { + if (is_priming) { + /* reactor/simulator takes responsibility for delivering events */ + reactor->notify_source_primed(ref::brw::from_native(this)); + } + } else { + /* special case if no reactor: deliver immediately */ + + //this->deliver_one(); + this->deliver_all(); + } + } + } /*notify_secondary_event_v*/ + + // ----- inherited from EventSource ----- + + CallbackId add_callback(ref::rp const & cb) override { + return this->cb_set_.add_callback(cb); + } /*add_callback*/ + + void remove_callback(CallbackId id) override { + this->cb_set_.remove_callback(id); + } /*remove_callback*/ + + // ----- inherited from ReactorSource ----- + + virtual bool is_empty() const override { return this->reducer_.is_empty(); } + virtual bool is_exhausted() const override { return this->upstream_exhausted_ && this->is_empty(); } + + virtual utc_nanos sim_current_tm() const override { + using xo::scope; + using xo::xtag; + + if (this->reducer_.is_empty()) { + /* this is a tricky case. + * it means this source doesn't + * _know_ specific next event yet; however new events + * may appear at any time by way of .notify_event() + * + * If event doesn't know next event, then .current_tm isn't useful + * for establishing priority relative to other sources. + * rely on priming mechanism instead, + * which means that control should never come here. + */ + return this->current_tm_; + } else { + scope log(XO_DEBUG(false /*this->debug_sim_flag_*/), + xtag("name", this->name_), + xtag("next_tm", this->reducer_.next_tm())); + + return this->reducer_.next_tm(); + } + } /*sim_current_tm*/ + + virtual std::uint64_t deliver_one() override { + return this->deliver_one_aux(true /*replay_flag*/); + } + + virtual std::uint64_t sim_advance_until(utc_nanos target_tm, + bool replay_flag) override + { + uint64_t retval = 0; + + while (!this->reducer_.is_empty()) { + utc_nanos tm = this->sim_current_tm(); + + if (tm < target_tm) { + retval += this->deliver_one_aux(replay_flag); + } else { + break; + } + } + + return retval; + } /*sim_advance_until*/ + + virtual void notify_reactor_add(Reactor * reactor) override { + assert(!this->parent_reactor_); + + this->parent_reactor_ = reactor; + } /*notify_reactor_add*/ + + virtual void notify_reactor_remove(Reactor * /*reactor*/) override {} + + // ----- inherited from AbstractSource ----- + + virtual TypeDescr source_ev_type() const override { + return reflect::Reflect::require(); + } /*source_ev_type*/ + + virtual uint32_t n_out_ev() const override { return n_out_ev_; } + /* #of events queued for delivery */ + virtual uint32_t n_queued_out_ev() const override { return this->reducer_.n_event(); } + + virtual bool debug_sim_flag() const override { return debug_sim_flag_; } + virtual void set_debug_sim_flag(bool x) override { this->debug_sim_flag_ = x; } + + virtual CallbackId attach_sink(ref::rp const & sink) override { + ref::rp native_sink + = EventSink::require_native("SecondarySource::attach_sink", sink); + + if (native_sink) { + if (!this->is_volatile() + || native_sink->allow_volatile_source()) + { + return this->add_callback(native_sink); + } else { + throw std::runtime_error("SecondarySource::attach_sink" + ": sink requires non-volatile source " + + std::string(reflect::type_name())); + } + } else { + throw std::runtime_error("SecondarySource::attach_sink" + ": expected sink accepting " + + std::string(reflect::type_name())); + } + } /*attach_sink*/ + + virtual void detach_sink(CallbackId id) override { + this->remove_callback(id); + } /*detach_sink*/ + + // ----- Inherited from AbstractEventProcessor ----- + + virtual std::string const & name() const override { return name_; } + virtual void set_name(std::string const & x) override { this->name_ = x; } + + virtual void visit_direct_consumers(std::function ep)> const & fn) override { + + for(auto x : this->cb_set_) + fn(x.fn_.borrow()); + } /*visit_direct_consumers*/ + + private: + /* event book-keeping on receiving an event. + */ + bool preprocess_secondary_event(Event const & ev) + { + if (this->upstream_exhausted_) { + throw std::runtime_error("SecondarySource::notify_secondary_event" + ": not allowed after upstream exhausted"); + } + + utc_nanos evtm = this->reducer_.event_tm(ev); + + if (this->current_tm_ < evtm) + this->current_tm_ = evtm; + + /* if reducer is empty when .notify_event() begins, + * then reactor/simulator needs to be notified that source is no longer empty + */ + bool is_priming = this->reducer_.is_empty(); + + return is_priming; + } /*preprocess_secondary_event*/ + + /* event bookkeeping after receiving an event. + * + * Require: event has been propagated to .reducer + * + * is_priming. true if event causes source to + * become non-empty --> must notify reactor + */ + void postprocess_secondary_event(bool is_priming) { + using xo::scope; + using xo::xtag; + + Reactor * reactor = this->parent_reactor_; + + scope log(XO_DEBUG(this->debug_sim_flag_), + xtag("name", name_), + xtag("reactor", (void*)reactor), + xtag("is_priming", is_priming)); + + if (reactor) { + if (is_priming) { + /* reactor/simulator takes responsibility for delivering events */ + reactor->notify_source_primed(ref::brw::from_native(this)); + } + } else { + /* if no reactor, deliver immediately */ + this->deliver_one(); + } + } /*postprocess_secondary_event*/ + + /* deliver one event from reducer; + * invoke callback whenever replay_flag is true + */ + std::uint64_t deliver_one_aux(bool replay_flag) { + scope log(XO_DEBUG(this->debug_sim_flag_), + xtag("name", this->name_), + xtag("reducer.empty", this->reducer_.is_empty()), + xtag("replay_flag", replay_flag)); + + if (this->reducer_.is_empty()) + return 0; + + /* need to remove event _before_ invoking callbacks; + * callbacks may indirectly call this->notify_secondary_event(), + * modifiying .reducer + * + * reducer may use double-buffering scheme or similar to + * mitigate copying, esp when Event objects are heavy + */ + Event & ev = this->reducer_.annex_one(); + + /* if SecondarySource: + * Event ev = this->event_heap_.front(); + * std::pop_heap(this->event_heap_.begin(), + * this->event_heap_.end(), + * std::greater()); + * this->event_heap_.pop_back(); + */ + + if (replay_flag) { + ++(this->n_out_ev_); + this->cb_set_.invoke(&EventSink::notify_ev, ev); + } + + return 1; + } /*deliver_one_aux*/ + + private: + /* current time for this source */ + utc_nanos current_tm_; + + /* reporting name for this source (use when .debug_sim_flag set) + */ + std::string name_; + + /* if true, reactor/simulator to log interaction with this source + */ + bool debug_sim_flag_ = false; + + /* count lifetime #of outgoing events */ + uint32_t n_out_ev_ = 0; + + /* set this to true, once, to announce that upstream will send + * no more events. see .notify_upstream_exhausted() + */ + bool upstream_exhausted_ = false; + + /* events to be delivered to callbacks. + * multiple events may be collapsed depending on Reducer implementation + */ + Reducer reducer_; + + /* reactor/simulator being used to schedule consumption. if ommitted, + * will borrow thread calling .notify_secondary_event() + */ + Reactor * parent_reactor_ = nullptr; + + /* invoke callbacks in this set to send an outgoing event */ + RpCallbackSet cb_set_; + }; /*SecondarySource*/ + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end SecondarySource.hpp */ diff --git a/include/xo/reactor/Sink.hpp b/include/xo/reactor/Sink.hpp new file mode 100644 index 00000000..2ba561c7 --- /dev/null +++ b/include/xo/reactor/Sink.hpp @@ -0,0 +1,222 @@ +/* @file Sink.hpp */ + +#pragma once + +#include "AbstractSink.hpp" +#include "AbstractSource.hpp" +#include "PolyAdapterSink.hpp" +#include "xo/reflect/Reflect.hpp" +#include "xo/indentlog/print/time.hpp" +#include "xo/indentlog/print/tag.hpp" +#include "xo/cxxutil/demangle.hpp" +#include + +namespace xo { + namespace reactor { + /* Sink for events of type T + * + * inheritance: + * ref::Refcount + * ^ + * isa + * | + * reactor::AbstractEventProcessor + * ^ + * isa + * | + * reactor::AbstractSink + * ^ + * isa + * | + * reactor::Sink1 + */ + template + class Sink1 : public AbstractSink { + public: + using Reflect = reflect::Reflect; + using TypeDescr = reflect::TypeDescr; + + public: + /* convenience: convert abstract sink to Sink1*, + * or throw + */ + static ref::rp> require_native(std::string_view caller, + ref::rp const & sink) + { + using xo::scope; + using xo::xtag; + + /* 1. if sink expects events of type T, + * make direct connection + */ + Sink1 * native_sink = nullptr; + + native_sink = dynamic_cast *>(sink.get()); + + if (native_sink) + return native_sink; + + /* 2. if sink is polymorphic, + * make type-erasing adapter + */ + + if (sink->allow_polymorphic_source()) { +#ifdef DEBUG_NOT_USING + scope lscope("Sink1::require_native: create PolyAdapterSink"); + lscope.log(xtag("caller", caller)); +#endif + + return PolyAdapterSink::make(sink); + } + + if (!native_sink) { +#ifdef DEBUG_EVENT_TYPEINFO + std::type_info const * sink_parent_typeinfo + = sink->parent_typeinfo(); +#endif + + std::size_t src_hashcode = typeid(T).hash_code(); + + throw std::runtime_error(tostr("Sink1::require_native" + ": wanted to sink S, but sink expects T", + xtag("caller", caller), + xtag("T", sink->sink_ev_type()->canonical_name()), + xtag("S", reflect::type_name()), + xtag("required_hashcode", typeid(Sink1).hash_code()), + xtag("required_name", typeid(Sink1).name()), + xtag("src_hashcode", src_hashcode), + xtag("sink_hashcode", sink->sink_ev_type()->typeinfo()->hash_code()) +#ifdef DEBUG_EVENT_TYPEINFO + , xtag("sink_hashcode", sink->item_typeinfo()->hash_code()) + , xtag("sink_parent_hashcode", sink_parent_typeinfo->hash_code()) + , xtag("sink_parent_name", sink_parent_typeinfo->name()) + , xtag("sink.type", sink->self_typename()) + , xtag("sink.parent_type", sink->parent_typename()) +#endif + )); + } + + return native_sink; + } /*require_native*/ + + virtual TypeDescr sink_ev_type() const override { return reflect::Reflect::require(); } + /* accept incoming event */ + virtual void notify_ev(T const & ev) = 0; + + /* invoke these when this sink added to, or removed from, a source */ + virtual void notify_add_callback() {} + virtual void notify_remove_callback() {} + + // ----- inherited from AbstractSink ----- + + /* Sink1 only allows source providing T */ + virtual bool allow_polymorphic_source() const override { return false; } + + virtual void attach_source(ref::rp const & src) override { + src->attach_sink(this); + } /*attach_source*/ + + virtual void notify_ev_tp(TaggedPtr const & ev_tp) override { + using xo::xtag; + + T * p_ev = ev_tp.recover_native(); + + if (p_ev) { + this->notify_ev(*p_ev); + } else { + throw std::runtime_error(tostr("Sink1::notify_ev_tp" + ": unable to convert ev_tp to T", + xtag("ev_tp.type", ev_tp.td()->canonical_name()), + xtag("T", reflect::type_name()))); + } + } /*notify_ev_tp*/ + }; /*Sink1*/ + + /* a sink with no further downstream processors */ + template + class SinkEndpoint : public Sink1 { + public: + // ----- Inherited from AbstractEventProcessor ----- + + virtual std::string const & name() const override { return name_; } + virtual void set_name(std::string const & x) override { name_ = x; } + + virtual void visit_direct_consumers(std::function)> const &) override { + /* *this is not an event source */ + } /*visit_direct_consumers*/ + + private: + /* reporting name for this sink */ + std::string name_; + }; /*SinkEndpoint*/ + + template + class SinkToFunction : public SinkEndpoint { + public: + SinkToFunction(Fn fn) : fn_{std::move(fn)} {} + + /* NOTE: conservative choice here, could templatize on this */ + virtual bool allow_volatile_source() const override { return false; } + virtual uint32_t n_in_ev() const override { return n_in_ev_; } + virtual void notify_ev(T const & ev) override { + ++(this->n_in_ev_); + fn_(ev); + } /*notify_ev*/ + + virtual void display(std::ostream & os) const override { + using xo::xtag; + + os << "name()) + << xtag("n_in_ev", this->n_in_ev()) + << ">"; + } /*display*/ + + private: + Fn fn_; + /* counts lifetime #of incoming events (see .notify_ev()) */ + uint32_t n_in_ev_ = 0; + }; /*SinkToFunction*/ + + /* sink that prints to console */ + template + class SinkToConsole : public SinkEndpoint { + public: + SinkToConsole() {} + + virtual bool allow_volatile_source() const override { return true; } + virtual uint32_t n_in_ev() const override { return n_in_ev_; } + virtual void notify_ev(T const & ev) override { + //using logutil::operator<<; + + ++(this->n_in_ev_); + + std::cout << ev << std::endl; + } /*notify_ev*/ + + virtual void display(std::ostream & os) const override { + using xo::xtag; + + os << "name()) + << xtag("n_in_ev", this->n_in_ev()) + << ">"; + } /*display*/ + + private: + /* reporting name for this sink */ + std::string name_; + /* counts lifetime #of incoming events (see .notify_ev()) */ + uint32_t n_in_ev_ = 0; + }; /*SinkToConsole*/ + +#ifdef NOT_USING + class TemporaryTest { + public: + static ref::rp>> realization_printer(); + }; /*TemporaryTest*/ +#endif + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end Sink.hpp */ diff --git a/include/xo/reactor/init_reactor.hpp b/include/xo/reactor/init_reactor.hpp new file mode 100644 index 00000000..5977d7ad --- /dev/null +++ b/include/xo/reactor/init_reactor.hpp @@ -0,0 +1,20 @@ +/* file init_reactor.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "xo/subsys/Subsystem.hpp" + +namespace xo { + enum S_reactor_tag {}; + + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; +} /*namespace xo*/ + +/* end init_reactor.hpp */ diff --git a/src/reactor/AbstractEventProcessor.cpp b/src/reactor/AbstractEventProcessor.cpp new file mode 100644 index 00000000..e49cc374 --- /dev/null +++ b/src/reactor/AbstractEventProcessor.cpp @@ -0,0 +1,93 @@ +/* @file AbstractEventProcessor.cp */ + +#include "AbstractEventProcessor.hpp" +#include "xo/indentlog/print/tostr.hpp" +#include +#include + +namespace xo { + using ref::rp; + using ref::brw; + using xo::tostr; + using std::uint32_t; + + namespace reactor { + namespace { + /* search all event processors ep reachable (dowstream) from x, + * add to *m; + */ + void + map_network_helper(brw x, + uint32_t * tsort_ix, + std::unordered_map * m) + { + if (m->contains(x.get())) + return; + + auto fn = [tsort_ix, m] + (brw ep) + { + map_network_helper(ep, tsort_ix, m); + }; + + x->visit_direct_consumers(fn); + + /* postorder! */ + (*m)[x.get()] = ++(*tsort_ix); + + } /*map_network_helper*/ + } /*namespace*/ + + std::vector> + AbstractEventProcessor::map_network(rp const & x) + { + std::unordered_map network_map; + + /* index event processors in reverse topological order: + * if B is (directly or indirectly) downstream from A, + * then tsort_ix(B) < tsort_ix(A) + */ + uint32_t tsort_ix = 0; + + /* depth-first traversal, detect and short-circuit on dup paths */ + map_network_helper(x.borrow(), &tsort_ix, &network_map); + + /* invariant: tsort_ix = #of event processors in network */ + uint32_t n = tsort_ix; + + /* network_map, now in a topologically sorted order */ + std::map tsorted_map; + { + for(auto const & x : network_map) { + uint32_t tsort_ix = x.second; + AbstractEventProcessor * ep = x.first; + + tsorted_map[n - tsort_ix] = ep; + } + } + + std::vector> retval; + { + for(auto const & x : tsorted_map) + retval.push_back(x.second); + } + + return retval; + } /*map_network*/ + + void + AbstractEventProcessor::display(std::ostream & os) const + { + os << ""; + } /*display*/ + + std::string + AbstractEventProcessor::display_string() const + { + return tostr(*this); + } /*display_string*/ + + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end AbstractEventProcessor.cpp */ diff --git a/src/reactor/AbstractSource.cpp b/src/reactor/AbstractSource.cpp new file mode 100644 index 00000000..973ec4c6 --- /dev/null +++ b/src/reactor/AbstractSource.cpp @@ -0,0 +1,84 @@ +/* @file AbstractSource.cpp */ + +#include "AbstractSource.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/webutil/StreamEndpointDescr.hpp" +//#include "indentlog/scope.hpp" + +namespace xo { + using xo::web::StreamEndpointDescr; + using xo::reactor::AbstractSink; + using xo::ref::rp; + //using xo::scope; + //using xo::tostr; + + namespace reactor { + StreamEndpointDescr + AbstractSource::stream_endpoint_descr(std::string const & url_prefix) + { + auto subscribe_fn + = ([this] + (rp const & ws_sink) + { + //scope lscope("AbstractSource::stream_endpoint_descr.subscribe_fn"); + + /* ws_sink created by websocket, sends events to websocket as json + * see [websock/WebsocketSink] + */ + return this->attach_sink(ws_sink); + }); + + auto unsubscribe_fn + = ([this] + (CallbackId id) + { + this->detach_sink(id); + }); + + return StreamEndpointDescr(url_prefix, + subscribe_fn, + unsubscribe_fn); + } /*stream_endpoint_descr*/ + + uint64_t + AbstractSource::deliver_n(uint64_t n) + { + uint64_t retval = 0; + + for (uint64_t i=0; ideliver_one(); + + if (n1 == 0) { + /* short-circuit if source has less than n + * events available + */ + break; + } + + retval += n1; + } + + return retval; + } /*deliver_n*/ + + uint64_t + AbstractSource::deliver_all() + { + uint64_t retval = 0; + + for (;;) { + uint64_t n1 = this->deliver_one(); + + if (n1 == 0) + break; + + retval += n1; + } + + return retval; + } /*deliver_all*/ + + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end AbstractSource.cpp */ diff --git a/src/reactor/CMakeLists.txt b/src/reactor/CMakeLists.txt new file mode 100644 index 00000000..6e26ee50 --- /dev/null +++ b/src/reactor/CMakeLists.txt @@ -0,0 +1,21 @@ +# xo-reactor/src/reactor/CMakeLists.txt + +set(SELF_LIB reactor) +set(SELF_SRCS + AbstractEventProcessor.cpp AbstractSource.cpp ReactorSource.cpp + Sink.cpp + Reactor.cpp PollingReactor.cpp + init_reactor.cpp) + +xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) + +# ---------------------------------------------------------------- +# external dependencies + +# note: changes to xo_dependency() calls here +# must coordinate with find_dependency() calls +# in xo-reactor/cmake/reactorConfig.cmake.in +# +xo_dependency(${SELF_LIB} reflect) +xo_dependency(${SELF_LIB} webutil) +xo_dependency(${SELF_LIB} callback) diff --git a/src/reactor/PollingReactor.cpp b/src/reactor/PollingReactor.cpp new file mode 100644 index 00000000..348ca739 --- /dev/null +++ b/src/reactor/PollingReactor.cpp @@ -0,0 +1,88 @@ +/* @file PollingReactor.cpp */ + +#include "PollingReactor.hpp" + +namespace xo { + using ref::brw; + using std::size_t; + using std::uint64_t; + using std::int64_t; + + namespace reactor { + bool + PollingReactor::add_source(brw src) + { + /* make sure src does not already appear in .source_v[] */ + for(ReactorSourcePtr const & x : this->source_v_) { + if(x.get() == src.get()) { + throw std::runtime_error("PollingReactor::add_source; source already present"); + return false; + } + } + + src->notify_reactor_add(this); + + this->source_v_.push_back(src.get()); + + return true; + } /*add_source*/ + + bool + PollingReactor::remove_source(brw src) + { + auto ix = std::find(this->source_v_.begin(), + this->source_v_.end(), + src); + + if(ix != this->source_v_.end()) { + src->notify_reactor_remove(this); + + this->source_v_.erase(ix); + + return true; + } + + return false; + } /*remove_source*/ + + int64_t + PollingReactor::find_nonempty_source(size_t start_ix) + { + size_t z = this->source_v_.size(); + + /* search sources [ix .. z) */ + for(size_t ix = start_ix; ix < z; ++ix) { + brw src = this->source_v_[ix]; + + if(src->is_nonempty()) + return ix; + } + + /* search source [0 .. ix) */ + for(size_t ix = 0, n = std::min(start_ix, z); ix < n; ++ix) { + brw src = this->source_v_[ix]; + + if(src->is_nonempty()) + return ix; + } + + return -1; + } /*find_nonempty_source*/ + + uint64_t + PollingReactor::run_one() + { + int64_t ix = this->find_nonempty_source(this->next_ix_); + + if(ix >= 0) { + brw src = this->source_v_[ix]; + + return src->deliver_one(); + } else { + return 0; + } + } /*run_one*/ + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end PollingReactor.cpp */ diff --git a/src/reactor/Reactor.cpp b/src/reactor/Reactor.cpp new file mode 100644 index 00000000..2ef80e46 --- /dev/null +++ b/src/reactor/Reactor.cpp @@ -0,0 +1,26 @@ +/* file Reactor.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "Reactor.hpp" + +namespace xo { + namespace reactor { + void + Reactor::run_n(int32_t n) + { + if (n == -1) { + for (;;) { + this->run_one(); + } + } else { + for (int32_t i=0; irun_one(); + } + } + } /*run_n*/ + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end Reactor.cpp */ diff --git a/src/reactor/ReactorSource.cpp b/src/reactor/ReactorSource.cpp new file mode 100644 index 00000000..0bb29397 --- /dev/null +++ b/src/reactor/ReactorSource.cpp @@ -0,0 +1,34 @@ +/* @file Source.cpp */ + +#include "ReactorSource.hpp" +#include "xo/indentlog/print/time.hpp" +#include + +namespace xo { + using xo::time::utc_nanos; + + namespace reactor { + utc_nanos + ReactorSource::online_current_tm() const + { + /* for an online source: + * .is_exhausted() must always be false; + * this implies that .sim_current_tm() should + * not be called in the first place + */ + + assert(false); + + return time::timeutil::epoch(); + } /*online_current_tm*/ + + std::uint64_t + ReactorSource::online_advance_until(utc_nanos /*tm*/, + bool /*replay_flag*/) + { + return 0; + } /*online_advance_until*/ + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end Source.cpp */ diff --git a/src/reactor/Sink.cpp b/src/reactor/Sink.cpp new file mode 100644 index 00000000..1dbcdb62 --- /dev/null +++ b/src/reactor/Sink.cpp @@ -0,0 +1,18 @@ +/* @file Sink.cpp */ + +#include "Sink.hpp" +#include "xo/refcnt/Refcounted.hpp" + +namespace xo { + namespace reactor { +#ifdef NOT_USING + ref::rp>> + TemporaryTest::realization_printer() + { + return new SinkToConsole>(); + } /*realization_printer*/ +#endif + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end Sink.cpp */ diff --git a/src/reactor/init_reactor.cpp b/src/reactor/init_reactor.cpp new file mode 100644 index 00000000..b33e4775 --- /dev/null +++ b/src/reactor/init_reactor.cpp @@ -0,0 +1,31 @@ +/* file init_reactor.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "init_reactor.hpp" +#include "xo/reflect/init_reflect.hpp" + +namespace xo { + void + InitSubsys::init() + { + /* TODO: reflect reactor types */ + } /*init*/ + + InitEvidence + InitSubsys::require() + { + InitEvidence retval; + + /* subsystem dependencies for reactor/ */ + retval ^= InitSubsys::require(); + + /* reactor/'s own initialization code */ + retval ^= Subsystem::provide("reactor", &init); + + return retval; + } /*require*/ +} /*namespace xo*/ + +/* end init_reactor.cpp */ From fe9b36140afa0abbc44c435cda254b7724bb230f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 15:21:36 -0400 Subject: [PATCH 0327/2693] + .gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..41983345 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# lsp keep state here +.cache +# typical build directories +build +ccov From 43a08a9628ceb3df96e15a3f6eabbc691c3d85b5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 15:22:31 -0400 Subject: [PATCH 0328/2693] gitignore: + compile_commands.json --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 41983345..8ea1f615 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ # typical build directories build ccov +# for lsp: manual symlink to chosen build directory +compile_commands.json From 03807c5c232e65c5975131b695d946253e11fdc3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 17:42:27 -0400 Subject: [PATCH 0329/2693] build: supply indentlog config to cmake customers --- CMakeLists.txt | 5 ++--- indentlogConfig.cmake.in | 4 ++++ 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 indentlogConfig.cmake.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a780b05..9e53335a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,13 +33,12 @@ add_subdirectory(utest) # header-only library. # see [[https://stackoverflow.com/questions/47718485/install-and-export-interface-only-library-cmake]] # -add_library(indentlog INTERFACE) -xo_include_headeronly_options2(indentlog) +xo_add_headeronly_library(indentlog) # ---------------------------------------------------------------- # standard install + provide find_package() support -xo_install_library2(${PROJECT_NAME}) +xo_install_library2(indentlog) xo_install_include_tree() xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/indentlogConfig.cmake.in b/indentlogConfig.cmake.in new file mode 100644 index 00000000..cc57615e --- /dev/null +++ b/indentlogConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/indentlogTargets.cmake") +check_required_components("@PROJECT_NAME@") From 4c5992ae40dbdf2269b236eab81fd5ab27b1e2fc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 17:43:01 -0400 Subject: [PATCH 0330/2693] cosmetic: tidy comment for atavism --- 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 9e2099cf..98a16304 100644 --- a/include/xo/indentlog/log_config.hpp +++ b/include/xo/indentlog/log_config.hpp @@ -27,7 +27,7 @@ namespace xo { static bool nesting_level_enabled; /* color to use for explicit nesting level */ static color_spec_type nesting_level_color; - /* display style for function names. FS_Simple|FS_Pretty|FS_Streamlined */ + /* display style for function names. function_style:: literal|simple|pretty|streamlined */ static function_style style; /* color to use for function name, on entry/exit (xo::scope creation/destruction) * (ansi color codes, see Select Graphics Rendition subset) From bdde88ebae9f396c3e9f2a9e8e02ae627de35182 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 17:43:17 -0400 Subject: [PATCH 0331/2693] print: pair.hpp: default printer for std::pair<> --- include/xo/indentlog/print/pair.hpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 include/xo/indentlog/print/pair.hpp diff --git a/include/xo/indentlog/print/pair.hpp b/include/xo/indentlog/print/pair.hpp new file mode 100644 index 00000000..0453fba0 --- /dev/null +++ b/include/xo/indentlog/print/pair.hpp @@ -0,0 +1,24 @@ +/* @file pair.hpp */ + +#pragma once + +#include +#include + +namespace std { + template + inline std::ostream & + operator<<(std::ostream & os, + std::pair const & x) + { + os << "[" + << x.first + << " " + << x.second + << "]"; + + return os; + } /*operator<<*/ +} /*namespace std*/ + +/* end pair.hpp */ From 756e6c521f643daa62d3769a5e276f7c6838e07c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 17:43:48 -0400 Subject: [PATCH 0332/2693] function: bugfix: exclude [with T = ...] suffix when printing --- include/xo/indentlog/print/function.hpp | 82 +++++++++++++++---------- utest/function.test.cpp | 2 + 2 files changed, 52 insertions(+), 32 deletions(-) diff --git a/include/xo/indentlog/print/function.hpp b/include/xo/indentlog/print/function.hpp index 2689ef1a..3c0f97a6 100644 --- a/include/xo/indentlog/print/function.hpp +++ b/include/xo/indentlog/print/function.hpp @@ -54,31 +54,35 @@ namespace xo { std::string_view const & pretty() const { return pretty_; } /* e.g. - * <------------------------------------- s2 -------------------------------------> - * <--------------------- s3 -----------------> - * <----- s4 -----> - * std::vector xo::sometemplateclass::fib(int, char**) const + * <----------------------------------------------------- s ----------------------------------------------------------> + * <----------------------------------------- s2 ---------------------------------------> + * <------------------------------------- s3 -------------------------------------> + * <--------------------- s4 -----------------> + * <----- s5 -----> + * std::vector xo::sometemplateclass::fib(int, char**) const [with T = int; with U = char] * ^ ^ * p q * * fib <- .print_aux() */ static void print_simple(std::ostream & os, std::string_view const & s) { - std::size_t p = exclude_const_suffix(s); - std::string_view s2 = s.substr(0, p); /* no const suffix */ - std::size_t q = exclude_return_type(s2); - std::string_view s3 = s2.substr(q); /* no return type */ - std::size_t r = find_toplevel_sep(s3, true /*last_flag*/); - std::string_view s4 = s3.substr(r); + std::string_view s2 = exclude_template_footnote_suffix(s); + std::string_view s3 = exclude_const_suffix(s2) /*no const suffix*/; + std::size_t q = exclude_return_type(s3); + std::string_view s4 = s3.substr(q); /* no return type */ + std::size_t r = find_toplevel_sep(s4, true /*last_flag*/); + std::string_view s5 = s4.substr(r); - print_aux(os, s4); + print_aux(os, s5); } /*print_simple*/ /* e.g. - * <----------------------------------- s2 ---------------------------------------> - * <--------------------- s3 -----------------> - * <----------------- s4 -----------------> - * std::vector xo::sometemplateclass::fib(int, char**) const + * <------------------------------------------------------------- s --------------------------------------------------> + * <----------------------------------------------- s2 ---------------------------------> + * <----------------------------------- s3 ---------------------------------------> + * <--------------------- s4 -----------------> + * <----------------- s5 -----------------> + * std::vector xo::sometemplateclass::fib(int, char**) const [with T = int; with U = char] * ^ ^ ^ * q r p * @@ -86,19 +90,20 @@ namespace xo { * */ static void print_streamlined(std::ostream & os, std::string_view const & s) { - std::size_t p = exclude_const_suffix(s); - std::string_view s2 = s.substr(0, p); /*no const suffix */ - std::size_t q = exclude_return_type(s2); - std::string_view s3 = s2.substr(q); /*no return type*/ - std::size_t r = find_toplevel_sep(s3, false /*!last_flag*/); - std::string_view s4 = s3.substr(r); /*no namespace qualifier (unless function)*/ + std::string_view s2 = exclude_template_footnote_suffix(s); + std::string_view s3 = exclude_const_suffix(s2) /*no const suffix */; + std::size_t q = exclude_return_type(s3); + std::string_view s4 = s3.substr(q); /*no return type*/ + 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: s=[" << s << "], p=" << p << std::endl; - //std::cerr << "print_streamlined: s2=[" << s2 << "], q=" << q << std::endl; - //std::cerr << "print_streamlined: s3=[" << s3 << "], r=" << r << std::endl; - //std::cerr << "print_streamlined: s4=[" << s4 << "]" << 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 << "], p=" << p << " (excluded const suffix)" << std::endl; + //std::cerr << "print_streamlined: s4=[" << s4 << "], q=" << q << " (excluded return type)" << std::endl; + //std::cerr << "print_streamlined: s5=[" << s5 << "], r=" << r << " (excluded ns qualifier)" << std::endl; - print_aux(os, s4); + print_aux(os, s5); } /*print_streamlined*/ private: @@ -130,16 +135,29 @@ namespace xo { return 0; } /*exclude_return_type*/ - static std::size_t exclude_const_suffix(std::string_view const & s) { - constexpr std::uint32_t c_prefix_z = 6 /*strlen(" const")*/; + /* e.g. + * void xo::foo::Foo::notify(const T&) [with T = std::pair; S = xo::foo::Bar] + */ + static std::string_view exclude_template_footnote_suffix(std::string_view const & s) { + /* strategy: + * - left-to-right + * - exclude ' [with '... to end of string + */ + std::size_t p = s.find(" [with "); - if ((s.size() > c_prefix_z) - && (s.substr(s.size() - c_prefix_z) == " const")) + return s.substr(0, p); + } /*exclude_template_footnote_suffix*/ + + static std::string_view exclude_const_suffix(std::string_view const & s) { + constexpr std::uint32_t c_suffix_z = 6 /*strlen(" const")*/; + + if ((s.size() > c_suffix_z) + && (s.substr(s.size() - c_suffix_z) == " const")) { - return s.size() - c_prefix_z; + return s.substr(0, s.size() - c_suffix_z); } - return s.size(); + return s; } /*exclude_const_suffix*/ /* e.g. diff --git a/utest/function.test.cpp b/utest/function.test.cpp index 2a21b077..42d3c9b6 100644 --- a/utest/function.test.cpp +++ b/utest/function.test.cpp @@ -36,6 +36,8 @@ namespace ut { function_tcase(function_style::simple, color_spec_type::none(), "void xo::class::foo() const", "foo"), function_tcase(function_style::pretty, color_spec_type::blue(), "void xo::class::foo() const", "[\033[31;34mvoid xo::class::foo() const\033[0m]"), + + function_tcase(function_style::streamlined, color_spec_type::none(), "void xo::reactor::FifoQueue::notify_ev(const T&) [with T = std::pair > >, long unsigned int>; EvTimeFn = xo::reactor::EventTimeFn > >, long unsigned int> >]", "FifoQueue::notify_ev"), }); TEST_CASE("function", "[function]") { From 0157f8dc04b056bc3dc241cc1eda42d0ecd779f9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 17:46:24 -0400 Subject: [PATCH 0333/2693] initial implementation --- include/xo/reactor/AbstractSource.hpp | 8 +- include/xo/reactor/DirectSource.hpp | 3 +- include/xo/reactor/EventSource.hpp | 30 +- include/xo/reactor/EventTimeFn.hpp | 14 +- include/xo/reactor/HeapReducer.hpp | 112 ++--- include/xo/reactor/PollingReactor.hpp | 56 +-- include/xo/reactor/PolyAdapterSink.hpp | 126 +++--- include/xo/reactor/Reactor.hpp | 98 ++-- include/xo/reactor/ReactorSource.hpp | 208 ++++----- include/xo/reactor/SecondarySource.hpp | 597 +++++++++++++------------ src/reactor/PollingReactor.cpp | 21 +- src/reactor/ReactorSource.cpp | 2 +- 12 files changed, 660 insertions(+), 615 deletions(-) diff --git a/include/xo/reactor/AbstractSource.hpp b/include/xo/reactor/AbstractSource.hpp index ed31d04f..4d995ab2 100644 --- a/include/xo/reactor/AbstractSource.hpp +++ b/include/xo/reactor/AbstractSource.hpp @@ -38,10 +38,16 @@ namespace xo { virtual TypeDescr source_ev_type() const = 0; /* if true: event objects (see .source_ev_type()) - * maybe overwritten between callbacks. + * may be overwritten between callbacks. * A sink that wants to capture events * (e.g. EventStore<>) will need to deep-copy them * if false: event objects are preserved between callbacks. + * + * A source that stores events received from elsewhere (e.g. FifoQueue) + * is probably volatile. + * + * A source that remembers (in explicit memory) every event it produces + * is not volatile */ virtual bool is_volatile() const = 0; diff --git a/include/xo/reactor/DirectSource.hpp b/include/xo/reactor/DirectSource.hpp index 03710281..7187e56d 100644 --- a/include/xo/reactor/DirectSource.hpp +++ b/include/xo/reactor/DirectSource.hpp @@ -2,7 +2,7 @@ #pragma once -#include "time/Time.hpp" +//#include "time/Time.hpp" #include "reactor/Sink.hpp" #include "reactor/EventSource.hpp" #include "reactor/HeapReducer.hpp" @@ -16,4 +16,3 @@ namespace xo { } /*namespace xo*/ /* end DirectSource.hpp */ - diff --git a/include/xo/reactor/EventSource.hpp b/include/xo/reactor/EventSource.hpp index a60297f7..055c7d6b 100644 --- a/include/xo/reactor/EventSource.hpp +++ b/include/xo/reactor/EventSource.hpp @@ -2,24 +2,24 @@ #pragma once -#include "reactor/ReactorSource.hpp" -#include "callback/CallbackSet.hpp" +#include "ReactorSource.hpp" +#include "xo/callback/CallbackSet.hpp" namespace xo { - namespace reactor { - template - class EventSource : public ReactorSource { - public: - using CallbackId = fn::CallbackId; + namespace reactor { + template + class EventSource : public ReactorSource { + public: + using CallbackId = fn::CallbackId; - public: - virtual CallbackId add_callback(ref::rp const & cb) = 0; - virtual void remove_callback(CallbackId id) = 0; - }; /*EventSource*/ - - } /*namespace reactor*/ + public: + virtual CallbackId add_callback(ref::rp const & cb) = 0; + virtual void remove_callback(CallbackId id) = 0; + }; /*EventSource*/ + + } /*namespace reactor*/ } /*namespace xo*/ /* end EventSource.hpp */ diff --git a/include/xo/reactor/EventTimeFn.hpp b/include/xo/reactor/EventTimeFn.hpp index 28c36d0b..ee206d11 100644 --- a/include/xo/reactor/EventTimeFn.hpp +++ b/include/xo/reactor/EventTimeFn.hpp @@ -2,7 +2,8 @@ #pragma once -#include "time/Time.hpp" +//#include "time/Time.hpp" +#include "xo/indentlog/timeutil/timeutil.hpp" #include namespace xo { @@ -17,7 +18,7 @@ namespace xo { public: using event_t = Event; using utc_nanos = xo::time::utc_nanos; - + public: utc_nanos operator()(Event const & ev) const { return ev.tm(); } }; /*StructEventTimeFn*/ @@ -32,6 +33,15 @@ namespace xo { utc_nanos operator()(Event const & ev) const { return ev->tm(); } }; /*PtrEventTimeFn*/ + template + class PairEventTimeFn { + public: + using utc_nanos = xo::time::utc_nanos; + using event_t = std::pair; + + public: + utc_nanos operator()(event_t const & ev) const { return ev.first; } + }; /*PairEventTimeFn*/ } /*namespace reactor*/ } /*namespace xo*/ diff --git a/include/xo/reactor/HeapReducer.hpp b/include/xo/reactor/HeapReducer.hpp index 5355e056..7a7456ae 100644 --- a/include/xo/reactor/HeapReducer.hpp +++ b/include/xo/reactor/HeapReducer.hpp @@ -1,72 +1,72 @@ /* @file HeapReducer.hpp */ -#pragma once +#pragma once -#include "reactor/Reducer.hpp" +#include "Reducer.hpp" namespace xo { - namespace reactor { - /* collect incoming events in a heap, - * ordered by timestamp. - * output events in increasing timestamp order. - * Information preserving in all other respects - * - * Require: - * - Event is null-constructible - * - Event is copyable - * - EventTimeFn :: Event -> utc_nanos - */ - template> - class HeapReducer : public ReducerBase { - public: - using utc_nanos = xo::time::utc_nanos; - public: - HeapReducer() = default; - HeapReducer(EventTimeFn const & evtfn) : ReducerBase(evtfn) {} + namespace reactor { + /* collect incoming events in a heap, + * ordered by timestamp. + * output events in increasing timestamp order. + * Information preserving in all other respects + * + * Require: + * - Event is null-constructible + * - Event is copyable + * - EventTimeFn :: Event -> utc_nanos + */ + template> + class HeapReducer : public ReducerBase { + public: + using utc_nanos = xo::time::utc_nanos; + public: + HeapReducer() = default; + HeapReducer(EventTimeFn const & evtfn) : ReducerBase(evtfn) {} - bool is_empty() const { return this->event_heap_.empty(); } - /* require: .is_empty() = false */ - utc_nanos next_tm() const { return this->event_tm(this->event_heap_.front()); } - /* #of events stored in this reducer */ - uint32_t n_event() const { return this->event_heap_.size(); } + bool is_empty() const { return this->event_heap_.empty(); } + /* require: .is_empty() = false */ + utc_nanos next_tm() const { return this->event_tm(this->event_heap_.front()); } + /* #of events stored in this reducer */ + uint32_t n_event() const { return this->event_heap_.size(); } - Event const & last_annexed_ev() const { return this->annexed_ev_; } + Event const & last_annexed_ev() const { return this->annexed_ev_; } - void include_event(Event const & ev) { - this->event_heap_.push_back(ev); - std::push_heap(this->event_heap_.begin(), - this->event_heap_.end(), - std::greater()); - } /*include_event*/ + void include_event(Event const & ev) { + this->event_heap_.push_back(ev); + std::push_heap(this->event_heap_.begin(), + this->event_heap_.end(), + std::greater()); + } /*include_event*/ - void include_event(Event && ev) { - this->event_heap_.push_back(std::move(ev)); - std::push_heap(this->event_heap_.begin(), - this->event_heap_.end(), - std::greater()); - } /*include_event*/ + void include_event(Event && ev) { + this->event_heap_.push_back(std::move(ev)); + std::push_heap(this->event_heap_.begin(), + this->event_heap_.end(), + std::greater()); + } /*include_event*/ - Event & annex_one() { - this->annexed_ev_ = this->event_heap_.front(); - std::pop_heap(this->event_heap_.begin(), - this->event_heap_.end(), - std::greater()); - this->event_heap_.pop_back(); + Event & annex_one() { + this->annexed_ev_ = this->event_heap_.front(); + std::pop_heap(this->event_heap_.begin(), + this->event_heap_.end(), + std::greater()); + this->event_heap_.pop_back(); - return this->annexed_ev_; - } /*annex_one*/ + return this->annexed_ev_; + } /*annex_one*/ - // ----- Inherited from ReducerBase ----- + // ----- Inherited from ReducerBase ----- - // utc_nanos event_tm(Event const & x); - - private: - /* queued Events, in increasing timestamp order */ - std::vector event_heap_; - /* annexed event, removed from .event_heap */ - Event annexed_ev_; - }; /*HeapReducer*/ - } /*namespace reactor*/ + // utc_nanos event_tm(Event const & x); + + private: + /* queued Events, in increasing timestamp order */ + std::vector event_heap_; + /* annexed event, removed from .event_heap */ + Event annexed_ev_; + }; /*HeapReducer*/ + } /*namespace reactor*/ } /*namespace xo*/ /* end HeapReducer.hpp */ diff --git a/include/xo/reactor/PollingReactor.hpp b/include/xo/reactor/PollingReactor.hpp index 7dbad4ab..8cbe2c2f 100644 --- a/include/xo/reactor/PollingReactor.hpp +++ b/include/xo/reactor/PollingReactor.hpp @@ -8,37 +8,41 @@ #include namespace xo { - namespace reactor { - /* reactor that runs by polling an ordered set of sources */ - class PollingReactor : public Reactor { - public: - PollingReactor() = default; + namespace reactor { + /* reactor that runs by polling an ordered set of sources */ + class PollingReactor : public Reactor { + public: + /* named ctor idiom */ + static ref::rp make() { return new PollingReactor(); } - // ----- inherited from Reactor ----- + // ----- inherited from Reactor ----- - virtual bool add_source(ref::brw src) override; - virtual bool remove_source(ref::brw src) override; - virtual std::uint64_t run_one() override; + virtual bool add_source(ref::brw src) override; + virtual bool remove_source(ref::brw src) override; + virtual void notify_source_primed(ref::brw src) override; + virtual std::uint64_t run_one() override; - private: - /* find non-empty source, starting from .source_v_[start_ix], - * wrapping around to .source_v_[start_ix - 1]. - * - * return index of first available non-empty source, - * or -1 if all sources are empty - */ - std::int64_t find_nonempty_source(std::size_t start_ix); + private: + PollingReactor() = default; - private: - /* next source to poll will be .source_v_[.next_ix_] */ - std::size_t next_ix_ = 0; + /* find non-empty source, starting from .source_v_[start_ix], + * wrapping around to .source_v_[start_ix - 1]. + * + * return index of first available non-empty source, + * or -1 if all sources are empty + */ + std::int64_t find_nonempty_source(std::size_t start_ix); - /* ordered set of sources (see reactor::Source) - * reactor will poll sources in round-robin order - */ - std::vector source_v_; - }; /*PollingReactor*/ - } /*namespace reactor*/ + private: + /* next source to poll will be .source_v_[.next_ix_] */ + std::size_t next_ix_ = 0; + + /* ordered set of sources (see reactor::Source) + * reactor will poll sources in round-robin order + */ + std::vector source_v_; + }; /*PollingReactor*/ + } /*namespace reactor*/ } /*namespace xo*/ /* end PollingReactor.hpp */ diff --git a/include/xo/reactor/PolyAdapterSink.hpp b/include/xo/reactor/PolyAdapterSink.hpp index e8f68eb9..60ec4ccf 100644 --- a/include/xo/reactor/PolyAdapterSink.hpp +++ b/include/xo/reactor/PolyAdapterSink.hpp @@ -9,84 +9,84 @@ #include "xo/reflect/Reflect.hpp" namespace xo { - namespace reactor { - /* adapter between a source that delivers a particular event type T, - * and a sink that accepts arbitrarily-typed events via .notify_ev_tp() - * Use this to connect to a polymorphic sink. - * - * Require: - * - .poly_sink.allow_polymorphic_source() - * (ofc. otherwise no point in using PolyAdapterSink) - * - .poly_sink.allow_volatile_source() - * need this bc will be wrapping event with TaggedPtr, - * which doesn't manage event lifetime - */ - template - class PolyAdapterSink : public reactor::Sink1 { - public: - using Reflect = reflect::Reflect; - using TaggedPtr = reflect::TaggedPtr; + namespace reactor { + /* adapter between a source that delivers a particular event type T, + * and a sink that accepts arbitrarily-typed events via .notify_ev_tp() + * Use this to connect to a polymorphic sink. + * + * Require: + * - .poly_sink.allow_polymorphic_source() + * (ofc. otherwise no point in using PolyAdapterSink) + * - .poly_sink.allow_volatile_source() + * need this bc will be wrapping event with TaggedPtr, + * which doesn't manage event lifetime + */ + template + class PolyAdapterSink : public reactor::Sink1 { + public: + using Reflect = reflect::Reflect; + using TaggedPtr = reflect::TaggedPtr; - public: - /* named ctor idiom */ - static ref::rp make(ref::rp poly_sink) { - //xo::scope lscope("PolyAdapterSink::make"); + public: + /* named ctor idiom */ + static ref::rp make(ref::rp poly_sink) { + //xo::scope lscope("PolyAdapterSink::make"); - ref::rp retval(new PolyAdapterSink(poly_sink)); + ref::rp retval(new PolyAdapterSink(poly_sink)); - //lscope.log("adapter", (void*)retval.get()); + //lscope.log("adapter", (void*)retval.get()); - return retval; - } /*make*/ + return retval; + } /*make*/ - // ----- Inherited from Sink1 ----- + // ----- Inherited from Sink1 ----- - virtual void notify_ev(T const & ev) override { - //xo::scope lscope("PolyAdapterSink::notify_ev"); - //lscope.log(xo::xtag("ev", ev)); + virtual void notify_ev(T const & ev) override { + //xo::scope lscope("PolyAdapterSink::notify_ev"); + //lscope.log(xo::xtag("ev", ev)); - TaggedPtr ev_tp = Reflect::make_tp(const_cast(&ev)); + TaggedPtr ev_tp = Reflect::make_tp(const_cast(&ev)); - this->notify_ev_tp(ev_tp); - } /*notify_ev*/ + this->notify_ev_tp(ev_tp); + } /*notify_ev*/ - // ----- Inherited from AbstractSink ----- + // ----- Inherited from AbstractSink ----- - virtual bool allow_volatile_source() const override { return true; } - virtual uint32_t n_in_ev() const override { return this->poly_sink_->n_in_ev(); } - /* note: ok to do this, however if expecting to use this entry point, - * maybe don't need to interpose PolyAdapterSink ahead of .poly_sink - */ - virtual void notify_ev_tp(TaggedPtr const & ev_tp) override { - //xo::scope lscope("PolyAdapterSink::notify_ev_tp"); + virtual bool allow_volatile_source() const override { return true; } + virtual uint32_t n_in_ev() const override { return this->poly_sink_->n_in_ev(); } + /* note: ok to do this, however if expecting to use this entry point, + * maybe don't need to interpose PolyAdapterSink ahead of .poly_sink + */ + virtual void notify_ev_tp(TaggedPtr const & ev_tp) override { + //xo::scope lscope("PolyAdapterSink::notify_ev_tp"); - return this->poly_sink_->notify_ev_tp(ev_tp); - } + return this->poly_sink_->notify_ev_tp(ev_tp); + } - // ----- Inherited from AbstractEventProcessor ----- + // ----- Inherited from AbstractEventProcessor ----- - virtual std::string const & name() const override { return this->poly_sink_->name(); } - virtual void set_name(std::string const & x) override { this->poly_sink_->set_name(x); } - virtual void visit_direct_consumers(std::function ep)> const & fn) override { - this->poly_sink_->visit_direct_consumers(fn); - } - virtual void display(std::ostream & os) const override { - using xo::xtag; - os << "()) - << xtag("poly", this->poly_sink_) - << ">"; - } /*display*/ + virtual std::string const & name() const override { return this->poly_sink_->name(); } + virtual void set_name(std::string const & x) override { this->poly_sink_->set_name(x); } + virtual void visit_direct_consumers(std::function ep)> const & fn) override { + this->poly_sink_->visit_direct_consumers(fn); + } + virtual void display(std::ostream & os) const override { + using xo::xtag; + os << "()) + << xtag("poly", this->poly_sink_) + << ">"; + } /*display*/ - private: - PolyAdapterSink(ref::rp poly_sink) : poly_sink_{std::move(poly_sink)} {} + private: + PolyAdapterSink(ref::rp poly_sink) : poly_sink_{std::move(poly_sink)} {} - private: - /* mandate: .poly_sink.allow_polymorphic_source() is true */ - ref::rp poly_sink_; - }; /*PolyAdapterSink*/ - } /*namespace reactor*/ + private: + /* mandate: .poly_sink.allow_polymorphic_source() is true */ + ref::rp poly_sink_; + }; /*PolyAdapterSink*/ + } /*namespace reactor*/ } /*namespace xo*/ /* end PolyAdapterSink.hpp */ diff --git a/include/xo/reactor/Reactor.hpp b/include/xo/reactor/Reactor.hpp index c56a590c..453312a4 100644 --- a/include/xo/reactor/Reactor.hpp +++ b/include/xo/reactor/Reactor.hpp @@ -3,61 +3,69 @@ #pragma once #include "xo/refcnt/Refcounted.hpp" +#include "xo/indentlog/log_level.hpp" #include namespace xo { - namespace reactor { - class ReactorSource; + namespace reactor { + class ReactorSource; - /* abtract api for a reactor: - * something that arranges to have work done on a set of Sources. - */ - class Reactor : public ref::Refcount { - public: - virtual ~Reactor() = default; + /* abtract api for a reactor: + * something that arranges to have work done on a set of Sources. + */ + class Reactor : public ref::Refcount { + public: + virtual ~Reactor() = default; - /* add source src to this reactor. - * on success, invoke src.notify_reactor_add(this) - * - * returns true if source added; false if already present - */ - virtual bool add_source(ref::brw src) = 0; + log_level loglevel() const { return loglevel_; } + void set_loglevel(log_level loglevel) { loglevel_ = loglevel; } - /* remove source src from this reactor. - * source must previously have been added by - * .add_source(src). - * - * on success, invoke src.notify_reactor_remove(this) - * - * returns true if source removed; false if not present - */ - virtual bool remove_source(ref::brw src) = 0; + /* add source src to this reactor. + * on success, invoke src.notify_reactor_add(this) + * + * returns true if source added; false if already present + */ + virtual bool add_source(ref::brw src) = 0; - /* notification when non-primed source (source with no known events) - * becomes primed (source with at least one event) - */ - virtual void notify_source_primed(ref::brw src) = 0; + /* remove source src from this reactor. + * source must previously have been added by + * .add_source(src). + * + * on success, invoke src.notify_reactor_remove(this) + * + * returns true if source removed; false if not present + */ + virtual bool remove_source(ref::brw src) = 0; - /* dispatch one reactor event, borrowing the calling thread - * amount of work this represents is Source/Sink specific. - * - * returns #of events dispatched (0 or 1) - */ - virtual std::uint64_t run_one() = 0; + /* notification when non-primed source (source with no known events) + * becomes primed (source with at least one event) + */ + virtual void notify_source_primed(ref::brw src) = 0; - /* borrow calling thread to dispatch reactor events. - * if n is -1, run indefinitely - * otherwise dispatch up to n events. - * n = 0 is a noop - */ - void run_n(int32_t n); + /* dispatch one reactor event, borrowing the calling thread + * amount of work this represents is Source/Sink specific. + * + * returns #of events dispatched (0 or 1) + */ + virtual std::uint64_t run_one() = 0; - /* borrow calling thread to run indefinitely. - * suitable implementation for dedicated reactor threads - */ - void run() { this->run_n(-1); } - }; /*Reactor*/ - } /*namespace reactor*/ + /* borrow calling thread to dispatch reactor events. + * if n is -1, run indefinitely + * otherwise dispatch up to n events. + * n = 0 is a noop + */ + void run_n(int32_t n); + + /* borrow calling thread to run indefinitely. + * suitable implementation for dedicated reactor threads + */ + void run() { this->run_n(-1); } + + private: + /* control logging verbosity */ + log_level loglevel_; + }; /*Reactor*/ + } /*namespace reactor*/ } /*namespace xo*/ /* end Reactor.hpp */ diff --git a/include/xo/reactor/ReactorSource.hpp b/include/xo/reactor/ReactorSource.hpp index 92f275d4..8b217a4b 100644 --- a/include/xo/reactor/ReactorSource.hpp +++ b/include/xo/reactor/ReactorSource.hpp @@ -7,124 +7,124 @@ #include namespace xo { - namespace reactor { - class Reactor; + namespace reactor { + class Reactor; - /* abstract api for a source of events. - * Event representation is left open: Sources and Sinks - * need to have compatible event representations, - * and coordination is left to such (Source, Sink) pairs. - * - * Source->Sink activity may be expected to be mediated by a reactor, - * that implements the Reactor api. - * - * At any time, A Source can be associated with at most one reactor. - * Sources are informed of Reactor<->Source association being - * formed/broken by the - * .notify_reactor_add(), .notify_reactor_remove() - * methods - * - * The source api intends also to provide for simulation. - * There introduces two simulation-specific methods: - * .sim_current_tm() - * .sim_advance_until() - * - * A non-simulation source can implement these as calls to - * .online_current_tm(), .online_advance_until() respectively - * .online_current_tm() aborts since an online source is never exhausted - * .online_advance_until() is a no-op that returns 0 - * - * Loop for consuming from a primary simulation source: - * - * brw s = ...; - * while(!s->is_exhausted()) - * s->deliver_one(); - * - * Secondary sources (sources that depend on other sources) can be - * in a state where they don't know their next event, in which case: - * - * s->is_notprimed() == true - */ - class ReactorSource : public AbstractSource { - public: - using utc_nanos = xo::time::utc_nanos; + /* abstract api for a source of events. + * Event representation is left open: Sources and Sinks + * need to have compatible event representations, + * and coordination is left to such (Source, Sink) pairs. + * + * Source->Sink activity may be expected to be mediated by a reactor, + * that implements the Reactor api. + * + * At any time, A Source can be associated with at most one reactor. + * Sources are informed of Reactor<->Source association being + * formed/broken by the + * .notify_reactor_add(), .notify_reactor_remove() + * methods + * + * The source api intends also to provide for simulation. + * There introduces two simulation-specific methods: + * .sim_current_tm() + * .sim_advance_until() + * + * A non-simulation source can implement these as calls to + * .online_current_tm(), .online_advance_until() respectively + * .online_current_tm() aborts since an online source is never exhausted + * .online_advance_until() is a no-op that returns 0 + * + * Loop for consuming from a primary simulation source: + * + * brw s = ...; + * while(!s->is_exhausted()) + * s->deliver_one(); + * + * Secondary sources (sources that depend on other sources) can be + * in a state where they don't know their next event, in which case: + * + * s->is_notprimed() == true + */ + class ReactorSource : public AbstractSource { + public: + using utc_nanos = xo::time::utc_nanos; - public: - virtual ~ReactorSource() = default; + public: + virtual ~ReactorSource() = default; - /* true if source is currently empty (has 0 events to deliver) */ - virtual bool is_empty() const = 0; - bool is_nonempty() const { return !this->is_empty(); } + /* true if source is currently empty (has 0 events to deliver) */ + virtual bool is_empty() const = 0; + bool is_nonempty() const { return !this->is_empty(); } - /* true when source knows its next event - * A source that isn't primed is also excluded from simulation - * heap until it becomes primed. - * This make feasible simulation sources that - * depend on other simulation sources - */ - virtual bool is_primed() const { return !this->is_empty(); } - virtual bool is_notprimed() const { return this->is_empty(); } + /* true when source knows its next event + * A source that isn't primed is also excluded from simulation + * heap until it becomes primed. + * This make feasible simulation sources that + * depend on other simulation sources + */ + virtual bool is_primed() const { return !this->is_empty(); } + virtual bool is_notprimed() const { return this->is_empty(); } - /* if true, this source has no events, and will never publish more events - * - for sim, return true for a standalone source that has replayed all events - * - for rt, set during orderly - */ - virtual bool is_exhausted() const = 0; + /* if true, this source has no events, and will never publish more events + * - for sim, return true for a standalone source that has replayed all events + * - for rt, set during orderly + */ + virtual bool is_exhausted() const = 0; - /* if this is a simulation source and .is_exhausted is false: - * returns next event time; more precisely, no events exist prior to - * this time. - * - * if sim, and .is_primed = true, - * returns timestamp of next event - */ - virtual utc_nanos sim_current_tm() const = 0; + /* if this is a simulation source and .is_exhausted is false: + * returns next event time; more precisely, no events exist prior to + * this time. + * + * if sim, and .is_primed = true, + * returns timestamp of next event + */ + virtual utc_nanos sim_current_tm() const = 0; - /* promise: - * - .current_tm() > tm || .is_notprimed() || .is_exhausted() = true - * - if replay_flag is true, then any events between previous .current_tm() - * and new .current_tm() will have been published - * - * returns #of events delivered. - * does not count events that were skipped, so always returns 0 if - * replay_flag is false - */ - virtual std::uint64_t sim_advance_until(utc_nanos tm, bool replay_flag) = 0; + /* promise: + * - .current_tm() > tm || .is_notprimed() || .is_exhausted() = true + * - if replay_flag is true, then any events between previous .current_tm() + * and new .current_tm() will have been published + * + * returns #of events delivered. + * does not count events that were skipped, so always returns 0 if + * replay_flag is false + */ + virtual std::uint64_t sim_advance_until(utc_nanos tm, bool replay_flag) = 0; - /* informs source when it's added to a reactor + /* informs source when it's added to a reactor - * (see Reactor.add_source()) - */ - virtual void notify_reactor_add(Reactor * /*reactor*/) {} + * (see Reactor.add_source()) + */ + virtual void notify_reactor_add(Reactor * /*reactor*/) {} - /* informs source when it's removed from a reactor - * (see Reactor.remove_source()) - */ - virtual void notify_reactor_remove(Reactor * /*reactor*/) {} + /* informs source when it's removed from a reactor + * (see Reactor.remove_source()) + */ + virtual void notify_reactor_remove(Reactor * /*reactor*/) {} - // ----- Inherited from AbstractSource ----- + // ----- Inherited from AbstractSource ----- - /* deliver one event to attached sink - * interpretation of 'one event' is source-specific; - * could be a collapsed or batched event in practice. - * - * no-op if source is empty. - * - * if sim, promise: - * - new .current_tm >= old .current_tm() || .is_notprimed() || .is_exhausted() - * - * returns #of events delivered. Must be 0 or 1 in this context - */ - virtual std::uint64_t deliver_one() override = 0; + /* deliver one event to attached sink + * interpretation of 'one event' is source-specific; + * could be a collapsed or batched event in practice. + * + * no-op if source is empty. + * + * if sim, promise: + * - new .current_tm >= old .current_tm() || .is_notprimed() || .is_exhausted() + * + * returns #of events delivered. Must be 0 or 1 in this context + */ + virtual std::uint64_t deliver_one() override = 0; - protected: - /* default implementations for online sources */ - utc_nanos online_current_tm() const; - uint64_t online_advance_until(utc_nanos tm, bool replay_flag); - }; /*ReactorSource*/ + protected: + /* default implementations for online sources */ + utc_nanos online_current_tm() const; + uint64_t online_advance_until(utc_nanos tm, bool replay_flag); + }; /*ReactorSource*/ - using ReactorSourcePtr = ref::rp; - } /*namespace reactor*/ + using ReactorSourcePtr = ref::rp; + } /*namespace reactor*/ } /*namespace xo*/ /* end ReactorSource.hpp */ diff --git a/include/xo/reactor/SecondarySource.hpp b/include/xo/reactor/SecondarySource.hpp index 398a17bb..c58c2e92 100644 --- a/include/xo/reactor/SecondarySource.hpp +++ b/include/xo/reactor/SecondarySource.hpp @@ -2,358 +2,359 @@ #pragma once -#include "time/Time.hpp" -#include "reactor/Sink.hpp" -#include "reactor/DirectSource.hpp" -#include "reactor/Reactor.hpp" -#include "callback/CallbackSet.hpp" -#include "reflect/demangle.hpp" +//#include "time/Time.hpp" +#include "Sink.hpp" +//#include "xo/reactor/DirectSource.hpp" +#include "Reactor.hpp" +#include "HeapReducer.hpp" +#include "xo/callback/CallbackSet.hpp" +#include "xo/cxxutil/demangle.hpp" #include namespace xo { - namespace reactor { - /* A passive event source. - * Can use as backend publisher when implementating another - * event processor. - */ - template> - class SecondarySource : public EventSource> { - public: - using EventSink = Sink1; - template - using RpCallbackSet = fn::RpCallbackSet; - using CallbackId = fn::CallbackId; - using TypeDescr = xo::reflect::TypeDescr; - using utc_nanos = xo::time::utc_nanos; + namespace reactor { + /* A passive event source. + * Can use as backend publisher when implementating another + * event processor. + */ + template> + class SecondarySource : public EventSource> { + public: + using EventSink = Sink1; + template + using RpCallbackSet = fn::RpCallbackSet; + using CallbackId = fn::CallbackId; + using TypeDescr = xo::reflect::TypeDescr; + using utc_nanos = xo::time::utc_nanos; - public: - ~SecondarySource() = default; + public: + ~SecondarySource() = default; - static ref::rp make() { return new SecondarySource(); } + static ref::rp make() { return new SecondarySource(); } - /* last event delivered from this source -- - * i.e. event in most recent call to .deliver_one_aux() - */ - Event const & last_annexed_ev() const { return this->reducer_.last_annexed_ev(); } + /* last event delivered from this source -- + * i.e. event in most recent call to .deliver_one_aux() + */ + Event const & last_annexed_ev() const { return this->reducer_.last_annexed_ev(); } - void notify_upstream_exhausted() { this->upstream_exhausted_ = true; } + void notify_upstream_exhausted() { this->upstream_exhausted_ = true; } - /* make event available to reactor, by adding to internal reducer */ - void notify_secondary_event(Event const & ev) { - /* test if ev is priming, update .current_tm */ - bool is_priming = this->preprocess_secondary_event(ev); + /* make event available to reactor, by adding to internal reducer */ + void notify_secondary_event(Event const & ev) { + /* test if ev is priming, update .current_tm */ + bool is_priming = this->preprocess_secondary_event(ev); - this->reducer_.include_event(ev); + this->reducer_.include_event(ev); - this->postprocess_secondary_event(is_priming); - } /*notify_secondary_event*/ + this->postprocess_secondary_event(is_priming); + } /*notify_secondary_event*/ - void notify_secondary_event(Event && ev) { - bool is_priming = this->preprocess_secondary_event(ev); + void notify_secondary_event(Event && ev) { + bool is_priming = this->preprocess_secondary_event(ev); - this->reducer_.include_event(ev); + this->reducer_.include_event(ev); - this->postprocess_secondary_event(is_priming); - } /*notify_secondary_event*/ + this->postprocess_secondary_event(is_priming); + } /*notify_secondary_event*/ - template - void notify_secondary_event_v(T const & v) { - using xo::scope; - using xo::xtag; + template + void notify_secondary_event_v(T const & v) { + using xo::scope; + using xo::xtag; - if (v.empty()) - return; + if (v.empty()) + return; - scope log(XO_DEBUG(this->debug_sim_flag_)); + scope log(XO_DEBUG(this->debug_sim_flag_)); - log && log(xtag("name", this->name())); + log && log(xtag("name", this->name())); - if (this->upstream_exhausted_) { - throw std::runtime_error("SecondarySource::notify_secondary_event_v" - ": not allowed after upstream exhausted"); + if (this->upstream_exhausted_) { + throw std::runtime_error("SecondarySource::notify_secondary_event_v" + ": not allowed after upstream exhausted"); + } + + uint32_t n_ev = 0; + + for (Event const & ev : v) { + utc_nanos evtm = this->reducer_.event_tm(ev); + + if (this->current_tm_ < evtm) + this->current_tm_ = evtm; + + ++n_ev; + } + + log && log(xtag("T", reflect::type_name()), + xtag("n_ev", n_ev)); + + if (n_ev > 0) { + /* if reducer is empty when .notify_secondary_event_v() begins, + * then reactor/simulator needs to be notified that source is no longer empty + */ + bool is_priming = this->reducer_.is_empty(); + + for (Event const & ev : v) + this->reducer_.include_event(ev); + + Reactor * reactor = this->parent_reactor_; + + if (reactor) { + if (is_priming) { + /* reactor/simulator takes responsibility for delivering events */ + reactor->notify_source_primed(ref::brw::from_native(this)); + } + } else { + /* special case if no reactor: deliver immediately */ + + //this->deliver_one(); + this->deliver_all(); + } + } + } /*notify_secondary_event_v*/ + + // ----- inherited from EventSource ----- + + virtual CallbackId add_callback(ref::rp const & cb) override { + return this->cb_set_.add_callback(cb); + } /*add_callback*/ + + virtual void remove_callback(CallbackId id) override { + this->cb_set_.remove_callback(id); + } /*remove_callback*/ + + // ----- inherited from ReactorSource ----- + + virtual bool is_empty() const override { return this->reducer_.is_empty(); } + virtual bool is_exhausted() const override { return this->upstream_exhausted_ && this->is_empty(); } + + virtual utc_nanos sim_current_tm() const override { + using xo::scope; + using xo::xtag; + + if (this->reducer_.is_empty()) { + /* this is a tricky case. + * it means this source doesn't + * _know_ specific next event yet; however new events + * may appear at any time by way of .notify_event() + * + * If event doesn't know next event, then .current_tm isn't useful + * for establishing priority relative to other sources. + * rely on priming mechanism instead, + * which means that control should never come here. + */ + return this->current_tm_; + } else { + scope log(XO_DEBUG(false /*this->debug_sim_flag_*/), + xtag("name", this->name_), + xtag("next_tm", this->reducer_.next_tm())); + + return this->reducer_.next_tm(); + } + } /*sim_current_tm*/ + + virtual std::uint64_t deliver_one() override { + return this->deliver_one_aux(true /*replay_flag*/); } - uint32_t n_ev = 0; + virtual std::uint64_t sim_advance_until(utc_nanos target_tm, + bool replay_flag) override + { + uint64_t retval = 0; - for (Event const & ev : v) { - utc_nanos evtm = this->reducer_.event_tm(ev); + while (!this->reducer_.is_empty()) { + utc_nanos tm = this->sim_current_tm(); - if (this->current_tm_ < evtm) - this->current_tm_ = evtm; + if (tm < target_tm) { + retval += this->deliver_one_aux(replay_flag); + } else { + break; + } + } - ++n_ev; - } + return retval; + } /*sim_advance_until*/ - log && log(xtag("T", reflect::type_name()), - xtag("n_ev", n_ev)); + virtual void notify_reactor_add(Reactor * reactor) override { + assert(!this->parent_reactor_); - if (n_ev > 0) { - /* if reducer is empty when .notify_secondary_event_v() begins, - * then reactor/simulator needs to be notified that source is no longer empty - */ - bool is_priming = this->reducer_.is_empty(); + this->parent_reactor_ = reactor; + } /*notify_reactor_add*/ - for (Event const & ev : v) - this->reducer_.include_event(ev); + virtual void notify_reactor_remove(Reactor * /*reactor*/) override {} + + // ----- inherited from AbstractSource ----- + + virtual TypeDescr source_ev_type() const override { + return reflect::Reflect::require(); + } /*source_ev_type*/ + + virtual uint32_t n_out_ev() const override { return n_out_ev_; } + /* #of events queued for delivery */ + virtual uint32_t n_queued_out_ev() const override { return this->reducer_.n_event(); } + + virtual bool debug_sim_flag() const override { return debug_sim_flag_; } + virtual void set_debug_sim_flag(bool x) override { this->debug_sim_flag_ = x; } + + virtual CallbackId attach_sink(ref::rp const & sink) override { + ref::rp native_sink + = EventSink::require_native("SecondarySource::attach_sink", sink); + + if (native_sink) { + if (!this->is_volatile() + || native_sink->allow_volatile_source()) + { + return this->add_callback(native_sink); + } else { + throw std::runtime_error("SecondarySource::attach_sink" + ": sink requires non-volatile source " + + std::string(reflect::type_name())); + } + } else { + throw std::runtime_error("SecondarySource::attach_sink" + ": expected sink accepting " + + std::string(reflect::type_name())); + } + } /*attach_sink*/ + + virtual void detach_sink(CallbackId id) override { + this->remove_callback(id); + } /*detach_sink*/ + + // ----- Inherited from AbstractEventProcessor ----- + + virtual std::string const & name() const override { return name_; } + virtual void set_name(std::string const & x) override { this->name_ = x; } + + virtual void visit_direct_consumers(std::function ep)> const & fn) override { + + for(auto x : this->cb_set_) + fn(x.fn_.borrow()); + } /*visit_direct_consumers*/ + + private: + /* event book-keeping on receiving an event. + */ + bool preprocess_secondary_event(Event const & ev) + { + if (this->upstream_exhausted_) { + throw std::runtime_error("SecondarySource::notify_secondary_event" + ": not allowed after upstream exhausted"); + } + + utc_nanos evtm = this->reducer_.event_tm(ev); + + if (this->current_tm_ < evtm) + this->current_tm_ = evtm; + + /* if reducer is empty when .notify_event() begins, + * then reactor/simulator needs to be notified that source is no longer empty + */ + bool is_priming = this->reducer_.is_empty(); + + return is_priming; + } /*preprocess_secondary_event*/ + + /* event bookkeeping after receiving an event. + * + * Require: event has been propagated to .reducer + * + * is_priming. true if event causes source to + * become non-empty --> must notify reactor + */ + void postprocess_secondary_event(bool is_priming) { + using xo::scope; + using xo::xtag; Reactor * reactor = this->parent_reactor_; + scope log(XO_DEBUG(this->debug_sim_flag_), + xtag("name", name_), + xtag("reactor", (void*)reactor), + xtag("is_priming", is_priming)); + if (reactor) { if (is_priming) { /* reactor/simulator takes responsibility for delivering events */ reactor->notify_source_primed(ref::brw::from_native(this)); } } else { - /* special case if no reactor: deliver immediately */ - - //this->deliver_one(); - this->deliver_all(); + /* if no reactor, deliver immediately */ + this->deliver_one(); } - } - } /*notify_secondary_event_v*/ + } /*postprocess_secondary_event*/ - // ----- inherited from EventSource ----- - - CallbackId add_callback(ref::rp const & cb) override { - return this->cb_set_.add_callback(cb); - } /*add_callback*/ - - void remove_callback(CallbackId id) override { - this->cb_set_.remove_callback(id); - } /*remove_callback*/ - - // ----- inherited from ReactorSource ----- - - virtual bool is_empty() const override { return this->reducer_.is_empty(); } - virtual bool is_exhausted() const override { return this->upstream_exhausted_ && this->is_empty(); } - - virtual utc_nanos sim_current_tm() const override { - using xo::scope; - using xo::xtag; - - if (this->reducer_.is_empty()) { - /* this is a tricky case. - * it means this source doesn't - * _know_ specific next event yet; however new events - * may appear at any time by way of .notify_event() - * - * If event doesn't know next event, then .current_tm isn't useful - * for establishing priority relative to other sources. - * rely on priming mechanism instead, - * which means that control should never come here. - */ - return this->current_tm_; - } else { - scope log(XO_DEBUG(false /*this->debug_sim_flag_*/), + /* deliver one event from reducer; + * invoke callback whenever replay_flag is true + */ + std::uint64_t deliver_one_aux(bool replay_flag) { + scope log(XO_DEBUG(this->debug_sim_flag_), xtag("name", this->name_), - xtag("next_tm", this->reducer_.next_tm())); + xtag("reducer.empty", this->reducer_.is_empty()), + xtag("replay_flag", replay_flag)); - return this->reducer_.next_tm(); - } - } /*sim_current_tm*/ + if (this->reducer_.is_empty()) + return 0; - virtual std::uint64_t deliver_one() override { - return this->deliver_one_aux(true /*replay_flag*/); - } + /* need to remove event _before_ invoking callbacks; + * callbacks may indirectly call this->notify_secondary_event(), + * modifiying .reducer + * + * reducer may use double-buffering scheme or similar to + * mitigate copying, esp when Event objects are heavy + */ + Event & ev = this->reducer_.annex_one(); - virtual std::uint64_t sim_advance_until(utc_nanos target_tm, - bool replay_flag) override - { - uint64_t retval = 0; + /* if SecondarySource: + * Event ev = this->event_heap_.front(); + * std::pop_heap(this->event_heap_.begin(), + * this->event_heap_.end(), + * std::greater()); + * this->event_heap_.pop_back(); + */ - while (!this->reducer_.is_empty()) { - utc_nanos tm = this->sim_current_tm(); - - if (tm < target_tm) { - retval += this->deliver_one_aux(replay_flag); - } else { - break; - } - } - - return retval; - } /*sim_advance_until*/ - - virtual void notify_reactor_add(Reactor * reactor) override { - assert(!this->parent_reactor_); - - this->parent_reactor_ = reactor; - } /*notify_reactor_add*/ - - virtual void notify_reactor_remove(Reactor * /*reactor*/) override {} - - // ----- inherited from AbstractSource ----- - - virtual TypeDescr source_ev_type() const override { - return reflect::Reflect::require(); - } /*source_ev_type*/ - - virtual uint32_t n_out_ev() const override { return n_out_ev_; } - /* #of events queued for delivery */ - virtual uint32_t n_queued_out_ev() const override { return this->reducer_.n_event(); } - - virtual bool debug_sim_flag() const override { return debug_sim_flag_; } - virtual void set_debug_sim_flag(bool x) override { this->debug_sim_flag_ = x; } - - virtual CallbackId attach_sink(ref::rp const & sink) override { - ref::rp native_sink - = EventSink::require_native("SecondarySource::attach_sink", sink); - - if (native_sink) { - if (!this->is_volatile() - || native_sink->allow_volatile_source()) - { - return this->add_callback(native_sink); - } else { - throw std::runtime_error("SecondarySource::attach_sink" - ": sink requires non-volatile source " - + std::string(reflect::type_name())); - } - } else { - throw std::runtime_error("SecondarySource::attach_sink" - ": expected sink accepting " - + std::string(reflect::type_name())); - } - } /*attach_sink*/ - - virtual void detach_sink(CallbackId id) override { - this->remove_callback(id); - } /*detach_sink*/ - - // ----- Inherited from AbstractEventProcessor ----- - - virtual std::string const & name() const override { return name_; } - virtual void set_name(std::string const & x) override { this->name_ = x; } - - virtual void visit_direct_consumers(std::function ep)> const & fn) override { - - for(auto x : this->cb_set_) - fn(x.fn_.borrow()); - } /*visit_direct_consumers*/ - - private: - /* event book-keeping on receiving an event. - */ - bool preprocess_secondary_event(Event const & ev) - { - if (this->upstream_exhausted_) { - throw std::runtime_error("SecondarySource::notify_secondary_event" - ": not allowed after upstream exhausted"); - } - - utc_nanos evtm = this->reducer_.event_tm(ev); - - if (this->current_tm_ < evtm) - this->current_tm_ = evtm; - - /* if reducer is empty when .notify_event() begins, - * then reactor/simulator needs to be notified that source is no longer empty - */ - bool is_priming = this->reducer_.is_empty(); - - return is_priming; - } /*preprocess_secondary_event*/ - - /* event bookkeeping after receiving an event. - * - * Require: event has been propagated to .reducer - * - * is_priming. true if event causes source to - * become non-empty --> must notify reactor - */ - void postprocess_secondary_event(bool is_priming) { - using xo::scope; - using xo::xtag; - - Reactor * reactor = this->parent_reactor_; - - scope log(XO_DEBUG(this->debug_sim_flag_), - xtag("name", name_), - xtag("reactor", (void*)reactor), - xtag("is_priming", is_priming)); - - if (reactor) { - if (is_priming) { - /* reactor/simulator takes responsibility for delivering events */ - reactor->notify_source_primed(ref::brw::from_native(this)); + if (replay_flag) { + ++(this->n_out_ev_); + this->cb_set_.invoke(&EventSink::notify_ev, ev); } - } else { - /* if no reactor, deliver immediately */ - this->deliver_one(); - } - } /*postprocess_secondary_event*/ - /* deliver one event from reducer; - * invoke callback whenever replay_flag is true - */ - std::uint64_t deliver_one_aux(bool replay_flag) { - scope log(XO_DEBUG(this->debug_sim_flag_), - xtag("name", this->name_), - xtag("reducer.empty", this->reducer_.is_empty()), - xtag("replay_flag", replay_flag)); + return 1; + } /*deliver_one_aux*/ - if (this->reducer_.is_empty()) - return 0; + private: + /* current time for this source */ + utc_nanos current_tm_; - /* need to remove event _before_ invoking callbacks; - * callbacks may indirectly call this->notify_secondary_event(), - * modifiying .reducer - * - * reducer may use double-buffering scheme or similar to - * mitigate copying, esp when Event objects are heavy + /* reporting name for this source (use when .debug_sim_flag set) */ - Event & ev = this->reducer_.annex_one(); + std::string name_; - /* if SecondarySource: - * Event ev = this->event_heap_.front(); - * std::pop_heap(this->event_heap_.begin(), - * this->event_heap_.end(), - * std::greater()); - * this->event_heap_.pop_back(); + /* if true, reactor/simulator to log interaction with this source */ + bool debug_sim_flag_ = false; - if (replay_flag) { - ++(this->n_out_ev_); - this->cb_set_.invoke(&EventSink::notify_ev, ev); - } + /* count lifetime #of outgoing events */ + uint32_t n_out_ev_ = 0; - return 1; - } /*deliver_one_aux*/ + /* set this to true, once, to announce that upstream will send + * no more events. see .notify_upstream_exhausted() + */ + bool upstream_exhausted_ = false; - private: - /* current time for this source */ - utc_nanos current_tm_; + /* events to be delivered to callbacks. + * multiple events may be collapsed depending on Reducer implementation + */ + Reducer reducer_; - /* reporting name for this source (use when .debug_sim_flag set) - */ - std::string name_; + /* reactor/simulator being used to schedule consumption. if ommitted, + * will borrow thread calling .notify_secondary_event() + */ + Reactor * parent_reactor_ = nullptr; - /* if true, reactor/simulator to log interaction with this source - */ - bool debug_sim_flag_ = false; - - /* count lifetime #of outgoing events */ - uint32_t n_out_ev_ = 0; - - /* set this to true, once, to announce that upstream will send - * no more events. see .notify_upstream_exhausted() - */ - bool upstream_exhausted_ = false; - - /* events to be delivered to callbacks. - * multiple events may be collapsed depending on Reducer implementation - */ - Reducer reducer_; - - /* reactor/simulator being used to schedule consumption. if ommitted, - * will borrow thread calling .notify_secondary_event() - */ - Reactor * parent_reactor_ = nullptr; - - /* invoke callbacks in this set to send an outgoing event */ - RpCallbackSet cb_set_; - }; /*SecondarySource*/ - } /*namespace reactor*/ + /* invoke callbacks in this set to send an outgoing event */ + RpCallbackSet cb_set_; + }; /*SecondarySource*/ + } /*namespace reactor*/ } /*namespace xo*/ /* end SecondarySource.hpp */ diff --git a/src/reactor/PollingReactor.cpp b/src/reactor/PollingReactor.cpp index 348ca739..a9c52cb7 100644 --- a/src/reactor/PollingReactor.cpp +++ b/src/reactor/PollingReactor.cpp @@ -45,6 +45,11 @@ namespace xo { return false; } /*remove_source*/ + void + PollingReactor::notify_source_primed(brw) { + /* nothing to do here -- all sources always checked by polling loop */ + } /*notify_source_primed*/ + int64_t PollingReactor::find_nonempty_source(size_t start_ix) { @@ -74,13 +79,25 @@ namespace xo { { int64_t ix = this->find_nonempty_source(this->next_ix_); + scope log(XO_DEBUG(this->loglevel() == log_level::chatty)); + + log && log(xtag("self", this), xtag("src_ix", ix)); + + uint64_t retval = 0; + if(ix >= 0) { brw src = this->source_v_[ix]; - return src->deliver_one(); + log && log(xtag("src.name", src->name())); + + retval = src->deliver_one(); } else { - return 0; + retval = 0; } + + log.end_scope(xtag("retval", retval)); + + return retval; } /*run_one*/ } /*namespace reactor*/ } /*namespace xo*/ diff --git a/src/reactor/ReactorSource.cpp b/src/reactor/ReactorSource.cpp index 0bb29397..5f3735ea 100644 --- a/src/reactor/ReactorSource.cpp +++ b/src/reactor/ReactorSource.cpp @@ -1,4 +1,4 @@ -/* @file Source.cpp */ +/* @file ReactorSource.cpp */ #include "ReactorSource.hpp" #include "xo/indentlog/print/time.hpp" From 1192ef307321d3e4b49af15b9ad45860047e04de Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 17:46:36 -0400 Subject: [PATCH 0334/2693] + EventTimeFn<> template; automate common timestamp patterns --- include/xo/reactor/EventTimeFn2.hpp | 60 +++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 include/xo/reactor/EventTimeFn2.hpp diff --git a/include/xo/reactor/EventTimeFn2.hpp b/include/xo/reactor/EventTimeFn2.hpp new file mode 100644 index 00000000..6d2b0170 --- /dev/null +++ b/include/xo/reactor/EventTimeFn2.hpp @@ -0,0 +1,60 @@ +/* @file EventTimeFn2.hpp */ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" +#include + +namespace xo { + namespace reactor { + template + class EventTimeFn { + public: + using utc_nanos = xo::time::utc_nanos; + using event_t = Event; + + public: + static utc_nanos event_tm(event_t const & ev) { return ev.tm(); } + + utc_nanos operator()(event_t const & ev) const { return EventTimeFn::event_tm(ev); } + }; + + template + class EventTimeFn> { + public: + using utc_nanos = xo::time::utc_nanos; + using event_t = xo::ref::rp; + + public: + static utc_nanos event_tm(event_t const & ev) { return ev->tm(); } + + utc_nanos operator()(event_t const & ev) const { return EventTimeFn::event_tm(ev); } + }; + + template + class EventTimeFn { + public: + using utc_nanos = xo::time::utc_nanos; + using event_t = T*; + + public: + static utc_nanos event_tm(event_t ev) { return ev->tm(); } + + utc_nanos operator()(event_t const & ev) const { return EventTimeFn::event_tm(ev); } + }; + + template + class EventTimeFn> { + public: + using utc_nanos = xo::time::utc_nanos; + using event_t = std::pair; + + public: + static utc_nanos event_tm(event_t const & ev) { return ev.first; } + + utc_nanos operator()(event_t const & ev) const { return EventTimeFn::event_tm(ev); } + }; + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end EventTimeFn2.hpp */ From 1d7de75889a90a90fd30486c65877a7612576609 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 17:47:07 -0400 Subject: [PATCH 0335/2693] + FifoQueue --- include/xo/reactor/FifoQueue.hpp | 267 +++++++++++++++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 include/xo/reactor/FifoQueue.hpp diff --git a/include/xo/reactor/FifoQueue.hpp b/include/xo/reactor/FifoQueue.hpp new file mode 100644 index 00000000..175836b3 --- /dev/null +++ b/include/xo/reactor/FifoQueue.hpp @@ -0,0 +1,267 @@ +/* @file FifoQueue.hpp */ + +#pragma once + +#include "Reactor.hpp" +#include "EventSource.hpp" +#include "Sink.hpp" +#include "EventTimeFn2.hpp" +#include "xo/callback/CallbackSet.hpp" +#include + +namespace xo { + namespace reactor { + /* require: + * T null constructible + * T movable + * + * T satisfies EventTimeConcept + */ + template > + class FifoQueue : public virtual Sink1, public virtual EventSource> { + public: + using EventSink = Sink1; + template + using RpCallbackSet = xo::fn::RpCallbackSet; + using CallbackId = xo::fn::CallbackId; + using Reflect = xo::reflect::Reflect; + using TypeDescr = xo::reflect::TypeDescr; + using utc_nanos = xo::time::utc_nanos; + + public: + static ref::rp make(EvTimeFn evtm_fn = EvTimeFn()) { return new FifoQueue(evtm_fn); } + + // ----- inherited from Sink1 ----- + + virtual void notify_ev(T const & ev) override { + bool is_priming = this->elt_q_.empty(); + + this->elt_q_.push_back(ev); + + ++(this->n_in_ev_); + + if (this->upstream_exhausted_) { + throw std::runtime_error("FifoQueue::notify_ev" + ": not allowed after upstream exhausted"); + } + + utc_nanos tm = evtm_fn_(ev); + + if (this->current_tm_ < tm) + this->current_tm_ = tm; + + Reactor * reactor = this->parent_reactor_; + + scope log(XO_DEBUG(this->debug_sim_flag_), + xtag("name", name_), + xtag("reactor", (void*)reactor), + xtag("is_priming", is_priming)); + + if (reactor) { + if (is_priming) { + /* reactor/simulator takes delivery/sequencing responsibility from here */ + reactor->notify_source_primed(ref::brw::from_native(this)); + } + } else { + /* if no reactor, deliver immediately */ + this->deliver_one(); + } + } /*notify_ev*/ + + // ----- inherited from AbstractSink ----- + + /* we don't care about volatile sources -- fifo queue copies incoming events */ + virtual bool allow_volatile_source() const override { return true; } + + virtual uint32_t n_in_ev() const override { return n_in_ev_; } + + // ----- inherited from ReactorSource ----- + + virtual bool is_empty() const override { return elt_q_.empty(); } + virtual bool is_exhausted() const override { return this->upstream_exhausted_ && this->is_empty(); } + + virtual utc_nanos sim_current_tm() const override { + if (this->elt_q_.empty()) { + /* (in practice control never comes here) + * + * queue doesn't know time of next event yet; + * new events may appear at any time by way of .notify_event() + * + * if queue doesn't know next event, can't use .sim_current_tm + * to establish priority relative to other sources. + * In that case rely instead on priming mechanism; + * priming mechanism implies control should never come here + */ + return this->current_tm_; + } else { + return evtm_fn_(this->elt_q_.front()); + } + } /*sim_current_tm*/ + + virtual uint64_t deliver_one() override { + return this->deliver_one_aux(true /*replay_flag*/); + } /*deliver_one*/ + + virtual uint64_t sim_advance_until(utc_nanos target_tm, + bool replay_flag) override { + uint64_t retval = 0; + + while (!this->elt_q_.empty()) { + utc_nanos tm = evtm_fn_(this->elt_q_.front()); + + if (tm < target_tm) { + retval += this->deliver_one_aux(replay_flag); + } else { + break; + } + } + + return retval; + } /*sim_advance_until*/ + + virtual void notify_reactor_add(Reactor * reactor) override { + assert(!this->parent_reactor_); + + this->parent_reactor_ = reactor; + } /*notify_reactor_add*/ + + virtual void notify_reactor_remove(Reactor *) override { + this->parent_reactor_ = nullptr; + } + + // ----- inherited from AbstractSource ----- + + virtual TypeDescr source_ev_type() const override { return Reflect::require(); } + /* events must be copied objects owned by FifoQueue. + * not expected to be pointers to shared storage or something + */ + virtual bool is_volatile() const override { return false; } + virtual uint32_t n_queued_out_ev() const override { return elt_q_.size(); } + virtual uint32_t n_out_ev() const override { return n_out_ev_; } + virtual bool debug_sim_flag() const override { return debug_sim_flag_; } + virtual void set_debug_sim_flag(bool x) override { this->debug_sim_flag_ = x; } + + virtual CallbackId attach_sink(ref::rp const & sink) override { + ref::rp native_sink + = EventSink::require_native("FifoQueue::attach_sink", sink); + + if (native_sink) { + if (!this->is_volatile() + || native_sink->allow_volatile_source()) + { + return this->add_callback(native_sink); + } else { + throw std::runtime_error("FifoQueue::attach_sink" + ": sink requires non-volatile source " + + std::string(reflect::type_name())); + } + } else { + throw std::runtime_error("FifoQueue::attach_sink" + ": expected sink accepting " + + std::string(reflect::type_name())); + } + } /*attach_sink*/ + + virtual void detach_sink(CallbackId id) override { + this->remove_callback(id); + } + + // ----- inherited from EventSource ----- + + virtual CallbackId add_callback(ref::rp const & cb) override { + return this->cb_set_.add_callback(cb); + } + + virtual void remove_callback(CallbackId id) override { + this->cb_set_.remove_callback(id); + } + + // ----- inherited from AbstractEventProcessor ----- + + virtual std::string const & name() const override { return name_; } + virtual void set_name(std::string const & x) override { this->name_ = x; } + + virtual void visit_direct_consumers(std::function ep)> const & fn) override { + for (auto x : this->cb_set_) + fn(x.fn_.borrow()); + } /*visit_direct_consumers*/ + + /* write human-readable representation to stream */ + virtual void display(std::ostream & os) const override { + os << "()) + << ">"; + } /*display*/ + + private: + FifoQueue(EvTimeFn evtm_fn) : evtm_fn_{std::move(evtm_fn)} {} + + uint64_t deliver_one_aux(bool replay_flag) { + scope log(XO_DEBUG(this->debug_sim_flag_), + xtag("name", this->name_), + xtag("elt_q.size", this->elt_q_.size()), + xtag("replay_flag", replay_flag)); + + if (this->elt_q_.empty()) + return 0; + + /* avoiding copy for efficiently-swappable T */ + T ev; + std::swap(ev, this->elt_q_.front()); + + this->elt_q_.pop_front(); + + if (replay_flag) { + log && log(xtag("deliver-ev", ev), + xtag("elt_q.size", this->elt_q_.size())); + + ++(this->n_out_ev_); + this->cb_set_.invoke(&EventSink::notify_ev, ev); + } + + return 1; + } /*deliver_one_aux*/ + + private: + /* name (ideally unique) for this queue */ + std::string name_; + + /* extract timestamp from an event */ + EvTimeFn evtm_fn_; + + /* if true, simulator/reactor will report interaction with this source */ + bool debug_sim_flag_ = false; + + /* largest event timestamp delivered + * (monotonically increases, event if events received out-of-timestamp-order) + */ + utc_nanos current_tm_; + + /* events waiting for delivery */ + std::deque elt_q_; + + /* lifetime #of events received */ + uint32_t n_in_ev_ = 0; + /* lifetime #of events delivered */ + uint32_t n_out_ev_ = 0; + + /* set to true, once, to announce that upstream will send no more events. + * see .notify_upstream_exhausted() ? + */ + bool upstream_exhausted_ = false; + + /* reactor/simulator being used to schedule event consumption. + * if omitted, borrow calling thread + */ + Reactor * parent_reactor_ = nullptr; + + /* invoke callbacks in this set to deliver queued events */ + RpCallbackSet cb_set_; + + }; /*FifoQueue*/ + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end FifoQueue.hpp */ From 6fd18236eb3733385d22497cac50acbb32bd3c13 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 17:47:19 -0400 Subject: [PATCH 0336/2693] + unit test --- CMakeLists.txt | 1 + utest/CMakeLists.txt | 35 +++++ utest/PollingReactor.test.cpp | 233 ++++++++++++++++++++++++++++++++++ utest/Sink.test.cpp | 100 +++++++++++++++ utest/reactor_utest_main.cpp | 6 + 5 files changed, 375 insertions(+) create mode 100644 utest/CMakeLists.txt create mode 100644 utest/PollingReactor.test.cpp create mode 100644 utest/Sink.test.cpp create mode 100644 utest/reactor_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9bbe8050..5ddd7483 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,7 @@ xo_toplevel_compile_options() # ---------------------------------------------------------------- add_subdirectory(src/reactor) +add_subdirectory(utest) # ---------------------------------------------------------------- # provide find_pacakge() support for reactor customers diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..1ab4b440 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,35 @@ +# build unittest reactor/unittest' + +set(SELF_EXE utest.reactor) +set(SELF_SRCS Sink.test.cpp PollingReactor.test.cpp reactor_utest_main.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) +target_code_coverage(${SELF_EXE} AUTO ALL) + +# ---------------------------------------------------------------- +# internal dependency (on this codebase) + +xo_self_dependency(${SELF_EXE} reactor) + +# ---------------------------------------------------------------- +# external dependencies + +xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) + +# should be getting this via xo_include_options2() + +## ---------------------------------------------------------------- +## make standard directories for std:: includes explicit +## so that +## (1) they appear in compile_commands.json. +## (2) clangd (run from emacs lsp-mode) can find them +## +#if(CMAKE_EXPORT_COMPILE_COMMANDS) +# set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES +# ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +#endif() + +# end CMakeLists.txt diff --git a/utest/PollingReactor.test.cpp b/utest/PollingReactor.test.cpp new file mode 100644 index 00000000..39845264 --- /dev/null +++ b/utest/PollingReactor.test.cpp @@ -0,0 +1,233 @@ +/* @file PollingReactor.test.cpp */ + +#include "xo/reactor/PollingReactor.hpp" +#include "xo/reactor/FifoQueue.hpp" +#include "xo/reactor/Sink.hpp" +#include "xo/randomgen/xoshiro256.hpp" +#include "xo/indentlog/print/pair.hpp" +#include "catch2/catch.hpp" + +namespace xo { + //using xo::reactor::Reactor; + using xo::reactor::PollingReactor; + using xo::reactor::FifoQueue; + using xo::reactor::SinkToFunction; + using xo::ref::rp; + using xo::time::timeutil; + using xo::time::seconds; + using xo::time::utc_nanos; + +/* note: trivial REQUIRE() call in else branch bc we still want + * catch2 to count assertions when verification succeeds + */ +# define REQUIRE_ORCAPTURE(ok_flag, catch_flag, expr) \ + if (catch_flag) { \ + REQUIRE((expr)); \ + } else { \ + REQUIRE(true); \ + ok_flag &= (expr); \ + } + +# define REQUIRE_ORFAIL(ok_flag, catch_flag, expr) \ + REQUIRE_ORCAPTURE(ok_flag, catch_flag, expr); \ + if (!ok_flag) \ + return ok_flag + + namespace { + using TestEvent = std::pair; + using TestQueue = FifoQueue; + + struct RandomTestData { + RandomTestData(std::size_t n, + xo::rng::xoshiro256ss * p_rgen); + + std::uint32_t size() const { return u1v_.size(); } + std::vector const & u1v() const { return u1v_; } + + private: + /* a set of n randomly chosen elements drawn from [0 .. 2n-1] */ + std::vector u1v_; + }; + + RandomTestData::RandomTestData(std::size_t n, + xo::rng::xoshiro256ss * p_rgen) + : u1v_(n) + { + std::shuffle(u1v_.begin(), u1v_.end(), *p_rgen); + } + } /*namespace*/ + + namespace ut { + TEST_CASE("polling0", "[reactor]") { + rp reactor = PollingReactor::make(); + + REQUIRE(reactor.get()); + + for (std::uint32_t i=0; i<3; ++i) { + INFO(xtag("i", i)); + REQUIRE(reactor->run_one() == 0); + } + } /*TEST_CASE(polling0)*/ + + /* return true=success, false=fail */ + bool + run_polling1_test(std::size_t n, + bool catch_flag, + xo::rng::xoshiro256ss * p_rgen) + { + scope log(XO_DEBUG(catch_flag)); + log && log(xtag("n", n)); + + bool ok_flag = true; + + rp reactor = PollingReactor::make(); + REQUIRE_ORFAIL(ok_flag, catch_flag, reactor.get() != nullptr); + + if (ok_flag) + reactor->set_loglevel(catch_flag + ? log_level::always + : log_level::error); + + rp q = TestQueue::make(); + REQUIRE_ORFAIL(ok_flag, catch_flag, q.get() != nullptr); + + if (ok_flag) + q->set_name("fifo"); + + /* capture delivered events */ + std::vector out_ev_v; + + auto sink_fn + = ([&out_ev_v](TestEvent const & x) { out_ev_v.push_back(x); }); + + q->add_callback(new SinkToFunction + >(sink_fn)); + + + reactor->add_source(q); + + /* max #of consecutive inserts */ + std::size_t max_enq = std::max(1UL, n/3); + /* max #of consecutive removes */ + std::size_t max_deq = std::max(1UL, n/3); + + RandomTestData seq(n, p_rgen); + + q->set_debug_sim_flag(catch_flag); + + /* verify: + * 1. queue conservation -- everything inserted gets delivered + * 2. events consumed in the same order they where inserted + * 3. no problem with queue being sometimes empty + */ + + utc_nanos t0 = timeutil::ymd_hms(20231011 /*ymd*/, 131300 /*hms*/); + + /* count #of events delivered by reactor */ + std::size_t n_delivered = 0; + + std::size_t i = 0; + while ((i < seq.u1v().size()) || (n_delivered < n)) { + /* sum of (#of enq, #of deq) attempted for this iteration */ + std::size_t n_work_attempted = 0; + /* sum of (#of enq, #of deq) accomplished for this iteration */ + std::size_t n_work_done = 0; + std::size_t n_enq = p_rgen->generate() % (max_enq + 1); + std::size_t n_deq_attempted = 1 + (p_rgen->generate() % (max_deq + 1)); + std::size_t n_deq_done = 0; + + /* pick random #of elements to insert (to back of queue) */ + { + for (std::size_t j = 0; (j < n_enq) && (i < seq.u1v().size()); ++j) { + utc_nanos ti = t0 + seconds(i); + + q->notify_ev(std::make_pair(ti, seq.u1v()[i++])); + } + + n_work_attempted += n_enq; + n_work_done += n_enq; + } + + /* pick random #of elements to remove (from front of queue) */ + { + + + for (std::size_t j = 0; j < n_deq_attempted; ++j) + n_deq_done += reactor->run_one(); + + n_work_attempted += n_deq_attempted; + n_work_done += n_deq_done; + n_delivered += n_deq_done; + } + + log && log(xtag("i", i), + xtag("n", n), + xtag("n_work_attempted", n_work_attempted), + xtag("n_work_done", n_work_done), + xtag("n_enq", n_enq), + xtag("n_deq_attempted", n_deq_attempted), + xtag("n_deq_done", n_deq_done)); + + if ((i == seq.u1v().size()) /*no more enqueues planned*/ + && (n_work_attempted > 0) + && (n_work_done == 0)) + { + /* expect incremental progress every iteration; + * want unit test to always terminate + */ + break; + } + } + + REQUIRE_ORFAIL(ok_flag, catch_flag, i == n); + REQUIRE_ORFAIL(ok_flag, catch_flag, n_delivered == n); + + /* check events delivered 1:1 and in order */ + for (std::size_t i=0; i seed; + auto rgen = xo::rng::xoshiro256ss(seed); + + for (std::size_t n = 4; n <= 1024; n *= 2) { + bool ok_flag = false; + + for (std::uint32_t attention = 0; !ok_flag && (attention < 2); ++attention) { + ok_flag = true; + + /* attention=0: + * - no logging + * - detect assertion failures, but don't report them to catch + * attention=1: + * - only runs if failure detected with attention=0 + * - full logging + * - report to catch + */ + + bool debug_flag = (attention == 1); + + ok_flag &= run_polling1_test(n, debug_flag, &rgen); + } + } + + } /*TEST_CASE(polling1)*/ + } /*namespace ut*/ + +} /*namespace xo*/ + + +/* end PollingReactor.test.cpp */ diff --git a/utest/Sink.test.cpp b/utest/Sink.test.cpp new file mode 100644 index 00000000..160530a0 --- /dev/null +++ b/utest/Sink.test.cpp @@ -0,0 +1,100 @@ +/* @file Sink.test.cpp */ + +#include "xo/reactor/PollingReactor.hpp" +#include "xo/reactor/Sink.hpp" +#include "xo/indentlog/print/pair.hpp" +#include "catch2/catch.hpp" + +namespace xo { + using xo::reactor::Reactor; + using xo::reactor::PollingReactor; + using xo::reactor::AbstractSink; + using xo::reactor::Sink1; + using xo::reactor::SinkEndpoint; + using xo::reactor::SinkToConsole; + using xo::time::utc_nanos; + using xo::ref::rp; + + namespace { + class TestSink : public SinkEndpoint { + public: + TestSink() = default; + + virtual uint32_t n_in_ev() const override { return 0; } + virtual bool allow_volatile_source() const override { return true; } + virtual void notify_ev(int const & ev) override {} + virtual void display(std::ostream & os) const override { os << ""; } + }; /*TestSink*/ + + class TestSink2 : public SinkEndpoint { + public: + TestSink2() = default; + + virtual uint32_t n_in_ev() const override { return 0; } + virtual bool allow_volatile_source() const override { return true; } + virtual void notify_ev(utc_nanos const & ev) override {} + virtual void display(std::ostream & os) const override { os << ""; } + }; /*TestSink2*/ + + using TestSink3 = SinkToConsole>; + } /*namespace*/ + + namespace ut { + TEST_CASE("sink-cast", "[reactor][sink]") { + rp test_sink = new TestSink(); + rp sink = test_sink; + + TestSink * cast_sink = dynamic_cast(sink.get()); + + REQUIRE(test_sink.get() == cast_sink); + + Sink1 * int_sink = dynamic_cast *>(sink.get()); + + REQUIRE(test_sink.get() == int_sink); + + rp> int_sink2 + = Sink1::require_native("TEST_CASE(sink-cast)", sink.get()); + + REQUIRE(test_sink.get() == int_sink2.get()); + } /*TEST_CASE(sink-cast)*/ + + TEST_CASE("sink-cast2", "[reactor]") { + rp test_sink = new TestSink2(); + rp sink = test_sink; + + TestSink2 * cast_sink = dynamic_cast(sink.get()); + + REQUIRE(test_sink.get() == cast_sink); + + Sink1 * dt_sink = dynamic_cast *>(sink.get()); + + REQUIRE(test_sink.get() == dt_sink); + + rp> dt_sink2 + = Sink1::require_native("TEST_CASE(sink-cast2)", sink.get()); + + REQUIRE(test_sink.get() == dt_sink2.get()); + } /*TEST_CASE(sink-cast2)*/ + + TEST_CASE("sink-cast3", "[reactor]") { + rp test_sink = new TestSink3(); + rp sink = test_sink; + + TestSink3 * cast_sink = dynamic_cast(sink.get()); + + REQUIRE(test_sink.get() == cast_sink); + + Sink1> * ev_sink + = dynamic_cast> *>(sink.get()); + + REQUIRE(test_sink.get() == ev_sink); + + rp>> ev_sink2 + = Sink1>::require_native("TEST_CASE(sink-cast3)", sink.get()); + + REQUIRE(test_sink.get() == ev_sink2.get()); + } /*TEST_CASE(sink-cast3)*/ + } /*namespace ut*/ +} /*namespace xo*/ + +/* end Sink.test.cpp */ diff --git a/utest/reactor_utest_main.cpp b/utest/reactor_utest_main.cpp new file mode 100644 index 00000000..c3b80295 --- /dev/null +++ b/utest/reactor_utest_main.cpp @@ -0,0 +1,6 @@ +/* @file reactor_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end reactor_utest_main.cpp */ From 30a524b22cf63dfd25381986fb45231ed70d0a04 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 17:51:31 -0400 Subject: [PATCH 0337/2693] build: install include tree --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1735ddc8..5b0079d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,4 +45,9 @@ add_subdirectory(src/callback) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) +# ---------------------------------------------------------------- +# install .hpp files + +xo_install_include_tree() + # end CMakeLists.txt From 4c825252ed85c8ebeeb980e540ef3a6bdfb718d8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 18:03:20 -0400 Subject: [PATCH 0338/2693] xo-cmake: + xo_add_headeronly_library() --- cmake/xo_cxx.cmake | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index f0e2e29b..13ee6b9a 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -108,6 +108,14 @@ macro(xo_add_shared_library target targetversion soversion sources) xo_install_library2(${target}) endmacro() +# ---------------------------------------------------------------- +# use this for a header-only library +# +macro(xo_add_headeronly_library target) + add_library(${target} INTERFACE) + xo_include_headeronly_options2(${target}) +endmacro() + # ---------------------------------------------------------------- # use this in subdirs that compile c++ code. # do not use for header-only subsystems; see xo_include_headeronly_options2() From 1c648d70c3c3a226f193abaa521149899a2a1c6f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 18:03:40 -0400 Subject: [PATCH 0339/2693] xo-cmake: + xo_install_library3() --- cmake/xo_cxx.cmake | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 13ee6b9a..64ec2822 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -184,6 +184,20 @@ macro(xo_install_library2 target) ) endmacro() +macro(xo_install_library3 target projectTargets) + install( + TARGETS ${target} + EXPORT ${projectTargets} + LIBRARY DESTINATION lib COMPONENT Runtime + ARCHIVE DESTINATION lib COMPONENT Development + RUNTIME DESTINATION bin COMPONENT Runtime + PUBLIC_HEADER DESTINATION include COMPONENT Development + BUNDLE DESTINATION bin COMPONENT Runtime + ) + + xo_install_include_tree() +endmacro() + # ---------------------------------------------------------------- # for projectname=foo, require: From 886ad07eb332125962cd712e49c20d9f045203a9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 18:26:49 -0400 Subject: [PATCH 0340/2693] initial implementation --- CMakeLists.txt | 50 ++++++++++++++ README.md | 28 ++++++++ cmake/webutilConfig.cmake.in | 4 ++ include/xo/webutil/Alist.hpp | 43 ++++++++++++ include/xo/webutil/HttpEndpointDescr.hpp | 76 +++++++++++++++++++++ include/xo/webutil/StreamEndpointDescr.hpp | 78 ++++++++++++++++++++++ 6 files changed, 279 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/webutilConfig.cmake.in create mode 100644 include/xo/webutil/Alist.hpp create mode 100644 include/xo/webutil/HttpEndpointDescr.hpp create mode 100644 include/xo/webutil/StreamEndpointDescr.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..dcce42b2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,50 @@ +# xo-webutil/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(webutil VERSION 1.0) +enable_language(CXX) + +# common XO cmake macros (see proj/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# unit test setup (no unit tests yet, but want 'make tests' to do something) + +enable_testing() +# activate code coverage for all executables + libraries (when -DCODE_COVERAGE=ON) +add_code_coverage() + +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) + +# ---------------------------------------------------------------- + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- + +# header-only library. +# see [[https://stackoverflow.com/questions/47718485/install-and-export-interface-only-library-cmake]] +# +xo_add_headeronly_library(webutil) + +# ---------------------------------------------------------------- +# standard install + +xo_install_library3(webutil ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# install bespoke targets, if any + +#install(TARGETS example DESTINATION bin/xo-webutil/example) diff --git a/README.md b/README.md new file mode 100644 index 00000000..a8eb8c77 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# webutil library (header-only) + +# dependencies + +- xo-cmake [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) + +# clone repo +``` +$ git clone git@github.com:Rconybea/xo-webutil.git +``` + +# build and install +``` +$ cd xo-webutil +$ BUILDDIR=build # for example +$ mkdir $BUILDDIR +$ cd $BUILDDIR +$ PREFIX=/usr/local # for example +$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} .. +$ make +$ make install +``` + +# LSP support +``` +$ cd xo-webutil +$ ln -s $BUILDDIR/compile_commands.json +``` diff --git a/cmake/webutilConfig.cmake.in b/cmake/webutilConfig.cmake.in new file mode 100644 index 00000000..c44284f2 --- /dev/null +++ b/cmake/webutilConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/webutilTargets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/webutil/Alist.hpp b/include/xo/webutil/Alist.hpp new file mode 100644 index 00000000..80fcaa20 --- /dev/null +++ b/include/xo/webutil/Alist.hpp @@ -0,0 +1,43 @@ +/* file Alist.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include +#include +#include + +namespace xo { + namespace web { + /* assocation list, maps strings to strings + * use this for arguments to dynamic-endpoint-callbacks + */ + class Alist { + public: + Alist() = default; + + /* lookup association by name */ + std::string_view lookup(std::string n) const { + for (auto const & ix : this->assoc_v_) { + if (ix.first == n) { + return ix.second; + } + } + + return ""; + } /*lookup*/ + + void push_back(std::string n, std::string v) { + this->assoc_v_.push_back(std::make_pair(std::move(n), std::move(v))); + } + + private: + std::vector> assoc_v_; + }; /*Alist*/ + + } /*namespace web*/ +} /*namespace xo*/ + +/* end Alist.hpp */ diff --git a/include/xo/webutil/HttpEndpointDescr.hpp b/include/xo/webutil/HttpEndpointDescr.hpp new file mode 100644 index 00000000..79a7070f --- /dev/null +++ b/include/xo/webutil/HttpEndpointDescr.hpp @@ -0,0 +1,76 @@ +/* file EndpointDescr.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "web_util/Alist.hpp" +#include "refcnt/Refcounted.hpp" +#include "indentlog/print/tag.hpp" +#include "indentlog/print/tostr.hpp" +#include + +namespace xo { + namespace web { + /* a function that can deliver http content on demand. */ + using HttpEndpointFn = std::function; + + /* describes an http endpoint -- + * this comprises: + * - a uri pattern. + * - a function that can deliver http content on demand + */ + class HttpEndpointDescr { + public: + HttpEndpointDescr(std::string uri_pattern, + HttpEndpointFn endpoint_fn) + : uri_pattern_{std::move(uri_pattern)}, + endpoint_fn_{std::move(endpoint_fn)} + {} + + std::string const & uri_pattern() const { return uri_pattern_; } + HttpEndpointFn const & endpoint_fn() const { return endpoint_fn_; } + + void display(std::ostream & os) const { + using xo::xtag; + + os << ""; + } /*display*/ + + std::string display_string() const { return xo::tostr(*this); } + + private: + /* unique pattern in URI-space for this endpoint. + * for example + * .uri_pattern = /stem/${foo}/${bar} + * means this endpoint generates contents for uri's + * /stem/apple/banana + * /stem/aphid/green + * but not for + * /stem/apple/banana/carrot + */ + std::string uri_pattern_; + /* a function that can construct http output on demand + * .endpoint_fn(uri, alist, &os) + * writes http output to os. output is parameterized + * by name-value pairs in alist, and is prepared on behalf + * of .uri_pattern + * alist will report name-value pairs for each variable that + * appears in .uri_pattern (surrounded by ${..}) + */ + HttpEndpointFn endpoint_fn_; + }; /*HttpEndpointDescr*/ + + inline std::ostream & + operator<<(std::ostream & os, HttpEndpointDescr const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace web*/ +} /*namespace xo*/ + +/* end EndpointDescr.hpp */ diff --git a/include/xo/webutil/StreamEndpointDescr.hpp b/include/xo/webutil/StreamEndpointDescr.hpp new file mode 100644 index 00000000..fe86b135 --- /dev/null +++ b/include/xo/webutil/StreamEndpointDescr.hpp @@ -0,0 +1,78 @@ +/* file StreamEndpointDescr.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "web_util/Alist.hpp" +#include "callback/CallbackSet.hpp" +#include "refcnt/Refcounted.hpp" +#include "indentlog/print/tag.hpp" +#include "indentlog/print/tostr.hpp" +#include + +namespace xo { + namespace reactor { class AbstractSink; } + + namespace web { + /* a function that creates an event subscription */ + using StreamSubscribeFn = std::function const & ws_sink)>; + using StreamUnsubscribeFn = std::function; + + /* describes a stream endpoint + * this comprises + * - a uri pattern (matches stream name) + * - a function that establishes subscription + * (by attaching supplied WebsocketSink to an event source) + */ + class StreamEndpointDescr { + public: + StreamEndpointDescr(std::string uri_pattern, + StreamSubscribeFn subscribe_fn, + StreamUnsubscribeFn unsubscribe_fn) + : uri_pattern_{std::move(uri_pattern)}, + subscribe_fn_{std::move(subscribe_fn)}, + unsubscribe_fn_{std::move(unsubscribe_fn)} {} + + std::string const & uri_pattern() const { return uri_pattern_; } + StreamSubscribeFn const & subscribe_fn() const { return subscribe_fn_; } + StreamUnsubscribeFn const & unsubscribe_fn() const { return unsubscribe_fn_; } + + void display(std::ostream & os) const { + using xo::xtag; + + os << ""; + } /*display*/ + + std::string display_string() const { return xo::tostr(*this); } + + private: + /* unique pattern in URI-space for this endpoint + * for example + * .uri_pattern = /stem/${foo}/${bar} + * means this endpoint generates contents for uri's + * /stem/apple/banana + * /stem/aphid/green + * but not for + * /stem/apple/banana/carrot + */ + std::string uri_pattern_; + /* a function that subscribes to an event stream + * (by attaching a websocket sink) + */ + StreamSubscribeFn subscribe_fn_; + /* reverses effect of a particular call to .subscribe_fn */ + StreamUnsubscribeFn unsubscribe_fn_; + }; /*StreamEndpointDescr*/ + + inline std::ostream & + operator<<(std::ostream & os, StreamEndpointDescr const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace web*/ +} /*namespace xo*/ + +/* end StreamEndpointDescr.hpp */ From 8759357ec7730cf3eca5941dfe6d261d4bf08c6a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 18:27:14 -0400 Subject: [PATCH 0341/2693] + .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..abafa2ca --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# typical build directories +build From 0f898ff01136b16ec0e8e2aec352d3194fbebe33 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 18:37:09 -0400 Subject: [PATCH 0342/2693] bugfix: #pragma once in log_level.hpp --- include/xo/indentlog/log_level.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/xo/indentlog/log_level.hpp b/include/xo/indentlog/log_level.hpp index 8b470891..a4392286 100644 --- a/include/xo/indentlog/log_level.hpp +++ b/include/xo/indentlog/log_level.hpp @@ -1,5 +1,7 @@ /* @file log_level.hpp */ +#pragma once + #include namespace xo { From 9a23b29c12d53f773d6e46a45e7b3e1f95633821 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 18:43:04 -0400 Subject: [PATCH 0343/2693] cosmetic: formatting --- include/xo/webutil/Alist.hpp | 50 +++++----- include/xo/webutil/HttpEndpointDescr.hpp | 102 ++++++++++---------- include/xo/webutil/StreamEndpointDescr.hpp | 104 ++++++++++----------- 3 files changed, 128 insertions(+), 128 deletions(-) diff --git a/include/xo/webutil/Alist.hpp b/include/xo/webutil/Alist.hpp index 80fcaa20..ccb317b3 100644 --- a/include/xo/webutil/Alist.hpp +++ b/include/xo/webutil/Alist.hpp @@ -10,34 +10,34 @@ #include namespace xo { - namespace web { - /* assocation list, maps strings to strings - * use this for arguments to dynamic-endpoint-callbacks - */ - class Alist { - public: - Alist() = default; - - /* lookup association by name */ - std::string_view lookup(std::string n) const { - for (auto const & ix : this->assoc_v_) { - if (ix.first == n) { - return ix.second; - } - } + namespace web { + /* assocation list, maps strings to strings + * use this for arguments to dynamic-endpoint-callbacks + */ + class Alist { + public: + Alist() = default; - return ""; - } /*lookup*/ + /* lookup association by name */ + std::string_view lookup(std::string n) const { + for (auto const & ix : this->assoc_v_) { + if (ix.first == n) { + return ix.second; + } + } - void push_back(std::string n, std::string v) { - this->assoc_v_.push_back(std::make_pair(std::move(n), std::move(v))); - } + return ""; + } /*lookup*/ - private: - std::vector> assoc_v_; - }; /*Alist*/ - - } /*namespace web*/ + void push_back(std::string n, std::string v) { + this->assoc_v_.push_back(std::make_pair(std::move(n), std::move(v))); + } + + private: + std::vector> assoc_v_; + }; /*Alist*/ + + } /*namespace web*/ } /*namespace xo*/ /* end Alist.hpp */ diff --git a/include/xo/webutil/HttpEndpointDescr.hpp b/include/xo/webutil/HttpEndpointDescr.hpp index 79a7070f..97ff4cee 100644 --- a/include/xo/webutil/HttpEndpointDescr.hpp +++ b/include/xo/webutil/HttpEndpointDescr.hpp @@ -12,65 +12,65 @@ #include namespace xo { - namespace web { - /* a function that can deliver http content on demand. */ - using HttpEndpointFn = std::function; + namespace web { + /* a function that can deliver http content on demand. */ + using HttpEndpointFn = std::function; - /* describes an http endpoint -- - * this comprises: - * - a uri pattern. - * - a function that can deliver http content on demand - */ - class HttpEndpointDescr { - public: - HttpEndpointDescr(std::string uri_pattern, - HttpEndpointFn endpoint_fn) - : uri_pattern_{std::move(uri_pattern)}, - endpoint_fn_{std::move(endpoint_fn)} - {} + /* describes an http endpoint -- + * this comprises: + * - a uri pattern. + * - a function that can deliver http content on demand + */ + class HttpEndpointDescr { + public: + HttpEndpointDescr(std::string uri_pattern, + HttpEndpointFn endpoint_fn) + : uri_pattern_{std::move(uri_pattern)}, + endpoint_fn_{std::move(endpoint_fn)} + {} - std::string const & uri_pattern() const { return uri_pattern_; } - HttpEndpointFn const & endpoint_fn() const { return endpoint_fn_; } + std::string const & uri_pattern() const { return uri_pattern_; } + HttpEndpointFn const & endpoint_fn() const { return endpoint_fn_; } - void display(std::ostream & os) const { - using xo::xtag; + void display(std::ostream & os) const { + using xo::xtag; - os << ""; - } /*display*/ + os << ""; + } /*display*/ - std::string display_string() const { return xo::tostr(*this); } + std::string display_string() const { return xo::tostr(*this); } - private: - /* unique pattern in URI-space for this endpoint. - * for example - * .uri_pattern = /stem/${foo}/${bar} - * means this endpoint generates contents for uri's - * /stem/apple/banana - * /stem/aphid/green - * but not for - * /stem/apple/banana/carrot - */ - std::string uri_pattern_; - /* a function that can construct http output on demand - * .endpoint_fn(uri, alist, &os) - * writes http output to os. output is parameterized - * by name-value pairs in alist, and is prepared on behalf - * of .uri_pattern - * alist will report name-value pairs for each variable that - * appears in .uri_pattern (surrounded by ${..}) - */ - HttpEndpointFn endpoint_fn_; - }; /*HttpEndpointDescr*/ + private: + /* unique pattern in URI-space for this endpoint. + * for example + * .uri_pattern = /stem/${foo}/${bar} + * means this endpoint generates contents for uri's + * /stem/apple/banana + * /stem/aphid/green + * but not for + * /stem/apple/banana/carrot + */ + std::string uri_pattern_; + /* a function that can construct http output on demand + * .endpoint_fn(uri, alist, &os) + * writes http output to os. output is parameterized + * by name-value pairs in alist, and is prepared on behalf + * of .uri_pattern + * alist will report name-value pairs for each variable that + * appears in .uri_pattern (surrounded by ${..}) + */ + HttpEndpointFn endpoint_fn_; + }; /*HttpEndpointDescr*/ - inline std::ostream & - operator<<(std::ostream & os, HttpEndpointDescr const & x) { - x.display(os); - return os; - } /*operator<<*/ + inline std::ostream & + operator<<(std::ostream & os, HttpEndpointDescr const & x) { + x.display(os); + return os; + } /*operator<<*/ - } /*namespace web*/ + } /*namespace web*/ } /*namespace xo*/ /* end EndpointDescr.hpp */ diff --git a/include/xo/webutil/StreamEndpointDescr.hpp b/include/xo/webutil/StreamEndpointDescr.hpp index fe86b135..1b361610 100644 --- a/include/xo/webutil/StreamEndpointDescr.hpp +++ b/include/xo/webutil/StreamEndpointDescr.hpp @@ -5,7 +5,7 @@ #pragma once -#include "web_util/Alist.hpp" +#include "Alist.hpp" #include "callback/CallbackSet.hpp" #include "refcnt/Refcounted.hpp" #include "indentlog/print/tag.hpp" @@ -13,66 +13,66 @@ #include namespace xo { - namespace reactor { class AbstractSink; } + namespace reactor { class AbstractSink; } - namespace web { - /* a function that creates an event subscription */ - using StreamSubscribeFn = std::function const & ws_sink)>; - using StreamUnsubscribeFn = std::function; + namespace web { + /* a function that creates an event subscription */ + using StreamSubscribeFn = std::function const & ws_sink)>; + using StreamUnsubscribeFn = std::function; - /* describes a stream endpoint - * this comprises - * - a uri pattern (matches stream name) - * - a function that establishes subscription - * (by attaching supplied WebsocketSink to an event source) - */ - class StreamEndpointDescr { - public: - StreamEndpointDescr(std::string uri_pattern, - StreamSubscribeFn subscribe_fn, - StreamUnsubscribeFn unsubscribe_fn) - : uri_pattern_{std::move(uri_pattern)}, - subscribe_fn_{std::move(subscribe_fn)}, - unsubscribe_fn_{std::move(unsubscribe_fn)} {} + /* describes a stream endpoint + * this comprises + * - a uri pattern (matches stream name) + * - a function that establishes subscription + * (by attaching supplied WebsocketSink to an event source) + */ + class StreamEndpointDescr { + public: + StreamEndpointDescr(std::string uri_pattern, + StreamSubscribeFn subscribe_fn, + StreamUnsubscribeFn unsubscribe_fn) + : uri_pattern_{std::move(uri_pattern)}, + subscribe_fn_{std::move(subscribe_fn)}, + unsubscribe_fn_{std::move(unsubscribe_fn)} {} - std::string const & uri_pattern() const { return uri_pattern_; } - StreamSubscribeFn const & subscribe_fn() const { return subscribe_fn_; } - StreamUnsubscribeFn const & unsubscribe_fn() const { return unsubscribe_fn_; } + std::string const & uri_pattern() const { return uri_pattern_; } + StreamSubscribeFn const & subscribe_fn() const { return subscribe_fn_; } + StreamUnsubscribeFn const & unsubscribe_fn() const { return unsubscribe_fn_; } - void display(std::ostream & os) const { - using xo::xtag; + void display(std::ostream & os) const { + using xo::xtag; - os << ""; - } /*display*/ + os << ""; + } /*display*/ - std::string display_string() const { return xo::tostr(*this); } + std::string display_string() const { return xo::tostr(*this); } - private: - /* unique pattern in URI-space for this endpoint - * for example - * .uri_pattern = /stem/${foo}/${bar} - * means this endpoint generates contents for uri's - * /stem/apple/banana - * /stem/aphid/green - * but not for - * /stem/apple/banana/carrot - */ - std::string uri_pattern_; - /* a function that subscribes to an event stream - * (by attaching a websocket sink) - */ - StreamSubscribeFn subscribe_fn_; - /* reverses effect of a particular call to .subscribe_fn */ - StreamUnsubscribeFn unsubscribe_fn_; - }; /*StreamEndpointDescr*/ + private: + /* unique pattern in URI-space for this endpoint + * for example + * .uri_pattern = /stem/${foo}/${bar} + * means this endpoint generates contents for uri's + * /stem/apple/banana + * /stem/aphid/green + * but not for + * /stem/apple/banana/carrot + */ + std::string uri_pattern_; + /* a function that subscribes to an event stream + * (by attaching a websocket sink) + */ + StreamSubscribeFn subscribe_fn_; + /* reverses effect of a particular call to .subscribe_fn */ + StreamUnsubscribeFn unsubscribe_fn_; + }; /*StreamEndpointDescr*/ - inline std::ostream & - operator<<(std::ostream & os, StreamEndpointDescr const & x) { - x.display(os); - return os; - } /*operator<<*/ + inline std::ostream & + operator<<(std::ostream & os, StreamEndpointDescr const & x) { + x.display(os); + return os; + } /*operator<<*/ - } /*namespace web*/ + } /*namespace web*/ } /*namespace xo*/ /* end StreamEndpointDescr.hpp */ From 38b8f34b2884768172dfd2394b5f44694e31ff54 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 19:19:44 -0400 Subject: [PATCH 0344/2693] refactor: move impl into .cpp + fix include paths --- CMakeLists.txt | 19 ++++++++------- cmake/webutilConfig.cmake.in | 2 ++ include/xo/webutil/Alist.hpp | 14 ++--------- include/xo/webutil/HttpEndpointDescr.hpp | 20 +++++----------- include/xo/webutil/StreamEndpointDescr.hpp | 19 ++++----------- src/webutil/Alist.cpp | 28 ++++++++++++++++++++++ src/webutil/CMakeLists.txt | 14 +++++++++++ src/webutil/HttpEndpointDescr.cpp | 27 +++++++++++++++++++++ src/webutil/StreamEndpointDescr.cpp | 28 ++++++++++++++++++++++ 9 files changed, 122 insertions(+), 49 deletions(-) create mode 100644 src/webutil/Alist.cpp create mode 100644 src/webutil/CMakeLists.txt create mode 100644 src/webutil/HttpEndpointDescr.cpp create mode 100644 src/webutil/StreamEndpointDescr.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index dcce42b2..04cc53f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,20 +24,19 @@ add_code_coverage() add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) # ---------------------------------------------------------------- +# common c++ settings + +# PROJECT_CXX_FLAGS: bespoke for this project - usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) xo_toplevel_compile_options() # ---------------------------------------------------------------- +# sources -# header-only library. -# see [[https://stackoverflow.com/questions/47718485/install-and-export-interface-only-library-cmake]] -# -xo_add_headeronly_library(webutil) - -# ---------------------------------------------------------------- -# standard install - -xo_install_library3(webutil ${PROJECT_NAME}Targets) +add_subdirectory(src/webutil) # ---------------------------------------------------------------- # provide find_package() support @@ -47,4 +46,6 @@ xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets # ---------------------------------------------------------------- # install bespoke targets, if any +xo_install_include_tree() + #install(TARGETS example DESTINATION bin/xo-webutil/example) diff --git a/cmake/webutilConfig.cmake.in b/cmake/webutilConfig.cmake.in index c44284f2..79a9df3d 100644 --- a/cmake/webutilConfig.cmake.in +++ b/cmake/webutilConfig.cmake.in @@ -1,4 +1,6 @@ @PACKAGE_INIT@ +include(CMakeFindDependencyMacro) +find_dependency(callback) include("${CMAKE_CURRENT_LIST_DIR}/webutilTargets.cmake") check_required_components("@PROJECT_NAME@") diff --git a/include/xo/webutil/Alist.hpp b/include/xo/webutil/Alist.hpp index ccb317b3..4bc5a305 100644 --- a/include/xo/webutil/Alist.hpp +++ b/include/xo/webutil/Alist.hpp @@ -19,19 +19,9 @@ namespace xo { Alist() = default; /* lookup association by name */ - std::string_view lookup(std::string n) const { - for (auto const & ix : this->assoc_v_) { - if (ix.first == n) { - return ix.second; - } - } + std::string_view lookup(std::string n) const; - return ""; - } /*lookup*/ - - void push_back(std::string n, std::string v) { - this->assoc_v_.push_back(std::make_pair(std::move(n), std::move(v))); - } + void push_back(std::string n, std::string v); private: std::vector> assoc_v_; diff --git a/include/xo/webutil/HttpEndpointDescr.hpp b/include/xo/webutil/HttpEndpointDescr.hpp index 97ff4cee..f3598cbb 100644 --- a/include/xo/webutil/HttpEndpointDescr.hpp +++ b/include/xo/webutil/HttpEndpointDescr.hpp @@ -5,11 +5,10 @@ #pragma once -#include "web_util/Alist.hpp" -#include "refcnt/Refcounted.hpp" -#include "indentlog/print/tag.hpp" -#include "indentlog/print/tostr.hpp" +#include "Alist.hpp" +#include "xo/refcnt/Refcounted.hpp" #include +#include namespace xo { namespace web { @@ -26,21 +25,14 @@ namespace xo { class HttpEndpointDescr { public: HttpEndpointDescr(std::string uri_pattern, - HttpEndpointFn endpoint_fn) - : uri_pattern_{std::move(uri_pattern)}, - endpoint_fn_{std::move(endpoint_fn)} - {} + HttpEndpointFn endpoint_fn); std::string const & uri_pattern() const { return uri_pattern_; } HttpEndpointFn const & endpoint_fn() const { return endpoint_fn_; } - void display(std::ostream & os) const { - using xo::xtag; + void display(std::ostream & os) const; - os << ""; - } /*display*/ - - std::string display_string() const { return xo::tostr(*this); } + std::string display_string() const; private: /* unique pattern in URI-space for this endpoint. diff --git a/include/xo/webutil/StreamEndpointDescr.hpp b/include/xo/webutil/StreamEndpointDescr.hpp index 1b361610..78053008 100644 --- a/include/xo/webutil/StreamEndpointDescr.hpp +++ b/include/xo/webutil/StreamEndpointDescr.hpp @@ -6,10 +6,8 @@ #pragma once #include "Alist.hpp" -#include "callback/CallbackSet.hpp" -#include "refcnt/Refcounted.hpp" -#include "indentlog/print/tag.hpp" -#include "indentlog/print/tostr.hpp" +#include "xo/callback/CallbackSet.hpp" +#include "xo/refcnt/Refcounted.hpp" #include namespace xo { @@ -30,22 +28,15 @@ namespace xo { public: StreamEndpointDescr(std::string uri_pattern, StreamSubscribeFn subscribe_fn, - StreamUnsubscribeFn unsubscribe_fn) - : uri_pattern_{std::move(uri_pattern)}, - subscribe_fn_{std::move(subscribe_fn)}, - unsubscribe_fn_{std::move(unsubscribe_fn)} {} + StreamUnsubscribeFn unsubscribe_fn); std::string const & uri_pattern() const { return uri_pattern_; } StreamSubscribeFn const & subscribe_fn() const { return subscribe_fn_; } StreamUnsubscribeFn const & unsubscribe_fn() const { return unsubscribe_fn_; } - void display(std::ostream & os) const { - using xo::xtag; + void display(std::ostream & os) const; - os << ""; - } /*display*/ - - std::string display_string() const { return xo::tostr(*this); } + std::string display_string() const; private: /* unique pattern in URI-space for this endpoint diff --git a/src/webutil/Alist.cpp b/src/webutil/Alist.cpp new file mode 100644 index 00000000..73905106 --- /dev/null +++ b/src/webutil/Alist.cpp @@ -0,0 +1,28 @@ +/* @file Alist.cpp */ + +#include "Alist.hpp" + +namespace xo { + namespace web { + /* lookup association by name */ + std::string_view + Alist::lookup(std::string n) const { + for (auto const & ix : this->assoc_v_) { + if (ix.first == n) { + return ix.second; + } + } + + return ""; + } /*lookup*/ + + void + Alist::push_back(std::string n, std::string v) { + this->assoc_v_.push_back(std::make_pair(std::move(n), std::move(v))); + } + } /*namespace web*/ +} /*namespace xo*/ + + + +/* end Alist.cpp */ diff --git a/src/webutil/CMakeLists.txt b/src/webutil/CMakeLists.txt new file mode 100644 index 00000000..57f96726 --- /dev/null +++ b/src/webutil/CMakeLists.txt @@ -0,0 +1,14 @@ +# webutil/CMakeLists.txt + +set(SELF_LIB webutil) +set(SELF_SRCS StreamEndpointDescr.cpp HttpEndpointDescr.cpp Alist.cpp) + +# reminder: can't be header-only library, because depends on non-header-only callback (bc of non-header-only refcnt) +xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) + +# ---------------------------------------------------------------- +# external dependencies + +xo_dependency(${SELF_LIB} callback) + +# end CMakeLists.txt diff --git a/src/webutil/HttpEndpointDescr.cpp b/src/webutil/HttpEndpointDescr.cpp new file mode 100644 index 00000000..fb9ab261 --- /dev/null +++ b/src/webutil/HttpEndpointDescr.cpp @@ -0,0 +1,27 @@ +/* @file HttpEndpointDescr.cpp */ + +#include "HttpEndpointDescr.hpp" +#include "xo/indentlog/print/tag.hpp" +#include "xo/indentlog/print/tostr.hpp" + +namespace xo { + namespace web { + HttpEndpointDescr::HttpEndpointDescr(std::string uri_pattern, + HttpEndpointFn endpoint_fn) + : uri_pattern_{std::move(uri_pattern)}, + endpoint_fn_{std::move(endpoint_fn)} + {} + + void + HttpEndpointDescr::display(std::ostream & os) const { + os << ""; + } /*display*/ + + std::string + HttpEndpointDescr::display_string() const { return tostr(*this); } + } /*namespace web*/ + +} /*namespace xo*/ + + +/* end HttpEndpointDescr.cpp */ diff --git a/src/webutil/StreamEndpointDescr.cpp b/src/webutil/StreamEndpointDescr.cpp new file mode 100644 index 00000000..03c2e961 --- /dev/null +++ b/src/webutil/StreamEndpointDescr.cpp @@ -0,0 +1,28 @@ +/* @file StreamEndpointDescr.cpp */ + +#include "StreamEndpointDescr.hpp" +#include "xo/indentlog/print/tag.hpp" +#include "xo/indentlog/print/tostr.hpp" + +namespace xo { + namespace web { + StreamEndpointDescr::StreamEndpointDescr(std::string uri_pattern, + StreamSubscribeFn subscribe_fn, + StreamUnsubscribeFn unsubscribe_fn) + : uri_pattern_{std::move(uri_pattern)}, + subscribe_fn_{std::move(subscribe_fn)}, + unsubscribe_fn_{std::move(unsubscribe_fn)} + {} + + void + StreamEndpointDescr::display(std::ostream & os) const { + os << ""; + } /*display*/ + + std::string + StreamEndpointDescr::display_string() const { return tostr(*this); } + } /*namespace web*/ +} /*namespace xo*/ + + +/* end StreamEndpointDescr.cpp */ From 937109896c1fac15779ae94e27bd18d93493cc5e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 19:20:36 -0400 Subject: [PATCH 0345/2693] ext .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index abafa2ca..3e50f879 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ +# lsp keeps state here +.cache +# symlink -> build/compile_commands.json should be created manually +compile_commands.json # typical build directories build From 12d38a7e4a87b08ddfbf0590e6b6c92929b211a1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 20:12:16 -0400 Subject: [PATCH 0346/2693] build: + reflect dependency --- README.md | 9 ++++++--- cmake/printjsonConfig.cmake.in | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ab049018..99492884 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # printjson library -### build + install +# build + install ``` $ cd xo-printjson $ mkdir build @@ -11,15 +11,18 @@ $ make $ make install ``` -### build for unit test coverage +# build for unit test coverage ``` $ cd xo-printjson $ mkdir ccov $ cd ccov $ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +$ make # builds executables +$ make ccov # runs instrumented unit tests +$ make ccov-all # generates lcov report ``` -### LSP support +# LSP support ``` $ cd xo-printjson $ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree diff --git a/cmake/printjsonConfig.cmake.in b/cmake/printjsonConfig.cmake.in index c7d8974c..e7f7f1be 100644 --- a/cmake/printjsonConfig.cmake.in +++ b/cmake/printjsonConfig.cmake.in @@ -1,6 +1,6 @@ @PACKAGE_INIT@ -#include(CMakeFindDependencyMacro) -#find_dependency(refcnt) +include(CMakeFindDependencyMacro) +find_dependency(reflect) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") From daa09aff863715b4b56083544a9977e9239970ae Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 20:32:18 -0400 Subject: [PATCH 0347/2693] reactor: auto-initialization hooks --- include/xo/reactor/Reactor.hpp | 3 +++ include/xo/reactor/init_reactor.hpp | 12 ++++----- src/reactor/Reactor.cpp | 40 ++++++++++++++++++----------- utest/PollingReactor.test.cpp | 7 +++++ 4 files changed, 41 insertions(+), 21 deletions(-) diff --git a/include/xo/reactor/Reactor.hpp b/include/xo/reactor/Reactor.hpp index 453312a4..2b265b0b 100644 --- a/include/xo/reactor/Reactor.hpp +++ b/include/xo/reactor/Reactor.hpp @@ -61,6 +61,9 @@ namespace xo { */ void run() { this->run_n(-1); } + protected: + Reactor(); + private: /* control logging verbosity */ log_level loglevel_; diff --git a/include/xo/reactor/init_reactor.hpp b/include/xo/reactor/init_reactor.hpp index 5977d7ad..3e5e51bb 100644 --- a/include/xo/reactor/init_reactor.hpp +++ b/include/xo/reactor/init_reactor.hpp @@ -8,13 +8,13 @@ #include "xo/subsys/Subsystem.hpp" namespace xo { - enum S_reactor_tag {}; + enum S_reactor_tag {}; - template<> - struct InitSubsys { - static void init(); - static InitEvidence require(); - }; + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; } /*namespace xo*/ /* end init_reactor.hpp */ diff --git a/src/reactor/Reactor.cpp b/src/reactor/Reactor.cpp index 2ef80e46..199ec562 100644 --- a/src/reactor/Reactor.cpp +++ b/src/reactor/Reactor.cpp @@ -4,23 +4,33 @@ */ #include "Reactor.hpp" +#include "init_reactor.hpp" +#include "xo/subsys/Subsystem.hpp" namespace xo { - namespace reactor { - void - Reactor::run_n(int32_t n) - { - if (n == -1) { - for (;;) { - this->run_one(); - } - } else { - for (int32_t i=0; irun_one(); - } - } - } /*run_n*/ - } /*namespace reactor*/ + namespace reactor { + Reactor::Reactor() { + /* ensure reactor subsystem + deps initialized */ + + InitSubsys::require(); + + Subsystem::initialize_all(); + } + + void + Reactor::run_n(int32_t n) + { + if (n == -1) { + for (;;) { + this->run_one(); + } + } else { + for (int32_t i=0; irun_one(); + } + } + } /*run_n*/ + } /*namespace reactor*/ } /*namespace xo*/ /* end Reactor.cpp */ diff --git a/utest/PollingReactor.test.cpp b/utest/PollingReactor.test.cpp index 39845264..97d679cb 100644 --- a/utest/PollingReactor.test.cpp +++ b/utest/PollingReactor.test.cpp @@ -1,5 +1,6 @@ /* @file PollingReactor.test.cpp */ +#include "xo/reactor/init_reactor.hpp" #include "xo/reactor/PollingReactor.hpp" #include "xo/reactor/FifoQueue.hpp" #include "xo/reactor/Sink.hpp" @@ -57,8 +58,12 @@ namespace xo { } } /*namespace*/ + static InitEvidence s_evidence = InitSubsys::require(); + namespace ut { TEST_CASE("polling0", "[reactor]") { + Subsystem::initialize_all(); + rp reactor = PollingReactor::make(); REQUIRE(reactor.get()); @@ -193,6 +198,8 @@ namespace xo { } /*run_polling1_test*/ TEST_CASE("polling1", "[reactor]") { + Subsystem::initialize_all(); + //log_config::style = function_style::streamlined; log_config::location_tab = 100; From 9adfbb822dc268756cbef93dfd7f1ec134f4010d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 12 Oct 2023 01:31:20 -0400 Subject: [PATCH 0348/2693] Reactor.run_n() returns #events delivered --- include/xo/reactor/Reactor.hpp | 2 +- src/reactor/Reactor.cpp | 10 +++++++--- utest/PollingReactor.test.cpp | 5 +---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/include/xo/reactor/Reactor.hpp b/include/xo/reactor/Reactor.hpp index 2b265b0b..229b7421 100644 --- a/include/xo/reactor/Reactor.hpp +++ b/include/xo/reactor/Reactor.hpp @@ -54,7 +54,7 @@ namespace xo { * otherwise dispatch up to n events. * n = 0 is a noop */ - void run_n(int32_t n); + std::uint64_t run_n(int32_t n); /* borrow calling thread to run indefinitely. * suitable implementation for dedicated reactor threads diff --git a/src/reactor/Reactor.cpp b/src/reactor/Reactor.cpp index 199ec562..d0803d14 100644 --- a/src/reactor/Reactor.cpp +++ b/src/reactor/Reactor.cpp @@ -17,18 +17,22 @@ namespace xo { Subsystem::initialize_all(); } - void + std::uint64_t Reactor::run_n(int32_t n) { + std::uint64_t retval = 0; + if (n == -1) { for (;;) { - this->run_one(); + retval += this->run_one(); } } else { for (int32_t i=0; irun_one(); + retval += this->run_one(); } } + + return retval; } /*run_n*/ } /*namespace reactor*/ } /*namespace xo*/ diff --git a/utest/PollingReactor.test.cpp b/utest/PollingReactor.test.cpp index 97d679cb..106acaf6 100644 --- a/utest/PollingReactor.test.cpp +++ b/utest/PollingReactor.test.cpp @@ -155,10 +155,7 @@ namespace xo { /* pick random #of elements to remove (from front of queue) */ { - - - for (std::size_t j = 0; j < n_deq_attempted; ++j) - n_deq_done += reactor->run_one(); + n_deq_done += reactor->run_n(n_deq_attempted); n_work_attempted += n_deq_attempted; n_work_done += n_deq_done; From f2c1c26f40cf8ab8882b46bd4d11e5b3b025e908 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 12 Oct 2023 09:52:17 -0400 Subject: [PATCH 0349/2693] minor: improve AbstractEventProcessor.display + include .name --- src/reactor/AbstractEventProcessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reactor/AbstractEventProcessor.cpp b/src/reactor/AbstractEventProcessor.cpp index e49cc374..4b1bcc52 100644 --- a/src/reactor/AbstractEventProcessor.cpp +++ b/src/reactor/AbstractEventProcessor.cpp @@ -78,7 +78,7 @@ namespace xo { void AbstractEventProcessor::display(std::ostream & os) const { - os << ""; + os << ""; } /*display*/ std::string From be6d36a5c80f564de7c6ea100a0a3329da55fa3c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 12 Oct 2023 10:51:25 -0400 Subject: [PATCH 0350/2693] build: print CMAKE_MODULE_PATH / CMAKE_PREFIX_PATH --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 40c2c41d..6bd4ca7a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,13 @@ -# reflect/CMakeLists.txt +# xo-reflect/CMakeLists.txt cmake_minimum_required(VERSION 3.10) project(reflect VERSION 0.1) enable_language(CXX) +message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +message(STATUS "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}") + # common XO cmake macros (see proj/xo-cmake) include(xo_macros/xo_cxx) include(xo_macros/code-coverage) From d99acc979d3c018e0d21e023f4b586c7747a4192 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 16 Oct 2023 23:12:14 -0400 Subject: [PATCH 0351/2693] xo-cmake: additions to README --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a06e0291..c5c6353d 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,17 @@ Collects cmake macros to be shared across XO projects (e.g. indentlog, reflect, In some XO project `foo`: ``` $ cd build -$ cmake -DCMAKE_MODULE_PATH=/usr/local/share/cmake .. +$ PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} .. +$ make install ``` then in `foo/CMakeLists.txt`: ``` include(xo_macros/xo_cxx) ``` + +when configuring `foo`: +``` +$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake path/to/foo +``` From 4c78144b472029bafcddc144641e073387dae9f3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 16 Oct 2023 23:17:24 -0400 Subject: [PATCH 0352/2693] xo-cmake: need CMAKE_INSTALL_PREFIX at least for OSX --- cmake/xo_cxx.cmake | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 64ec2822..2246fd5d 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -165,7 +165,9 @@ endmacro() # use this to install typical include file subtree # macro(xo_install_include_tree) - install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) + install( + DIRECTORY ${PROJECT_SOURCE_DIR}/include/ + DESTINATION ${CMAKE_INSTALL_PREFIX}/include) endmacro() # ---------------------------------------------------------------- @@ -222,13 +224,10 @@ macro(xo_export_cmake_config projectname projectversion projecttargets) ) install( EXPORT ${projecttargets} - DESTINATION lib/cmake/${projectname} - ) - install( FILES "${PROJECT_BINARY_DIR}/${projectname}ConfigVersion.cmake" "${PROJECT_BINARY_DIR}/${projectname}Config.cmake" - DESTINATION lib/cmake/${projectname} + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/cmake/${projectname} ) endmacro() From 9a1e33dfd6fea415976caed215c0a5f1993efb94 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 00:17:02 -0400 Subject: [PATCH 0353/2693] cmake: set readonly permissions on install --- CMakeLists.txt | 1 + cmake/xo_cxx.cmake | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index c018ef2d..9c9aa85f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,5 +14,6 @@ install( FILES "cmake/xo_cxx.cmake" "cmake/code-coverage.cmake" + PERMISSIONS OWNER_READ GROUP_READ WORLD_READ DESTINATION share/cmake/${XO_PROJECT_NAME} ) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 2246fd5d..c2d64247 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -167,6 +167,7 @@ endmacro() macro(xo_install_include_tree) install( DIRECTORY ${PROJECT_SOURCE_DIR}/include/ + FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ DESTINATION ${CMAKE_INSTALL_PREFIX}/include) endmacro() @@ -224,9 +225,13 @@ macro(xo_export_cmake_config projectname projectversion projecttargets) ) install( EXPORT ${projecttargets} + PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/cmake/${projectname}) + install( FILES "${PROJECT_BINARY_DIR}/${projectname}ConfigVersion.cmake" "${PROJECT_BINARY_DIR}/${projectname}Config.cmake" + PERMISSIONS OWNER_READ GROUP_READ WORLD_READ DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/cmake/${projectname} ) endmacro() From f88484a79d13648030dcdc8b47adf5be831bab1c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 00:17:58 -0400 Subject: [PATCH 0354/2693] indentlog: xo_install_library2() -> xo_install_library3() --- BUILD.md | 4 ++++ CMakeLists.txt | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/BUILD.md b/BUILD.md index 29023957..4223d6b5 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,5 +1,9 @@ # indentlog build details +## mac osx + +Note: ~ expansion doesn't work in a pure build environment. + ## Test Coverage ### enable coverage build diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e53335a..1cce6e83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,8 +38,9 @@ xo_add_headeronly_library(indentlog) # ---------------------------------------------------------------- # standard install + provide find_package() support -xo_install_library2(indentlog) -xo_install_include_tree() +#xo_install_library2(indentlog) +#xo_install_include_tree() +xo_install_library3(indentlog ${PROJECT_NAME}Targets) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- From e149d44a930cbfa5c8643fd560a12f4b19192010 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 00:18:42 -0400 Subject: [PATCH 0355/2693] indentlog: compile fixes for OSX build (clang 11) --- include/xo/indentlog/scope.hpp | 4 +-- include/xo/indentlog/timeutil/timeutil.hpp | 14 +++++--- utest/timeutil.test.cpp | 37 +++++++++++++--------- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/include/xo/indentlog/scope.hpp b/include/xo/indentlog/scope.hpp index 551589e9..30476664 100644 --- a/include/xo/indentlog/scope.hpp +++ b/include/xo/indentlog/scope.hpp @@ -232,7 +232,7 @@ namespace xo { } /*nesting_level*/ template - basic_scope::state_impl_type * + typename basic_scope::state_impl_type * basic_scope::require_indent_thread_local_state() { state_impl_type * local_state = require_thread_local_state(); @@ -244,7 +244,7 @@ namespace xo { } /*require_thread_local_stream*/ template - basic_scope::state_impl_type * + typename basic_scope::state_impl_type * basic_scope::require_thread_local_state() { if(!s_threadlocal_state) { diff --git a/include/xo/indentlog/timeutil/timeutil.hpp b/include/xo/indentlog/timeutil/timeutil.hpp index 740283f3..5e57aeb0 100644 --- a/include/xo/indentlog/timeutil/timeutil.hpp +++ b/include/xo/indentlog/timeutil/timeutil.hpp @@ -13,10 +13,6 @@ namespace xo { namespace time { - - using utc_nanos = std::chrono::time_point; - using nanos = std::chrono::nanoseconds; using microseconds = std::chrono::microseconds; using milliseconds = std::chrono::milliseconds; @@ -24,6 +20,12 @@ namespace xo { using hours = std::chrono::hours; using days = std::chrono::days; + using utc_nanos = std::chrono::time_point; + using utc_micros = std::chrono::time_point; + + struct timeutil { static utc_nanos now() { return utc_nanos(std::chrono::system_clock::now()); @@ -168,7 +170,9 @@ namespace xo { /* use yyyymmdd.hh:mm:ss.nnnnnn */ - time_t t0_time_t = (std::chrono::system_clock::to_time_t(t0)); + time_t t0_time_t + = (std::chrono::system_clock::to_time_t + (std::chrono::time_point_cast(t0))); //time_t t0_time_t = (std::chrono::system_clock::to_time_t(std::chrono::time_point_cast(t0))); /* convert to std::tm, in UTC coords, diff --git a/utest/timeutil.test.cpp b/utest/timeutil.test.cpp index fb50ca63..c0c17bf2 100644 --- a/utest/timeutil.test.cpp +++ b/utest/timeutil.test.cpp @@ -10,75 +10,82 @@ using namespace xo::time; using namespace std::chrono; namespace ut { + template + inline utc_micros to_micros(FromTime tm) { + return std::chrono::time_point_cast(tm); + } /*to_micros*/ + TEST_CASE("epoch", "[timeutil]") { //tag_config::tag_color = color_spec_type::none(); + using xo::time::microseconds; + utc_nanos t0 = timeutil::epoch(); - REQUIRE(std::chrono::system_clock::to_time_t(t0) == std::time_t(0)); + REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(0)); } /*TEST_CASE(epoch)*/ TEST_CASE("ymd_hms", "[timeutil]") { { utc_nanos t0 = timeutil::ymd_hms(19700101 /*ymd*/, 0 /*hms*/); - REQUIRE(std::chrono::system_clock::to_time_t(t0) == std::time_t(0)); + REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(0)); } { utc_nanos t0 = timeutil::ymd_hms(19700101 /*ymd*/, 1 /*hms*/); - REQUIRE(std::chrono::system_clock::to_time_t(t0) == std::time_t(1)); + REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(1)); } { utc_nanos t0 = timeutil::ymd_hms(19700101 /*ymd*/, 100 /*hms*/); - REQUIRE(std::chrono::system_clock::to_time_t(t0) == std::time_t(60)); + REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(60)); } { utc_nanos t0 = timeutil::ymd_hms(19700101 /*ymd*/, 10000 /*hms*/); - REQUIRE(std::chrono::system_clock::to_time_t(t0) == std::time_t(3600)); + REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(3600)); } { utc_nanos t0 = timeutil::ymd_hms(19700101 /*ymd*/, 235959 /*hms*/); - REQUIRE(std::chrono::system_clock::to_time_t(t0) == std::time_t(86399)); + REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(86399)); } { utc_nanos t0 = timeutil::ymd_hms(19700102 /*ymd*/, 235959 /*hms*/); - REQUIRE(std::chrono::system_clock::to_time_t(t0) == std::time_t(86400 + 86399)); + REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(86400 + 86399)); } { utc_nanos t0 = timeutil::ymd_hms(19700131 /*ymd*/, 235959 /*hms*/); - REQUIRE(std::chrono::system_clock::to_time_t(t0) == std::time_t(30 * 86400 + 86399)); + REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(30 * 86400 + 86399)); } { utc_nanos t0 = timeutil::ymd_hms(19700201 /*ymd*/, 235959 /*hms*/); - REQUIRE(std::chrono::system_clock::to_time_t(t0) == std::time_t(31 * 86400 + 86399)); + REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(31 * 86400 + 86399)); } } /*TEST_CASE(ymd_hms)*/ TEST_CASE("ymd_midnight", "[timeutil]") { { utc_nanos t0 = timeutil::ymd_midnight(19700101 /*ymd*/); - REQUIRE(std::chrono::system_clock::to_time_t(t0) == std::time_t(0)); + REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(0)); } { utc_nanos t0 = timeutil::ymd_midnight(19700102 /*ymd*/); - REQUIRE(std::chrono::system_clock::to_time_t(t0) == std::time_t(86400)); + REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(86400)); } { utc_nanos t0 = timeutil::ymd_midnight(19700131 /*ymd*/); - REQUIRE(std::chrono::system_clock::to_time_t(t0) == std::time_t(30 * 86400)); + REQUIRE(std::chrono::system_clock::to_time_t(to_micros(t0)) == std::time_t(30 * 86400)); } { utc_nanos t0 = timeutil::ymd_midnight(19700201 /*ymd*/); - REQUIRE(system_clock::to_time_t(t0) == std::time_t(31 * 86400)); + REQUIRE(system_clock::to_time_t(to_micros(t0)) == std::time_t(31 * 86400)); } } /*TEST_CASE(ymd_midnight)*/ @@ -144,10 +151,10 @@ namespace ut { INFO(xtag("tc.utc_ymd_hms_usec_str", tc.utc_ymd_hms_usec_str_)); utc_nanos const t0 = timeutil::ymd_hms_usec(tc.ymd_, tc.hms_, tc.usec_); - REQUIRE(system_clock::to_time_t(t0) == std::time_t(tc.epoch_sec_)); + REQUIRE(system_clock::to_time_t(to_micros(t0)) == std::time_t(tc.epoch_sec_)); auto x = timeutil::utc_split_vs_midnight(t0); - REQUIRE(system_clock::to_time_t(x.first) == tc.midnight_sec_); + REQUIRE(system_clock::to_time_t(to_micros(x.first)) == tc.midnight_sec_); REQUIRE(x.second == seconds(tc.fractional_sec_) + microseconds(tc.fractional_usec_)); { From 9227ed0ecfa5c785e023afa9227a3c9c77b3f269 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 00:19:13 -0400 Subject: [PATCH 0356/2693] build: drop default gcc-only concept diagnostic --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index aef9d414..655a14a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,8 @@ add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) # c++ settings set(XO_PROJECT_NAME refcnt) -set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") # gcc-only! add_definitions(${PROJECT_CXX_FLAGS}) From f0208ec71aab7c3bd45b179700b1b0aab76babd5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 00:20:00 -0400 Subject: [PATCH 0357/2693] randomgen: compile fixes for OSX (clang 11) --- include/xo/randomgen/engine_concept.hpp | 39 ++++++++++++++++++++++++- include/xo/randomgen/random_seed.hpp | 2 +- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/include/xo/randomgen/engine_concept.hpp b/include/xo/randomgen/engine_concept.hpp index 42557b0e..38b65cd7 100644 --- a/include/xo/randomgen/engine_concept.hpp +++ b/include/xo/randomgen/engine_concept.hpp @@ -5,6 +5,37 @@ #include #include +namespace std { +#ifdef __clang__ + template < class T > + concept integral = std::is_integral_v; + + template < class T > + concept signed_integral = std::integral && std::is_signed_v; + + template < class T > + concept unsigned_integral + = std::integral && !std::signed_integral; + + template< class F, class... Args > + concept invocable + = requires(F&& f, Args&&... args) { + std::invoke(std::forward(f), std::forward(args)...); + /* not required to be equality-preserving */ + }; + + template< typename G > + concept uniform_random_bit_generator + = std::invocable + && std::unsigned_integral> + && requires { { G::min() } -> std::same_as>; + { G::max() } -> std::same_as>; + requires std::bool_constant<(G::min() < G::max())>::value; }; +#else + /* uniform_random_bit_generator provided by gcc 12.3.2 */ +#endif +} /*namespace std*/ + namespace xo { namespace rng { /* an engine generates psuedo-random bits. @@ -29,7 +60,13 @@ namespace xo { { engine.seed(r) }; { engine == engine }; { engine != engine }; - } && std::copyable && std::uniform_random_bit_generator; + } +#ifdef __clang__ + // std::copyable apparently not available in clang11 ? +#else + && std::copyable +#endif + && std::uniform_random_bit_generator; } /*namespace rng*/ } /*namespace xo*/ diff --git a/include/xo/randomgen/random_seed.hpp b/include/xo/randomgen/random_seed.hpp index 67246c01..92447b3c 100644 --- a/include/xo/randomgen/random_seed.hpp +++ b/include/xo/randomgen/random_seed.hpp @@ -24,7 +24,7 @@ namespace xo { */ template void random_seed(T * p_seed) { -# ifdef _BSD_SOURCE +# ifdef __clang__ /* NOTE: arc4random_buf() works on darwin/nix; * probably need to do something else on intel linux */ From 54413efce4bc26b9c98f6807e77efa8d812129f9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 14:33:52 -0400 Subject: [PATCH 0358/2693] + xo_add_shared_library3() --- cmake/xo_cxx.cmake | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 64ec2822..f55a851e 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -85,6 +85,32 @@ endmacro() # ---------------------------------------------------------------- # use this for a shared library. # +macro(xo_add_shared_library3 target projectTargets targetversion soversion sources) + add_library(${target} SHARED ${sources}) + foreach(arg IN ITEMS ${ARGN}) + #message("target=${target}; arg=${arg}") + + # to use PUBLIC here would need to split: + # $ + # $ + # but shouldn't need that, since we arrange to install includes via + # xo_include_options2() below + # + target_sources(${target} PRIVATE ${arg}) + endforeach() + set_target_properties( + ${target} + PROPERTIES + VERSION ${targetversion} + SOVERSION ${soversion}) + xo_compile_options(${target}) + xo_include_options2(${target}) + xo_install_library3(${target} ${projectTargets}) +endmacro() + +# ---------------------------------------------------------------- +# OBSOLETE. prefer xo_add_shared_library3() +# macro(xo_add_shared_library target targetversion soversion sources) add_library(${target} SHARED ${sources}) foreach(arg IN ITEMS ${ARGN}) @@ -133,11 +159,11 @@ macro(xo_include_options2 target) # target_include_directories( ${target} PUBLIC - $ # e.g. for #include "indentlog/scope.hpp" $ + $ + $ # e.g. for #include "indentlog/scope.hpp" $ # e.g. for #include "Refcounted.hpp" in refcnt/src [DEPRECATED] $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect -# $ $ # e.g. for generated .hpp files ) @@ -346,8 +372,9 @@ endmacro() # 1. a directory pyfoo/ -> library pyfoo # 2. pyfoo/pyfoo.hpp.in -> pyfoo/pyfoo.hpp # -macro(xo_pybind11_library target source_files) - configure_file(${target}.hpp.in ${target}.hpp) +macro(xo_pybind11_library target projectTargets source_files) + configure_file(${target}.hpp.in + ${PROJECT_SOURCE_DIR}/include/xo/${target}/${target}.hpp) # find_package(Python..) finds python in # /Library/Frameworks/Python.framework/... @@ -376,7 +403,7 @@ macro(xo_pybind11_library target source_files) xo_pybind11_link_flags() xo_include_options2(${target}) - xo_install_library2(${target}) + xo_install_library3(${target} ${projectTargets}) endmacro() # ---------------------------------------------------------------- From 298f7a2888f1a94bf756002fedafd5025daff467 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 14:35:58 -0400 Subject: [PATCH 0359/2693] log_level printing + change XO_LITERAL --- CMakeLists.txt | 2 +- include/xo/indentlog/log_level.hpp | 18 ++++++++++++++++++ include/xo/indentlog/scope.hpp | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e53335a..53d75605 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,7 +38,7 @@ xo_add_headeronly_library(indentlog) # ---------------------------------------------------------------- # standard install + provide find_package() support -xo_install_library2(indentlog) +xo_install_library3(indentlog ${PROJECT_NAME}Targets) xo_install_include_tree() xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/include/xo/indentlog/log_level.hpp b/include/xo/indentlog/log_level.hpp index a4392286..dbe49b29 100644 --- a/include/xo/indentlog/log_level.hpp +++ b/include/xo/indentlog/log_level.hpp @@ -2,6 +2,7 @@ #pragma once +#include #include namespace xo { @@ -54,6 +55,23 @@ namespace xo { return (static_cast(x) <= static_cast(y)); } + inline std::ostream & + operator<<(std::ostream & os, + log_level x) { + switch(x) { + case log_level::never: os << "never"; break; + case log_level::verbose: os << "verbose"; break; + case log_level::chatty: os << "chatty"; break; + case log_level::info: os << "info"; break; + case log_level::warning: os << "warning"; break; + case log_level::error: os << "error"; break; + case log_level::severe: os << "severe"; break; + case log_level::always: os << "always"; break; + case log_level::silent: os << "silent"; break; + //default: os << "???"; break; + } + return os; + } /* operator<<*/ } /*namespace xo*/ /* end log_level.hpp */ diff --git a/include/xo/indentlog/scope.hpp b/include/xo/indentlog/scope.hpp index 551589e9..107dfc3e 100644 --- a/include/xo/indentlog/scope.hpp +++ b/include/xo/indentlog/scope.hpp @@ -23,7 +23,7 @@ namespace xo { # define XO_DEBUG(debug_flag) XO_ENTER1(always, debug_flag) # define XO_DEBUG2(debug_flag, name1) XO_ENTER2(always, debug_flag, name1) -# define XO_LITERAL(lvl, name1, name2) xo::scope_setup(xo::log_level::lvl, function_style::literal, name1, name2, __FILE__, __LINE__) +# define XO_LITERAL(lvl, name1, name2) xo::scope_setup(lvl, function_style::literal, name1, name2, __FILE__, __LINE__) //# define XO_SSETUP0() xo::scope_setup(__FUNCTION__) //# define XO_SSETUP0(lvl) xo::scope_setup(xo::log_level::lvl, xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__) From fb36eee3d0906331e85b4de00b334d3a911b9388 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 14:42:33 -0400 Subject: [PATCH 0360/2693] track XO_LITERAL api change --- src/Refcounted.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Refcounted.cpp b/src/Refcounted.cpp index 11c8cb62..eade7a50 100644 --- a/src/Refcounted.cpp +++ b/src/Refcounted.cpp @@ -14,7 +14,7 @@ namespace xo { void * this_ptr, Refcount * x) { - scope lscope(XO_LITERAL(verbose, self_type, method_name), + scope lscope(XO_LITERAL(log_level::verbose, self_type, method_name), "enter", xtag("this", this_ptr), xtag("x", x), From 47f86347a4842d6a5e728d76372fcb489b98fee6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 14:52:09 -0400 Subject: [PATCH 0361/2693] doc updates --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 3c5d34be..40032c62 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,12 @@ # reflection library +## Getting Started + +### build + install dependencies + +- [github/Rconybea/xo-refcnt](https://github.com/Rconybea/xo-refcnt) +- [github/Rconybea/xo-subsys](https://github.com/Rconybea/xo-subsys) + ### build + install ``` $ cd reflect From 8231420c5e4dfe226f68a0d5c144d389c7cf46e2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 14:52:27 -0400 Subject: [PATCH 0362/2693] cosmetic: indenting --- include/xo/reflect/TypeDescr.hpp | 351 ++++++++++++++++--------------- 1 file changed, 180 insertions(+), 171 deletions(-) diff --git a/include/xo/reflect/TypeDescr.hpp b/include/xo/reflect/TypeDescr.hpp index d908a77b..94aa3aa3 100644 --- a/include/xo/reflect/TypeDescr.hpp +++ b/include/xo/reflect/TypeDescr.hpp @@ -98,204 +98,213 @@ namespace std { } /*namespace std*/ namespace xo { - namespace reflect { - inline bool operator==(TypeInfoRef x, TypeInfoRef y) { return TypeInfoRef::is_equal(x, y); } - inline bool operator!=(TypeInfoRef x, TypeInfoRef y) { return !TypeInfoRef::is_equal(x, y); } + namespace reflect { + inline bool operator==(TypeInfoRef x, TypeInfoRef y) { return TypeInfoRef::is_equal(x, y); } + inline bool operator!=(TypeInfoRef x, TypeInfoRef y) { return !TypeInfoRef::is_equal(x, y); } #ifdef NOT_IN_USE - namespace detail { - class HashTypeInfoRef { - public: - std::size_t operator()(TypeInfoRef x) const noexcept { return x.hash_code(); } - }; /*HashTypeInfoRef*/ + namespace detail { + class HashTypeInfoRef { + public: + std::size_t operator()(TypeInfoRef x) const noexcept { return x.hash_code(); } + }; /*HashTypeInfoRef*/ - class EqualTypeInfoRef { - public: - bool operator()(TypeInfoRef x, TypeInfoRef y) const noexcept { return TypeInfoRef::is_equal(x, y); } - }; /*EqualTypeInfoRef*/ - } /*namespace detail*/ + class EqualTypeInfoRef { + public: + bool operator()(TypeInfoRef x, TypeInfoRef y) const noexcept { return TypeInfoRef::is_equal(x, y); } + }; /*EqualTypeInfoRef*/ + } /*namespace detail*/ #endif - class TypeDescrExtra; + class TypeDescrExtra; - /* run-time description for a native c++ type */ - class TypeDescrBase { - public: - /* type-description objects for a type T is unique, - * --> can always use its address - */ - TypeDescrBase(TypeDescrBase const & x) = delete; + /* run-time description for a native c++ type */ + class TypeDescrBase { + public: + /* type-description objects for a type T is unique, + * --> can always use its address + */ + TypeDescrBase(TypeDescrBase const & x) = delete; - /* test whether a type has been reflected. - * introducing this for unit testing - */ - static bool is_reflected(std::type_info const * tinfo) { - return (s_type_table_map.find(TypeInfoRef(tinfo)) - != s_type_table_map.end()); - } /*is_reflected*/ + /* test whether a type has been reflected. + * introducing this for unit testing + */ + static bool is_reflected(std::type_info const * tinfo) { + return (s_type_table_map.find(TypeInfoRef(tinfo)) + != s_type_table_map.end()); + } /*is_reflected*/ - /* NOTE: - * implementation here will be defeated if std::type_info - * objects violate ODR. This occurs with clang + 2-level namespaces, - * so important to linke with --flat_namespace defined. - * See FAQ - * [Build Issues|Q2 - dynamic_cast> fails] - */ - static TypeDescrW require(std::type_info const * tinfo, - std::string_view canonical_name, - std::unique_ptr tdextra); + /* NOTE: + * implementation here will be defeated if std::type_info + * objects violate ODR. This occurs with clang + 2-level namespaces, + * so important to linke with --flat_namespace defined. + * See FAQ + * [Build Issues|Q2 - dynamic_cast> fails] + */ + static TypeDescrW require(std::type_info const * tinfo, + std::string_view canonical_name, + std::unique_ptr tdextra); - /* print table of reflected types to os */ - static void print_reflected_types(std::ostream & os); + /* print table of reflected types to os */ + static void print_reflected_types(std::ostream & os); - TypeId id() const { return id_; } - std::type_info const * typeinfo() const { return typeinfo_; } - std::string_view const & canonical_name() const { return canonical_name_; } - std::string_view const & short_name() const { return short_name_; } - bool complete_flag() const { return complete_flag_; } - TypeDescrExtra * tdextra() const { return tdextra_.get(); } - Metatype metatype() const { return tdextra_->metatype(); } + TypeId id() const { return id_; } + std::type_info const * typeinfo() const { return typeinfo_; } + std::string_view const & canonical_name() const { return canonical_name_; } + std::string_view const & short_name() const { return short_name_; } + bool complete_flag() const { return complete_flag_; } + TypeDescrExtra * tdextra() const { return tdextra_.get(); } + Metatype metatype() const { return tdextra_->metatype(); } - /* true iff the type represented by *this is the same as the type T. - * - * Warning: comparing typeinfo address can give false negatives. - * suspect this is caused by problems coalescing linker symbols - * in the clang toolchain. - */ - template - bool is_native() const { - return ((this->typeinfo() == &typeid(T)) - || (this->typeinfo()->hash_code() == typeid(T).hash_code()) - || (this->typeinfo()->name() == typeid(T).name())); - } /*is_native*/ + /* true iff the type represented by *this is the same as the type T. + * + * Warning: comparing typeinfo address can give false negatives. + * suspect this is caused by problems coalescing linker symbols + * in the clang toolchain. + */ + template + bool is_native() const { + return ((this->typeinfo() == &typeid(T)) + || (this->typeinfo()->hash_code() == typeid(T).hash_code()) + || (this->typeinfo()->name() == typeid(T).name())); + } /*is_native*/ - /* safe downcast -- like dynamic_cast<>, but does not require a source type */ - template - T * recover_native(void * address) const { - if (this->is_native()) { - return reinterpret_cast(address); - } else { - return nullptr; - } - } /*recover_native*/ + /* safe downcast -- like dynamic_cast<>, but does not require a source type */ + template + T * recover_native(void * address) const { + if (this->is_native()) { + return reinterpret_cast(address); + } else { + return nullptr; + } + } /*recover_native*/ - bool is_vector() const { return this->tdextra_->is_vector(); } - bool is_struct() const { return this->tdextra_->is_struct(); } + bool is_vector() const { return this->tdextra_->is_vector(); } + bool is_struct() const { return this->tdextra_->is_struct(); } - /* given a T-instance object, return tagged pointer with T replaced - * by the most-derived-subtype of T to which *object belongs. - * This works only for descendants of reflect::SelfTagging - */ - TaggedPtr most_derived_self_tp(void * object) const; + /* given a T-instance object, return tagged pointer with T replaced + * by the most-derived-subtype of T to which *object belongs. + * This works only for descendants of reflect::SelfTagging + */ + TaggedPtr most_derived_self_tp(void * object) const; - /* if generalized vector (std::vector, std::array, ..): - * .n_child() reports #of elements - * if struct/class: - * .n_child() reports #of instance variables (that have been reflected) - */ - uint32_t n_child(void * object) const { return this->tdextra_->n_child(object); } - TaggedPtr child_tp(uint32_t i, void * object) const; + /* if generalized vector (std::vector, std::array, ..): + * .n_child() reports #of elements + * if struct/class: + * .n_child() reports #of instance variables (that have been reflected) + */ + uint32_t n_child(void * object) const { return this->tdextra_->n_child(object); } + TaggedPtr child_tp(uint32_t i, void * object) const; - /* require: - * - .is_struct() = true - * - i in [0 .. .n_child() - 1] - */ - std::string const & struct_member_name(uint32_t i) const { - return this->tdextra_->struct_member_name(i); - } - /* fetch runtime description for i'th reflected instance variable. - * - * require: - * - .is_struct() = true - * - i in [0 .. .n_child() - 1] - */ - StructMember const & struct_member(uint32_t i) const { - StructMember const * sm = this->tdextra_->struct_member(i); + /* require: + * - .is_struct() = true + * - i in [0 .. .n_child() - 1] + */ + std::string const & struct_member_name(uint32_t i) const { + return this->tdextra_->struct_member_name(i); + } + /* fetch runtime description for i'th reflected instance variable. + * + * require: + * - .is_struct() = true + * - i in [0 .. .n_child() - 1] + */ + StructMember const & struct_member(uint32_t i) const { + StructMember const * sm = this->tdextra_->struct_member(i); - assert(sm); - return *sm; - } /*struct_member*/ + assert(sm); + return *sm; + } /*struct_member*/ - void display(std::ostream & os) const; - std::string display_string() const; + void display(std::ostream & os) const; + std::string display_string() const; - /* mark this TypeDescr complete; - * returns the value of .complete_flag from _before_ - * this call - */ - bool mark_complete(); + /* mark this TypeDescr complete; + * returns the value of .complete_flag from _before_ + * this call + */ + bool mark_complete(); - /* call this once to attach extended type information to a type-description - * (e.g. description of struct members for a record type) - */ - void assign_tdextra(std::unique_ptr tdx); + /* call this once to attach extended type information to a type-description + * (e.g. description of struct members for a record type) + */ + void assign_tdextra(std::unique_ptr tdx); - private: - TypeDescrBase(TypeId id, - std::type_info const * tinfo, - std::string_view canonical_name, - std::unique_ptr tdextra); + private: + TypeDescrBase(TypeId id, + std::type_info const * tinfo, + std::string_view canonical_name, + std::unique_ptr tdextra); - private: - /* invariant: - * - for all TypeDescrImpl instances x: - * - s_type_table_v[x->id()] = x - * - s_type_table_map[TypeInfoRef(x->typeinfo())] = x - */ + private: + /* invariant: + * - for all TypeDescrImpl instances x: + * - s_type_table_v[x->id()] = x + * - s_type_table_map[TypeInfoRef(x->typeinfo())] = x + */ - /* hashmap of all TypeDescr instances, indexed by . singleton */ - static std::unordered_map> s_type_table_map; - /* hashmap of (presumed) duplicate TypeInfoRef values. - * This happens with clang sometimes when the same type is referenced - * from multiple modules (i.e. shared libs). - */ - static std::unordered_map s_coalesced_type_table_map; + /* hashmap of all TypeDescr instances, indexed by . singleton */ + static std::unordered_map> s_type_table_map; + /* hashmap of (presumed) duplicate TypeInfoRef values. + * This happens with clang sometimes when the same type is referenced + * from multiple modules (i.e. shared libs). + */ + static std::unordered_map s_coalesced_type_table_map; - /* vector of all TypeDescr instances. singleton. */ - static std::vector s_type_table_v; + /* vector of all TypeDescr instances. singleton. */ + static std::vector s_type_table_v; - private: - /* unique id# for this type */ - TypeId id_; - /* typeinfo for type T */ - std::type_info const * typeinfo_ = nullptr; - /* canonical name for this type (see demangle.hpp for type_name()) - * e.g. - * xo::option::Px2 - */ - std::string_view canonical_name_; - /* suffix of .canonical_name, just after last ':' - * e.g. - * Px2 - */ - std::string_view short_name_; - /* set to true once final value for .tdextra is established - * intially all TypeDescr objects will use AtomicTdx for .tdextra - * Reflect::require() upgrades .tdextra for particular types. - * When that procedure makes a decision for a type T, - * .complete_flag will be set to true for the corresponding TypeDescrBase instance - */ - bool complete_flag_ = false; - /* additional type information that either: - * (a) isn't universal across all types, - * e.g. dereferencing instance of a pointer type - * (b) can't be captured with template-fu, - * e.g. struct member names - * - * generally .tdextra will be populated some time after TypeDescrBase's ctor exits. - * This is necessary because of (b) above, also because of possibility of recursive - * types. - */ - std::unique_ptr tdextra_; - }; /*TypeDescrBase*/ + private: + /* unique id# for this type */ + TypeId id_; + /* typeinfo for type T */ + std::type_info const * typeinfo_ = nullptr; + /* canonical name for this type (see demangle.hpp for type_name()) + * e.g. + * xo::option::Px2 + */ + std::string_view canonical_name_; + /* suffix of .canonical_name, just after last ':' + * e.g. + * Px2 + */ + std::string_view short_name_; + /* set to true once final value for .tdextra is established + * intially all TypeDescr objects will use AtomicTdx for .tdextra + * Reflect::require() upgrades .tdextra for particular types. + * When that procedure makes a decision for a type T, + * .complete_flag will be set to true for the corresponding TypeDescrBase instance + */ + bool complete_flag_ = false; + /* additional type information that either: + * (a) isn't universal across all types, + * e.g. dereferencing instance of a pointer type + * (b) can't be captured with template-fu, + * e.g. struct member names + * + * generally .tdextra will be populated some time after TypeDescrBase's ctor exits. + * This is necessary because of (b) above, also because of possibility of recursive + * types. + */ + std::unique_ptr tdextra_; + }; /*TypeDescrBase*/ - inline std::ostream & - operator<<(std::ostream & os, TypeDescrBase const & x) { - x.display(os); - return os; - } /*operator<<*/ + inline std::ostream & + operator<<(std::ostream & os, TypeDescrBase const & x) { + x.display(os); + return os; + } /*operator<<*/ - } /*namespace reflect*/ + /* tag to drive overload resolution */ + struct reflected_types_printer {}; + + inline std::ostream & + operator<<(std::ostream & os, reflected_types_printer) { + TypeDescrBase::print_reflected_types(os); + return os; + } + + } /*namespace reflect*/ } /*namespace xo*/ /* end TypeDescr.hpp */ From 0707571945155eb9e05e51426d3c96a83eea99c6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 14:52:44 -0400 Subject: [PATCH 0363/2693] cosmetic: indentation --- src/reflect/TypeDescr.cpp | 300 +++++++++++++++++++------------------- 1 file changed, 150 insertions(+), 150 deletions(-) diff --git a/src/reflect/TypeDescr.cpp b/src/reflect/TypeDescr.cpp index 93a0bfc1..b0a0dfd7 100644 --- a/src/reflect/TypeDescr.cpp +++ b/src/reflect/TypeDescr.cpp @@ -7,188 +7,188 @@ #include "xo/indentlog/scope.hpp" namespace xo { - using xo::scope; - using xo::xtag; - using xo::tostr; + using xo::scope; + using xo::xtag; + using xo::tostr; - namespace reflect { - uint32_t - TypeId::s_next_id = 1; + namespace reflect { + uint32_t + TypeId::s_next_id = 1; - std::unordered_map> - TypeDescrBase::s_type_table_map; + std::unordered_map> + TypeDescrBase::s_type_table_map; - std::unordered_map - TypeDescrBase::s_coalesced_type_table_map; + std::unordered_map + TypeDescrBase::s_coalesced_type_table_map; - std::vector - TypeDescrBase::s_type_table_v; + std::vector + TypeDescrBase::s_type_table_v; - TypeDescrW - TypeDescrBase::require(std::type_info const * tinfo, - std::string_view canonical_name, - std::unique_ptr tdextra) - { - /* 1. lookup by tinfo hash_code in s_type_table_map */ - { - auto ix = s_type_table_map.find(TypeInfoRef(tinfo)); + TypeDescrW + TypeDescrBase::require(std::type_info const * tinfo, + std::string_view canonical_name, + std::unique_ptr tdextra) + { + /* 1. lookup by tinfo hash_code in s_type_table_map */ + { + auto ix = s_type_table_map.find(TypeInfoRef(tinfo)); - if ((ix != s_type_table_map.end()) && ix->second) - return ix->second.get(); - } + if ((ix != s_type_table_map.end()) && ix->second) + return ix->second.get(); + } - /* 2. lookup by tinfo hash_code in s_coalesced_type_table_map */ - { - auto ix = s_coalesced_type_table_map.find(TypeInfoRef(tinfo)); + /* 2. lookup by tinfo hash_code in s_coalesced_type_table_map */ + { + auto ix = s_coalesced_type_table_map.find(TypeInfoRef(tinfo)); - if ((ix != s_coalesced_type_table_map.end()) && ix->second) - return ix->second; - } + if ((ix != s_coalesced_type_table_map.end()) && ix->second) + return ix->second; + } - /* 3. O(n) lookup by canonical_name, before we create a new slot. - * - * Have to accept that on clang type_info objects aren't always unique (!$@#!!) - * - * TODO: lookup table keyed by canonical_name - */ - for (TypeDescrBase * x : s_type_table_v) { - if (x && (x->canonical_name() == canonical_name)) { - /* 1. assume *x represents the type associated with tinfo. - * 2. *do* store tinfo in s_coalesced_type_table_map[], - * for faster lookup next time - */ - s_coalesced_type_table_map[TypeInfoRef(tinfo)] = x; + /* 3. O(n) lookup by canonical_name, before we create a new slot. + * + * Have to accept that on clang type_info objects aren't always unique (!$@#!!) + * + * TODO: lookup table keyed by canonical_name + */ + for (TypeDescrBase * x : s_type_table_v) { + if (x && (x->canonical_name() == canonical_name)) { + /* 1. assume *x represents the type associated with tinfo. + * 2. *do* store tinfo in s_coalesced_type_table_map[], + * for faster lookup next time + */ + s_coalesced_type_table_map[TypeInfoRef(tinfo)] = x; - return x; - } - } + return x; + } + } - TypeId id = TypeId::allocate(); + TypeId id = TypeId::allocate(); - std::unique_ptr & slot = s_type_table_map[TypeInfoRef(tinfo)]; + std::unique_ptr & slot = s_type_table_map[TypeInfoRef(tinfo)]; - slot.reset(new TypeDescrBase(id, - tinfo, - canonical_name, - std::move(tdextra))); + slot.reset(new TypeDescrBase(id, + tinfo, + canonical_name, + std::move(tdextra))); - if (s_type_table_v.size() <= id.id()) - s_type_table_v.resize(id.id() + 1); + if (s_type_table_v.size() <= id.id()) + s_type_table_v.resize(id.id() + 1); - s_type_table_v[id.id()] = slot.get(); + s_type_table_v[id.id()] = slot.get(); - return slot.get(); - } /*require*/ + return slot.get(); + } /*require*/ - void - TypeDescrBase::print_reflected_types(std::ostream & os) - { - os << "display(os); - } - } + for (TypeDescrBase * td : s_type_table_v) { + os << "\n "; + if (td) { + td->display(os); + } + } - os << ">\n"; - } /*print_reflected_types*/ + os << ">\n"; + } /*print_reflected_types*/ - namespace { - /* readability hack: - * foo::bar::Quux ==> Quux - * but lookout for template names: - * std::pair ==> pair - */ - std::string_view - unqualified_name(std::string_view const & canonical_name) - { - size_t m = canonical_name.find_first_of('<'); + namespace { + /* readability hack: + * foo::bar::Quux ==> Quux + * but lookout for template names: + * std::pair ==> pair + */ + std::string_view + unqualified_name(std::string_view const & canonical_name) + { + size_t m = canonical_name.find_first_of('<'); - /* skip ':', but only in range [0..m) */ - size_t p = canonical_name.find_last_of(':', m); + /* skip ':', but only in range [0..m) */ + size_t p = canonical_name.find_last_of(':', m); - if (p == std::string_view::npos) { - return canonical_name; - } else { - if ((canonical_name.substr(0, 9) == "std::pair") - || (canonical_name.substr(0, 13) == "std::_1::pair")) - { - return std::string_view("pair"); - } else { - return std::string_view(canonical_name.substr(p+1)); - } - } - } /*unqualified_name*/ - } /*namespace*/ + if (p == std::string_view::npos) { + return canonical_name; + } else { + if ((canonical_name.substr(0, 9) == "std::pair") + || (canonical_name.substr(0, 13) == "std::_1::pair")) + { + return std::string_view("pair"); + } else { + return std::string_view(canonical_name.substr(p+1)); + } + } + } /*unqualified_name*/ + } /*namespace*/ - TypeDescrBase::TypeDescrBase(TypeId id, - std::type_info const * tinfo, - std::string_view canonical_name, - std::unique_ptr tdextra) - : id_{std::move(id)}, - typeinfo_{tinfo}, - canonical_name_{std::move(canonical_name)}, - short_name_{unqualified_name(canonical_name_)}, - tdextra_{std::move(tdextra)} - { - } + TypeDescrBase::TypeDescrBase(TypeId id, + std::type_info const * tinfo, + std::string_view canonical_name, + std::unique_ptr tdextra) + : id_{std::move(id)}, + typeinfo_{tinfo}, + canonical_name_{std::move(canonical_name)}, + short_name_{unqualified_name(canonical_name_)}, + tdextra_{std::move(tdextra)} + { + } - TaggedPtr - TypeDescrBase::most_derived_self_tp(void * object) const - { - return this->tdextra_->most_derived_self_tp(this, object); - } /*most_derived_self_tp*/ + TaggedPtr + TypeDescrBase::most_derived_self_tp(void * object) const + { + return this->tdextra_->most_derived_self_tp(this, object); + } /*most_derived_self_tp*/ - TaggedPtr - TypeDescrBase::child_tp(uint32_t i, void * object) const - { - return this->tdextra_->child_tp(i, object); - } /*child_tp*/ + TaggedPtr + TypeDescrBase::child_tp(uint32_t i, void * object) const + { + return this->tdextra_->child_tp(i, object); + } /*child_tp*/ - void - TypeDescrBase::display(std::ostream & os) const - { - os << "metatype()) - << ">"; - } /*display*/ + void + TypeDescrBase::display(std::ostream & os) const + { + os << "metatype()) + << ">"; + } /*display*/ - std::string - TypeDescrBase::display_string() const - { - return tostr(*this); - } /*display_string*/ + std::string + TypeDescrBase::display_string() const + { + return tostr(*this); + } /*display_string*/ - bool - TypeDescrBase::mark_complete() - { - bool retval = this->complete_flag_; + bool + TypeDescrBase::mark_complete() + { + bool retval = this->complete_flag_; - this->complete_flag_ = true; + this->complete_flag_ = true; - return retval; - } /*mark_complete*/ + return retval; + } /*mark_complete*/ - void - TypeDescrBase::assign_tdextra(std::unique_ptr tdx) - { - scope log(XO_ENTER0(verbose), - xtag("canonical_name", this->canonical_name()), - xtag("tdextra.old", this->tdextra_.get()), - xtag("metatype.old", (this->tdextra_ - ? this->tdextra_->metatype() - : Metatype::mt_invalid)), - xtag("metatype.new", tdx->metatype())); + void + TypeDescrBase::assign_tdextra(std::unique_ptr tdx) + { + scope log(XO_ENTER0(verbose), + xtag("canonical_name", this->canonical_name()), + xtag("tdextra.old", this->tdextra_.get()), + xtag("metatype.old", (this->tdextra_ + ? this->tdextra_->metatype() + : Metatype::mt_invalid)), + xtag("metatype.new", tdx->metatype())); - this->complete_flag_ = true; - this->tdextra_ = std::move(tdx); - } /*assign_tdextra*/ - } /*namespace reflect*/ + this->complete_flag_ = true; + this->tdextra_ = std::move(tdx); + } /*assign_tdextra*/ + } /*namespace reflect*/ } /*namespace xo*/ /* end TypeDescr.cpp */ From 0d6e9afc0a757c0275591341b664632777714582 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 14:52:53 -0400 Subject: [PATCH 0364/2693] build: tidy in CMakeLists.txt --- utest/CMakeLists.txt | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 8d142b9b..b701f2a6 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -9,22 +9,6 @@ xo_include_options2(${SELF_EXECUTABLE_NAME}) add_test(NAME ${SELF_EXECUTABLE_NAME} COMMAND ${SELF_EXECUTABLE_NAME}) target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) -# ---------------------------------------------------------------- -# generic project dependency - -# PROJECT_SOURCE_DIR: -# so we can for example write -# #include "indentlog/scope.hpp" -# from anywhere in the project -# PROJECT_BINARY_DIR: -# since version file will be in build directory, need that directory -# to also be included in compiler's include path -# -#xo_include_options2(${SELF_EXECUTABLE_NAME}) -#target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC -# ${PROJECT_SOURCE_DIR} -# ${PROJECT_BINARY_DIR}) - # ---------------------------------------------------------------- # internal dependencies: logutil, ... @@ -35,15 +19,6 @@ xo_self_dependency(${SELF_EXECUTABLE_NAME} reflect) xo_external_target_dependency(${SELF_EXECUTABLE_NAME} Catch2 Catch2::Catch2) -# need this so that catch2/include appears in compile_commands.json, -# on which lsp integration relies. -# -# See also /nix/store/*-catch2-*/lib/cmake/Catch2/ParseAndAddCatchTests.cmake; -# commands here derived from ^ .cmake file -# -#find_path(CATCH_INCLUDE_DIR "catch2/catch.hpp") -#target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC ${CATCH_INCLUDE_DIR}) - # ---------------------------------------------------------------- # make standard directories for std:: includes explicit # so that From a4c264d8db2f02e7d7bc433e22f571b65450aba1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 14:54:27 -0400 Subject: [PATCH 0365/2693] randomgen: distribution + related concepts --- include/xo/randomgen/distribution_concept.hpp | 35 +++++++++++++ include/xo/randomgen/generator.hpp | 49 +++++++++++++++++++ include/xo/randomgen/normalgen.hpp | 14 ++++++ 3 files changed, 98 insertions(+) create mode 100644 include/xo/randomgen/distribution_concept.hpp create mode 100644 include/xo/randomgen/generator.hpp create mode 100644 include/xo/randomgen/normalgen.hpp diff --git a/include/xo/randomgen/distribution_concept.hpp b/include/xo/randomgen/distribution_concept.hpp new file mode 100644 index 00000000..61c764e0 --- /dev/null +++ b/include/xo/randomgen/distribution_concept.hpp @@ -0,0 +1,35 @@ +/* @file distribution_concept.hpp */ + +#pragma once + +#include + +namespace xo { + namespace rng { + template + concept distribution_concept = requires(Distribution dist, typename Distribution::param_type p) { + typename Distribution::result_type; + typename Distribution::param_type; + { Distribution() }; + { Distribution(p) }; + { dist.reset() }; + { dist.param() }; + { dist.param(p) }; + // { dist(g) }; // generator g satisfying engine_concept + // { dist(g, p) }; + { dist.min() }; + { dist.max() }; + { dist == dist }; + { dist != dist }; + // os << dist + // is >> dist + + } && std::copyable + && std::copyable + && std::equality_comparable; + } /*namespace rng*/ + +} /*namespace xo*/ + + +/* end distribution_concept.hpp */ diff --git a/include/xo/randomgen/generator.hpp b/include/xo/randomgen/generator.hpp new file mode 100644 index 00000000..f35c0994 --- /dev/null +++ b/include/xo/randomgen/generator.hpp @@ -0,0 +1,49 @@ +/* @file generator.hpp */ + +#pragma once + +#include "engine_concept.hpp" +#include "distribution_concept.hpp" +#include + +namespace xo { + namespace rng { + /* Engine: uniform integer random number generator, e.g. xoshiro256ss + * Distribution: random number distribution, e.g. std::normal_distribution + */ + template requires engine_concept && distribution_concept + class generator { + public: + using result_type = typename Distribution::result_type; + using engine_type = Engine; + + public: + generator(Engine & e, Distribution const & d) + : engine_{e}, + distribution_{d} {} + generator(Engine && e, Distribution && d) + : engine_{std::move(e)}, + distribution_{std::move(d)} {} + + static generator make(Engine e, Distribution d) { + return generator(e, d); + } + + result_type operator()() { return this->distribution_(this->engine_); } + + private: + /* random number generator; generates uniformly-distributed integers */ + Engine engine_; + /* distribution object */ + Distribution distribution_; + }; /*generator*/ + + template + generator make_generator(Engine e, Distribution d) { + return generator::make(std::move(e), + std::move(d)); + } /*make_generator*/ + } /*namespace rng*/ +} /*namespace xo*/ + +/* end generator.hpp */ diff --git a/include/xo/randomgen/normalgen.hpp b/include/xo/randomgen/normalgen.hpp new file mode 100644 index 00000000..dad04328 --- /dev/null +++ b/include/xo/randomgen/normalgen.hpp @@ -0,0 +1,14 @@ +/* @file normalgen.hpp */ + +#pragma once + +#include "generator.hpp" +#include + +namespace xo { + namespace rng { + /* Engine: e.g. xo::rng::xoshiro256 or std::mt19937 */ + template + using normalgen = generator>; + } /*namespace rng*/ +} /*namespace xo*/ From 2040d530b8c91d12d78df5e920bf588456714348 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 14:55:25 -0400 Subject: [PATCH 0366/2693] build: streamlining, use xo_install_library3() --- CMakeLists.txt | 9 +++++---- utest/CMakeLists.txt | 26 ++------------------------ 2 files changed, 7 insertions(+), 28 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 94bd08fa..6a7b8377 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,14 +33,15 @@ xo_toplevel_compile_options() add_subdirectory(utest) -set(SELF_LIB xo_ordinaltree) +# ---------------------------------------------------------------- +# header-only library -add_library(${SELF_LIB} INTERFACE) -xo_include_headeronly_options2(${SELF_LIB}) +set(SELF_LIB ordinaltree) +xo_add_headeronly_library(${SELF_LIB}) # ---------------------------------------------------------------- # -xo_install_library2(${PROJECT_NAME}) +xo_install_library3(${SELF_LIB} ${PROJECT_NAME}Targets) xo_install_include_tree() # (note: ..Targets from xo_install_library2()) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 26b370b4..30311ce5 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -1,4 +1,4 @@ -# tree/utest/CMakeLists.txt +# ordinaltree/utest/CMakeLists.txt # note: tests in this directory use Catch2-provided main set(SELF_EXE utest.tree) @@ -22,26 +22,4 @@ xo_dependency(${SELF_EXE} randomgen) xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) -# need this so that catch2/include appears in compile_commands.json, -# on which lsp integration relies. -# -# See also /nix/store/*-catch2-*/lib/cmake/Catch2/ParseAndAddCatchTests.cmake; -# commands here derived from ^ .cmake file -# - -# let's see if xo_external_target_dependency() works for these.. -#find_path(CATCH_INCLUDE_DIR "catch2/catch.hpp") -#target_include_directories(${SELF_UTEST_NAME} PUBLIC ${CATCH_INCLUDE_DIR}) - -## ---------------------------------------------------------------- -## make standard directories for std:: includes explicit -## so that -## (1) they appear in compile_commands.json. -## (2) clangd (run from emacs lsp-mode) can find them -## -#if(CMAKE_EXPORT_COMPILE_COMMANDS) -# set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES -# ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) -#endif() - -# end tree/utest/CMakeLists.txt +# end ordinaltree/utest/CMakeLists.txt From b8d15cc1fa5aeeceb76c55511db01a7dfe220d30 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:02:57 -0400 Subject: [PATCH 0367/2693] mention dependencies in README --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 41625b26..9adda0d5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,12 @@ # ordinal tree library +## Getting Started + +### build + install dependencies + +- see [github/Rconybea/randomgen](https://github.com/Rconybea/randomgen) -- random number generators e.g. xoshiro256ss +- see [github/Rconybea/refcnt](https://github.com/Rconybea/refcnt) -- intrusive reference-counting + ### build + install ``` $ cd xo-ordinaltree From 4468aa0ee173b482c48b0bac3656cc49ef14ff9c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:06:30 -0400 Subject: [PATCH 0368/2693] doc: README improvements --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d9bbae39..3aa5ee81 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ # pybind11 utilities for XO projects -# to build + install locally +## Getting Started + +### build + install dependencies + +- see [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) + +### to build + install locally ``` $ cd xo-pyutil @@ -11,3 +17,9 @@ $ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=$(PREFIX) $ make $ make install ``` + +### LSP support +``` +$ cd xo-pyutil +$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree +``` From 4246f336425a1730cb4bbab244594e94c1670922 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:13:19 -0400 Subject: [PATCH 0369/2693] build: track xo-cmake streamlining --- CMakeLists.txt | 2 +- src/pyreflect/CMakeLists.txt | 4 ++-- src/pyreflect/pyreflect.cpp | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c372fd5..ccf4b129 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,7 +42,7 @@ add_subdirectory(src/pyreflect) # ---------------------------------------------------------------- # provide find_package() support -xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} pyreflectTargets) +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- # install .hpp files diff --git a/src/pyreflect/CMakeLists.txt b/src/pyreflect/CMakeLists.txt index 0579d761..94a71814 100644 --- a/src/pyreflect/CMakeLists.txt +++ b/src/pyreflect/CMakeLists.txt @@ -1,4 +1,4 @@ -# xo_pyreflect/CMakeLists.txt +# xo_pyreflect/src/pyreflect/CMakeLists.txt set(SELF_LIB pyreflect) set(SELF_SRCS pyreflect.cpp) @@ -6,7 +6,7 @@ set(SELF_SRCS pyreflect.cpp) # ---------------------------------------------------------------- # pybind11 dep -xo_pybind11_library(${SELF_LIB} ${SELF_SRCS}) +xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} reflect) diff --git a/src/pyreflect/pyreflect.cpp b/src/pyreflect/pyreflect.cpp index 3a9f81bb..0b638658 100644 --- a/src/pyreflect/pyreflect.cpp +++ b/src/pyreflect/pyreflect.cpp @@ -26,7 +26,8 @@ namespace xo { //py::class_(m, "utc_nanos"); //py::class_(m, "TypeDescr"); - py::class_>(m, "TypeDescr") + py::class_>(m, "TypeDescr") .def_static("print_reflected_types", [](){ TypeDescrBase::print_reflected_types(std::cout); }) .def_property_readonly("canonical_name", &TypeDescrBase::canonical_name) From bc55885778898247204a3b2392ace02de95e24a2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:13:58 -0400 Subject: [PATCH 0370/2693] doc: README additions --- README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 434a80a5..7be07dd9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ # python bindings for c++ reflection library (xo-reflect) -## build + install +## Getting Started + +### build + install dependencies + +- [github/Rconybea/xo-pyutil](https://github.com/Rconybea/xo-pyutil) +- [github/Rconybea/xo-reflect](https://github.com/Rconybea/xo-reflect) + +### build + install ``` $ cd xo-pyreflect $ mkdir build @@ -13,8 +20,9 @@ $ cmake \ $ make $ make install ``` +(also see .github/workflows/main.yml) -## build for unit test coverage +### build for unit test coverage ``` $ cd xo-pyreflect $ mkdir build-ccov @@ -26,7 +34,7 @@ $ cmake \ -DCMAKE_BUILD_TYPE=Debug .. ``` -## LSP (language server) support +### LSP (language server) support LSP looks for compile commands in the root of the source tree; while Cmake creates them in the root of its build directory. From 465a712252eefdd0af9559193b6cda0ae8b1211b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:18:40 -0400 Subject: [PATCH 0371/2693] build: xo_add_shared_library() -> xo_add_shared_library3() --- cmake/printjsonConfig.cmake.in | 1 + src/printjson/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cmake/printjsonConfig.cmake.in b/cmake/printjsonConfig.cmake.in index e7f7f1be..9417e239 100644 --- a/cmake/printjsonConfig.cmake.in +++ b/cmake/printjsonConfig.cmake.in @@ -2,5 +2,6 @@ include(CMakeFindDependencyMacro) find_dependency(reflect) + include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") diff --git a/src/printjson/CMakeLists.txt b/src/printjson/CMakeLists.txt index cf191d02..b0a4d532 100644 --- a/src/printjson/CMakeLists.txt +++ b/src/printjson/CMakeLists.txt @@ -3,7 +3,7 @@ set(SELF_LIB printjson) set(SELF_SRCS PrintJson.cpp init_printjson.cpp) -xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_add_shared_library3(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- # dependencies: indentlog, ... From 652362a127e70c06e2c26bdffb9bb1aaea2eacfc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:18:57 -0400 Subject: [PATCH 0372/2693] doc: README additions --- README.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 99492884..28ef1b15 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ # printjson library -# build + install +## Getting Started + +### build + install dependencies + +- [github/Rconybea/reflect](https://github.com/Rconybea/reflect) + +### build + install + ``` $ cd xo-printjson $ mkdir build @@ -11,7 +18,8 @@ $ make $ make install ``` -# build for unit test coverage +### build for unit test coverage + ``` $ cd xo-printjson $ mkdir ccov @@ -22,7 +30,8 @@ $ make ccov # runs instrumented unit tests $ make ccov-all # generates lcov report ``` -# LSP support +### LSP support + ``` $ cd xo-printjson $ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree From add10041086a807c9c984f3e0506b2aaaf20584f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:21:24 -0400 Subject: [PATCH 0373/2693] doc: README additions --- CMakeLists.txt | 2 +- README.md | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b0079d4..d67984af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,7 @@ add_code_coverage() add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) # ---------------------------------------------------------------- -# c++ settings +# common c++ settings # PROJECT_CXX_FLAGS: bespoke for this project - usually empty set(PROJECT_CXX_FLAGS "") diff --git a/README.md b/README.md index fcfa8e8a..53cc5849 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,14 @@ Reentrant: even while being invoked. 2. Any such re-entrant operations are deferred until callback invocation completes. -# build + install +## Getting Started + +### build + install dependencies + +- [github/Rconybea/refcnt](https://github.com/Rconybea/refcnt) + +### build + install + ``` $ cd xo-callback $ mkdir build @@ -20,7 +27,8 @@ $ make install ``` (also see .github/workflows/main.yml) -# build for unit test coverage +### build for unit test coverage + ``` $ cd xo-callback $ mkdir build-ccov @@ -32,7 +40,7 @@ $ cmake \ -DCMAKE_BUILD_TYPE=Debug .. ``` -# LSP (language server) support +### LSP (language server) support LSP looks for compile commands in the root of the source tree; cmake creates them in the root of its build directory. From 86b9a40ae7740f6f7e6aea2917138a405deea1d0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:22:06 -0400 Subject: [PATCH 0374/2693] header-only implementation --- include/xo/callback/CallbackSet.hpp | 27 ++++++++++++++++++++------- src/callback/CallbackSet.cpp | 9 --------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/include/xo/callback/CallbackSet.hpp b/include/xo/callback/CallbackSet.hpp index c15aeb34..04c83800 100644 --- a/include/xo/callback/CallbackSet.hpp +++ b/include/xo/callback/CallbackSet.hpp @@ -17,23 +17,36 @@ namespace xo { * * can use id to remove callback later: * cbset.remove_callback(cb_id); + * + * Tag so xo-callback can be header-only */ - class CallbackId { + template + class CallbackIdImpl { public: - CallbackId() = default; - explicit CallbackId(uint32_t id) : id_{id} {} + CallbackIdImpl() = default; + explicit CallbackIdImpl(uint32_t id) : id_{id} {} /* generate a globally-unique id (not threadsafe) */ - static CallbackId generate(); + static CallbackIdImpl generate() { + static CallbackIdImpl s_last_id; + + s_last_id = CallbackIdImpl(s_last_id.id() + 1); + + return s_last_id; + } /*generate*/ uint32_t id() const { return id_; } private: uint32_t id_ = 0; - }; /*CallbackId*/ + }; /*CallbackIdImpl*/ - inline bool operator==(CallbackId lhs, CallbackId rhs) { return lhs.id() == rhs.id(); } - inline bool operator!=(CallbackId lhs, CallbackId rhs) { return lhs.id() != rhs.id(); } + template + inline bool operator==(CallbackIdImpl lhs, CallbackIdImpl rhs) { return lhs.id() == rhs.id(); } + template + inline bool operator!=(CallbackIdImpl lhs, CallbackIdImpl rhs) { return lhs.id() != rhs.id(); } + + using CallbackId = CallbackIdImpl; /* queue add/remove callback instructions encountered during callback * execution, to avoid invalidating vector iterator. diff --git a/src/callback/CallbackSet.cpp b/src/callback/CallbackSet.cpp index 881f57f8..1a889f3a 100644 --- a/src/callback/CallbackSet.cpp +++ b/src/callback/CallbackSet.cpp @@ -7,15 +7,6 @@ namespace xo { namespace fn { - CallbackId - CallbackId::generate() - { - static CallbackId s_last_id; - - s_last_id = CallbackId(s_last_id.id() + 1); - - return s_last_id; - } /*generate*/ } /*namespace fn*/ } /*namespace xo*/ From 4b65f3bb9577ca6949face5bfcc1895bda2d51e6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:26:46 -0400 Subject: [PATCH 0375/2693] callbackset: drop Refcounted header --- include/xo/callback/CallbackSet.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/callback/CallbackSet.hpp b/include/xo/callback/CallbackSet.hpp index 04c83800..9bba6514 100644 --- a/include/xo/callback/CallbackSet.hpp +++ b/include/xo/callback/CallbackSet.hpp @@ -2,11 +2,11 @@ #pragma once -#include "xo/refcnt/Refcounted.hpp" //#include "indentlog/scope.hpp" //#include "indentlog/print/tag.hpp" #include #include +#include namespace xo { namespace fn { From 2de57c1ec3643544d40b0781e29eeba3c74e32de Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:30:13 -0400 Subject: [PATCH 0376/2693] doc: README additions --- README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a8eb8c77..34463fb0 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,19 @@ # webutil library (header-only) -# dependencies +## Getting Started -- xo-cmake [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) +### build + install dependencies + +- xo-callback [github/Rconybea/xo-callback](https://github.com/Rconybea/xo-callback) + +### clone repo -# clone repo ``` $ git clone git@github.com:Rconybea/xo-webutil.git ``` -# build and install +### build and install + ``` $ cd xo-webutil $ BUILDDIR=build # for example @@ -21,7 +25,8 @@ $ make $ make install ``` -# LSP support +### LSP support + ``` $ cd xo-webutil $ ln -s $BUILDDIR/compile_commands.json From 8e1f89440edcc8fa2853be4de451bb4eb973f77e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:35:13 -0400 Subject: [PATCH 0377/2693] build: make callback header-only --- src/callback/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/callback/CMakeLists.txt b/src/callback/CMakeLists.txt index 58dc3de5..114a89e2 100644 --- a/src/callback/CMakeLists.txt +++ b/src/callback/CMakeLists.txt @@ -1,14 +1,14 @@ # callback/CMakeLists.txt set(SELF_LIB callback) -set(SELF_SRCS CallbackSet.cpp) +#set(SELF_SRCS CallbackSet.cpp) -# reminder: can't be header-only library, because depends on non-header-only refcnt -xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_add_headeronly_library(${SELF_LIB}) +xo_install_library3(${SELF_LIB} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- # external dependencies: -xo_dependency(${SELF_LIB} refcnt) +#xo_dependency(${SELF_LIB} refcnt) # end CMakeLists.txt From 8d0a327aadc6003508ee9582b34ab15f2f90160e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:36:11 -0400 Subject: [PATCH 0378/2693] .gitignore: + compile_commands.json --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 9e716afc..e9746d99 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +# symlink to path/to/build/compile_commands.json should be manual +compile_commands.json # lsp keeps state here .cache # typical build directories From 4215fd98ec8e2239efc8ddf8e7707d4d47a243ef Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:49:33 -0400 Subject: [PATCH 0379/2693] doc: README additions --- README.md | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 02b7e7a2..8ab8e106 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,17 @@ in-memory queuing system -# dependencies +## Getting Started + +### build + install dependencies build+install these first -- xo-reflect [github.com/Rconybea/xo-reflect] -- xo-callback [github.com/Rconybea/xo-callback] +- xo-reflect [github.com/Rconybea/xo-reflect](https://github.com/Rconybea/reflect) +- xo-callback [github.com/Rconybea/xo-callback](https://github.com/Rconybea/xo-callback) +- xo-webutil [github.com/Rconybea/xo-webutil](https://github.com/Rconybea/xo-webutil) -# build + install - -# build +### build + install xo-reactor ``` $ cd reactor $ mkdir build @@ -23,7 +24,7 @@ $ make install ``` (also see .github/workflows/main.yml) -# build for unit test coverage +### build for unit test coverage ``` $ cd xo-reactor $ mkdir ccov @@ -31,7 +32,9 @@ $ cd ccov $ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. ``` -# LSP support +## Development + +### LSP support LSP looks for compile commands in the root of the source tree; cmake creates them in the root of its build directory. @@ -40,3 +43,14 @@ cmake creates them in the root of its build directory. $ cd xo-reactor $ ln -s build/compile_commands.json ``` + +### display cmake variables + +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text + +``` +$ cd reactor/build +$ cmake -LAH +``` From 10cef974742257a5e116e2fe240f339f0cba40ad Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:49:55 -0400 Subject: [PATCH 0380/2693] build: + dependencies --- CMakeLists.txt | 4 ++-- cmake/reactorConfig.cmake.in | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ddd7483..2e0be181 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,12 +40,12 @@ add_subdirectory(src/reactor) add_subdirectory(utest) # ---------------------------------------------------------------- -# provide find_pacakge() support for reactor customers +# provide find_package() support for reactor customers xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- -# install .hpp files +# install project .hpp files xo_install_include_tree() diff --git a/cmake/reactorConfig.cmake.in b/cmake/reactorConfig.cmake.in index 5456d16b..19ea52a2 100644 --- a/cmake/reactorConfig.cmake.in +++ b/cmake/reactorConfig.cmake.in @@ -6,7 +6,10 @@ include(CMakeFindDependencyMacro) # must coordinate with xo_dependency() calls # in xo-reactor/src/reactor/CMakeLists.txt # +find_dependency(xo_ordinaltree) find_dependency(reflect) +find_dependency(webutil) +find_dependency(printjson) find_dependency(callback) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") From f7d2b0494178d90d9ad0d238eb1f39fbe9812272 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:50:23 -0400 Subject: [PATCH 0381/2693] build: xo_ordinaltree / printjson deps --- src/reactor/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/reactor/CMakeLists.txt b/src/reactor/CMakeLists.txt index 6e26ee50..884e466c 100644 --- a/src/reactor/CMakeLists.txt +++ b/src/reactor/CMakeLists.txt @@ -16,6 +16,8 @@ xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) # must coordinate with find_dependency() calls # in xo-reactor/cmake/reactorConfig.cmake.in # +#xo_dependency(${SELF_LIB} xo_ordinaltree) # for some reason wants to link -lxo_ordinaltree ?? xo_dependency(${SELF_LIB} reflect) xo_dependency(${SELF_LIB} webutil) +xo_dependency(${SELF_LIB} printjson) xo_dependency(${SELF_LIB} callback) From 7e9eb6b6df75968b1ad5ac1b7383c1c3f8b765d4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:50:47 -0400 Subject: [PATCH 0382/2693] bugfix: logging verbosity --- src/reactor/PollingReactor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reactor/PollingReactor.cpp b/src/reactor/PollingReactor.cpp index a9c52cb7..c8c9fe66 100644 --- a/src/reactor/PollingReactor.cpp +++ b/src/reactor/PollingReactor.cpp @@ -79,7 +79,7 @@ namespace xo { { int64_t ix = this->find_nonempty_source(this->next_ix_); - scope log(XO_DEBUG(this->loglevel() == log_level::chatty)); + scope log(XO_DEBUG(this->loglevel() <= log_level::chatty)); log && log(xtag("self", this), xtag("src_ix", ix)); From c07d1c23e17a0e2750c8631a5d7cc7179524deca Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:51:02 -0400 Subject: [PATCH 0383/2693] compile fixes + indentation --- include/xo/reactor/EventStore.hpp | 500 +++++++++++++++--------------- include/xo/reactor/Reducer.hpp | 42 +-- 2 files changed, 271 insertions(+), 271 deletions(-) diff --git a/include/xo/reactor/EventStore.hpp b/include/xo/reactor/EventStore.hpp index 62ab7f9a..4a5479b2 100644 --- a/include/xo/reactor/EventStore.hpp +++ b/include/xo/reactor/EventStore.hpp @@ -2,316 +2,316 @@ #pragma once -#include "reactor/Reducer.hpp" -#include "reactor/EventTimeFn.hpp" -#include "reactor/Sink.hpp" -#include "web_util/HttpEndpointDescr.hpp" -#include "printjson/PrintJson.hpp" -#include "reflect/Reflect.hpp" -#include "tree/RedBlackTree.hpp" +#include "Reducer.hpp" +#include "EventTimeFn.hpp" +#include "Sink.hpp" +#include "xo/webutil/HttpEndpointDescr.hpp" +#include "xo/printjson/PrintJson.hpp" +#include "xo/reflect/Reflect.hpp" +#include "xo/ordinaltree/RedBlackTree.hpp" namespace xo { - namespace reactor { + namespace reactor { - /* abstract event store api */ - class AbstractEventStore : virtual public ref::Refcount { - public: - using PrintJson = xo::json::PrintJson; - using TaggedPtr = xo::reflect::TaggedPtr; - using HttpEndpointDescr = xo::web::HttpEndpointDescr; - using Alist = xo::web::Alist; + /* abstract event store api */ + class AbstractEventStore : virtual public ref::Refcount { + public: + using PrintJson = xo::json::PrintJson; + using TaggedPtr = xo::reflect::TaggedPtr; + using HttpEndpointDescr = xo::web::HttpEndpointDescr; + using Alist = xo::web::Alist; - public: - /* true iff .size() == 0 */ - virtual bool empty() const = 0; + public: + /* true iff .size() == 0 */ + virtual bool empty() const = 0; - /* #of events currently held in this store */ - virtual std::uint32_t size() const = 0; + /* #of events currently held in this store */ + virtual std::uint32_t size() const = 0; - /* TODO: - * 1. TaggedGptr = discriminated union of - * a. TaggedRcptr (i.e. refcounted semantics) - * b. Unique (i.e. unique_ptr semantics) - * c. Exptr (i.e. unowned_ptr semantics) - * d. compact (special-case -- value fits in pointer) - * will need mpl copy/assign stuff for TaggedUnique - * 2. provide .last_n(), .last_dt() - */ + /* TODO: + * 1. TaggedGptr = discriminated union of + * a. TaggedRcptr (i.e. refcounted semantics) + * b. Unique (i.e. unique_ptr semantics) + * c. Exptr (i.e. unowned_ptr semantics) + * d. compact (special-case -- value fits in pointer) + * will need mpl copy/assign stuff for TaggedUnique + * 2. provide .last_n(), .last_dt() + */ - virtual void http_snapshot(ref::rp const & pjson, - std::ostream * p_os) const = 0; + virtual void http_snapshot(ref::rp const & pjson, + std::ostream * p_os) const = 0; - /* http endpoint; generates http output for this eventstore */ - virtual HttpEndpointDescr http_endpoint_descr(ref::rp const & pjson, - std::string const & url_prefix) const { + /* http endpoint; generates http output for this eventstore */ + virtual HttpEndpointDescr http_endpoint_descr(ref::rp const & pjson, + std::string const & url_prefix) const { - /* important that lambda contains its own rp; - * reference to stack will not do - */ - ref::rp pjson_rp = pjson; + /* important that lambda contains its own rp; + * reference to stack will not do + */ + ref::rp pjson_rp = pjson; - auto http_fn = ([this, pjson_rp] - (std::string const & /*uri*/, - Alist const & /*alist*/, - std::ostream * p_os) - { - /* WARNING: race condition here, - * given webserver runs from a separate thread - */ + auto http_fn = ([this, pjson_rp] + (std::string const & /*uri*/, + Alist const & /*alist*/, + std::ostream * p_os) + { + /* WARNING: race condition here, + * given webserver runs from a separate thread + */ - this->http_snapshot(pjson_rp, p_os); - }); + this->http_snapshot(pjson_rp, p_os); + }); - return HttpEndpointDescr(url_prefix + "/snap", http_fn); - } /*http_endpoint_descr*/ + return HttpEndpointDescr(url_prefix + "/snap", http_fn); + } /*http_endpoint_descr*/ - virtual void clear() = 0; + virtual void clear() = 0; - virtual void insert_tp(TaggedPtr const & ev_tp) = 0; - }; /*AbstractEventStore*/ + virtual void insert_tp(TaggedPtr const & ev_tp) = 0; + }; /*AbstractEventStore*/ - /* in-memory storage for a set of events. - * - * Require: - * - Event is null-constructible - * - Event is copyable - * - EventTimeFn :: Event -> utc_nanos - * - * inheritance - * ref::Refcount - * ^ - * isa - * | - * reactor::AbstractEventProcessor + req .visit_direct_consumers() - * ^ - * isa - * | - * reactor::AbstractSink + req .sink_ev_type(), .notify_ev() etc. - * ^ - * isa - * | - * reactor::Sink1 + .attach_source(), .sink_ev_type(), - * ^ req .notify_ev() etc - * | - * isa - * | - * reactor::SinkEndpoint + impl .visit_direct_consumers() - * ^ - * isa - * | - * reactor::StructEventStore + .last_n() .last_dt() etc. - */ - template - class EventStoreImpl : public SinkEndpoint, - public AbstractEventStore, - ReducerBase - { - static_assert(EventTimeConcept); + /* in-memory storage for a set of events. + * + * Require: + * - Event is null-constructible + * - Event is copyable + * - EventTimeFn :: Event -> utc_nanos + * + * inheritance + * ref::Refcount + * ^ + * isa + * | + * reactor::AbstractEventProcessor + req .visit_direct_consumers() + * ^ + * isa + * | + * reactor::AbstractSink + req .sink_ev_type(), .notify_ev() etc. + * ^ + * isa + * | + * reactor::Sink1 + .attach_source(), .sink_ev_type(), + * ^ req .notify_ev() etc + * | + * isa + * | + * reactor::SinkEndpoint + impl .visit_direct_consumers() + * ^ + * isa + * | + * reactor::StructEventStore + .last_n() .last_dt() etc. + */ + template + class EventStoreImpl : public SinkEndpoint, + public AbstractEventStore, + ReducerBase + { + static_assert(EventTimeConcept); - public: - using utc_nanos = xo::time::utc_nanos; - using nanos = xo::time::nanos; - using EventTree = xo::tree::RedBlackTree>; - using PrintJson = xo::json::PrintJson; - using Alist = xo::web::Alist; - using HttpEndpointDescr = xo::web::HttpEndpointDescr; + public: + using utc_nanos = xo::time::utc_nanos; + using nanos = xo::time::nanos; + using EventTree = xo::tree::RedBlackTree>; + using PrintJson = xo::json::PrintJson; + using Alist = xo::web::Alist; + using HttpEndpointDescr = xo::web::HttpEndpointDescr; - static ref::rp make() { return new EventStoreImpl(); } + static ref::rp make() { return new EventStoreImpl(); } - /* visit most recent n events in this store. - * returns #of events actually visited - * - * if events visited are e1 .. en, then: - * (1) en is the most recent recorded event - * (.event_tm(en) is .tree.max_key()) - * (2) there are no events between e(i) and e(i+1) - * (i.e. visit does not skip over any events) - * (3) if v < n, then v = .size(), - * where v is the #of events visited - * - * require: - * - Fn :: (Event -> ) - */ - template - std::uint32_t visit_last_n(std::uint32_t n, Fn && fn) const { - std::uint32_t z = this->size(); - std::uint32_t lo = ((n >= z) ? 0 : z - n); + /* visit most recent n events in this store. + * returns #of events actually visited + * + * if events visited are e1 .. en, then: + * (1) en is the most recent recorded event + * (.event_tm(en) is .tree.max_key()) + * (2) there are no events between e(i) and e(i+1) + * (i.e. visit does not skip over any events) + * (3) if v < n, then v = .size(), + * where v is the #of events visited + * + * require: + * - Fn :: (Event -> ) + */ + template + std::uint32_t visit_last_n(std::uint32_t n, Fn && fn) const { + std::uint32_t z = this->size(); + std::uint32_t lo = ((n >= z) ? 0 : z - n); - typename EventTree::const_iterator lo_ix = this->tree_.find_ith(lo); - typename EventTree::const_iterator hi_ix = this->tree_.cend(); + typename EventTree::const_iterator lo_ix = this->tree_.find_ith(lo); + typename EventTree::const_iterator hi_ix = this->tree_.cend(); - return this->visit_range(lo_ix, hi_ix, fn); - } /*visit_last_n*/ + return this->visit_range(lo_ix, hi_ix, fn); + } /*visit_last_n*/ - /* visit suffix of events sufficient to cover interval of length dt. - * visit events in increasing timestamp order. - * - * if events visited are e1 .. en, then: - * (1) en is the most recent recorded event - * (.event_tm(en) is .tree.max_key()) - * (2) there are no events between e(i) and e(i+1) - * (i.e. visit does not skip over any events) - * (3) if .event_tm(en) - .event_tm(e1) < dt, - * then e1 is the earliest recorded event - * (.event_tm(e1) is .tree.min_key()) - * (4) if .event_tm(en) - .event_tm(e1) > dt, - * then (.event_tm(en) - .event_tm(e2)) < dt - * - * |<---------- dt ----------->| - * ^ ^ ^ - * e1 e2 en - */ - template - std::uint32_t visit_last_dt(nanos dt, Fn && fn) const { - if (tree_.empty()) - return 0; + /* visit suffix of events sufficient to cover interval of length dt. + * visit events in increasing timestamp order. + * + * if events visited are e1 .. en, then: + * (1) en is the most recent recorded event + * (.event_tm(en) is .tree.max_key()) + * (2) there are no events between e(i) and e(i+1) + * (i.e. visit does not skip over any events) + * (3) if .event_tm(en) - .event_tm(e1) < dt, + * then e1 is the earliest recorded event + * (.event_tm(e1) is .tree.min_key()) + * (4) if .event_tm(en) - .event_tm(e1) > dt, + * then (.event_tm(en) - .event_tm(e2)) < dt + * + * |<---------- dt ----------->| + * ^ ^ ^ + * e1 e2 en + */ + template + std::uint32_t visit_last_dt(nanos dt, Fn && fn) const { + if (tree_.empty()) + return 0; - /* tree not empty -> has max key */ - utc_nanos tn = this->tree_.max_key(); - utc_nanos tk = tn - dt; + /* tree not empty -> has max key */ + utc_nanos tn = this->tree_.max_key(); + utc_nanos tk = tn - dt; - typename EventTree::const_iterator lo_ix = this->tree_.find_glb(tk, true /*closed*/); - typename EventTree::const_iterator hi_ix = this->tree_.end(); + typename EventTree::const_iterator lo_ix = this->tree_.find_glb(tk, true /*closed*/); + typename EventTree::const_iterator hi_ix = this->tree_.end(); - return this->visit_range(lo_ix, hi_ix, fn); - } /*visit_last_dt*/ + return this->visit_range(lo_ix, hi_ix, fn); + } /*visit_last_dt*/ - std::vector last_n(std::uint32_t n) const { - std::vector retval; + std::vector last_n(std::uint32_t n) const { + std::vector retval; - auto fn = [&retval](Event const &ev) { retval.push_back(ev); }; + auto fn = [&retval](Event const &ev) { retval.push_back(ev); }; - this->visit_last_n(n, fn); + this->visit_last_n(n, fn); - return retval; - } /*last_n*/ + return retval; + } /*last_n*/ - std::vector last_dt(nanos dt) const { - std::vector retval; + std::vector last_dt(nanos dt) const { + std::vector retval; - auto fn = [&retval](Event const &ev) { retval.push_back(ev); }; + auto fn = [&retval](Event const &ev) { retval.push_back(ev); }; - this->visit_last_dt(dt, fn); + this->visit_last_dt(dt, fn); - return retval; - } /*last_dt*/ + return retval; + } /*last_dt*/ - void insert(Event const & ev) { this->tree_.insert(typename EventTree::value_type(this->event_tm(ev), ev)); } + void insert(Event const & ev) { this->tree_.insert(typename EventTree::value_type(this->event_tm(ev), ev)); } - // ----- Inherited from AbstractEventStore ----- + // ----- Inherited from AbstractEventStore ----- - virtual bool empty() const override { return tree_.empty(); } - virtual std::uint32_t size() const override { return tree_.size(); } + virtual bool empty() const override { return tree_.empty(); } + virtual std::uint32_t size() const override { return tree_.size(); } - /* write http snapshot of current state to *p_os */ - virtual void http_snapshot(ref::rp const & pjson, std::ostream * p_os) const override { - using xo::reflect::Reflect; + /* write http snapshot of current state to *p_os */ + virtual void http_snapshot(ref::rp const & pjson, std::ostream * p_os) const override { + using xo::reflect::Reflect; - /* visit last 100 events; - * write them to *p_os in increasing time order - */ - auto ev_v = this->last_n(100); + /* visit last 100 events; + * write them to *p_os in increasing time order + */ + auto ev_v = this->last_n(100); - pjson->print_tp(Reflect::make_tp(&ev_v), p_os); - } /*http_snapshot*/ + pjson->print_tp(Reflect::make_tp(&ev_v), p_os); + } /*http_snapshot*/ - virtual void clear() override { this->tree_.clear(); } + virtual void clear() override { this->tree_.clear(); } - virtual void insert_tp(TaggedPtr const & ev_tp) override { - using xo::xtag; + virtual void insert_tp(TaggedPtr const & ev_tp) override { + using xo::xtag; - Event * p_ev = ev_tp.recover_native(); + Event * p_ev = ev_tp.recover_native(); - if (p_ev) { - this->insert(*p_ev); - } else { - throw std::runtime_error(tostr("StructEventStore::insert_tp" - ": unable to convert ev_tp to Event", - xtag("ev_tp.type", ev_tp.td()->canonical_name()), - xtag("Event", reflect::type_name()))); - } - } /*insert_tp*/ + if (p_ev) { + this->insert(*p_ev); + } else { + throw std::runtime_error(tostr("StructEventStore::insert_tp" + ": unable to convert ev_tp to Event", + xtag("ev_tp.type", ev_tp.td()->canonical_name()), + xtag("Event", reflect::type_name()))); + } + } /*insert_tp*/ - // ----- Inherited from AbstractSink ----- + // ----- Inherited from AbstractSink ----- - virtual uint32_t n_in_ev() const override { return n_in_ev_; } - virtual bool allow_volatile_source() const override { return false; } - virtual void notify_ev(Event const & ev) override { - ++(this->n_in_ev_); - this->insert(ev); - } + virtual uint32_t n_in_ev() const override { return n_in_ev_; } + virtual bool allow_volatile_source() const override { return false; } + virtual void notify_ev(Event const & ev) override { + ++(this->n_in_ev_); + this->insert(ev); + } - // ----- Inherited from AbstractSource ----- + // ----- Inherited from AbstractSource ----- - virtual void display(std::ostream & os) const override { - using xo::xtag; + virtual void display(std::ostream & os) const override { + using xo::xtag; - os << "name()) - << xtag("n_in_ev", this->n_in_ev()) - << ">"; - } /*display*/ + os << "name()) + << xtag("n_in_ev", this->n_in_ev()) + << ">"; + } /*display*/ - // ----- Inherited from AbstractEventProcessor ----- + // ----- Inherited from AbstractEventProcessor ----- - virtual std::string const & name() const override { return name_; } - virtual void set_name(std::string const & x) override { name_ = x; } + virtual std::string const & name() const override { return name_; } + virtual void set_name(std::string const & x) override { name_ = x; } - private: - EventStoreImpl() = default; + private: + EventStoreImpl() = default; - template - std::uint32_t visit_range(typename EventTree::const_iterator lo_ix, - typename EventTree::const_iterator hi_ix, - Fn && fn) const { - std::uint32_t n = 0; - for (; lo_ix != hi_ix; ++lo_ix, ++n) { - fn(lo_ix->second); - } + template + std::uint32_t visit_range(typename EventTree::const_iterator lo_ix, + typename EventTree::const_iterator hi_ix, + Fn && fn) const { + std::uint32_t n = 0; + for (; lo_ix != hi_ix; ++lo_ix, ++n) { + fn(lo_ix->second); + } - return n; - } /*visit_range*/ + return n; + } /*visit_range*/ - private: - /* reporting name for this store */ - std::string name_; - /* fetches per-event timestamp */ - EventTimeFn event_tm_fn_; - /* counts lifetime #of incoming events (see .notify_ev()) */ - uint32_t n_in_ev_ = 0; - /* events stored here */ - EventTree tree_; - }; /*EventStoreImpl*/ + private: + /* reporting name for this store */ + std::string name_; + /* fetches per-event timestamp */ + EventTimeFn event_tm_fn_; + /* counts lifetime #of incoming events (see .notify_ev()) */ + uint32_t n_in_ev_ = 0; + /* events stored here */ + EventTree tree_; + }; /*EventStoreImpl*/ - template - using StructEventStore = EventStoreImpl>; + template + using StructEventStore = EventStoreImpl>; - template - using PtrEventStore = EventStoreImpl>; + template + using PtrEventStore = EventStoreImpl>; - /* Require: - * EventTimeConcept> - */ - template - class SinkToEventStore : public SinkEndpoint { - public: - using EventStore = StructEventStore; + /* Require: + * EventTimeConcept> + */ + template + class SinkToEventStore : public SinkEndpoint { + public: + using EventStore = StructEventStore; - public: - SinkToEventStore() = default; + public: + SinkToEventStore() = default; - virtual void notify_ev(T const & ev) override { - store_.insert(ev); - } /*notify_ev*/ + virtual void notify_ev(T const & ev) override { + store_.insert(ev); + } /*notify_ev*/ - private: - /* stash remembered events (all of them!) here */ - EventStore store_; - }; /*SinkToEventStore*/ + private: + /* stash remembered events (all of them!) here */ + EventStore store_; + }; /*SinkToEventStore*/ - } /*namespace reactor*/ + } /*namespace reactor*/ } /*namespace xo*/ /* end EventStore.hpp */ diff --git a/include/xo/reactor/Reducer.hpp b/include/xo/reactor/Reducer.hpp index 907d9d04..a457dfe0 100644 --- a/include/xo/reactor/Reducer.hpp +++ b/include/xo/reactor/Reducer.hpp @@ -2,32 +2,32 @@ #pragma once -#include "reactor/EventTimeFn.hpp" +#include "xo/reactor/EventTimeFn.hpp" namespace xo { - namespace reactor { - /* LastReducer, HeapReducer inherit ReducerBase */ - template - class ReducerBase { - static_assert(EventTimeConcept); + namespace reactor { + /* LastReducer, HeapReducer inherit ReducerBase */ + template + class ReducerBase { + static_assert(EventTimeConcept); - public: - using utc_nanos = xo::time::utc_nanos; + public: + using utc_nanos = xo::time::utc_nanos; - public: - ReducerBase() = default; - ReducerBase(EventTimeFn const & evtfn) : event_tm_fn_{evtfn} {} + public: + ReducerBase() = default; + ReducerBase(EventTimeFn const & evtfn) : event_tm_fn_{evtfn} {} - utc_nanos event_tm(Event const & ev) const { return this->event_tm_fn_(ev); } - - private: - /* Event ev = ...; - * .event_tm_fn(ev) -> utc_nanos - * reports event time associated with ev - */ - EventTimeFn event_tm_fn_; - }; /*ReducerBase*/ - } /*namespace reactor*/ + utc_nanos event_tm(Event const & ev) const { return this->event_tm_fn_(ev); } + + private: + /* Event ev = ...; + * .event_tm_fn(ev) -> utc_nanos + * reports event time associated with ev + */ + EventTimeFn event_tm_fn_; + }; /*ReducerBase*/ + } /*namespace reactor*/ } /*namespace xo*/ /* end Reducer.hpp */ From 3913e07f81a504ea5d3c8ba1844368d2dd10e99e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:06:45 -0400 Subject: [PATCH 0384/2693] first commit --- README.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..314f7fab --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# websock library + +http library including websocket support. +Built around the C-library libwebsocket + +# dependencies + +build+install these first + +- xo-somelib [github.com/Rconybea/xo-somelib] + +# build + install + +## build +``` +$ cd websock +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +## build for unit test coverage +``` +$ cd xo-websock +$ mkdir ccov +$ cd ccov +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + +# development + +## LSP support + +LSP looks for compile commands in the root of the source tree; +cmake creates them in the root of its build directory. + +``` +$ cd xo-websock +$ ln -s build/compile_commands.json +``` + +## display cmake variables + +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text + +``` +$ cd websock/build +$ cmake -LAH +``` From c634f33e67f44ecb0e0b710d6e0669f5f3746eee Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:07:33 -0400 Subject: [PATCH 0385/2693] implementation + compile as independent module --- CMakeLists.txt | 52 + cmake/websockConfig.cmake.in | 13 + include/xo/websock/DynamicEndpoint.hpp | 126 ++ include/xo/websock/EndpointUtil.hpp | 26 + include/xo/websock/SafetyToken.hpp | 40 + include/xo/websock/Webserver.hpp | 116 + include/xo/websock/WebsockUtil.hpp | 18 + include/xo/websock/WebsocketSink.hpp | 28 + include/xo/websock/WsSafetyToken.hpp | 29 + src/websock/CMakeLists.txt | 16 + src/websock/DynamicEndpoint.cpp | 146 ++ src/websock/EndpointUtil.cpp | 38 + src/websock/Webserver.cpp | 1939 +++++++++++++++++ src/websock/WebsockUtil.cpp | 141 ++ src/websock/WebsocketSink.cpp | 148 ++ utest/CMakeLists.txt | 47 + utest/README | 13 + utest/mount-origin/bluecircle.svg | 7 + utest/mount-origin/d3ex/d3ex.ch5.ex1.html | 15 + utest/mount-origin/ex_websock.html | 13 + utest/mount-origin/ex_websock.js | 842 +++++++ utest/mount-origin/example.js | 64 + utest/mount-origin/index.html | 19 + utest/mount-origin/libwebsockets.org-logo.svg | 66 + utest/mount-origin/script-csp.svg | 53 + utest/websock_utest_main.cpp | 282 +++ 26 files changed, 4297 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/websockConfig.cmake.in create mode 100644 include/xo/websock/DynamicEndpoint.hpp create mode 100644 include/xo/websock/EndpointUtil.hpp create mode 100644 include/xo/websock/SafetyToken.hpp create mode 100644 include/xo/websock/Webserver.hpp create mode 100644 include/xo/websock/WebsockUtil.hpp create mode 100644 include/xo/websock/WebsocketSink.hpp create mode 100644 include/xo/websock/WsSafetyToken.hpp create mode 100644 src/websock/CMakeLists.txt create mode 100644 src/websock/DynamicEndpoint.cpp create mode 100644 src/websock/EndpointUtil.cpp create mode 100644 src/websock/Webserver.cpp create mode 100644 src/websock/WebsockUtil.cpp create mode 100644 src/websock/WebsocketSink.cpp create mode 100644 utest/CMakeLists.txt create mode 100644 utest/README create mode 100644 utest/mount-origin/bluecircle.svg create mode 100644 utest/mount-origin/d3ex/d3ex.ch5.ex1.html create mode 100644 utest/mount-origin/ex_websock.html create mode 100644 utest/mount-origin/ex_websock.js create mode 100644 utest/mount-origin/example.js create mode 100644 utest/mount-origin/index.html create mode 100644 utest/mount-origin/libwebsockets.org-logo.svg create mode 100644 utest/mount-origin/script-csp.svg create mode 100644 utest/websock_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..51006eed --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,52 @@ +# xo-websock/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(websock VERSION 1.0) +enable_language(CXX) + +# common XO cmake macros (see proj/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() +# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) +add_code_coverage() +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) + +# ---------------------------------------------------------------- +# c++ settings + +# one-time project-specific c++ flags. usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- + +add_subdirectory(src/websock) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support for websock customers + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# install .hpp files + +xo_install_include_tree() + +# end CMakeLists.txt diff --git a/cmake/websockConfig.cmake.in b/cmake/websockConfig.cmake.in new file mode 100644 index 00000000..ac43847f --- /dev/null +++ b/cmake/websockConfig.cmake.in @@ -0,0 +1,13 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# note: changes to find_dependency() calls here +# must coordinate with xo_dependency() calls +# in xo-reactor/src/reactor/CMakeLists.txt +# +#find_dependency(reflect) +#find_dependency(callback) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/websock/DynamicEndpoint.hpp b/include/xo/websock/DynamicEndpoint.hpp new file mode 100644 index 00000000..8753b835 --- /dev/null +++ b/include/xo/websock/DynamicEndpoint.hpp @@ -0,0 +1,126 @@ +/* file DynamicEndpoint.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "EndpointUtil.hpp" +#include "xo/webutil/HttpEndpointDescr.hpp" +#include "xo/webutil/StreamEndpointDescr.hpp" +#include "xo/webutil/Alist.hpp" +#include + +namespace xo { + namespace web { + /* a dynamic http endpoint. content served on-browser-demand + * by user-provided callback + */ + class DynamicEndpoint { + public: + using AbstractSink = xo::reactor::AbstractSink; + using CallbackId = fn::CallbackId; + + public: + static std::unique_ptr make_http(std::string uri_pattern, + HttpEndpointFn http_cb) { + return (std::unique_ptr + (new DynamicEndpoint(std::move(uri_pattern), + std::move(http_cb), + nullptr, + nullptr))); + } /*make_http*/ + + static std::unique_ptr make_stream(std::string uri_pattern, + StreamSubscribeFn sub_fn, + StreamUnsubscribeFn unsub_fn) { + return (std::unique_ptr + (new DynamicEndpoint(std::move(uri_pattern), + nullptr, + std::move(sub_fn), + std::move(unsub_fn)))); + } /*make_stream*/ + + std::string stem() const { + return EndpointUtil::stem(this->uri_pattern_); + } /*stem*/ + +#ifdef NOT_USING + /* true iff incoming_uri matches .uri_pattern */ + bool is_match(std::string const & incoming_uri) const { + /* c++ regex = javascript regexes, + * so these characters are special: + * ^ $ \ . * + ? ( ) [ ] { } | + */ + } /*is_match*/ +#endif + + /* get html from this endpoint, on behalf of uri=incoming_uri; + * write html on *p_os + * + * require: non-null http_fn + */ + void http_response(std::string const & incoming_uri, + std::ostream * p_os) const; + + /* subscribe stream from this endpoint, on behalf of uri=incoming_uri. + * send output to ws_sink + */ + CallbackId subscribe(std::string const & incoming_uri, + ref::rp const & ws_sink) const; + + /* unsubscribe stream from this endpoint; + * reverses the effect of a previous call to .subscribe() + * that returned id + */ + void unsubscribe(CallbackId id) const; + + private: + explicit DynamicEndpoint(std::string uri_pattern, + HttpEndpointFn http_fn, + StreamSubscribeFn subscribe_fn, + StreamUnsubscribeFn unsubscribe_fn); + + private: + /* pattern for this endpoint + * can be string like + * /fixed/stem/${a}/more/fixed/stuff/${b} + * in which case: + * + * 1. will match uris like: + * /fixed/stem/apple/more/fixed/stuff/bananas + * --> invoke callback with Alist + * ("a" -> "apple", "b" -> "bananas") + * endpoint will be stored in WebserverImpl.stem_map + * under fixed prefix, in this case + * /fixed/stem/ + * + * 2. will not match uris like: + * /fixed/stem/app/le/more/fixed/stuff/bononos + */ + std::string uri_pattern_; + /* regex for matching input that satisfies .uri_pattern: + * each occurrence of + * ${...} replaced by [[:alnum:]]+ + */ + std::regex uri_regex_; + /* variables found in .uri_pattern, + * in the order in which they appear + * if .uri_pattern is + * /fixed/stem/${a}/more/fixed/stuff/${b} + * then .var_v will be: + * ["a", "b"] + */ + std::vector var_v_; + /* run this function to produce an http response */ + HttpEndpointFn http_fn_; + /* run this function to subscribe event stream */ + StreamSubscribeFn subscribe_fn_; + /* run this function to unsubscribe event stream */ + StreamUnsubscribeFn unsubscribe_fn_; + }; /*DynamicEndpoint*/ + + } /*namespace web*/ +} /*namespace xo*/ + +/* end DynamicEndpoint.hpp */ diff --git a/include/xo/websock/EndpointUtil.hpp b/include/xo/websock/EndpointUtil.hpp new file mode 100644 index 00000000..e0257cd1 --- /dev/null +++ b/include/xo/websock/EndpointUtil.hpp @@ -0,0 +1,26 @@ +/* file EndpointUtil.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include + +namespace xo { + namespace web { + class EndpointUtil { + public: + /* find fixed prefix for a URI pattern. + * patterns are used with both http endpoints (see DynamicEndpoint), + * and stream endpoints (see StreamEndpoint) + * + * e.g. stem("/dyn/uls/${ulticker}/snap") => "/dyn/uls/" + */ + static std::string stem(std::string const & pattern); + }; /*EndpointUtil*/ + + } /*namespace web*/ +} /*namespace xo*/ + +/* end EndpointUtil.hpp */ diff --git a/include/xo/websock/SafetyToken.hpp b/include/xo/websock/SafetyToken.hpp new file mode 100644 index 00000000..f156b98a --- /dev/null +++ b/include/xo/websock/SafetyToken.hpp @@ -0,0 +1,40 @@ +/* file SafetyToken.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +namespace xo { + namespace web { + /* token for cooperative compile-time threadsafety checking. + * + * requirements for cooperating code: + * - token contains no state, so in principle can be optimized away + * - token is deliberately not copyable, and not moveable + * - derive from token, and make derived ctor private + * - make method/class responsible for threadsafety a friend of token, + * so it can have exclusive right to create a token instance. + * - pass token reference down stack + * to demonstrate ownership of protected resource, + * limited to the lifetime of called function. + */ + template + class SafetyToken { + public: + SafetyToken(SafetyToken const & x) = delete; + SafetyToken(SafetyToken && x) = delete; + + /* optionally: invoke this to "announce use of a protected resource" */ + bool verify() const { return true; } + + SafetyToken & operator=(SafetyToken const & x) = delete; + SafetyToken & operator=(SafetyToken && x) = delete; + + protected: + SafetyToken() = default; + }; /*SafetyToken*/ + } /*namespace web*/ +} /*namespace xo*/ + +/* end SafetyToken.hpp */ diff --git a/include/xo/websock/Webserver.hpp b/include/xo/websock/Webserver.hpp new file mode 100644 index 00000000..57b0ccf7 --- /dev/null +++ b/include/xo/websock/Webserver.hpp @@ -0,0 +1,116 @@ +/* @file Webserver.hpp */ + +#pragma once + +#include "xo/refcnt/Displayable.hpp" +#include "xo/printjson/PrintJson.hpp" +#include "xo/webutil/HttpEndpointDescr.hpp" +#include "xo/webutil/StreamEndpointDescr.hpp" +#include // temporary, while moving callbacks +#include +#include +#include + +namespace xo { + namespace web { + enum class Runstate { stopped, stop_requested, running }; + + class RunstateUtil { + public: + static char const * runstate_descr(Runstate x); + }; /*RunstateUtil*/ + + inline std::ostream & operator<<(std::ostream &os, Runstate x) { + os << RunstateUtil::runstate_descr(x); + return os; + } /*operator<<*/ + + class WebserverConfig { + public: + WebserverConfig() = default; + WebserverConfig(std::int32_t port, + bool tls_flag, + bool host_check_flag, + bool use_retry_flag) + : port_{port}, + tls_flag_{tls_flag}, + host_check_flag_{host_check_flag}, + use_retry_flag_{use_retry_flag} {} + + std::int32_t port() const { return port_; } + bool tls_flag() const { return tls_flag_; } + bool host_check_flag() const { return host_check_flag_; } + bool use_retry_flag() const { return use_retry_flag_; } + + private: + /* accept incoming http requests on this port# */ + std::int32_t port_ = 0; + /* if true, support https */ + bool tls_flag_ = false; + /* see LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK */ + bool host_check_flag_ = false; + /* see lws_context_creation_info.retry_and_idle_policy */ + bool use_retry_flag_ = false; + }; /*WebserverConfig*/ + + /* libwebsocket: + * 1. doesn't support multiple threads + * (actually, looks like it does on further examination) + * 2. doesn't expose listening ports etc (at least afaik); + * in other words it expects to take over application's main thread + * + * enforce this property by making webserver a singleton + * + * .state .start_webserver() .state + * +---------+ -------------------> +---------+ + * | stopped | | running | + * +---------+ +---------+ + * ^ | + * | | .stop_webserver() + * | | + * +----------------+ | + * | stop_requested | <------------------/ + * +----------------+ + * + */ + class Webserver : public ref::Displayable { + public: + using Alist = xo::web::Alist; + using PrintJson = xo::json::PrintJson; + + public: + /* note: although webserver allows creating multiple instances, + * the underlying libwebsocket library is not advertised to be + * threadsafe + */ + static ref::rp make(WebserverConfig const & ws_config, + ref::rp const & pjson); + + /* current state */ + virtual Runstate state() const = 0; + virtual void register_http_endpoint(HttpEndpointDescr const & endpoint) = 0; + virtual void register_stream_endpoint(StreamEndpointDescr const & endpoint) = 0; + + /* start thread for this webserver; idempotent */ + virtual void start_webserver() = 0; + /* stop thread for this webserver; suitable for calling + * from interrupt handler + */ + virtual void interrupt_stop_webserver() = 0; + /* stop thread for this webserver; idempotent */ + virtual void stop_webserver() = 0; + /* wait until webserver thread stopped */ + virtual void join_webserver() = 0; + + /* send text to a websocket session identified by session_id */ + virtual void send_text(uint32_t session_id, + std::string text) = 0; + + // ----- Inherited from Displayable ----- + + virtual void display(std::ostream & os) const; + }; /*Webserver*/ + } /*namespace web*/ +} /*namespace xo*/ + +/* end Webserver.hpp */ diff --git a/include/xo/websock/WebsockUtil.hpp b/include/xo/websock/WebsockUtil.hpp new file mode 100644 index 00000000..f8626c68 --- /dev/null +++ b/include/xo/websock/WebsockUtil.hpp @@ -0,0 +1,18 @@ +/* @file WebsockUtil.hpp */ + +#pragma once + +#include + +namespace xo { + namespace web { + /* class-as-namespace idiom */ + class WebsockUtil { + public: + /* string representation for callback category enum */ + static char const * ws_callback_reason_descr(lws_callback_reasons x); + }; /*WebsockUtil*/ + } /*namespace web*/ +} /*namespace xo*/ + +/* end WebsockUtil.hpp */ diff --git a/include/xo/websock/WebsocketSink.hpp b/include/xo/websock/WebsocketSink.hpp new file mode 100644 index 00000000..863bda34 --- /dev/null +++ b/include/xo/websock/WebsocketSink.hpp @@ -0,0 +1,28 @@ +/* file WebsocketSink.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "xo/reactor/AbstractSink.hpp" +#include "xo/printjson/PrintJson.hpp" + +namespace xo { + namespace web { + class Webserver; + + class WebsocketSink : public reactor::AbstractSink { + public: + using PrintJson = xo::json::PrintJson; + + public: + static ref::rp make(ref::rp const & websrv, + ref::rp const & pjson, + uint32_t session_id, + std::string const & stream_name); + }; /*WebsocketSink*/ + } /*namespace web*/ +} /*namespace xo*/ + +/* end WebsocketSink.hpp */ diff --git a/include/xo/websock/WsSafetyToken.hpp b/include/xo/websock/WsSafetyToken.hpp new file mode 100644 index 00000000..0d5302aa --- /dev/null +++ b/include/xo/websock/WsSafetyToken.hpp @@ -0,0 +1,29 @@ +/* file WsSafetyToken.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "SafetyToken.hpp" +#include + +namespace xo { + namespace web { + class WebserverImplWsThread; + class WebsocketSessionRecd; + + /* only websocket thread can obtain this token */ + class WsSafetyToken : public SafetyToken { + private: + friend class WebserverImplWsThread; + + private: + /* only WebserverImpl should construct this */ + WsSafetyToken() = default; + }; /*WsSafetyToken*/ + + } /*namespace web*/ +} /*namespace xo*/ + +/* end WsSafetyToken.hpp */ diff --git a/src/websock/CMakeLists.txt b/src/websock/CMakeLists.txt new file mode 100644 index 00000000..e8aa7df2 --- /dev/null +++ b/src/websock/CMakeLists.txt @@ -0,0 +1,16 @@ +# xo-websock/CMakeLists.txt + +set(SELF_LIB websock) +set(SELF_SRCS EndpointUtil.cpp DynamicEndpoint.cpp WebsockUtil.cpp WebsocketSink.cpp Webserver.cpp) + +xo_add_shared_library3(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) + +# ---------------------------------------------------------------- +# external dependencies + +xo_dependency(${SELF_LIB} reactor) +xo_dependency(${SELF_LIB} webutil) + +# note: changes to xo_dependency() calls here +# must coordinate with find_dependency() calls in +# xo-websock/cmake/websockConfig.cmake.in diff --git a/src/websock/DynamicEndpoint.cpp b/src/websock/DynamicEndpoint.cpp new file mode 100644 index 00000000..3df46d47 --- /dev/null +++ b/src/websock/DynamicEndpoint.cpp @@ -0,0 +1,146 @@ +/* file DynamicEndpoint.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "DynamicEndpoint.hpp" + +namespace xo { + using xo::web::Alist; + using xo::fn::CallbackId; + using xo::ref::rp; + + namespace web { + DynamicEndpoint::DynamicEndpoint(std::string uri_pattern, + HttpEndpointFn http_fn, + StreamSubscribeFn subscribe_fn, + StreamUnsubscribeFn unsubscribe_fn) + : uri_pattern_{std::move(uri_pattern)}, + http_fn_{std::move(http_fn)}, + subscribe_fn_{std::move(subscribe_fn)}, + unsubscribe_fn_{std::move(unsubscribe_fn)} + { + std::string r_pat; + + /* 1st pass -- construct pattern regex .uri_regex + * to identify urls that belong to this endpoint + * + * using regex like: + * \$\{[[:alnum:]]+\} + */ + { + std::regex var_rgx("\\$\\{[[:alnum:]]+\\}"); + + /* e.g. if .uri_pattern: + * /fixed/stem/${a}/more/fixed/stuff/${b} + * then want r_pat: + * /fixed/stem/[[:alnum:]]+/more/fixed/stuff/[[:alnum:]]+ + * to find values pattern variables like ${a}, ${b} + */ + std::regex_replace(std::back_inserter(r_pat), + this->uri_pattern_.begin(), + this->uri_pattern_.end(), + var_rgx, + std::string("([[:alnum:]]+)")); + + this->uri_regex_ = std::regex(r_pat); + } + + /* 2nd pass -- identify pattern variables */ + { + /* regex for: + * \$\{([[:alnum:]]+)\} + * use to match input like + * ${apple} + * and also extract the variable name + * apple + */ + std::regex var_rgx("\\$\\{([[:alnum:]]+)\\}"); + std::smatch match; + + std::string subject = this->uri_pattern_; + + /* if subject like + * /fixed/stem/${a}/more/fixed/stuff/${b} + * extract + * ["a", "b"] + * + * for + * /fixed/stem/${a}/more/fixed/stuff/${b}/${a} + * also extract + * ["a", "b"] + * i.e. avoid extracting the same variable name twice + */ + while (std::regex_search(subject, match, var_rgx)) { + std::string v = match[1]; + + bool present_flag = false; + + for (auto const & x : this->var_v_) { + if (x == v) { + present_flag = true; + break; + } + } + + if (!present_flag) + this->var_v_.push_back(match[1]); + + subject = match.suffix().str(); + } + } + } /*ctor*/ + + void + DynamicEndpoint::http_response(std::string const & incoming_uri, + std::ostream * p_os) const + { + /* send this uri argument list callback. + * contains variables extracted from .uri_pattern + * (variables surrounded by ${...}) + */ + Alist alist; + + /* extract pattern variables in uri + * c.f. 2nd pass in DynamicEndpoint.ctor + */ + std::smatch match; + std::string subject = incoming_uri; + + /* if subject like + * /fixed/stem/apple/more/fixed/stuff/beagle + * with .uri_pattern + * /fixed/stem/${a}/more/fixed/stuff/${b} + * then we have .uri_regex + * /fixed/stem/([[:alnum:]]+)/more/fixed/stuff/([[:alnum:]]+) + * use this to extract values for keys in .var_v, + * in the same order + */ + if (std::regex_match(subject, match, this->uri_regex_)) { + for (size_t i = 0, n = this->var_v_.size(); ivar_v_[i]; + std::string i_value = match[1+i]; + + alist.push_back(i_name, i_value); + } + } + + this->http_fn_(incoming_uri, alist, p_os); + } /*http_response*/ + + CallbackId + DynamicEndpoint::subscribe(std::string const & /*incoming_uri*/, + rp const & ws_sink) const + { + return this->subscribe_fn_(ws_sink); + } /*subscribe*/ + + void + DynamicEndpoint::unsubscribe(CallbackId id) const + { + return this->unsubscribe_fn_(id); + } /*unsubscribe*/ + } /*namespace web*/ +} /*namespace xo*/ + +/* end DynamicEndpoint.cpp */ diff --git a/src/websock/EndpointUtil.cpp b/src/websock/EndpointUtil.cpp new file mode 100644 index 00000000..f53dbd43 --- /dev/null +++ b/src/websock/EndpointUtil.cpp @@ -0,0 +1,38 @@ +/* file EndpointUtil.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "EndpointUtil.hpp" + +namespace xo { + namespace web { + std::string + EndpointUtil::stem(std::string const & pattern) + { + std::size_t p = 0; + do { + p = pattern.find_first_of("$", p); + + if ((p != std::string::npos) && (pattern[p+1] == '{')) { + /* fixed stem is chars [0 .. p-1], i.e. 1st p characters */ + break; + } + + if (p != std::string::npos) { + /* skip to next '$' */ + ++p; + } + } while (p != std::string::npos); + + if (p == std::string::npos) { + /* pattern has no variable components */ + return pattern; + } else { + return pattern.substr(0, p); + } + } /*stem*/ + } /*namespace web*/ +} /*namespace xo*/ + +/* end EndpointUtil.cpp */ diff --git a/src/websock/Webserver.cpp b/src/websock/Webserver.cpp new file mode 100644 index 00000000..6e1dbdb1 --- /dev/null +++ b/src/websock/Webserver.cpp @@ -0,0 +1,1939 @@ +/* @file Webserver.cpp + * + * Webserver + websocket container/scaffold. + * Originally adapted from libwebsocket example + * + * 19aug2022 + * This version probably overly rigid. + * Initial goal is to adpat example application so that: + * a. it works as a library + * b. doesn't hijack main thread + * + * Subsequently, want to wrap to demonstrate library working from within python + * + * In the meantime, adopting example code as-is without looking closely + * at globals/singletons + * + */ + +#include "Webserver.hpp" +#include "WebsocketSink.hpp" +#include "WebsockUtil.hpp" +#include "WsSafetyToken.hpp" +#include "DynamicEndpoint.hpp" +#include "xo/printjson/PrintJson.hpp" +#include // for Json::Reader, to parse json input +#include +#include +#include +#include +#include + +namespace xo { + using xo::web::Alist; + using xo::reactor::AbstractSink; + using xo::json::PrintJson; + using xo::fn::CallbackId; + using xo::ref::rp; + using xo::scope; + using xo::xtag; + + namespace web { + char const * + RunstateUtil::runstate_descr(Runstate x) + { +# define CASE(x) case Runstate::x: return #x + switch(x) { + CASE(stopped); + CASE(stop_requested); + CASE(running); + } +# undef CASE + + return "???"; + } /*runstate_descr*/ + + /* both websocket and appl thread can obtain this token. + * see WebsocketSessionRecd. Posession of this token is evidence + * caller holds WebsocketSessionRecd.mutex + */ + class WsSessionSafetyToken : public SafetyToken { + private: + friend class WebsocketSessionRecd; + + private: + /* only WebsocketSessionRecd should construct this + * mutex argument present just to alert reader + */ + WsSessionSafetyToken(std::unique_lock const &) {} + }; /*WsSessionSafetyToken*/ + + + namespace { + /* one of these is created for each client connecting to us */ + + struct OutputBuffer; + + /* editor bait: + * WebserverImpl::send_text() + * ws_pss + * + * NOTE: + * 1. per_session_data__http instances are created by libwebsocket library. + * since that's implemented in C, ctor/dtors won't be invoked for this class + */ + struct per_session_data__minimal { + public: + /* output state; allocated as bona fide c++ object */ + OutputBuffer * output_buf_; + }; /*per_session_data__minimal*/ + + /* one of these created for each message */ + + /* output destined for a particular websocket. + * 'struct msg' that folllows is a POD C struct inherited from + * libwebsocket example code; intend to retire that. + * + * An OutputMsg instance sends bytes [lo..hi), + * using as many trips as necessary + * + * +---...---+---...--------+ + * | LWS_PRE | text payload | + * +---...---+---...--------+ + * ^ ^ ^ + * .buf .text() text() + text.size() + * + * |<---A--->|<------B------->| + * |<------------C----------->| + * + * A: (LWS_PRE bytes) populated by ::lws_write(), in particular encodes .buf_z + * B: (.text_z bytes) WebserverImpl passes this range to ::lws_write() + * C: (LWS_PRE + .text_z bytes) ::lws_write() actually sends this range + * + * Note trailing null isn't required, since length is explicitly sent + */ + struct OutputBuffer { + public: + OutputBuffer(uint32_t session_id) : session_id_{session_id} {} + ~OutputBuffer() = default; + + uint32_t session_id() const { return session_id_; } + struct lws * wsi() const { return wsi_; } + + /* non-const access required. + * lws_write() will prepend headers in .buf_v[0..LWS_PRE-1] + */ + unsigned char * text() { return &(buf_v_[LWS_PRE]); } + unsigned char const * text() const { return &(buf_v_[LWS_PRE]); } + size_t text_z() const { return text_z_; } + + std::string_view text_view() const { + return std::string_view((char const *)(this->text()), + this->text_z()); + } + + bool is_busy() const { return this->sent_seq_ < this->stored_seq_; } + bool is_idle() const { return this->sent_seq_ == this->stored_seq_; } + + void establish_wsi(struct lws * wsi) { this->wsi_ = wsi; } + + bool is_writeable(WsSafetyToken const &) const { return is_writeable_; } + void set_is_writeable(bool x, WsSafetyToken const &) { is_writeable_ = x; } + + /* caller must hold WebsocketSessionRecd.mutex; + * evidenced by wsession_token + */ + void store_message(uint32_t msg_seq, + std::string const & text, + WsSessionSafetyToken const & wsession_token) { + scope log(XO_ENTER0(info)); + + wsession_token.verify(); + + if (sent_seq_ != stored_seq_) { + log && log("store_message: attempt storing new msg_seq but sent_seq!=stored_seq", + xtag("sent_seq", sent_seq_), + xtag("stored_seq", stored_seq_), + xtag("msg_seq", msg_seq)); + assert(false); + } + + size_t req_z = LWS_PRE + text.size(); + + if (this->buf_v_.size() < req_z) + this->buf_v_.resize(req_z); + + this->text_z_ = text.size(); + + ::memcpy(&(this->buf_v_[LWS_PRE]), text.c_str(), this->text_z_); + + log && log(xtag("buf", (void*)&(this->buf_v_[0])), + xtag("msg_seq", msg_seq), + xtag("text", text), + xtag("text.size", text.size()), + xtag("req_z", req_z)); + + this->stored_seq_ = msg_seq; + } /*store_message*/ + + int lws_write_aux(WsSafetyToken const & ws_safety_token) + { + scope log(XO_ENTER0(info)); + + ws_safety_token.verify(); + + this->set_is_writeable(false, ws_safety_token); + + log && log("write to websocket", + xtag("wsi", (void*)wsi_), + xtag("text_z", this->text_z())); + log && log(xtag("text", this->text_view())); + + /* 1. notice we allowed for LWS_PRE in the payload already; + * this is mandatory for LWS_WRITE_TEXT. + * 2. LWS_WRITE_TEXT requires valid utf-8 payload + * 3. lws_write() writes entire contents, using + * multiple network writes if necessary. + * Application side can ignore the possibility of partial writes. + */ + int m = ::lws_write(this->wsi_, + this->text(), + this->text_z(), + LWS_WRITE_TEXT); + + if (m < (int)this->text_z()) { + /* note: first time we observed this, browser console + * showed that entire message was eventually received, + * (though not if we exit() before returning) + */ + lwsl_user("lws_write_aux: PARTIAL WRITE: session=[%u], m=lws_write(z) with msession_id_, + m, + this->text_z()); + + /* 23sep2022: consistent with observed behavior: + * - lws will write remainder of message + * - lws will call appl via LWS_CALLBACK_SERVER_WRITEABLE + * once write has been completed + * according to docs lws buffers message -- if true, + * probably pay for message to be copied + */ + return 0; + } + + this->lws_write_completion(ws_safety_token); + + return m; + } /*lws_write_aux*/ + + /* call this after successfully sending a message */ + void lws_write_completion(WsSafetyToken const & ws_safety_token) { + /* session now writeable again. either: + * - lws_write(z) successful + * - lws_write(z) incomplete, followed by lws callback + * with reason = LWS_CALLBACK_SERVER_WRITEABLE + */ + this->set_is_writeable(true, ws_safety_token); + + /* message completely written */ + this->sent_seq_ = this->stored_seq_; + } /*lws_write_completion*/ + + private: + /* identifies websocket session associated with this buffer + * established permanently in ctor + */ + uint32_t session_id_; + + /* opaque pointer; owned by libwebsocket + identifies this session. + * established once (per websocket session) from LWS_CALLBACK_ESTABLISHED + */ + struct lws * wsi_ = nullptr; + + /* ::lws_write() takes responsibility for writing and buffering full message; + * IIU docs that means it doesn't return until full write has completed; + * this suggests it may also make reentrant callbacks for other sessions, + * while an incomplete call to lws_write() is on the stack. + * + * set .is_writeable to false during lws_write() calls, + * so that application threads can refrain from attempting nested + * lws_write() calls for the same session. + */ + bool is_writeable_ = false; + + /* seq# of last message sent using this buffer; .sent_seq chases .stored_seq */ + uint32_t sent_seq_ = 0; + /* seq# of last message stored using this buffer */ + uint32_t stored_seq_ = 0; + + /* buffer for outbound text. + * using the first LWS_PRE + .text_z bytes. + * 1st LWS_PRE bytes owned by lws library, must not touch these + */ + std::vector buf_v_; + size_t text_z_ = 0; + }; /*OutputBuffer*/ + + /* + * Unlike ws, http is a stateless protocol. This pss only exists for the + * duration of a single http transaction. With http/1.1 keep-alive and + * http/2, that is unrelated to (shorter than) the lifetime of the network + * connection. + * + * NOTE + * 1. per_session_data__http instances are created by libwebsocket library. + * since that's implemented in C, we need to arrange for manual initialization + * 2. since libwebsocket implemented in C, we don't expect auto-initialization + * or constructors to be invoked. + * 3. libwebsocket gives us several alternatives for organizing resource + * allocation. We use these callback reasons: + * LWS_CALLBACK_HTTP_BIND_PROTOCOL for setup, allocate .output_ss + * LWS_CALLBACK_HTTP_DROP_PROTOCOL for teardown free .output_ss + */ + struct per_session_data__http { + int test; + /* store http reply in .output_str */ + std::string * output_str; + }; + + /* one of these is created for each vhost our protocol is used with + * + * NOTE + * 1. per_vhost_data__minimal instances are created by libwebsocket library. + * since that's implemented in C, ctors/dtors aren't used here + * + * editor bait: vhd + */ + struct per_vhost_data__minimal { + struct lws_context * context; + struct lws_vhost * vhost; + const struct lws_protocols * protocol; + + struct per_session_data__minimal * pss_list; /* linked-list of live pss*/ + + uint32_t next_session_id_; + + //struct msg amsg; /* the one pending message... */ + //int current; /* the current message number we are caching */ + }; /*per_vhost_data__minimal*/ + } /*namespace*/ + + /* bookkeeping record for a websocket subscription. */ + class WebsocketSubscriptionRecd { + public: + WebsocketSubscriptionRecd(std::string const & incoming_uri, + DynamicEndpoint * endpoint, + rp const & ws_sink) + : incoming_uri_{incoming_uri}, + endpoint_{endpoint}, + ws_sink_{ws_sink} + {} + + void subscribe() { + this->callback_id_ = this->endpoint_->subscribe(this->incoming_uri_, + this->ws_sink_); + } /*subscribe*/ + + void unsubscribe() { + this->endpoint_->unsubscribe(this->callback_id_); + } /*unsubscribe*/ + + private: + /* original subscription url */ + std::string incoming_uri_; + /* endpoint that matched .subscribe_cmd + * (see WebserverImpl.stream_map) + */ + DynamicEndpoint * endpoint_ = nullptr; + /* id created when subscription established + * (see CallbackSetImpl.add_callback()) + */ + CallbackId callback_id_; + /* sink established to receive (& forward) events on behalf + * of this subscription. application code writes to this sink. + */ + rp ws_sink_; + }; /*WebsocketSubscriptionRecd*/ + + /* bookkeeping record for a websocket session. + * WebserverImpl (below) keeps exactly one of these + * for each active websocket session + */ + class WebsocketSessionRecd { + public: + WebsocketSessionRecd(OutputBuffer * output_buf) : output_buf_{output_buf} { + assert(this->output_buf_); + } + + bool is_output_busy() const { + return (this->output_buf_ + && this->output_buf_->is_busy()); + } + bool outbound_q_empty() const { return this->outbound_q_.empty(); } + + void subscribe_endpoint(std::string const & incoming_cmd, + DynamicEndpoint * endpoint, + rp const & ws_sink) { + + scope log(XO_ENTER0(info), + xtag("incoming_cmd", incoming_cmd)); + + std::unique_ptr sub_recd_uptr + (new WebsocketSubscriptionRecd(incoming_cmd, + endpoint, + ws_sink)); + WebsocketSubscriptionRecd * sub_recd_addr = sub_recd_uptr.get(); + + { + std::lock_guard lock(this->mutex_); + + this->active_subscription_v_.push_back(std::move(sub_recd_uptr)); + } + + /* note: need to call with lock dropped, + * since subscribe may in principle call WebserverImpl.send_text() + */ + if (sub_recd_addr) + sub_recd_addr->subscribe(); + } /*subscribe_endpoint*/ + + void send_text(std::string text) { + scope log(XO_ENTER0(info)); + + std::unique_lock lock(this->mutex_); + + if (!(this->output_buf_)) { + log && log("ws_pss.output_buf not present -> exit"); + } else if (this->is_output_busy()) { + log && log("ws_pss.output_msg busy, enqueue"); + + /* previous message already in progress, enq or drop */ + this->enqueue_text(std::move(text), + WsSessionSafetyToken(lock)); + + /* this message will eventually get sent via + * .lws_write_pending_traffic() + */ + } else { + /* send message now! */ + log && log("output_msg idle, send now"); + + this->prepare_outbound_message(std::move(text), + WsSessionSafetyToken(lock)); + + /* can release lock, won't be using for remainder of this function */ + lock.unlock(); + + lws_context * lws_cx = ::lws_get_context(this->output_buf_->wsi()); + + /* interrupt libwebsocket event loop. + * will send 'wait cancelled' event to all sockets. + * + * Actually, after testing -- looks like this sends to one wsi per protocol: + * basically to the "listening" wsi, not to websocket "session" wsi + */ + ::lws_cancel_service(lws_cx); + + /* NOTE: web documentation seems to suggest using lws_callback_on_writable(): + * + * trigger call from websocket thread to send data. + * will cause reentry via websocket thread into + * WebserverImpl::notify_minimal() + * with reason=LWS_CALLBACK_SERVER_WRITEABLE + * + * ^^^ Hmm, doesn't seem to work this way. + * Suspect this would only work if socket currently + * in non-writeable state. + */ + //lws_callback_on_writable(ws_pss->wsi); + } + } /*send_text*/ + + /* write some pending traffic from lws event loop + * + * Require: + * - MUST be invoked from lws event loop, for threadsafety; + * ws_safety_token provides evidence of this + */ + void lws_write_pending(WsSafetyToken const & ws_safety_token) { + scope log(XO_ENTER0(info)); + + log && log(xtag("output_buf", (void*)this->output_buf_)); + +#ifdef OBSOLETE + per_session_data__minimal * ws_pss = this->ws_pss_; + + if (!ws_pss) { + lscope.log("null ws_pss, exit"); + return; + } +#endif + + if (!(this->output_buf_)) { + /* output message buffer not established, + * implies nothing sent yet + */ + log && log("output_msg either not established or destroyed, exit"); + return; + } + + /* loop until no queued messages for this session */ + for (;;) { + if (!(this->output_buf_->is_writeable(ws_safety_token))) { + /* call to lws_write() already in progress */ + log && log("output_buf not writeable (bc lws_write in progress)"); + return; + } + + if (this->output_buf_->is_idle()) { + /* already up-to-date for this session */ + log && log("output idle (up-to-date)"); + return; + } + + this->output_buf_->lws_write_aux(ws_safety_token); + + /* if there are any appl messages queued, prepare to send another one */ + if (this->outbound_q_.empty()) { + /* all caught up, nothing left to send */ + log && log("up-to-date after write"); + } else { + std::unique_lock lock(this->mutex_); + + std::string text(this->dequeue_text(WsSessionSafetyToken(lock))); + + this->prepare_outbound_message(std::move(text), + WsSessionSafetyToken(lock)); + } + } + } /*lws_write_pending*/ + + /* threadsafe */ + void unsubscribe_all() { + std::lock_guard lock(this->mutex_); + + /* also drop .output_buf, + * to short-circuit any subsequent attempts to use .lws_write_pending() + * (which will happen in response to LWS_CALLBACK_EVENT_WAIT_CANCELLED + * on any session) + */ + for (auto & sub_ptr : this->active_subscription_v_) + sub_ptr->unsubscribe(); + + this->output_buf_ = nullptr; + this->active_subscription_v_.clear(); + } /*unsubscribe_all*/ + + private: + uint32_t generate_msg_seq() { return ++(this->last_msg_seq_); } + + /* enqueue application-level message. + * use this when .ws_pss.outbound_buf is busy + */ + void enqueue_text(std::string text, + WsSessionSafetyToken const & /*wss_token*/) { + this->outbound_q_.push_back(std::move(text)); + } /*enqueue_text*/ + + /* remove a deferred message from .outbound_q, + * and return it. This can happen if output becomes + * available after being write-blocked + */ + std::string dequeue_text(WsSessionSafetyToken const & /*wss_token*/) { + assert(!this->outbound_q_.empty()); + + std::string retval = std::move(this->outbound_q_.front()); + + this->outbound_q_.pop_front(); + + return retval; + } /*dequeue_text*/ + + /* prepare outbound message for sending in contiguous memory; + * in particular prepends header. + * + * this can be called from either websocket or appl thread, + * so needs to be threadsafe. + */ + void prepare_outbound_message(std::string text, + WsSessionSafetyToken const & wss_token) { + scope log(XO_ENTER0(info)); + + /* sequence# for this outbound message */ + uint32_t msg_seq = this->generate_msg_seq(); + + this->output_buf_->store_message(msg_seq, + std::move(text), + wss_token); + + /* now ws_pss->output_msg_->is_busy() */ + log && log("staged next write", + xtag("wsi", (void*)this->output_buf_->wsi()), + xtag("text_z", this->output_buf_->text_z())); + } /*prepare_outbound_message*/ + + private: + /* output destined for this session + * libws (via per_session_data__minimal) also points to + * .output_buf + */ + OutputBuffer * output_buf_ = nullptr; + /* protects .active_subscription_v, .last_msg_seq, .outbound_q */ + std::mutex mutex_; + /* active subscriptions established by this session */ + std::vector> active_subscription_v_; + /* generate seq#'s for outgoing messages */ + uint32_t last_msg_seq_ = 0; + /* when new outgoing message appears: + * 1. if .pss->output_msg empty, allocate it and store message there; + * invoke lws_callback_on_writeable(.pss->wsi) to get message sent asap + * 2. otherwise sending a previous message is in-progress; + * put outgoing message to the back of .outbound_q + */ + std::deque outbound_q_; + }; /*WebsocketSessionRecd*/ + + using EndpointMap = std::unordered_map>; + + /* defined in this translation unit, after WebserverImpl */ + class WebserverImplWsThread; + + class WebserverImpl : public Webserver { + public: + WebserverImpl(WebserverConfig const & ws_config, + rp const & pjson) + : ws_config_{ws_config}, + pjson_{pjson}, + readjson_{Json::CharReaderBuilder().newCharReader()}, + interrupt_flag_{false}, + state_{Runstate::stopped} + { + } /*ctor*/ + + virtual ~WebserverImpl() { + /* if webserver is running, initiate shutdown. + * webserver thread will eventually exit + */ + this->stop_webserver(); + /* wait for shutdown to complete */ + this->join_webserver(); + } /*dtor*/ + + virtual void run() = 0; + + // ----- Inherited from Webserver ----- + + virtual Runstate state() const override { return state_; } + virtual void register_http_endpoint(HttpEndpointDescr const & endpoint) override; + virtual void register_stream_endpoint(StreamEndpointDescr const & endpoint) override; + virtual void start_webserver() override; + virtual void interrupt_stop_webserver() override; + virtual void stop_webserver() override; + virtual void join_webserver() override; + + protected: + void set_lws_log_level() { + lws_set_log_level(LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE + /* for LLL_ verbosity above NOTICE to be built into + * lws, lws must have been configured and built with + * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ + /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ + /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ + /* | LLL_DEBUG */, + NULL); + } /*set_lws_log_level*/ + +#if defined(LWS_WITH_PLUGINS) + void init_pvo(lws_protocol_vhost_options * p_pvo) { + /* {next, options, name, value} */ + *p_pvo = {NULL, NULL, "lws-minimal", ""}; + } /*init_pvo*/ +#endif + + /* called once during webserver initialization; + * identifies protocols (channels) that libws is expected to support + */ + virtual void init_protocols(std::vector * p_v) = 0; + + void init_mount_dynamic(lws_http_mount * p_mount) { + *p_mount = { + /* .mount_next */ NULL, /* linked-list "next" */ + /* .mountpoint */ "/dyn", /* mountpoint URL */ + /* .origin */ NULL, /* protocol */ + /* .def */ NULL, + /* .protocol */ "http", + /* .cgienv */ NULL, + /* .extra_mimetypes */ NULL, + /* .interpret */ NULL, + /* .cgi_timeout */ 0, + /* .cache_max_age */ 0, + /* .auth_mask */ 0, + /* .cache_reusable */ 0, + /* .cache_revalidate */ 0, + /* .cache_intermediaries */ 0, + /* .origin_protocol */ LWSMPRO_CALLBACK, /* dynamic */ + /* .mountpoint_len */ 4, /* char count */ + /* .basic_auth_login_file */ NULL, + }; + } /*init_mount_dynamic*/ + + void init_mount_static(lws_http_mount const * dynamic, + lws_http_mount * p_mount) { + /* default mount serves the URL space from ./mount-origin */ + *p_mount = { + /* .mount_next */ dynamic, /* linked-list "next" */ + /* .mountpoint */ "/", /* mountpoint URL */ + /* .origin */ "./mount-origin", /* serve from dir */ + /* .def */ "index.html", /* default filename */ + /* .protocol */ NULL, + /* .cgienv */ NULL, + /* .extra_mimetypes */ NULL, + /* .interpret */ NULL, + /* .cgi_timeout */ 0, + /* .cache_max_age */ 0, + /* .auth_mask */ 0, + /* .cache_reusable */ 0, + /* .cache_revalidate */ 0, + /* .cache_intermediaries */ 0, + /* .origin_protocol */ LWSMPRO_FILE, /* files in a dir */ + /* .mountpoint_len */ 1, /* char count */ + /* .basic_auth_login_file */ NULL, + }; + } /*init_mount_static*/ + + void init_retry(lws_retry_bo_t * p_retry) { + p_retry->secs_since_valid_ping = 3; + p_retry->secs_since_valid_hangup = 10; + } /*init_retry*/ + + /* requires: + * - .pvo initialized, see .init_pvo() + * - .protocol_v[] initialized, see .init_protocols() + * - .mount_dynamic initialized, see .init_mount_dynamic() + * - .mount_static initialized, see .init_mount_static() + * - .retry initialized, see .init_retry() + */ + void init_cx_config(lws_context_creation_info * p_cx_config) { + ::memset(p_cx_config, 0, sizeof(*p_cx_config)); + p_cx_config->port = this->ws_config_.port(); + p_cx_config->vhost_name = "localhost"; + p_cx_config->pvo = &(this->pvo_); + p_cx_config->protocols = this->protocol_v_.data(); + p_cx_config->mounts = &(this->mount_static_); + /* userdata -- accessible from context with lws_context_user() */ + p_cx_config->user = (void*)this; + +#if defined(LWS_WITH_TLS) + if (this->ws_config_.tls_flag()) { + lwsl_user("Server using TLS\n"); + p_cx_config->options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + p_cx_config->ssl_cert_filepath = "localhost-100y.cert"; + p_cx_config->ssl_private_key_filepath = "localhost=100y.key"; + } +#endif + + if (this->ws_config_.host_check_flag()) { + p_cx_config->options |= LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK; + } + + if (this->ws_config_.use_retry_flag()) { + p_cx_config->retry_and_idle_policy = &(this->retry_); + } + } /*init_cx_config*/ + + /* check for a DynamicEndpoint stored under stem; + * if found, invoke it on incoming_uri to respond + * + * return. true iff stem matched a dynamic endpoint; + */ + DynamicEndpoint * lookup_dynamic_http_stem(std::string const & stem); + + /* write dynamic http response for incoming_uri, on *p_os + * incoming_uri will be suffix of original uri from browser, + * following dynamic mount point [/dyn]. + * see .init_mount_dynamic() + */ + void dynamic_http_response(std::string const & incoming_uri, + std::ostream * p_os); + + /* act on incoming websocket command + * expecting json like + * {"command": "subscribe", "stream": "uls"} + */ + void perform_ws_cmd(uint32_t session_id, + std::string_view incoming_svw); + +#ifdef DEFINED_BUT_NOT_USED + /* called from libwebsocket thread when session manager + * (aka "virtual host") is created for the websocket protocol + */ + void notify_vhd(per_vhost_data__minimal * vhd); +#endif + /* called from libwebsocket thread when creating a new websocket session */ + void notify_ws_session_open(OutputBuffer * output_buf, + per_vhost_data__minimal * vhd, + WsSafetyToken const & ws_safety_token); + /* called from libwebsocket thread whenever a websocket session is closed */ + void notify_ws_session_close(OutputBuffer * output_buf, + per_vhost_data__minimal * vhd, + WsSafetyToken const & ws_safety_token); + + /* send text to the websocket session identified by session_id */ + void send_text(uint32_t session_id, + std::string text) override; + + /* from lws event loop, write any pending outbound traffic + * see .pending_session_q + */ + void lws_write_pending_traffic(WsSafetyToken const & ws_safety_token); + + protected: + /* callback for http protocol */ + static int notify_dynamic_http(struct lws * wsi, + lws_callback_reasons reason, + void * user_data, + void * incoming_uri, + size_t len); + + protected: + /* see WebserverImplWsThread below, for methods + * that are exclusive to libws thread + */ + + /* initial configuration for embedded webserver */ + WebserverConfig ws_config_; + + /* json printer (w/ plugins for reflected types) */ + rp pjson_; + + /* json reader */ + std::unique_ptr readjson_; + + /* --- 1. LWS configuration stuff (set once) ---*/ + +#if defined(LWS_WITH_PLUGINS) + /* protocols listed here will "bind to vhost". + * (I don't know for sure what this means -- it's a magic spell for now) + * + */ + lws_protocol_vhost_options pvo_; +#endif + + /* protocols to accept for this webserver */ + std::vector protocol_v_; + + /* mount point for dynamic urls + * (these will be served by executing c++ code, + * instead of serving static disk files) + */ + lws_http_mount mount_dynamic_; + /* mount point for static urls + * (serve static files from file system) + */ + lws_http_mount mount_static_; + + /* retry settings + * (not sure how these are used) + */ + lws_retry_bo_t retry_; + + /* configuration record for lws context + * AFAIK require lifetime >= lws_context + */ + lws_context_creation_info cx_config_; + + /* runtime state owned by LWS library + * can get application-determined user data from a lws_context by + * lws_context_user(.lws_cx) + */ + lws_context * lws_cx_ = nullptr; + + /* --- 2. startup/shutdown control --- */ + + /* set this to true to prevent further service loop iteration */ + std::atomic interrupt_flag_; + + /* protects .state */ + std::mutex mutex_; + std::condition_variable cond_; + + /* valid states + * + * .state .thread_ptr + * ----------------------------------------------- + * running thread in WebserverImpl::run() + * stop_requested thread in WebserverImpl::run() + * stopped nullptr + */ + Runstate state_; + std::unique_ptr thread_ptr_; + + /* --- 3. plugin state (writable while server runs) --- */ + + /* map :: stem->http_fn, + * where + * stem = "longest non-variable URI prefix" + * + * use .register_http_endpoint() to insert a new URI into this map + * + * this map used for http endpoints + */ + EndpointMap stem_map_; + /* map :: stem->subscribe_fn + * where + * stem = "longest non-variable URI prefix" + * + * use .register_stream_endpoint() to insert a new URI into this map + * + * this map used for stream endpoints + */ + EndpointMap stream_map_; + + /* --- 4. libwebsocket session manager --- */ + + /* websocket-associated libwebsocket data. + * created by libwebsocket; our appl code informed via + * LWS_CALLBACK_PROTOCOL_INIT + * + * list of all active sessions is in .ws_vhd->pss_list + * (see LWS_CALLBACK_PROTOCOL_INIT, LWS_CALLBACK_ESTABLISHED, LWS_CALLBACK_CLOSED) + * + * can visit sessions with macros: + * lws_start_foreach_llp(struct per_session_data__minimal **, ppss, vhd->pss_list) { + * ..do stuff with (*ppss)->wsi for example.. + * } lws_end_foreach_llp(ppss, pss_list); + */ + per_vhost_data__minimal * ws_vhd_ = nullptr; + + /* indexed by session id# (see per_session_data__minimal.session_id) + * .session_v.size() = {max #of simultaneously-open websocket sessions}. + * may contain empty slots. if .session_v[i] is empty, + * then i appears in .free_session_id_v[], i..e .free_session_id_v[j]=i for some j + */ + std::vector> session_v_; + + /* When a session closes, its session id becomes available. + * track such session ids here, so they can be recycled. + * want to recycle because they're indexes into .session_v[], + * and we don't want that to grow without bound + */ + std::vector free_session_id_v_; + + }; /*WebserverImpl*/ + + void + WebserverImpl::register_http_endpoint(HttpEndpointDescr const & endpoint_descr) + { + auto endpoint = DynamicEndpoint::make_http(endpoint_descr.uri_pattern(), + endpoint_descr.endpoint_fn()); + + this->stem_map_[endpoint->stem()] = std::move(endpoint); + } /*register_http_endpoint*/ + + void + WebserverImpl::register_stream_endpoint(StreamEndpointDescr const & endpoint_descr) + { + auto endpoint = DynamicEndpoint::make_stream(endpoint_descr.uri_pattern(), + endpoint_descr.subscribe_fn(), + endpoint_descr.unsubscribe_fn()); + + this->stream_map_[endpoint->stem()] = std::move(endpoint); + } /*register_stream_endpoint*/ + +#ifdef DEFINED_BUT_NOT_USED + void + WebserverImpl::notify_vhd(per_vhost_data__minimal * vhd) + { + this->ws_vhd_ = vhd; + } /*notify_vhd*/ +#endif + + void + WebserverImpl::notify_ws_session_open(OutputBuffer * output_buf, + per_vhost_data__minimal * vhd, + WsSafetyToken const & ws_safety_token) + { + ws_safety_token.verify(); + + uint32_t new_id = output_buf->session_id(); + + if (this->session_v_.size() <= new_id) + this->session_v_.resize(new_id + 1); + + this->session_v_[new_id].reset(new WebsocketSessionRecd(output_buf)); + + /* control comes here when a new websocket session is created, + * after LWS_CALLBACK_HTTP_BIND_PROTOCOL + */ + output_buf->set_is_writeable(true, ws_safety_token); + + /* compute next available session id + store in vhost struct */ + + if (this->free_session_id_v_.empty()) { + /* generate a new session id */ + uint32_t id = this->session_v_.size(); + + vhd->next_session_id_ = id; + } else { + /* recycle a previously-used session id */ + uint32_t id = this->free_session_id_v_[this->free_session_id_v_.size() - 1]; + this->free_session_id_v_.pop_back(); + + vhd->next_session_id_ = id; + } + } /*notify_ws_session_open*/ + + void + WebserverImpl::notify_ws_session_close(OutputBuffer * output_buf, + per_vhost_data__minimal * /*vhd*/, + WsSafetyToken const & ws_safety_token) + { + scope log(XO_ENTER0(info)); + + log && log("enter", + xtag("this", (void*)this), + xtag("output_buf", (void*)output_buf)); + + assert(output_buf->session_id() < this->session_v_.size()); + + ws_safety_token.verify(); + + WebsocketSessionRecd * ws_session_recd + = this->session_v_[output_buf->session_id()].get(); + + if (ws_session_recd) { + ws_session_recd->unsubscribe_all(); + } + + this->free_session_id_v_.push_back(output_buf->session_id()); + } /*notify_ws_session_close*/ + + /* note: to access lws_protocols.user, + * would use lws_get_protocol(wsi)->user + */ + int + WebserverImpl::notify_dynamic_http(struct lws * wsi, + lws_callback_reasons reason, + void * user_data, + void * incoming_data, + size_t len) + { + lws_context * lws_cx = lws_get_context(wsi); + void * cx_user_data = lws_context_user(lws_cx); + WebserverImpl * websrv = reinterpret_cast(cx_user_data); + + struct per_session_data__http * http_pss + = reinterpret_cast(user_data); + + lwsl_user("notify_dynamic_http: enter: reason %d (%s): lws_cx %p websrv %p\n", + reason, + WebsockUtil::ws_callback_reason_descr(reason), + lws_cx, + websrv); + + /* scratch space for http header + * (probably only need LWS_PRE here, I think the +256 debris + * from o.g. example) + */ + uint8_t buf[LWS_PRE + 256]; + uint8_t * start = &buf[LWS_PRE]; + uint8_t * p = start; + uint8_t * end = &buf[sizeof(buf) - 1]; + + switch (reason) { + case LWS_CALLBACK_HTTP: + { + /* incoming_uri contains the uri suffix following our mountpoint [/dyn] + * (see WebserverImpl.init_mount_dynamic()). + * + * looks like this gets spuriously invoked for non-dynamic mountpoints + * given that we serve both filesystem tree + * (in url-space at /, from dir ./mount-origin) and dynamic http (in url-space at /dyn); + * + * however output from the spurious invocation seems to be discarded + */ + char const * incoming_uri + = reinterpret_cast(incoming_data); + + assert(http_pss->output_str == nullptr); + + if (http_pss->output_str == nullptr) { + http_pss->output_str = new std::string; + } + + lwsl_user("allocate output_str [%p] in http_pss [%p]", + http_pss->output_str, http_pss); + + std::stringstream response_ss; + + assert(websrv); + + websrv->dynamic_http_response(incoming_uri, + &response_ss); + + *(http_pss->output_str) = response_ss.str(); + + lwsl_user("LWS_CALLBACK_HTTP: got response [%s]", + http_pss->output_str->c_str()); + + /* choose mime type */ + constexpr char const * c_mime_type = "application/json"; + + /* prepare and write http headers + * (do these precede &p ??) + */ + if (lws_add_http_common_headers(wsi, + HTTP_STATUS_OK, + c_mime_type, + http_pss->output_str->length(), + &p, end)) + return 1; + + if (lws_finalize_write_http_header(wsi, start, &p, end)) + return 1; + + /* write the body separately */ + lws_callback_on_writable(wsi); + + return 0; + } + + case LWS_CALLBACK_HTTP_WRITEABLE: + { + if (!http_pss || !http_pss->output_str || (http_pss->output_str->length() == 0)) + break; + + /* + * Use LWS_WRITE_HTTP (instead of LWS_WRITE_HTTP_FINAL) for intermediate writes, + * on http/2 lws uses this to understand to end the stream with this + * frame. + * + * TODO: if output is large, write it in smaller chunks. + * expecting mtu like 1500 bytes, so maybe 128k + * chunks will work well? + */ + if (lws_write(wsi, + (uint8_t *)(http_pss->output_str->c_str()), + http_pss->output_str->length(), + LWS_WRITE_HTTP_FINAL) + != static_cast(http_pss->output_str->length())) + { + return 1; + } + + /* + * HTTP/1.0 no keepalive: close network connection + * HTTP/1.1 or HTTP1.0 + KA: wait / process next transaction + * HTTP/2: stream ended, parent connection remains up + */ + if (lws_http_transaction_completed(wsi)) + return -1; + + return 0; + } + + case LWS_CALLBACK_HTTP_BIND_PROTOCOL: + { + /* from libwebsocket docs: + * By default, all HTTP handling is done in protocols[0]. + * However you can bind different protocols (by name) to different parts of the URL space using callback mounts. + * This callback occurs in the new protocol when a wsi is bound to that protocol. + * Any protocol allocation related to the http transaction processing should be created then. + * These specific callbacks are necessary because with HTTP/1.1, + * a single connection may perform a series of different transactions at different URLs, + * thus the lifetime of the protocol bind is just for one transaction, not connection. + */ + if (!http_pss) + break; + + /* although we could allocate http_pss->output_ss here, + * instead delay until LWS_CALLBACK_HTTP. + * this reduces new/delete churn, since BIND/DROP callbacks + * will get invoked on every incoming request, not just for + * dynamic http requests. + */ + http_pss->output_str = nullptr; + + lwsl_user("initialize http_pss->output_str to null in http_pss [%p]", + http_pss); + } + break; + + case LWS_CALLBACK_HTTP_DROP_PROTOCOL: + /* from libwebsocket docs: + * This is called when a transaction is unbound from a protocol. + * It indicates the connection completed its transaction and may do something different now. + * Any protocol allocation related to the http transaction processing should be destroyed. + */ + if (!http_pss) + break; + + if (http_pss->output_str) { + lwsl_user("destroy string [%p] in http_pss [%p]", + http_pss->output_str, http_pss); + + delete http_pss->output_str; + + http_pss->output_str = nullptr; /*hygiene*/ + } + + default: + break; + } + + return lws_callback_http_dummy(wsi, reason, user_data, incoming_data, len); + + } /*notify_dynamic_http*/ + + void + WebserverImpl::start_webserver() + { + switch(state_) { + case Runstate::stopped: + { + std::unique_lock lock(this->mutex_); + + this->thread_ptr_.reset(new std::thread(&WebserverImpl::run, this)); + this->state_ = Runstate::running; + } + break; + case Runstate::stop_requested: + throw std::runtime_error("webserver in stop-requested state"); + /* could invent a "restart-requested" state, I suppose */ + break; + case Runstate::running: + throw std::runtime_error("webserver already running"); + break; + } + } /*start_webserver*/ + + namespace { + DynamicEndpoint * + lookup_stem(std::string const & stem, + EndpointMap const & ep_map) + { + scope log(XO_DEBUG(true /*debug_flag*/), + xtag("stem", stem)); + + auto ix = ep_map.find(stem); + + if (ix != ep_map.end()) + return ix->second.get(); + else + return nullptr; + } /*lookup_stem*/ + + DynamicEndpoint * + lookup_pattern(std::string const & incoming_uri, + EndpointMap const & ep_map) + { + if (incoming_uri.empty()) + return nullptr; + + /* find longest prefix of incoming_uri that appears in .stem_map. + * + * 1. try the whole uri + * 2. try successively shorter prefixes of uri that end in '/' + * 3. try successively shorter prefixes of uri that do not end in '/' + */ + + /* 1. try the whole uri */ + DynamicEndpoint * endpoint = nullptr; + + endpoint = lookup_stem(incoming_uri, ep_map); + + if (!endpoint) { + /* 2. try successively shorter prefixes of uri that end in '/'. + * we already checked for the whole uri, so look for a match + * at or before the 2nd-last character + */ + if (incoming_uri.size() >= 2) { + std::string::size_type p = incoming_uri.size() - 1; + + while (!endpoint) { + p = incoming_uri.find_last_of('/', p-1); + + if (p == std::string::npos) + break; + + endpoint + = lookup_stem(incoming_uri.substr(0, p+1), ep_map); + + if (p == 0) + break; + } + } + } + + if (!endpoint) { + /* 3. try successively shorter prefixes of uri that don't end in '/'. + */ + if (incoming_uri.size() >= 2) { + std::string::size_type p = incoming_uri.size() - 2; + + while (!endpoint) { + if (incoming_uri[p] == '/') { + /* all stems ending in '/' have already been excluded */ + ; + } else { + endpoint + = lookup_stem(incoming_uri.substr(0, p+1), ep_map); + } + + if (p == 0) + break; + + --p; + } + } + } + + return endpoint; + } /*lookup_pattern*/ + } /*namespace*/ + + void + WebserverImpl::dynamic_http_response(std::string const & incoming_uri, + std::ostream * p_os) + { + DynamicEndpoint * endpoint = lookup_pattern(incoming_uri, + this->stem_map_); + + if (endpoint) { + endpoint->http_response(incoming_uri, p_os); + return; + } else { + /* if control here, no match */ + + /* or replace pss->str, pss->len with whatever dynamic content you like */ + time_t t0 = ::time(nullptr); + + *p_os << ("" + "" + "
no dynamic content for uri [") + << incoming_uri + << ("]" + " from mountpoint." + "
time: ") + << ctime(&t0) + << ""; + } + } /*dynamic_http_response*/ + + void + WebserverImpl::perform_ws_cmd(uint32_t session_id, + std::string_view incoming_cmd) + { + /* expecting input like: + * {"command": "subscribe", + * "stream": "usl"} + */ + + scope log(XO_ENTER0(info), + xtag("incoming_cmd", incoming_cmd)); + + Json::Value root; + + JSONCPP_STRING err; + bool ok = this->readjson_->parse(incoming_cmd.data(), + incoming_cmd.data() + incoming_cmd.size(), + &root, + &err); + + if (!ok) { + log && log("error: parsing failed", + xtag("incoming_cmd", incoming_cmd)); + } + + //std::cout << "WebserverImpl::perform_ws_cmd :root [" << root << "]" << std::endl; + + std::string cmd = root["cmd"].asString(); + + log && log("ws command", xtag("cmd", cmd)); + //std::cout << "WebserverImpl::perform_ws_cmd :cmd [" << cmd << "]" << std::endl; + + if (cmd == "subscribe") { + std::string stream_name = root["stream"].asString(); + + log && log("subscribe stream", xtag("stream", stream_name)); + + DynamicEndpoint * endpoint = lookup_pattern(stream_name, + this->stream_map_); + + if (endpoint) { + log && log("endpoint found"); + + /* sink to receive outbound events bound for session_id, + * for stream_name + */ + rp ws_sink + = WebsocketSink::make(this, + this->pjson_, + session_id, + stream_name); + + log && log("sink created"); + + assert(ws_sink->allow_polymorphic_source()); + assert(ws_sink->allow_volatile_source()); + + WebsocketSessionRecd * ws_recd = this->session_v_[session_id].get(); + + assert(ws_recd); + + ws_recd->subscribe_endpoint(std::string(incoming_cmd), + endpoint, + ws_sink); + } else { + log && log("endpoint not found"); + } + } + } /*perform_ws_cmd*/ + + void + WebserverImpl::interrupt_stop_webserver() + { + /* NOTE: this is threadsafe - ::lws_cancel_service() + * writes to a pipe to interrupt polling loop + */ + { + this->interrupt_flag_ = true; + + if (this->lws_cx_) { + ::lws_cancel_service(this->lws_cx_); + } + } + + std::unique_lock lock(this->mutex_); + + this->state_ = Runstate::stop_requested; + } /*interrupt_stop_webserver*/ + + void + WebserverImpl::stop_webserver() + { + std::unique_lock lock(this->mutex_); + + if(this->state_ == Runstate::running) { + this->interrupt_stop_webserver(); + } + } /*stop_webserver*/ + + void + WebserverImpl::join_webserver() { + while(true) { + std::unique_lock lock(this->mutex_); + + if (this->state_ == Runstate::stopped) + break; + + this->cond_.wait(lock); + } + + if (this->thread_ptr_) { + this->thread_ptr_->join(); + this->thread_ptr_ = nullptr; + } + } /*join_webserver*/ + + void + WebserverImpl::send_text(uint32_t session_id, + std::string text) + { + scope log(XO_ENTER0(info)); + log && log(xtag("session_id", session_id), + xtag(".session_v.size", this->session_v_.size())); + + if (session_id < this->session_v_.size()) { + WebsocketSessionRecd * p_session_recd = this->session_v_[session_id].get(); + + if (p_session_recd) + p_session_recd->send_text(text); + + //per_session_data__minimal * ws_pss = p_session_recd->ws_pss(); + + //lscope.log(xtag("ws_pss", ws_pss), + // xtag("ws_pss.wsi", ws_pss->wsi)); + + } else { + assert(false); + } + } /*send_text*/ + + void + WebserverImpl::lws_write_pending_traffic(WsSafetyToken const & ws_safety_token) + { + scope log(XO_ENTER0(info)); + + ws_safety_token.verify(); + + for (auto & session_ptr : this->session_v_) { + if (session_ptr) + session_ptr->lws_write_pending(ws_safety_token); + } + } /*lws_write_pending_traffic*/ + + /* sequester .ws_safety_token: + * it may only be used by dedicated websocket library thread + * (the unique thread that calls ::lws_service()) + */ + class WebserverImplWsThread : public WebserverImpl { + public: + WebserverImplWsThread(WebserverConfig const & ws_config, + rp const & pjson) + : WebserverImpl(ws_config, pjson) + { + scope log(XO_DEBUG(true /*debug_flag*/), + xtag("self", (void*)this)); + + this->set_lws_log_level(); +#if defined(LWS_WITH_PLUGINS) + this->init_pvo(&(this->pvo_)); +#endif + this->init_protocols(&(this->protocol_v_)); + this->init_mount_dynamic(&(this->mount_dynamic_)); + this->init_mount_static(&(this->mount_dynamic_), + &(this->mount_static_)); + this->init_cx_config(&(this->cx_config_)); + } /*ctor*/ + + /* create instance */ + static rp make(WebserverConfig const & ws_config, + rp const & pjson); + + // ----- Inherited from WebserverImpl ----- + + /* init helper */ + virtual void init_protocols(std::vector * p_v) override; + + /* run webserver. borrows calling thread, doesn't return + * until webserver stopped. + */ + virtual void run() override; + + private: + /* callback for lws-minimal protocol (websocket) */ + static int notify_minimal(struct lws * wsi, + lws_callback_reasons reason, + void * user_data, + void * incoming_uri, + size_t len); + + WsSafetyToken const & ws_safety_token() const { return ws_safety_token_; } + + private: + /* a function taking .ws_safety_token as an argument, + * announces that it is being called from the libws thread, + * i.e. reentrantly from ::lws_service() + */ + WsSafetyToken ws_safety_token_; + }; /*WebserverImplWsThread*/ + + /* 1. anything after the host:port prefix will get handled by callback_dynamic_http + * 2. host::port alone will upgrade to "lws-minimal" for websocket demo + * + * in practice p_v will be &WebserverImpl::protocol_v_ + */ + void + WebserverImplWsThread::init_protocols(std::vector * p_v) + { + /* lws_protocols: + * .name + * .callback + * .per_session_data_size + * .rx_buffer_size + * .id advertised as accessible from callback, but don't see how to use this + * .user advertised as accessible from callback, but don't see how to make this work. + * looks like libwebsocket allocates its own struct, even if .user is nonempty, + * and whether or not .per_session_data_size is 0. + * .tx_packet_size + */ + p_v->push_back({ + "http", + &WebserverImpl::notify_dynamic_http, + sizeof(struct per_session_data__http), + 0, + 0, + NULL, + 0 + }); + p_v->push_back({ + "lws-minimal", + &WebserverImplWsThread::notify_minimal, + sizeof(struct per_session_data__minimal), + 128, + 0, + NULL, + 0}); + /* mandatory end-of-array sentinel, requires by lws */ + p_v->push_back(LWS_PROTOCOL_LIST_TERM); + } /*init_protocols*/ + + /* called reentrantly from ::lws_service(), + * to do work on behalf of the websocket protocol "lws-minimal" + */ + int + WebserverImplWsThread::notify_minimal(struct lws * wsi, + lws_callback_reasons reason, + void * user_data, + void * input, + size_t input_z) + { + scope log(XO_ENTER0(info), + xtag("wsi", (void*)wsi)); + + lwsl_user("WebserverImpl::notify_minimal: enter" + ": reason %d (%s)", + reason, + WebsockUtil::ws_callback_reason_descr(reason)); + + assert(wsi); + + lws_context * lws_cx = lws_get_context(wsi); + + assert(lws_cx); + void * cx_user_data = lws_context_user(lws_cx); + + WebserverImplWsThread * websrv = reinterpret_cast(cx_user_data); + assert(websrv); + + WsSafetyToken const & ws_token = websrv->ws_safety_token(); + + struct per_session_data__minimal * ws_pss + = ((struct per_session_data__minimal *)user_data); + + lwsl_user("WebserverImpl::notify_minimal: enter" + ": reason %d (%s): wsi [%p], ws_pss [%p], lws_cx [%p] websrv [%p]\n", + reason, + WebsockUtil::ws_callback_reason_descr(reason), + wsi, + ws_pss, + lws_cx, + websrv); + + struct per_vhost_data__minimal * vhd + = ((struct per_vhost_data__minimal *) + lws_protocol_vh_priv_get(lws_get_vhost(wsi), + lws_get_protocol(wsi))); + int m; + + switch (reason) { + case LWS_CALLBACK_PROTOCOL_INIT: + { + vhd = (reinterpret_cast + (lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), + lws_get_protocol(wsi), + sizeof(struct per_vhost_data__minimal)))); + vhd->context = lws_get_context(wsi); + vhd->vhost = lws_get_vhost(wsi); + vhd->protocol = lws_get_protocol(wsi); + vhd->pss_list = nullptr; + vhd->next_session_id_ = 1; + //vhd->current = 0; + + lwsl_user("WebserverImpl::notify_minimal: vhost=%p, protocols=%p protocol.name=%s\n", + vhd->vhost, vhd->protocol, vhd->protocol->name); + } + break; + + case LWS_CALLBACK_WS_SERVER_BIND_PROTOCOL: + case LWS_CALLBACK_HTTP_BIND_PROTOCOL: + { + /* looks like control comes here with + * LWS_CALLBACK_HTTP_BIND_PROTOCOL, + * although based on docs would seem to expect + * LWS_CALLBACK_WS_SERVER_BIND_PROTOCOL + * + * In any case, control here when new websocket session created + */ + + if (!ws_pss) + break; + + assert(vhd); + + ws_pss->output_buf_ = new OutputBuffer(vhd->next_session_id_++); + + lwsl_user("establish pss->output_buf [%p] in ws_pss [%p]", + ws_pss->output_buf_, + ws_pss); + } + break; + case LWS_CALLBACK_WS_SERVER_DROP_PROTOCOL: + { + if (!ws_pss) + break; + + lwsl_user("destroy pss->output_msg [%p] in ws_pss [%p]", + ws_pss->output_buf_, ws_pss); + + /* don't do this here. need to access ws_pss->output_buf + * from LWS_CALLBACK_CLOSED + */ +#ifdef BROKEN + if (ws_pss->output_buf_) { + delete ws_pss->output_buf_; + ws_pss->output_buf_ = nullptr; + } +#endif + } + break; + case LWS_CALLBACK_ESTABLISHED: + { + /* control comes here when a websocket session is opened + * (after protocol negotiated) + */ + + OutputBuffer * output_buf = ws_pss->output_buf_; + + output_buf->establish_wsi(wsi); + + websrv->notify_ws_session_open(output_buf, vhd, ws_token); + } + break; + + case LWS_CALLBACK_CLOSED: + { + /* control comes here when a websocket session is closed */ + assert(websrv); + assert(ws_pss); + assert(ws_pss->output_buf_); + assert(vhd); + + websrv->notify_ws_session_close(ws_pss->output_buf_, vhd, ws_token); + + if (ws_pss->output_buf_) { + delete ws_pss->output_buf_; + ws_pss->output_buf_ = nullptr; + } + + lwsl_user("LWS_CALLBACK_CLOSED: done"); + } + break; + + case LWS_CALLBACK_EVENT_WAIT_CANCELLED: + { + if (websrv) + websrv->lws_write_pending_traffic(ws_token); + } + break; + + case LWS_CALLBACK_SERVER_WRITEABLE: + { + /* control here when: + * 1. application wants to send data + * (app uses lws_cancel_service() to trigger + * LWS_CALLBACK_EVENT_WAIT_CANCELLED) + * 2. websocket session that was previously blocked + * is now ready to receive data + * (see LWS_CALLBACK_SERVER_WRITEABLE above) + */ + +#ifdef NOT_USING + if (!vhd->amsg.payload) + break; +#endif + + if (!vhd) { + lwsl_user("client entry: vhd not yet established, return"); + break; + } + + if (!ws_pss) { + lwsl_user("client entry: ws_pss not yet established, return"); + break; + } + + if (!(ws_pss->output_buf_)) { + lwsl_user("client entry: output_msg buffer not established, return"); + /* output message container hasn't been established, + * probably bc nothing to send + */ + break; + } + + lwsl_user("notify_minimal: unblock writing, output_msg=[%p]", + ws_pss->output_buf_); + + if (ws_pss->output_buf_->is_writeable(ws_token)) { + //assert(false); + } else { + /* a previous call to lws_write() reported a partial write; + * that write has now completed + */ + ws_pss->output_buf_->lws_write_completion(ws_token); + break; + } + + if (ws_pss->output_buf_->is_idle()) { + lwsl_user("client entry: output_msg buffer up-to-date, return"); + /* already up-to-date, nothing new to send */ + break; + } + +#ifdef NOT_USING + if (ws_pss->last == vhd->current) { + /* already up-to-date */ + break; + } + + if (!pss->output_msg_.payload) { + pss->output_msg_.payload = ::malloc(LWS_PRE + output_z); + pss->output_msg_.len = output_z; + } + + ::memcpy((char *)pss->output_msg_.payload + LWS_PRE, output_cstr, output_z); + + lwsl_user("allocate pss->output_msg [%p] in pss [%p]", + pss->output_msg_.payload, pss); + m = lws_write(wsi, + ((unsigned char *)pss->output_msg_.payload) + LWS_PRE, + pss->output_msg_.len, + LWS_WRITE_TEXT); +#endif + + //XO_SCOPE(lscope); + + /* pss->output_msg_ was populated from WebserverImpl.send_text(), q.v. */ + + m = ws_pss->output_buf_->lws_write_aux(ws_token); + +#ifdef NOT_USING + m = lws_write(wsi, ((unsigned char *)vhd->amsg.payload) + + LWS_PRE, vhd->amsg.len, LWS_WRITE_TEXT); + if (m < (int)vhd->amsg.len) { .. } +#endif + if (m == -1) { + lwsl_err("WebserverImplWsThread::notify_minimal: return -1 from callback"); + return -1; + } + + //pss->last = vhd->current; + } + break; + + case LWS_CALLBACK_RECEIVE: + { + char const * incoming_cmd + = reinterpret_cast(input); + + std::string_view incoming_svw(incoming_cmd, input_z); + + //lwsl_user("receive: [%s], z [%d]", incoming_cmd, (int)input_z); + + assert(ws_pss); + assert(websrv); + + uint32_t session_id = ws_pss->output_buf_->session_id(); + + websrv->perform_ws_cmd(session_id, + incoming_svw); + +#ifdef OBSOLETE + if (vhd->amsg.payload) + minimal_destroy_message(&(vhd->amsg)); + + vhd->amsg.len = input_z; //output_z; + /* notice we over-allocate by LWS_PRE */ + vhd->amsg.payload = ::malloc(LWS_PRE + input_z); + if (!vhd->amsg.payload) { + lwsl_user("OOM: dropping\n"); + break; + } + + ::memcpy((char *)vhd->amsg.payload + LWS_PRE, input, input_z); + //vhd->current++; +#endif + +#ifdef OBSOLETE + /* + * let everybody know we want to write something on them + * as soon as they are ready + */ + lws_start_foreach_llp(struct per_session_data__minimal **, + ppss, vhd->pss_list) { + lws_callback_on_writable((*ppss)->wsi); + } lws_end_foreach_llp(ppss, pss_list); +#endif + } + break; + + default: + break; + } + + log.end_scope(); + + return 0; + } /*notify_minimal*/ + + void + WebserverImplWsThread::run() + { + scope log(XO_DEBUG(false /*debug_flag*/)); + + lwsl_user("LWS minimal http server dynamic" + " | visit http://localhost:%d\n", this->ws_config_.port()); +#if defined(LWS_WITH_PLUGINS) + lwsl_user("LWS_WITH_PLUGINS present"); + lwsl_user("LWS_WITH_TLS present"); +#endif + + /* exit when .state is stop_requested, setting state to .stopped */ + + this->lws_cx_ = lws_create_context(&(this->cx_config_)); + + if (!(this->lws_cx_)) { + lwsl_err("lws init failed\n"); + return; + } + + std::int32_t n_event = 0; + while ((n_event >= 0) && !(this->interrupt_flag_)) { + n_event = ::lws_service(this->lws_cx_, + 0 /*ignored (used to be timeout)*/); + } + + log && log("webserver runner returned - service loop exited", + xtag("n_event", n_event), + xtag("interrupted", this->interrupt_flag_.load())); + + lws_context_destroy(this->lws_cx_); + this->lws_cx_ = nullptr; + + { + std::unique_lock lock(this->mutex_); + + this->state_ = Runstate::stopped; + this->cond_.notify_all(); + } + + log && log("exit"); + } /*run*/ + + rp + WebserverImplWsThread::make(WebserverConfig const & ws_config, + rp const & pjson) + { + return new WebserverImplWsThread(ws_config, pjson); + } /*make*/ + + // ----- Webserver ----- + + rp + Webserver::make(WebserverConfig const & ws_config, + rp const & pjson) { + return WebserverImplWsThread::make(ws_config, pjson); + } /*make*/ + + void + Webserver::display(std::ostream & os) const { + os << "state()) + << ">"; + } /*display*/ + } /*namespace web*/ +} /*namespace xo*/ + +/* end Webserver.cpp */ diff --git a/src/websock/WebsockUtil.cpp b/src/websock/WebsockUtil.cpp new file mode 100644 index 00000000..7c7057fb --- /dev/null +++ b/src/websock/WebsockUtil.cpp @@ -0,0 +1,141 @@ +/* @file WebsockUtil.cpp */ + +#include "WebsockUtil.hpp" + +#define STRINGIFY(x) #x + +namespace xo { + namespace web { + char const * + WebsockUtil::ws_callback_reason_descr(lws_callback_reasons x) { + +#define CASE(x) case x: return STRINGIFY(x) + + switch (x) { + CASE(LWS_CALLBACK_PROTOCOL_INIT); + CASE(LWS_CALLBACK_PROTOCOL_DESTROY); + CASE(LWS_CALLBACK_WSI_CREATE); + CASE(LWS_CALLBACK_WSI_DESTROY); + CASE(LWS_CALLBACK_WSI_TX_CREDIT_GET); + CASE(LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS); + CASE(LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS); + CASE(LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION); + CASE(LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY); + CASE(LWS_CALLBACK_SSL_INFO); + CASE(LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION); + CASE(LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED); + CASE(LWS_CALLBACK_HTTP); + CASE(LWS_CALLBACK_HTTP_BODY); + CASE(LWS_CALLBACK_HTTP_BODY_COMPLETION); + CASE(LWS_CALLBACK_HTTP_FILE_COMPLETION); + CASE(LWS_CALLBACK_HTTP_WRITEABLE); + CASE(LWS_CALLBACK_CLOSED_HTTP); + CASE(LWS_CALLBACK_FILTER_HTTP_CONNECTION); + CASE(LWS_CALLBACK_ADD_HEADERS); + CASE(LWS_CALLBACK_VERIFY_BASIC_AUTHORIZATION); + CASE(LWS_CALLBACK_CHECK_ACCESS_RIGHTS); + CASE(LWS_CALLBACK_PROCESS_HTML); + CASE(LWS_CALLBACK_HTTP_BIND_PROTOCOL); + CASE(LWS_CALLBACK_HTTP_DROP_PROTOCOL); + CASE(LWS_CALLBACK_HTTP_CONFIRM_UPGRADE); + CASE(LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP); + CASE(LWS_CALLBACK_CLOSED_CLIENT_HTTP); + CASE(LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ); + CASE(LWS_CALLBACK_RECEIVE_CLIENT_HTTP); + CASE(LWS_CALLBACK_COMPLETED_CLIENT_HTTP); + CASE(LWS_CALLBACK_CLIENT_HTTP_WRITEABLE); + CASE(LWS_CALLBACK_CLIENT_HTTP_REDIRECT); + CASE(LWS_CALLBACK_CLIENT_HTTP_BIND_PROTOCOL); + CASE(LWS_CALLBACK_CLIENT_HTTP_DROP_PROTOCOL); + CASE(LWS_CALLBACK_ESTABLISHED); + CASE(LWS_CALLBACK_CLOSED); + CASE(LWS_CALLBACK_SERVER_WRITEABLE); + CASE(LWS_CALLBACK_RECEIVE); + CASE(LWS_CALLBACK_RECEIVE_PONG); + CASE(LWS_CALLBACK_WS_PEER_INITIATED_CLOSE); + CASE(LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION); + CASE(LWS_CALLBACK_CONFIRM_EXTENSION_OKAY); + CASE(LWS_CALLBACK_WS_SERVER_BIND_PROTOCOL); + CASE(LWS_CALLBACK_WS_SERVER_DROP_PROTOCOL); + CASE(LWS_CALLBACK_CLIENT_CONNECTION_ERROR); + CASE(LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH); + CASE(LWS_CALLBACK_CLIENT_ESTABLISHED); + CASE(LWS_CALLBACK_CLIENT_CLOSED); + CASE(LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER); + CASE(LWS_CALLBACK_CLIENT_RECEIVE); + CASE(LWS_CALLBACK_CLIENT_RECEIVE_PONG); + CASE(LWS_CALLBACK_CLIENT_WRITEABLE); + CASE(LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED); + CASE(LWS_CALLBACK_WS_EXT_DEFAULTS); + CASE(LWS_CALLBACK_FILTER_NETWORK_CONNECTION); + CASE(LWS_CALLBACK_WS_CLIENT_BIND_PROTOCOL); + CASE(LWS_CALLBACK_WS_CLIENT_DROP_PROTOCOL); + CASE(LWS_CALLBACK_GET_THREAD_ID); + CASE(LWS_CALLBACK_ADD_POLL_FD); + CASE(LWS_CALLBACK_DEL_POLL_FD); + CASE(LWS_CALLBACK_CHANGE_MODE_POLL_FD); + CASE(LWS_CALLBACK_LOCK_POLL); + CASE(LWS_CALLBACK_UNLOCK_POLL); + CASE(LWS_CALLBACK_CGI); + CASE(LWS_CALLBACK_CGI_TERMINATED); + CASE(LWS_CALLBACK_CGI_STDIN_DATA); + CASE(LWS_CALLBACK_CGI_STDIN_COMPLETED); + CASE(LWS_CALLBACK_CGI_PROCESS_ATTACH); + CASE(LWS_CALLBACK_SESSION_INFO); + CASE(LWS_CALLBACK_GS_EVENT); + CASE(LWS_CALLBACK_HTTP_PMO); + CASE(LWS_CALLBACK_RAW_PROXY_CLI_RX); + CASE(LWS_CALLBACK_RAW_PROXY_SRV_RX); + CASE(LWS_CALLBACK_RAW_PROXY_CLI_CLOSE); + CASE(LWS_CALLBACK_RAW_PROXY_SRV_CLOSE); + CASE(LWS_CALLBACK_RAW_PROXY_CLI_WRITEABLE); + CASE(LWS_CALLBACK_RAW_PROXY_SRV_WRITEABLE); + CASE(LWS_CALLBACK_RAW_PROXY_CLI_ADOPT); + CASE(LWS_CALLBACK_RAW_PROXY_SRV_ADOPT); + CASE(LWS_CALLBACK_RAW_PROXY_CLI_BIND_PROTOCOL); + CASE(LWS_CALLBACK_RAW_PROXY_SRV_BIND_PROTOCOL); + CASE(LWS_CALLBACK_RAW_PROXY_CLI_DROP_PROTOCOL); + CASE(LWS_CALLBACK_RAW_PROXY_SRV_DROP_PROTOCOL); + CASE(LWS_CALLBACK_RAW_RX); + CASE(LWS_CALLBACK_RAW_CLOSE); + CASE(LWS_CALLBACK_RAW_WRITEABLE); + CASE(LWS_CALLBACK_RAW_ADOPT); + CASE(LWS_CALLBACK_RAW_CONNECTED); + CASE(LWS_CALLBACK_RAW_SKT_BIND_PROTOCOL); + CASE(LWS_CALLBACK_RAW_SKT_DROP_PROTOCOL); + CASE(LWS_CALLBACK_RAW_ADOPT_FILE); + CASE(LWS_CALLBACK_RAW_RX_FILE); + CASE(LWS_CALLBACK_RAW_WRITEABLE_FILE); + CASE(LWS_CALLBACK_RAW_CLOSE_FILE); + CASE(LWS_CALLBACK_RAW_FILE_BIND_PROTOCOL); + CASE(LWS_CALLBACK_RAW_FILE_DROP_PROTOCOL); + CASE(LWS_CALLBACK_TIMER); + CASE(LWS_CALLBACK_EVENT_WAIT_CANCELLED); + CASE(LWS_CALLBACK_CHILD_CLOSING); + CASE(LWS_CALLBACK_CONNECTING); + CASE(LWS_CALLBACK_VHOST_CERT_AGING); + CASE(LWS_CALLBACK_VHOST_CERT_UPDATE); + CASE(LWS_CALLBACK_MQTT_NEW_CLIENT_INSTANTIATED); + CASE(LWS_CALLBACK_MQTT_IDLE); + CASE(LWS_CALLBACK_MQTT_CLIENT_ESTABLISHED); + CASE(LWS_CALLBACK_MQTT_SUBSCRIBED); + CASE(LWS_CALLBACK_MQTT_CLIENT_WRITEABLE); + CASE(LWS_CALLBACK_MQTT_CLIENT_RX); + CASE(LWS_CALLBACK_MQTT_UNSUBSCRIBED); + CASE(LWS_CALLBACK_MQTT_DROP_PROTOCOL); + CASE(LWS_CALLBACK_MQTT_CLIENT_CLOSED); + CASE(LWS_CALLBACK_MQTT_ACK); + CASE(LWS_CALLBACK_MQTT_RESEND); + CASE(LWS_CALLBACK_MQTT_UNSUBSCRIBE_TIMEOUT); + CASE(LWS_CALLBACK_MQTT_SHADOW_TIMEOUT); + CASE(LWS_CALLBACK_USER); + } + +#undef CASE + + return "???"; + } /*ws_callback_reason_descr*/ + } /*namespace web*/ +} /*namespace xo*/ + +/* end WebsockUtil.cpp */ diff --git a/src/websock/WebsocketSink.cpp b/src/websock/WebsocketSink.cpp new file mode 100644 index 00000000..1c406432 --- /dev/null +++ b/src/websock/WebsocketSink.cpp @@ -0,0 +1,148 @@ +/* file WebsocketSink.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "WebsocketSink.hpp" +#include "Webserver.hpp" +#include "xo/printjson/PrintJson.hpp" +#include "xo/reflect/Reflect.hpp" +#include "xo/reflect/TaggedPtr.hpp" +#include "xo/indentlog/scope.hpp" + +namespace xo { + using xo::reactor::AbstractSource; + using xo::json::PrintJson; + using xo::reflect::Reflect; + using xo::reflect::TaggedPtr; + using xo::reflect::TypeDescr; + using xo::ref::rp; + using xo::ref::brw; + using xo::print::quoted; + using xo::print::qcstr; + using xo::scope; + using xo::xtag; + + namespace web { + /* a sink that publishes to a websocket. + * The websocket api creates a WebsocketSink instance + * on behalf of an incoming subscription request. + * application code will hold onto the sink somewhere + * and publish events to it, to send them via websocket. + */ + class WebsocketSinkImpl : public WebsocketSink { + public: + using PrintJson = xo::json::PrintJson; + using AbstractSource = reactor::AbstractSource; + + public: + WebsocketSinkImpl(ref::rp const & websrv, + ref::rp const & pjson, + uint32_t session_id, + std::string stream_name) + : websrv_{std::move(websrv)}, + pjson_{std::move(pjson)}, + session_id_{session_id}, + stream_name_{std::move(stream_name)} + {} + + virtual std::string const & name() const override { return name_; } + virtual void set_name(std::string const & x) override { this->name_ = x; } + /* 0 consumers for websocket sink, since it's not a source */ + virtual void visit_direct_consumers(std::function)> const &) override {} + virtual void display(std::ostream & os) const override; + + virtual bool allow_polymorphic_source() const override { return true; } + virtual TypeDescr sink_ev_type() const override; + virtual bool allow_volatile_source() const override { return true; } + virtual uint32_t n_in_ev() const override { return n_in_ev_; } + virtual void attach_source(ref::rp const & src) override; + virtual void notify_ev_tp(TaggedPtr const & ev_tp) override; + + private: + /* (ideally unique) user-controlled name for this sink + * in practice not likely to be accessible, + * so probably want to generate a unique-y default + */ + std::string name_; + /* webserver implementation */ + ref::rp websrv_; + /* print arbitrary reflected stuff as json */ + ref::rp pjson_; + /* websocket session id# - events arriving at this sink + * will be sent only to the session identified by .session_id + */ + uint32_t session_id_; + /* name for stream. + * this will be the vale of the "stream" tag in + * initiating subscription message + * {"cmd": "subscribe", "stream", "/this/stream/name"} + * e.g. in python: + * web.register_stream_endpoint(kf.stream_endpoint_descr("/this/stream/name")) + */ + std::string stream_name_; + /* count #of events received */ + uint32_t n_in_ev_ = 0; + }; /*WebsocketSinkImpl*/ + + TypeDescr + WebsocketSinkImpl::sink_ev_type() const + { + return Reflect::require(); + } /*sink_ev_type*/ + + void + WebsocketSinkImpl::attach_source(rp const & src) { + src->attach_sink(this); + } /*attach_source*/ + + void + WebsocketSinkImpl::notify_ev_tp(TaggedPtr const & ev_tp) + { + scope log(XO_DEBUG(true /*debug_flag*/)); + + std::stringstream ss; + + /* format message envelope */ + ss << "{" << qcstr("stream") << ": " << quoted(this->stream_name_) + << ", " << qcstr("event") << ": "; + + /* format event as json */ + this->pjson_->print_tp(ev_tp, &ss); + + ss << "}"; + + log && log("sending", xtag("msg", ss.str())); + + ++(this->n_in_ev_); + + /* send event via associated websocket */ + this->websrv_->send_text(this->session_id_, ss.str()); + + } /*notify_ev_tp*/ + + void + WebsocketSinkImpl::display(std::ostream & os) const + { + os << ""; + } /*display*/ + + // ----- WebsocketSink ----- + + rp + WebsocketSink::make(rp const & websrv, + rp const & pjson, + uint32_t session_id, + std::string const & stream_name) + { + return new WebsocketSinkImpl(websrv, pjson, session_id, stream_name); + } /*make*/ + } /*namespace web*/ +} /*namespace xo*/ + +/* end WebsocketSink.cpp */ diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..c8fcdacc --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,47 @@ +# build unittest websock/utest + +set(SELF_EXE utest.websock) +set(SELF_SRCS websock_utest_main.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +## note: can't add this yet, because test not automated. +## requires manual interaction from browser +## +#add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) +#target_code_coverage(${SELF_EXE} AUTO ALL) + +# copy static {.html, .js, .svg} files to build directory +file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/mount-origin/" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/mount-origin") + +# ---------------------------------------------------------------- +# internal dependency (on this codebase) + +xo_self_dependency(${SELF_EXE} websock) + +# ---------------------------------------------------------------- +# external dependencies + +target_link_libraries(${SELF_EXE} PUBLIC websock) +# Need to port option, volfit before we can build this test here +target_link_libraries(${SELF_EXE} PUBLIC option) +target_link_libraries(${SELF_EXE} PUBLIC volfit) +#target_link_libraries(utest.option PUBLIC logutil) + + +# should be getting this via xo_include_options2() + +## ---------------------------------------------------------------- +## make standard directories for std:: includes explicit +## so that +## (1) they appear in compile_commands.json. +## (2) clangd (run from emacs lsp-mode) can find them +## +#if(CMAKE_EXPORT_COMPILE_COMMANDS) +# set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES +# ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +#endif() + +# end CMakeLists.txt diff --git a/utest/README b/utest/README new file mode 100644 index 00000000..8ed18777 --- /dev/null +++ b/utest/README @@ -0,0 +1,13 @@ +To run this unit test: + + $ cd path/to/kalman/build/src/websock/utest + $ ./utest.websock # listens for http requests on port 7682 + +point browser to + + localhost:7682/ex_websock.html + +static files served from + + path/to/kalman/build/src/websock/utest/mount-origin + \ No newline at end of file diff --git a/utest/mount-origin/bluecircle.svg b/utest/mount-origin/bluecircle.svg new file mode 100644 index 00000000..8c7b3296 --- /dev/null +++ b/utest/mount-origin/bluecircle.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/utest/mount-origin/d3ex/d3ex.ch5.ex1.html b/utest/mount-origin/d3ex/d3ex.ch5.ex1.html new file mode 100644 index 00000000..7b3c62d4 --- /dev/null +++ b/utest/mount-origin/d3ex/d3ex.ch5.ex1.html @@ -0,0 +1,15 @@ + + + + + + + simple d3 example + + + diff --git a/utest/mount-origin/ex_websock.html b/utest/mount-origin/ex_websock.html new file mode 100644 index 00000000..53963e20 --- /dev/null +++ b/utest/mount-origin/ex_websock.html @@ -0,0 +1,13 @@ + + + + pywebsock example page + + + +

pywebsock example page

+ +
+
+ + diff --git a/utest/mount-origin/ex_websock.js b/utest/mount-origin/ex_websock.js new file mode 100644 index 00000000..c26406ba --- /dev/null +++ b/utest/mount-origin/ex_websock.js @@ -0,0 +1,842 @@ +/* webpage to display kalman filter output + * coordinates with ex_websock.py in this directory + */ + +import * as d3 from "https://cdn.skypack.dev/d3@7"; +/* json5 accepts ieee floatingpoint special values; + * regular json excludes them (!?#) + */ +import JSON5 from "https://unpkg.com/json5@2/dist/index.min.mjs"; + +/* NOTE: put "export" in front of a variable/function + * that we want to make accessible outside this module + */ + +/* for use for browser's javascript console */ +globalThis.d3 = d3; +//globalThis.jparse = JSON5.parse; + +/* u: document.URL */ +function choose_ws_url(suffix_url) +{ + var pcol; + var u = document.URL; + + /* + * We open the websocket encrypted if this page came on an + * https:// url itself, otherwise unencrypted + */ + + if (u.substring(0, 5) === "https") { + pcol = "wss://"; + u = u.substr(8); + } else { + pcol = "ws://"; + if (u.substring(0, 4) === "http") + u = u.substr(7); + } + + u = u.split("/"); + + /* + "/xxx" bit is for IE10 workaround */ + + return pcol + u[0] + "/" + suffix_url; +} /*choose_ws_url*/ + +class Datatype { + #typename = null; + #nominal = null; + /* .from_json(x) convert a value received in json format + * to native representation. + */ + #from_json = null; + /* .make_scale(range) builds d3 scale object */ + #make_scale = null; + + constructor(typename, nominal, from_json, make_scale) { + this.#typename = typename; + this.#nominal = nominal; + this.#from_json = from_json; + this.#make_scale = make_scale; + } + + typename() { return this.#typename; } + nominal() { return this.#nominal; } + from_json(x) { return this.#from_json(x); } + make_scale(domain) { return this.#make_scale(domain); } +}; /*Datatype*/ + +class DatatypeFactory { + static dtype_map = DatatypeFactory.make_dtype_map(); + + static make_float_dtype() { + return new Datatype("float" /*typename*/, + 0.0 /*nominal*/, + (x) => { return x; } /*from_json*/, + (dom) => { return d3.scaleLinear().domain(dom); } /*make_scale*/ + ); } + + static make_datetime_dtype() { + return new Datatype("datetime" /*typename*/, + new Date() /*nominal*/, + (x) => { return new Date(x); } /*from_json*/, + (dom) => { return d3.scaleTime().domain(dom); } /*make_scale*/ + ); } + + static make_dtype_map() { + let retval = new Map(); + + retval.set("float", DatatypeFactory.make_float_dtype()); + retval.set("datetime", DatatypeFactory.make_datetime_dtype()); + + return retval; + } + + static lookup(typename) { + if (DatatypeFactory.dtype_map.has(typename)) { + return DatatypeFactory.dtype_map.get(typename); + } else { + throw new Error("DatatypeFactory: typename [" + + typename + + "] found where float|datetime expected"); + } + } +}; /*DatatypeFactory*/ + +/* class to extract event values for charting. + * 'traits' because applies to separately-represented event objects + */ +class DataTraits { + /* .x_slotlookup(ev) => x-value */ + #x_slotlookup = null; + /* .y_slotlookup(ev) => y-value */ + #y_slotlookup = null; + #x_datatype = null; //DatatypeFactory.lookup("datetime"); + #y_datatype = null; //DatatypeFactory.lookup("float"); + + /* x_nt, y_nt: each should be a pair [slotlookup, typename] + * - slotname is a function :: event -> jsonvalue, + * that extracts an attribute from incoming event in json format + * - typename is float|datetime + */ + constructor(x_nt, y_nt) { + this.#x_slotlookup = x_nt[0]; + this.#x_datatype = DatatypeFactory.lookup(x_nt[1]); + this.#y_slotlookup = y_nt[0]; + this.#y_datatype = DatatypeFactory.lookup(y_nt[1]); + } + + x_datatype() { return this.#x_datatype; } + y_datatype() { return this.#y_datatype; } + + x_nominal() { return this.#x_datatype.nominal(); } + y_nominal() { return this.#y_datatype.nominal(); } + + mapkey(data_ev) { return this.#x_slotlookup(data_ev); } + x_value(data_ev) { return this.#x_datatype.from_json(this.#x_slotlookup(data_ev)); } + y_value(data_ev) { return this.#y_datatype.from_json(this.#y_slotlookup(data_ev)); } + + make_x_scale(domain) { return this.#x_datatype.make_scale(domain); } + make_y_scale(domain) { return this.#y_datatype.make_scale(domain); } +}; /*DataTraits*/ + +function range_outer(lh, rh) { + return [Math.min(lh[0], rh[0]), + Math.max(lh[1], rh[1])]; +} /*range_outer*/ + +/* a dataset driving a chart. + * + * PLAN: multiple lines in the same chart + * - makeitso dataset can contain multiple data series + * - give each series within a dataset its own index# + * - each series computes its own min/max x/y values + * - take union across series to get chart x/y range + * - new class Dataset + */ +class Dataseries { + /* normalizing transformation for event objects. + * use to produce events {.x_value, .y_value} + key suitable for Map + */ + #data_traits = null; //new DataTraits(); + /* .dataset_map :: string -> {key value pair} + * must use string as keys, since Map uses object identity if key is Object + */ + #dataset_map = new Map(); + /* vector of key-value pairs, in increasing x-axis order */ + #dataset_v = []; + /* min,max value of dataset[i].x_value */ + #dset_min_x = null; + #dset_max_x = null; + /* min,max value of dataset[i].y_value */ + #dset_min_y = null; + #dset_max_y = null; + + #max_key = 0; + + constructor(data_traits) { + this.#data_traits = data_traits; + this.recalc_minmax(); + } + + data_traits() { return this.#data_traits; } + dataset_v() { return this.#dataset_v; } + x_range() { return [this.#dset_min_x, this.#dset_max_x]; } + y_range() { return [this.#dset_min_y, this.#dset_max_y]; } + + /* data_ev must have attributes consistent with what .#data_traits expects */ + update_dataset(data_ev) { + //console.log("Dataseries.update_dataset: data_ev=", data_ev); + + let x = this.#data_traits.x_value(data_ev); + let y = this.#data_traits.y_value(data_ev); + /* using this key to recognize + suppress duplicate points + * (e.g. if browser winds up sending multiple snapshot requests + * for the same dataset) + */ + let mapkey = this.#data_traits.mapkey(data_ev); + + //console.log("Dataseries.update_dataset: x=", x, ", y=", y, ", mapkey=", mapkey); + + /* in map must use time strings (not Dates) as keys */ + if (this.#dataset_map.has(mapkey)) { + /*skip -- assuming that source is immutable */; + } else { + /* kv.key is ordinal number identifying a datum. + * not related to mapkey, except in so far as both work as datum ids + */ + let kv = {key: this.#max_key, + x_value: x, + y_value: y}; + + /* (reminder: js map keys need to be strings) */ + this.#dataset_map.set(mapkey, kv); + this.#dataset_v.push(kv); + this.#max_key = this.#max_key+1; + } + } /*update_dataset*/ + + recalc_minmax() { + if (this.#dataset_v.length == 0) { + /* min,max value of dataset[i].x_value */ + this.#dset_min_x = this.#data_traits.x_nominal(); + this.#dset_max_x = this.#data_traits.x_nominal(); + /* min,max value of dataset[i].y_value */ + this.#dset_min_y = this.#data_traits.y_nominal(); + this.#dset_max_y = this.#data_traits.y_nominal(); + } else { + /* min,max value of dataset[i].x_value */ + this.#dset_min_x = d3.min(this.#dataset_v, (d) => { return d.x_value; }); + this.#dset_max_x = d3.max(this.#dataset_v, (d) => { return d.x_value; }); + /* min,max value of dataset[i].y_value */ + this.#dset_min_y = d3.min(this.#dataset_v, (d) => { return d.y_value; }); + this.#dset_max_y = d3.max(this.#dataset_v, (d) => { return d.y_value; }); + } + } /*recalc_minmax*/ + + /* note: caller should invoke .range() before using for drawing */ + make_x_scale(xrange) { + return this.#data_traits.make_x_scale(xrange /*domain*/); + } /*make_x_scale*/ + + /* note: caller should invoke .range() before using for drawing */ + make_y_scale(yrange) { + return this.#data_traits.make_y_scale(yrange /*domain*/); + } /*make_y_scale*/ +}; /*Dataseries*/ + +/* bundle multiple dataseries for charting + * for now: can have multiple series, but they need to be driven + * from the same native row storage + */ +class Dataset { + #dataseries_v = []; + /* min/max x-values across all members of .dataseries_v */ + #outer_x_range = null; + /* min/max y-values across all members of .dataseries_v */ + #outer_y_range = null; + + constructor(data_traits_v) { + for (let i=0, n=data_traits_v.length; i tag above in DOM sketch)*/ + #chart_svg = null; + + constructor(w, h, pad) { + this.#chart_w = w; + this.#chart_h = h; + this.#chart_pad = pad; + } + + x_range() { return [this.#chart_pad, + this.#chart_w - this.#chart_pad]; } + /* note: inverting bc svg y-values increase towards bottom of screen; + * we want y-values to increase towards top of screen + */ + y_range() { return [this.#chart_h - this.#chart_pad, + this.#chart_pad]; } + + require_gui(parent_d3sel, dataset) { + this.#require_x_scale(dataset); + this.#require_y_scale(dataset); + this.#require_linegen(); /*will use .chart_x_scale, .chart_y_scale */ + this.#require_x_axis(); /*will use .chart_x_scale*/ + this.#require_y_axis(); /*will use .chart_y_scale*/ + this.#require_svg(parent_d3sel, dataset); + } + + /* dom element id to use for the i'th dataseries in this chart */ + series_html_id(i_dataseries) { + return "pts-" + i_dataseries; + } + + /* update chart for new dataset contents + * + * Require: + * - .require_gui(_, dataset) has been called + * - #of dataseries has not changed since last call to .require_svg() + */ + update_chart(dataset) { + /* update d3 scales + * (shared across all series bundled into this dataset + */ + this.#rescale_chart(dataset); + + for (let i=0, n=dataset.n_dataseries(); i { return this.#chart_x_scale(d.x_value); }) + .y((d) => { return this.#chart_y_scale(d.y_value); })); + } + } /*require_linegen*/ + + #require_x_axis() { + if (!this.#chart_x_axis) { + this.#chart_x_axis = (d3 + .axisBottom() + .scale(this.#chart_x_scale) + .ticks(10)); + } + } + + #require_y_axis() { + if (!this.#chart_y_axis) { + this.#chart_y_axis = (d3 + .axisLeft() + .scale(this.#chart_y_scale) + .ticks(10)); + } + } + + #require_svg(parent_d3sel, dataset) { + if (!this.#chart_svg) { + this.#chart_svg = (parent_d3sel // .select("#uls") + .append("svg") + .attr("width", this.#chart_w) + .attr("height", this.#chart_h)); + + /* svg group comprising x-axis */ + this.#chart_svg.append("g") + .attr("class", "xaxis") + .attr("id", "x_axis") + .attr("transform", this.#x_axis_translate_str()) + .call(this.#chart_x_axis); + + /* svg group comprising y-axis */ + this.#chart_svg.append("g") + .attr("class", "yaxis") + .attr("id", "y_axis") + .attr("transform", this.#y_axis_translate_str()) + .call(this.#chart_y_axis); + + for (let i=0, n=dataset.n_dataseries(); i (will attach svg element here) + * +- + * +- (d3 will draw x-axis inside, .chart_x_axis() draws) + * +- (d3 will draw y-axis inside, .chart_y_axis() draws) + * +- + * +- (.chart_line_gen draws) + */ +class TimeseriesCtl extends Controller { + #dataset_uri = ''; + #dataset = null; + + #chart = new LineChart(500 /*w*/, + 250 /*h*/, + 50 /*pad*/); + + constructor(dataset_uri, data_traits_v) { + super(); + this.#dataset_uri = dataset_uri; + this.#dataset = new Dataset(data_traits_v); + } + + static rescale_dataset(dataset) { + dataset.recalc_minmax(); + } /*rescale_dataset*/ + + /* request dataseries snapshot from webserver; + * update+draw graph when snapshot arrives + * + * NOTE: + * 1. typical web docs (e.g. MDN) will advise using response.json(): + * fetch(uri) + * .then((response) => response.json()) + * .then((data) => dostuffwith(data)) + * + * however, this has a flaw: standard json is missing special floating-point values (!!); + * in particular it has no representation for nan/+inf/-inf + * 2. we want to use the extended json standard 'json5'; + * however need care since JSON5.parse() fails spuriously (at least JSON5/chrome asof 24sep2022) + * if given a promise + */ + request() { + fetch(this.#dataset_uri) + .then((response) => response.text()) + .then((text) => this.on_snapshot_text(text)); + +// .then((text) => JSON5.parse() +// .then((data) => this.on_snapshot(data)); + } /*request*/ + + /* update from snapshot json text */ + on_snapshot_text(text) { + const data = JSON5.parse(text); + + this.on_snapshot(data); + } /*on_snapshot_text*/ + + /* update from snapshot + * + * .on_snapshot() => .#dataset => .on_dataset() + */ + on_snapshot(data) { + //console.log("on_snapshot: data=", data); + + data.forEach((x, i) => { + // REFACTORME + + if (x._name_ == "UpxEvent") { + this.on_update(x); + } else if (x._name_ == "KalmanFilterStateExt") { + this.on_update(x); + } else { + console.log("unexpected json record x=", x); + } + }); + + this.on_dataset(this.#dataset); + } /*on_snapshot*/ + + /* update from websocket + * + * .on_update() => .#dataset => .on_dataset() + */ + on_update(data_ev) { + this.#dataset.update_dataset(data_ev); + + this.on_dataset(this.#dataset); + } /*on_update*/ + + /* call after modifying .#dataset + * + * .on_dataset() =|=> .rescale_dataset() =|========> .chart_x_axis ===\ + * | |========> .chart_x_axis =\ | + * | | | + * |=> .chart_svg.#x_axis <==========================/ | + * |=> .chart_svg.#y_axis <============================/ + */ + on_dataset(dataset) { + //console.log("on_dataset: dataset=", dataset); + + // update x-scale, y-scale + TimeseriesCtl.rescale_dataset(dataset); + + this.#chart.update_chart(dataset); + } /*on_dataset*/ + + /* e.g. + * ctl.require_gui(d3.select("#uls")) + * to build chart gui under DOM element with id="uls" + */ + require_gui(parent_d3sel) { + this.#chart.require_gui(parent_d3sel, this.#dataset); + } /*require_gui*/ + +}; /*TimeseriesCtl*/ + +/* controller for timeseries graph, from uri [/dyn/uls/snap] + [/ws/uls] */ +var uls_ctl = false; +var uls_ctl_enabled = true; + +if (uls_ctl_enabled) { + uls_ctl = new TimeseriesCtl('/dyn/uls/snap', + [new DataTraits([(ev) => ev.tm, "datetime"], + [(ev) => ev.upx, "float"])]); + + uls_ctl.require_gui(d3.select("#uls")); + uls_ctl.request(); +} + +/* controller for timeseries graph, from uri [/dyn/kfs/snap] + [/ws/kfs] */ +var kfs_ctl = false; +var kfs_ctl_enabled = true; + +if (kfs_ctl_enabled) { + kfs_ctl = new TimeseriesCtl('/dyn/kfs/snap', + [new DataTraits([(ev) => ev.tk, + "datetime"], + [(ev) => { return ev.x[0]; }, + "float"]), + /* 2.sigma below estimate */ + new DataTraits([(ev) => ev.tk, + "datetime"], + [(ev) => { return Math.max(0.0, ev.x[0] - 2.0 * Math.sqrt(ev.P[0][0])); }, + "float"]), + /* 2.sigma above estimate */ + new DataTraits([(ev) => ev.tk, + "datetime"], + [(ev) => { return Math.min(1.0, ev.x[0] + 2.0 * Math.sqrt(ev.P[0][0])); }, + "float"]) + ]); + + kfs_ctl.require_gui(d3.select("#kfs")); + kfs_ctl.request(); +} + +let key_fn = ((d) => { return d.key; }); + +/* controller for volsurface graph (strike -> volatility), + * from uri [/dyn/kf/snap] + */ + +d3.select("#refresh") + .on("click", + function() { + console.log("button[#refresh] clicked"); + + uls_ctl.request(); + }); + +var srv_ws = null; + +function content_loaded_fn() +{ + console.log("Hi Roly, DOM loaded"); + + /* use this url to create websocket to the server that delivered current webpage */ + let ws_url = choose_ws_url("" /*url_suffix*/); + console.log("ws_url: [", ws_url, "]"); + + srv_ws = new WebSocket(ws_url, "lws-minimal"); + + try { + // srv_ws.onopen = () => { ... }; + // srv_ws.onclose = () => { ... }; + srv_ws.onmessage = (msg) => { + /* msg has dozens of attributes, too many to list here + * actual application message appears in the .data attribute + * (as nested js string) + */ + //console.log("incoming ws msg: [", msg, "]"); + + let msgdata = JSON5.parse(msg.data); + + //console.log("msgdata: [", msgdata, "]"); + + let stream_name = msgdata.stream; + let event = msgdata.event; + + if (stream_name == "/ws/uls") { + if (uls_ctl) { + uls_ctl.on_update(event); + } + } else if (stream_name == "/ws/kfs") { + if (kfs_ctl) { + kfs_ctl.on_update(event); + } + } else { + console.log("unknown stream name [", stream_name, "]"); + } + }; + } catch(excetpion) { + asert("

Error: " + exception); + } + + console.log("srv_ws state [", srv_ws.readyState, "]"); + + srv_ws.addEventListener('open', + (event) => { + console.log("srv_ws state [", srv_ws.readyState, "]"); + if (uls_ctl) { + srv_ws.send("{\"cmd\": \"subscribe\", \"stream\": \"/ws/uls\"} "); + } + if (kfs_ctl) { + srv_ws.send("{\"cmd\": \"subscribe\", \"stream\": \"/ws/kfs\"} "); + } + //socket.send('Hello Server!'); + }); + +} /*content_loaded_fn*/ + +document.addEventListener("DOMContentLoaded", + content_loaded_fn, + false); + +/* end ex_websock.js */ diff --git a/utest/mount-origin/example.js b/utest/mount-origin/example.js new file mode 100644 index 00000000..4df6c234 --- /dev/null +++ b/utest/mount-origin/example.js @@ -0,0 +1,64 @@ +function get_appropriate_ws_url(extra_url) +{ + var pcol; + var u = document.URL; + + /* + * We open the websocket encrypted if this page came on an + * https:// url itself, otherwise unencrypted + */ + + if (u.substring(0, 5) === "https") { + pcol = "wss://"; + u = u.substr(8); + } else { + pcol = "ws://"; + if (u.substring(0, 4) === "http") + u = u.substr(7); + } + + u = u.split("/"); + + /* + "/xxx" bit is for IE10 workaround */ + + return pcol + u[0] + "/" + extra_url; +} + +function new_ws(urlpath, protocol) +{ + return new WebSocket(urlpath, protocol); +} + +document.addEventListener("DOMContentLoaded", function() { + + var ws = new_ws(get_appropriate_ws_url(""), "lws-minimal"); + try { + ws.onopen = function() { + document.getElementById("m").disabled = 0; + document.getElementById("b").disabled = 0; + }; + + ws.onmessage =function got_packet(msg) { + document.getElementById("r").value = + document.getElementById("r").value + msg.data + "\n"; + document.getElementById("r").scrollTop = + document.getElementById("r").scrollHeight; + }; + + ws.onclose = function(){ + document.getElementById("m").disabled = 1; + document.getElementById("b").disabled = 1; + }; + } catch(exception) { + alert("

Error " + exception); + } + + function sendmsg() + { + ws.send(document.getElementById("m").value); + document.getElementById("m").value = ""; + } + + document.getElementById("b").addEventListener("click", sendmsg); + +}, false); diff --git a/utest/mount-origin/index.html b/utest/mount-origin/index.html new file mode 100644 index 00000000..49738af1 --- /dev/null +++ b/utest/mount-origin/index.html @@ -0,0 +1,19 @@ + + + + + + + +
+ + LWS chat minimal ws server example.
+ Chat is sent to all browsers open on this page. +
+
+
+ + + + + diff --git a/utest/mount-origin/libwebsockets.org-logo.svg b/utest/mount-origin/libwebsockets.org-logo.svg new file mode 100644 index 00000000..ef241b37 --- /dev/null +++ b/utest/mount-origin/libwebsockets.org-logo.svg @@ -0,0 +1,66 @@ + + + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/utest/mount-origin/script-csp.svg b/utest/mount-origin/script-csp.svg new file mode 100644 index 00000000..cd128f1d --- /dev/null +++ b/utest/mount-origin/script-csp.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/utest/websock_utest_main.cpp b/utest/websock_utest_main.cpp new file mode 100644 index 00000000..fd2fd9aa --- /dev/null +++ b/utest/websock_utest_main.cpp @@ -0,0 +1,282 @@ +/* @file websock_utest_main.cpp */ + +#include "websock/Webserver.hpp" +#include "volfit/init_volfit.hpp" +#include "volfit/Volfit.hpp" +#include "volfit/VolfitInputCapture.hpp" +#include "filter/init_filter.hpp" +#include "filter/KalmanFilterSvc.hpp" +#include "option/StrikeSetOmd.hpp" +#include "option/StrikeSetMarketModel.hpp" +#include "option/UlMarketModel.hpp" +#include "option/PricingContext.hpp" +#include "option/OptionStrikeSet.hpp" +#include "process/init_process.hpp" +#include "process/RealizationSource.hpp" +#include "process/RealizationTracer.hpp" +#include "process/UpxEvent.hpp" +#include "process/ExpProcess.hpp" +#include "process/BrownianMotion.hpp" +#include "simulator/init_simulator.hpp" +#include "simulator/Simulator.hpp" +#include "reactor/EventStore.hpp" +#include "randomgen/random_seed.hpp" +#include "randomgen/xoshiro256.hpp" +#include "time/Time.hpp" +#include "printjson/PrintJson.hpp" +#include "indentlog/print/tag.hpp" +#include + +/* webserver instance */ +static xo::ref::rp g_ws; + +void sigint_handler(int /*sig*/) { + std::cerr << "main thread interrupt_handler\n"; + + if (g_ws) + g_ws->interrupt_stop_webserver(); +} + +int +main(int argc, char **argv) { + using xo::web::Webserver; + using xo::web::WebserverConfig; + using xo::json::PrintJsonSingleton; + using xo::vf::Volfit; + using xo::vf::VolfitInputCaptureSvc; + using xo::kalman::KalmanFilterStateExt; + using xo::kalman::KalmanFilterSvc; + using xo::kalman::KalmanFilterSpec; + using xo::option::FlatVolsfc; + using xo::option::BboTick; + using xo::option::StrikeSetOmd; + using xo::option::StrikeSetMarketModel; + using xo::option::UlMarketModel; + using xo::option::PricingContext; + using xo::option::OptionStrikeSet; + using xo::option::Secid; + using xo::option::Pxtick; + using xo::process::UpxEvent; + using xo::process::RealizationSource; + using xo::process::RealizationTracer; + using xo::process::StochasticProcess; + using xo::process::ExpProcess; + using xo::process::BrownianMotion; + using xo::sim::Simulator; + using xo::reactor::PtrEventStore; + using xo::reactor::StructEventStore; + using xo::reactor::ReactorSource; + using xo::rng::Seed; + using xo::rng::xoshiro256ss; + using xo::ref::rp; + using xo::time::utc_nanos; + using xo::time::timeutil; + using xo::json::PrintJsonSingleton; + using xo::json::PrintJson; + using xo::Subsystem; + using xo::scope; + using xo::xtag; + + try { +#ifdef NOT_USING + InitSubsys::require(); + InitSubsys::require(); + InitSubsys::require(); +#endif + + XO_SUBSYSTEM_REQUIRE(volfit); + XO_SUBSYSTEM_REQUIRE(filter); + XO_SUBSYSTEM_REQUIRE(simulator); + XO_SUBSYSTEM_REQUIRE(process); + + Subsystem::initialize_all(); + + signal(SIGINT, sigint_handler); + + scope log(XO_ENTER0(always)); + + rp pjson = PrintJsonSingleton::instance(); + + /* RC Sep 2022 - adding c++ translation of kalman/src/pywebsock/ex_websock.py; + * intending to debug server segfault without complication of running + * from python interpreter + */ + + Secid secid0(0, 0); + Secid ul0 = Secid::ul(0); + + utc_nanos t0 = timeutil::ymd_hms_usec(20220926 /*ymd*/, + 93000 /*hms*/, + 0 /*usec*/); + utc_nanos t1 = t0 + std::chrono::hours(30 * 24); + + /* sim = pysimulator.Simulator.make() */ + rp sim = Simulator::make(t0); + + /* ss = pyoption.make_option_strike_set(3, secid0, 10, 1, t1, Pxtick.penny_nickel) */ + rp ss + = OptionStrikeSet::regular(3 /*n*/, + secid0 /*start_id*/, + 10.0 /*lo_strike*/, + 1.0 /*d_strike*/, + t1 /*expiry*/, + Pxtick::penny_nickel); + + /* cx = pyoption.make_pricing_context(t0, 11.11, .5, .06) */ + rp cx + = PricingContext::make(t0, 11.11 /*ref_spot*/, + FlatVolsfc::make(0.5) /*volatility*/, + 0.06 /*rate*/); + + /* TODO: replace with constant to get deterministic behavior */ + Seed seed; + + /* ebm = pyprocess.make_exponential_brownian_motion(t0, 11.0, 0.5) */ + rp ebm + = ExpProcess::make(11.0, + BrownianMotion::make(t0, + 0.5 /*volatility*/, + seed)); + + /* src = pyprocess.make_realization_source(ebm, dt.timedelta(seconds=1)) */ + rp src + = RealizationSource::make(RealizationTracer::make(ebm), + std::chrono::seconds(1)); + src->set_name("src"); + + /* (A) + * ulm = pyoption.make_ul_market_model(ul0, cx) + */ + rp ulm = UlMarketModel::make(ul0, cx); + ulm->set_name("ulm"); + + /* (B) + * ssm = pyoption.make_strikeset_market_model(ss, cx) + */ + rp ssm = StrikeSetMarketModel::make(ss, cx); + ssm->set_name("ssm"); + + /* (C) + * ssmd = pyoption.make_strikeset_omd(ss) + */ + rp ssmd = StrikeSetOmd::make(ss); + ssmd->set_name("ssmd"); + + /* (D) + * bbos = pyoption.BboTickStore.make() + */ + using BboTickStore = StructEventStore; + rp bbos = StructEventStore::make(); + bbos->set_name("bbos"); + + /* (E) + * vfin = pyvolfit.make_volfit_input_capture(cx) + */ + rp vfin = VolfitInputCaptureSvc::make(cx); + vfin->set_name("vfin"); + + /* (F) + * kfspec = pyvolfit.make_kf_spec_m1(t0, s0=0.3, p0=1.0, q=5) + */ + KalmanFilterSpec kfspec = Volfit::kf_spec_m1(t0, 0.3, 1.0, 5.0); + /* kf = pyfilter.make_kalman_filter(spec=kfspec) */ + rp kf = KalmanFilterSvc::make(kfspec); + kf->set_debug_sim_flag(true); + kf->set_name("kf"); + + using KalmanFilterStateEventStore + = PtrEventStore>; + + /* (G) + * kfs = pyfilter.KalmanFilterStateEventStore.make() + */ + rp kfs + = KalmanFilterStateEventStore::make(); + + /* sim.add_source(src) */ + sim->add_source(src); + + /* uls = pyprocess.UpxEventStore.make() */ + using UpxEventStore = StructEventStore; + rp uls = UpxEventStore::make(); + src->attach_sink(uls); + + /* (A) */ + sim->add_source(ulm); + src->attach_sink(ulm); + + /* (B) */ + sim->add_source(ssm); + src->attach_sink(ssm); + + /* (C) */ + sim->add_source(ssmd); + ulm->attach_sink(ssmd); + ssm->attach_sink(ssmd); + + /* (D) */ + ulm->attach_sink(bbos); + ssm->attach_sink(bbos); + + /* (E) */ + // sim->add_source(vfin) ? + ssmd->attach_sink(vfin); + + /* (F) */ + // sim->add_source(kf) ? + vfin->attach_sink(kf); + + /* (G) */ + kf->attach_sink(kfs); + + /* wconfig=pywebsock.WebserverConfig(7682, False, False, False) + * web=pywebsock.Webserver.make(wconfig) + */ + g_ws = Webserver::make(WebserverConfig(7682 /*port*/, + false /*!tls_flag*/, + false /*!strict_host_check_flag*/, + false /*!use_retry_flag*/), + PrintJsonSingleton::instance()); + + /* web.register_http_endpoint(uls.http_endpoint_descr("/uls")) # /dyn/uls/snap + * web.register_http_endpoint(kfs.http_endpoint_descr("/kfs")) # /dyn/kfs/snap + */ + g_ws->register_http_endpoint(uls->http_endpoint_descr(pjson, "/uls")); + g_ws->register_http_endpoint(kfs->http_endpoint_descr(pjson, "/kfs")); + + /* web.register_stream_endpoint(src.stream_endpoint_descr("/ws/uls")) + * web.register_stream_endpoint(kf.stream_endpoint_descr("/ws/kfs")) + */ + g_ws->register_stream_endpoint(src->stream_endpoint_descr("/ws/uls")); + g_ws->register_stream_endpoint(kf->stream_endpoint_descr("/ws/kfs")); + + log("starting webserver.."); + + g_ws->start_webserver(); + + log("..webserver started"); + + /* attempt simulation. + * throttle so that we have time to connect browser, give url + * http://localhost:7682/ex_websock.html + * pywebsock/ex_websock.py implements a governed simulation loop + * in python, so that it's interruptible. + * here, we can rely on simulator instead + */ + sim->run_throttled_until(t0 /*t1 - ignored if <= sim.t0()*/, + 50 /*n_max*/, + 2.5 /*replay_factor*/); + + + log("joining webserver.."); + + g_ws->join_webserver(); + + log("..joined webserver"); + } catch (std::exception & ex) { + std::cerr << "caught exception" << xtag("ex", ex.what()) << std::endl; + } + +} /*main*/ + +/* end websock_utest_main.cpp */ From df951e4a4dea8acae4de07cfabb8db94e9628377 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:08:14 -0400 Subject: [PATCH 0386/2693] + .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..eff45bd2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +build*/* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json From d312d8a5a49efa3ec4f22fd2e0e8c70f73414020 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:13:07 -0400 Subject: [PATCH 0387/2693] build: fix for header-only callback dep --- include/xo/webutil/StreamEndpointDescr.hpp | 2 +- src/webutil/CMakeLists.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/include/xo/webutil/StreamEndpointDescr.hpp b/include/xo/webutil/StreamEndpointDescr.hpp index 78053008..53aa63c7 100644 --- a/include/xo/webutil/StreamEndpointDescr.hpp +++ b/include/xo/webutil/StreamEndpointDescr.hpp @@ -6,8 +6,8 @@ #pragma once #include "Alist.hpp" -#include "xo/callback/CallbackSet.hpp" #include "xo/refcnt/Refcounted.hpp" +#include "xo/callback/CallbackSet.hpp" #include namespace xo { diff --git a/src/webutil/CMakeLists.txt b/src/webutil/CMakeLists.txt index 57f96726..7d9f5665 100644 --- a/src/webutil/CMakeLists.txt +++ b/src/webutil/CMakeLists.txt @@ -9,6 +9,7 @@ xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- # external dependencies +xo_dependency(${SELF_LIB} refcnt) xo_dependency(${SELF_LIB} callback) # end CMakeLists.txt From 77a570f8a5c8b40cada66e35352dc7fc40d3b8ad Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:13:21 -0400 Subject: [PATCH 0388/2693] github: + workflow --- .github/workflows/main.yml | 117 +++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..8b3795c9 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,117 @@ +name: build xo-callback + xo dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/callback + + - name: Configure callback + # configure cmake for callback in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_callback -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/callback + + - name: Build callback + run: cmake --build ${{github.workspace}}/build_callback --config ${{env.BUILD_TYPE}} + + - name: Install callback + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_callback + + # ---------------------------------------------------------------- + + - name: Configure self (webutil) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_webutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (webutil) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_webutil --config ${{env.BUILD_TYPE}} + + - name: Test self (webutil) + working-directory: ${{github.workspace}}/build_webutil + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From 6d970d4f3c944d69de69a4d15dccb8fbedfdf4be Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:16:14 -0400 Subject: [PATCH 0389/2693] drop false header-only claim ! --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 34463fb0..0d906d20 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# webutil library (header-only) +# webutil library ## Getting Started From 0deb2a2ab3815e494597467af258b17a2d263896 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:20:14 -0400 Subject: [PATCH 0390/2693] github: + workflow --- .github/workflows/main.yml | 155 +++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..4c4f8a76 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,155 @@ +name: build xo-callback + xo dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/reflect + + - name: Configure reflect + # configure cmake for reflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reflect + + - name: Build reflect + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} + + - name: Install reflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reflect + + # ---------------------------------------------------------------- + + - name: Clone callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/callback + + - name: Configure callback + # configure cmake for callback in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_callback -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/callback + + - name: Build callback + run: cmake --build ${{github.workspace}}/build_callback --config ${{env.BUILD_TYPE}} + + - name: Install callback + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_callback + + # ---------------------------------------------------------------- + + - name: Clone webutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-webutil + path: repo/webutil + + - name: Configure webutil + # configure cmake for webutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_webutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/webutil + + - name: Build webutil + run: cmake --build ${{github.workspace}}/build_webutil --config ${{env.BUILD_TYPE}} + + - name: Install webutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_webutil + + # ---------------------------------------------------------------- + + - name: Configure self (reactor) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_reactor -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (reactor) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_reactor --config ${{env.BUILD_TYPE}} + + - name: Test self (reactor) + working-directory: ${{github.workspace}}/build_reactor + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From d8efcb7490983eca7427b784dd7c841afb1cccbd Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:22:41 -0400 Subject: [PATCH 0391/2693] github: fix missing subsys dep --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4c4f8a76..4cea3fc2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -82,6 +82,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + # configure cmake for subsys in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + - name: Clone reflect uses: actions/checkout@v3 with: From 638fb898ff2a06f6bef1bd3bdc4945f88263d84f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:25:17 -0400 Subject: [PATCH 0392/2693] github: + missing printjson --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4cea3fc2..c16f0b3d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -158,6 +158,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone printjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-printjson + path: repo/printjson + + - name: Configure printjson + # configure cmake for printjson in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_printjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/printjson + + - name: Build printjson + run: cmake --build ${{github.workspace}}/build_printjson --config ${{env.BUILD_TYPE}} + + - name: Install printjson + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_printjson + + # ---------------------------------------------------------------- + - name: Configure self (reactor) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From d392d5d593ecf39144691b247e92d583e18e7919 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:33:03 -0400 Subject: [PATCH 0393/2693] github: + missing randomgen dep --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c16f0b3d..aaef0421 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -177,6 +177,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone randomgen + uses: actions/checkout@v3 + with: + repository: Rconybea/randomgen + path: repo/randomgen + + - name: Configure randomgen + # configure cmake for randomgen in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/randomgen + + - name: Build randomgen + run: cmake --build ${{github.workspace}}/build_randomgen --config ${{env.BUILD_TYPE}} + + - name: Install randomgen + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_randomgen + + # ---------------------------------------------------------------- + - name: Configure self (reactor) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 3231cc6b4d413f4caffacd6e8a7333ebf4f847e1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:40:26 -0400 Subject: [PATCH 0394/2693] github: + workflow --- .github/workflows/main.yml | 231 +++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..656cb12c --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,231 @@ +name: build xo-callback + xo dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + # configure cmake for subsys in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + + - name: Clone reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/reflect + + - name: Configure reflect + # configure cmake for reflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reflect + + - name: Build reflect + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} + + - name: Install reflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reflect + + # ---------------------------------------------------------------- + + - name: Clone callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/callback + + - name: Configure callback + # configure cmake for callback in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_callback -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/callback + + - name: Build callback + run: cmake --build ${{github.workspace}}/build_callback --config ${{env.BUILD_TYPE}} + + - name: Install callback + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_callback + + # ---------------------------------------------------------------- + + - name: Clone webutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-webutil + path: repo/webutil + + - name: Configure webutil + # configure cmake for webutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_webutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/webutil + + - name: Build webutil + run: cmake --build ${{github.workspace}}/build_webutil --config ${{env.BUILD_TYPE}} + + - name: Install webutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_webutil + + # ---------------------------------------------------------------- + + - name: Clone printjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-printjson + path: repo/printjson + + - name: Configure printjson + # configure cmake for printjson in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_printjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/printjson + + - name: Build printjson + run: cmake --build ${{github.workspace}}/build_printjson --config ${{env.BUILD_TYPE}} + + - name: Install printjson + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_printjson + + # ---------------------------------------------------------------- + + - name: Clone randomgen + uses: actions/checkout@v3 + with: + repository: Rconybea/randomgen + path: repo/randomgen + + - name: Configure randomgen + # configure cmake for randomgen in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/randomgen + + - name: Build randomgen + run: cmake --build ${{github.workspace}}/build_randomgen --config ${{env.BUILD_TYPE}} + + - name: Install randomgen + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_randomgen + + # ---------------------------------------------------------------- + + - name: Clone reactor + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-reactor + path: repo/reactor + + - name: Configure reactor + # configure cmake for reactor in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reactor -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reactor + + - name: Build reactor + run: cmake --build ${{github.workspace}}/build_reactor --config ${{env.BUILD_TYPE}} + + - name: Install reactor + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reactor + + # ---------------------------------------------------------------- + + - name: Configure self (websock) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_websock -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (websock) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_websock --config ${{env.BUILD_TYPE}} + + - name: Test self (websock) + working-directory: ${{github.workspace}}/build_websock + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From 2af96edaea1070aa83cc1d74c79f8f85ffa8b5b5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:44:49 -0400 Subject: [PATCH 0395/2693] github: + missing ordinaltree dep --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 656cb12c..369f536a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -120,6 +120,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/ordinaltree + + - name: Configure ordinaltree + # configure cmake for ordinaltree in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/ordinaltree + + - name: Build ordinaltree + run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}} + + - name: Install ordinaltree + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_ordinaltree + + # ---------------------------------------------------------------- + - name: Clone callback uses: actions/checkout@v3 with: From 61787846cc4a7466dc8f2621590edc705aac7868 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:50:10 -0400 Subject: [PATCH 0396/2693] github: build ordinaltree after its dep randomgen --- .github/workflows/main.yml | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 369f536a..7ef8b800 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -120,25 +120,6 @@ jobs: # ---------------------------------------------------------------- - - name: Clone ordinaltree - uses: actions/checkout@v3 - with: - repository: Rconybea/xo-ordinaltree - path: repo/ordinaltree - - - name: Configure ordinaltree - # configure cmake for ordinaltree in dedicated build directory. - run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/ordinaltree - - - name: Build ordinaltree - run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}} - - - name: Install ordinaltree - # install into ${{github.workspace}}/local - run: cmake --install ${{github.workspace}}/build_ordinaltree - - # ---------------------------------------------------------------- - - name: Clone callback uses: actions/checkout@v3 with: @@ -215,6 +196,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/ordinaltree + + - name: Configure ordinaltree + # configure cmake for ordinaltree in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/ordinaltree + + - name: Build ordinaltree + run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}} + + - name: Install ordinaltree + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_ordinaltree + + # ---------------------------------------------------------------- + - name: Clone reactor uses: actions/checkout@v3 with: From 166cff50887494647a427aa79c3fc255ce54f76c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 17:03:38 -0400 Subject: [PATCH 0397/2693] github: + apt-get install libwebsockets-dev --- .github/workflows/main.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7ef8b800..5f26582f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,6 +25,10 @@ jobs: # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] run: sudo apt-get install -y catch2 + - name: Install libwebsockets + # install libwebsockets. + run: sudo apt-get install -y libwebsockets-dev + # ---------------------------------------------------------------- - name: Clone xo-cmake From cbd748d909741954960e24dcb8670353509b34b8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 17:12:52 -0400 Subject: [PATCH 0398/2693] minor carveouts for libwebsockets version < 4 --- src/websock/WebsockUtil.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/websock/WebsockUtil.cpp b/src/websock/WebsockUtil.cpp index 7c7057fb..324d9f79 100644 --- a/src/websock/WebsockUtil.cpp +++ b/src/websock/WebsockUtil.cpp @@ -11,6 +11,10 @@ namespace xo { #define CASE(x) case x: return STRINGIFY(x) + /* ubuntu build (available via github actions) has older version of libwebsockets. + * typically building (e.g. via nix) with libwebsockets 4.3.2 + */ + switch (x) { CASE(LWS_CALLBACK_PROTOCOL_INIT); CASE(LWS_CALLBACK_PROTOCOL_DESTROY); @@ -44,7 +48,9 @@ namespace xo { CASE(LWS_CALLBACK_RECEIVE_CLIENT_HTTP); CASE(LWS_CALLBACK_COMPLETED_CLIENT_HTTP); CASE(LWS_CALLBACK_CLIENT_HTTP_WRITEABLE); +#if LWS_LIBRARY_VERSION_MAJOR >= 4 CASE(LWS_CALLBACK_CLIENT_HTTP_REDIRECT); +#endif CASE(LWS_CALLBACK_CLIENT_HTTP_BIND_PROTOCOL); CASE(LWS_CALLBACK_CLIENT_HTTP_DROP_PROTOCOL); CASE(LWS_CALLBACK_ESTABLISHED); @@ -112,7 +118,9 @@ namespace xo { CASE(LWS_CALLBACK_TIMER); CASE(LWS_CALLBACK_EVENT_WAIT_CANCELLED); CASE(LWS_CALLBACK_CHILD_CLOSING); +#if LWS_LIBRARY_VERSION_MAJOR >= 4 CASE(LWS_CALLBACK_CONNECTING); +#endif CASE(LWS_CALLBACK_VHOST_CERT_AGING); CASE(LWS_CALLBACK_VHOST_CERT_UPDATE); CASE(LWS_CALLBACK_MQTT_NEW_CLIENT_INSTANTIATED); @@ -126,8 +134,10 @@ namespace xo { CASE(LWS_CALLBACK_MQTT_CLIENT_CLOSED); CASE(LWS_CALLBACK_MQTT_ACK); CASE(LWS_CALLBACK_MQTT_RESEND); +#if LWS_LIBRARY_VERSION_MAJOR >= 4 CASE(LWS_CALLBACK_MQTT_UNSUBSCRIBE_TIMEOUT); CASE(LWS_CALLBACK_MQTT_SHADOW_TIMEOUT); +#endif CASE(LWS_CALLBACK_USER); } From 0c438ebcf56e633aff8a77106dfcc1324b04d542 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 17:21:32 -0400 Subject: [PATCH 0399/2693] build: require 4.3 in carveout --- src/websock/WebsockUtil.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/websock/WebsockUtil.cpp b/src/websock/WebsockUtil.cpp index 324d9f79..7ba1dc22 100644 --- a/src/websock/WebsockUtil.cpp +++ b/src/websock/WebsockUtil.cpp @@ -13,6 +13,8 @@ namespace xo { /* ubuntu build (available via github actions) has older version of libwebsockets. * typically building (e.g. via nix) with libwebsockets 4.3.2 + * + * see lws_config.h for version numbers vars */ switch (x) { @@ -48,7 +50,7 @@ namespace xo { CASE(LWS_CALLBACK_RECEIVE_CLIENT_HTTP); CASE(LWS_CALLBACK_COMPLETED_CLIENT_HTTP); CASE(LWS_CALLBACK_CLIENT_HTTP_WRITEABLE); -#if LWS_LIBRARY_VERSION_MAJOR >= 4 +#if (LWS_LIBRARY_VERSION_MAJOR >= 4) && (LWS_LIBRARY_VERSION_MINOR >= 3) CASE(LWS_CALLBACK_CLIENT_HTTP_REDIRECT); #endif CASE(LWS_CALLBACK_CLIENT_HTTP_BIND_PROTOCOL); @@ -118,7 +120,7 @@ namespace xo { CASE(LWS_CALLBACK_TIMER); CASE(LWS_CALLBACK_EVENT_WAIT_CANCELLED); CASE(LWS_CALLBACK_CHILD_CLOSING); -#if LWS_LIBRARY_VERSION_MAJOR >= 4 +#if (LWS_LIBRARY_VERSION_MAJOR >= 4) && (LWS_LIBRARY_VERSION_MINOR >= 3) CASE(LWS_CALLBACK_CONNECTING); #endif CASE(LWS_CALLBACK_VHOST_CERT_AGING); @@ -134,7 +136,7 @@ namespace xo { CASE(LWS_CALLBACK_MQTT_CLIENT_CLOSED); CASE(LWS_CALLBACK_MQTT_ACK); CASE(LWS_CALLBACK_MQTT_RESEND); -#if LWS_LIBRARY_VERSION_MAJOR >= 4 +#if (LWS_LIBRARY_VERSION_MAJOR >= 4) && (LWS_LIBRARY_VERSION_MINOR >= 3) CASE(LWS_CALLBACK_MQTT_UNSUBSCRIBE_TIMEOUT); CASE(LWS_CALLBACK_MQTT_SHADOW_TIMEOUT); #endif From e0ed57b91a175d391aca8a58a9146046e1f1eff5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 21:16:52 -0400 Subject: [PATCH 0400/2693] github: + jsoncpp dep (transitive dep of libwebsockets) --- .github/workflows/main.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5f26582f..b4559637 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,6 +25,10 @@ jobs: # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] run: sudo apt-get install -y catch2 + - name: Install libjsoncpp (dep of libwebsockets) + # install jsoncpp + run: sudo apt-get install -y libjsoncpp-dev + - name: Install libwebsockets # install libwebsockets. run: sudo apt-get install -y libwebsockets-dev From 228c7cea971b78158f54db966ea65548733c1944 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 21:47:43 -0400 Subject: [PATCH 0401/2693] build: proper cmake deps for libwebsockets, jsoncpp --- src/websock/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/websock/CMakeLists.txt b/src/websock/CMakeLists.txt index e8aa7df2..88511c86 100644 --- a/src/websock/CMakeLists.txt +++ b/src/websock/CMakeLists.txt @@ -11,6 +11,11 @@ xo_add_shared_library3(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 $ xo_dependency(${SELF_LIB} reactor) xo_dependency(${SELF_LIB} webutil) +# see LibwebsocketsTargets-release.cmake for available targets +xo_external_target_dependency(${SELF_LIB} libwebsockets websockets_shared) +# see jsoncpp-namespaced-targets.cmake (maybe?) for available targets +xo_external_target_dependency(${SELF_LIB} jsoncpp jsoncpp_lib) + # note: changes to xo_dependency() calls here # must coordinate with find_dependency() calls in # xo-websock/cmake/websockConfig.cmake.in From 8fcfd185150dd61ee4d43eb5877b5ea918363080 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 21:57:23 -0400 Subject: [PATCH 0402/2693] github: try including /usr/lib/x86_64-linux-gnu in cmake prefix path --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b4559637..ced4fa08 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -245,7 +245,7 @@ jobs: - name: Configure self (websock) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build_websock -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: cmake -B ${{github.workspace}}/build_websock -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local;/usr/lib/x86_64-linux-gnu -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - name: Build self (websock) # Build your program with the given configuration From df2c0b3e208f2aff9cefd1d360673a9fc8bf6e0e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 22:02:26 -0400 Subject: [PATCH 0403/2693] github: try /cmake qualifier on CMAKE_PREFIX_PATH --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ced4fa08..3ea86dcd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -245,7 +245,7 @@ jobs: - name: Configure self (websock) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build_websock -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local;/usr/lib/x86_64-linux-gnu -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: cmake -B ${{github.workspace}}/build_websock -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local;/usr/lib/x86_64-linux-gnu/cmake -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - name: Build self (websock) # Build your program with the given configuration From 1a72a54633df0690065bde2bb039feaea88bd42b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 22:09:44 -0400 Subject: [PATCH 0404/2693] build + github: Libwebsockets spelling --- .github/workflows/main.yml | 5 ++++- src/websock/CMakeLists.txt | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3ea86dcd..bdeb801d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -242,10 +242,13 @@ jobs: # ---------------------------------------------------------------- + - name: check stuff + run: ls /usr/lib/x86_64-linux-gnu + - name: Configure self (websock) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build_websock -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local;/usr/lib/x86_64-linux-gnu/cmake -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: cmake -B ${{github.workspace}}/build_websock -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake "-DCMAKE_PREFIX_PATH=${{github.workspace}}/local;/usr/lib/x86_64-linux-gnu/cmake" -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - name: Build self (websock) # Build your program with the given configuration diff --git a/src/websock/CMakeLists.txt b/src/websock/CMakeLists.txt index 88511c86..a1f15e18 100644 --- a/src/websock/CMakeLists.txt +++ b/src/websock/CMakeLists.txt @@ -12,7 +12,7 @@ xo_dependency(${SELF_LIB} reactor) xo_dependency(${SELF_LIB} webutil) # see LibwebsocketsTargets-release.cmake for available targets -xo_external_target_dependency(${SELF_LIB} libwebsockets websockets_shared) +xo_external_target_dependency(${SELF_LIB} Libwebsockets websockets_shared) # see jsoncpp-namespaced-targets.cmake (maybe?) for available targets xo_external_target_dependency(${SELF_LIB} jsoncpp jsoncpp_lib) From c8326018bbe13c40ec3c6ecb5528a5c3c33686d4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 22:19:30 -0400 Subject: [PATCH 0405/2693] websock: initializer for lws_http_mount._unused[] with lws 4.0 --- .github/workflows/main.yml | 4 ++-- src/websock/Webserver.cpp | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bdeb801d..403432bb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -242,8 +242,8 @@ jobs: # ---------------------------------------------------------------- - - name: check stuff - run: ls /usr/lib/x86_64-linux-gnu + - name: check cmake-supporting packages + run: ls /usr/lib/x86_64-linux-gnu/cmake - name: Configure self (websock) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. diff --git a/src/websock/Webserver.cpp b/src/websock/Webserver.cpp index 6e1dbdb1..3f793111 100644 --- a/src/websock/Webserver.cpp +++ b/src/websock/Webserver.cpp @@ -657,6 +657,8 @@ namespace xo { virtual void init_protocols(std::vector * p_v) = 0; void init_mount_dynamic(lws_http_mount * p_mount) { + /* see lws-context-vhost.h for lws_http_mount */ + *p_mount = { /* .mount_next */ NULL, /* linked-list "next" */ /* .mountpoint */ "/dyn", /* mountpoint URL */ @@ -675,6 +677,9 @@ namespace xo { /* .origin_protocol */ LWSMPRO_CALLBACK, /* dynamic */ /* .mountpoint_len */ 4, /* char count */ /* .basic_auth_login_file */ NULL, +# if (LWS_LIBRARY_VERSION_MAJOR < 4) || (LWS_LIBRARY_VERSION_MINOR < 3) // -Werror=missing-field-initializers + /* ._unused[] */ { nullptr, nullptr }, +# endif }; } /*init_mount_dynamic*/ From b47cfebeb0f4c7b96fb3887bd52644034cedc5d8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 11:15:28 -0400 Subject: [PATCH 0406/2693] websock: populate lws_http_mount._unused prior to LWS version 4.3 --- src/websock/Webserver.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/websock/Webserver.cpp b/src/websock/Webserver.cpp index 3f793111..1a12fcdf 100644 --- a/src/websock/Webserver.cpp +++ b/src/websock/Webserver.cpp @@ -677,7 +677,8 @@ namespace xo { /* .origin_protocol */ LWSMPRO_CALLBACK, /* dynamic */ /* .mountpoint_len */ 4, /* char count */ /* .basic_auth_login_file */ NULL, -# if (LWS_LIBRARY_VERSION_MAJOR < 4) || (LWS_LIBRARY_VERSION_MINOR < 3) // -Werror=missing-field-initializers +# if ((LWS_LIBRARY_VERSION_MAJOR < 4) + || ((LWS_LIBRARY_VERSION_MAJOR == 4) && (LWS_LIBRARY_VERSION_MINOR < 3))) /* ._unused[] */ { nullptr, nullptr }, # endif }; @@ -704,6 +705,10 @@ namespace xo { /* .origin_protocol */ LWSMPRO_FILE, /* files in a dir */ /* .mountpoint_len */ 1, /* char count */ /* .basic_auth_login_file */ NULL, +# if ((LWS_LIBRARY_VERSION_MAJOR < 4) + || ((LWS_LIBRARY_VERSION_MAJOR == 4) && (LWS_LIBRARY_VERSION_MINOR < 3))) + /* ._unused[] */ { nullptr, nullptr }, +# endif }; } /*init_mount_static*/ From 209dfd69fc727b1b5c79ac638b82bf5abb7eaa06 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 11:20:07 -0400 Subject: [PATCH 0407/2693] cosmetic: formatting --- src/websock/Webserver.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/websock/Webserver.cpp b/src/websock/Webserver.cpp index 1a12fcdf..949cef16 100644 --- a/src/websock/Webserver.cpp +++ b/src/websock/Webserver.cpp @@ -677,8 +677,7 @@ namespace xo { /* .origin_protocol */ LWSMPRO_CALLBACK, /* dynamic */ /* .mountpoint_len */ 4, /* char count */ /* .basic_auth_login_file */ NULL, -# if ((LWS_LIBRARY_VERSION_MAJOR < 4) - || ((LWS_LIBRARY_VERSION_MAJOR == 4) && (LWS_LIBRARY_VERSION_MINOR < 3))) +# if ((LWS_LIBRARY_VERSION_MAJOR < 4) || ((LWS_LIBRARY_VERSION_MAJOR == 4) && (LWS_LIBRARY_VERSION_MINOR < 3))) /* ._unused[] */ { nullptr, nullptr }, # endif }; @@ -705,8 +704,7 @@ namespace xo { /* .origin_protocol */ LWSMPRO_FILE, /* files in a dir */ /* .mountpoint_len */ 1, /* char count */ /* .basic_auth_login_file */ NULL, -# if ((LWS_LIBRARY_VERSION_MAJOR < 4) - || ((LWS_LIBRARY_VERSION_MAJOR == 4) && (LWS_LIBRARY_VERSION_MINOR < 3))) +# if ((LWS_LIBRARY_VERSION_MAJOR < 4) || ((LWS_LIBRARY_VERSION_MAJOR == 4) && (LWS_LIBRARY_VERSION_MINOR < 3))) /* ._unused[] */ { nullptr, nullptr }, # endif }; From f9f37a7a486facc3bc5ea430fa0f9e5c0a340635 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 11:32:24 -0400 Subject: [PATCH 0408/2693] websock: compatibility for LWS without plugins --- src/websock/Webserver.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/websock/Webserver.cpp b/src/websock/Webserver.cpp index 949cef16..24594c4f 100644 --- a/src/websock/Webserver.cpp +++ b/src/websock/Webserver.cpp @@ -724,9 +724,13 @@ namespace xo { */ void init_cx_config(lws_context_creation_info * p_cx_config) { ::memset(p_cx_config, 0, sizeof(*p_cx_config)); - p_cx_config->port = this->ws_config_.port(); + p_cx_config->port = this->ws_config_.port(); p_cx_config->vhost_name = "localhost"; - p_cx_config->pvo = &(this->pvo_); +#if defined(LWS_WITH_PLUGINS) + p_cx_config->pvo = &(this->pvo_); +#else + p_cx_config->pvo = nullptr; +#endif p_cx_config->protocols = this->protocol_v_.data(); p_cx_config->mounts = &(this->mount_static_); /* userdata -- accessible from context with lws_context_user() */ From 0de914aea5ff5f9c3bb035b48f6e57ba63221130 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 11:38:32 -0400 Subject: [PATCH 0409/2693] websock: compile fixed for base ubuntu build --- src/websock/Webserver.cpp | 38 +++++++++++++++++++++---------------- src/websock/WebsockUtil.cpp | 6 +++--- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/websock/Webserver.cpp b/src/websock/Webserver.cpp index 24594c4f..09f90439 100644 --- a/src/websock/Webserver.cpp +++ b/src/websock/Webserver.cpp @@ -1561,25 +1561,29 @@ namespace xo { * and whether or not .per_session_data_size is 0. * .tx_packet_size */ - p_v->push_back({ - "http", - &WebserverImpl::notify_dynamic_http, - sizeof(struct per_session_data__http), - 0, - 0, - NULL, - 0 + p_v->push_back( + {"http", + &WebserverImpl::notify_dynamic_http, + sizeof(struct per_session_data__http), + 0, + 0, + NULL, + 0 }); - p_v->push_back({ - "lws-minimal", - &WebserverImplWsThread::notify_minimal, - sizeof(struct per_session_data__minimal), - 128, - 0, - NULL, - 0}); + p_v->push_back( + {"lws-minimal", + &WebserverImplWsThread::notify_minimal, + sizeof(struct per_session_data__minimal), + 128, + 0, + NULL, + 0}); /* mandatory end-of-array sentinel, requires by lws */ +#if ((LWS_LIBRARY_VERSION_MAJOR > 4) || (LWS_LIBRARY_VERSION_MAJOR == 4) && (LWS_LIBRARY_VERSION_MINOR >= 3)) p_v->push_back(LWS_PROTOCOL_LIST_TERM); +#else + p_v->push_back({ nullptr, nullptr, 0, 0, 0, nullptr, 0}); +#endif } /*init_protocols*/ /* called reentrantly from ::lws_service(), @@ -1889,6 +1893,8 @@ namespace xo { " | visit http://localhost:%d\n", this->ws_config_.port()); #if defined(LWS_WITH_PLUGINS) lwsl_user("LWS_WITH_PLUGINS present"); +#endif +#if defined(LWS_WITH_TLS) lwsl_user("LWS_WITH_TLS present"); #endif diff --git a/src/websock/WebsockUtil.cpp b/src/websock/WebsockUtil.cpp index 7ba1dc22..6cd6b19d 100644 --- a/src/websock/WebsockUtil.cpp +++ b/src/websock/WebsockUtil.cpp @@ -50,7 +50,7 @@ namespace xo { CASE(LWS_CALLBACK_RECEIVE_CLIENT_HTTP); CASE(LWS_CALLBACK_COMPLETED_CLIENT_HTTP); CASE(LWS_CALLBACK_CLIENT_HTTP_WRITEABLE); -#if (LWS_LIBRARY_VERSION_MAJOR >= 4) && (LWS_LIBRARY_VERSION_MINOR >= 3) +#if ((LWS_LIBRARY_VERSION_MAJOR > 4) || (LWS_LIBRARY_VERSION_MAJOR == 4) && (LWS_LIBRARY_VERSION_MINOR >= 3)) CASE(LWS_CALLBACK_CLIENT_HTTP_REDIRECT); #endif CASE(LWS_CALLBACK_CLIENT_HTTP_BIND_PROTOCOL); @@ -120,7 +120,7 @@ namespace xo { CASE(LWS_CALLBACK_TIMER); CASE(LWS_CALLBACK_EVENT_WAIT_CANCELLED); CASE(LWS_CALLBACK_CHILD_CLOSING); -#if (LWS_LIBRARY_VERSION_MAJOR >= 4) && (LWS_LIBRARY_VERSION_MINOR >= 3) +#if ((LWS_LIBRARY_VERSION_MAJOR > 4) || (LWS_LIBRARY_VERSION_MAJOR == 4) && (LWS_LIBRARY_VERSION_MINOR >= 3)) CASE(LWS_CALLBACK_CONNECTING); #endif CASE(LWS_CALLBACK_VHOST_CERT_AGING); @@ -136,7 +136,7 @@ namespace xo { CASE(LWS_CALLBACK_MQTT_CLIENT_CLOSED); CASE(LWS_CALLBACK_MQTT_ACK); CASE(LWS_CALLBACK_MQTT_RESEND); -#if (LWS_LIBRARY_VERSION_MAJOR >= 4) && (LWS_LIBRARY_VERSION_MINOR >= 3) +#if ((LWS_LIBRARY_VERSION_MAJOR > 4) || (LWS_LIBRARY_VERSION_MAJOR == 4) && (LWS_LIBRARY_VERSION_MINOR >= 3)) CASE(LWS_CALLBACK_MQTT_UNSUBSCRIBE_TIMEOUT); CASE(LWS_CALLBACK_MQTT_SHADOW_TIMEOUT); #endif From 51818852a4d4cb1f48387e4eecad7f8c3d300bb4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 11:47:12 -0400 Subject: [PATCH 0410/2693] initial implementation --- CMakeLists.txt | 52 +++ cmake/simulatorConfig.cmake.in | 13 + include/xo/simulator/EventSink.hpp | 19 + include/xo/simulator/Simulator.hpp | 299 ++++++++++++ include/xo/simulator/SourceTimestamp.hpp | 107 +++++ include/xo/simulator/TimeSlip.hpp | 39 ++ include/xo/simulator/init_simulator.hpp | 20 + src/simulator/CMakeLists.txt | 19 + src/simulator/Simulator.cpp | 550 +++++++++++++++++++++++ src/simulator/SourceTimestamp.cpp | 32 ++ src/simulator/init_simulator.cpp | 31 ++ 11 files changed, 1181 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/simulatorConfig.cmake.in create mode 100644 include/xo/simulator/EventSink.hpp create mode 100644 include/xo/simulator/Simulator.hpp create mode 100644 include/xo/simulator/SourceTimestamp.hpp create mode 100644 include/xo/simulator/TimeSlip.hpp create mode 100644 include/xo/simulator/init_simulator.hpp create mode 100644 src/simulator/CMakeLists.txt create mode 100644 src/simulator/Simulator.cpp create mode 100644 src/simulator/SourceTimestamp.cpp create mode 100644 src/simulator/init_simulator.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..8f99f0a2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,52 @@ +# xo-simulator/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(simulator VERSION 1.0) +enable_language(CXX) + +# common XO cmake macros (see proj/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() +# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) +add_code_coverage() +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) + +# ---------------------------------------------------------------- +# c++ settings + +# one-time project-specific c++ flags. usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- + +add_subdirectory(src/simulator) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support for reactor customers + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# install project .hpp files + +xo_install_include_tree() + +# end CMakeLists.txt diff --git a/cmake/simulatorConfig.cmake.in b/cmake/simulatorConfig.cmake.in new file mode 100644 index 00000000..4f721e9f --- /dev/null +++ b/cmake/simulatorConfig.cmake.in @@ -0,0 +1,13 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# note: changes to find_dependency() calls here +# must coordinate with xo_dependency() calls +# in xo-reactor/src/reactor/CMakeLists.txt +# +find_dependency(reactor) +#find_dependency(callback) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/simulator/EventSink.hpp b/include/xo/simulator/EventSink.hpp new file mode 100644 index 00000000..2804b1e6 --- /dev/null +++ b/include/xo/simulator/EventSink.hpp @@ -0,0 +1,19 @@ +/* @file EventSink.hpp */ + +#pragma once + +namespace xo { + namespace sim { + /* something that observes (consumes) events of type T. + * we deliberately hide event sinks from top-level of simulator scaffold, + * so that we don't have to impose a common event type for T + */ + template + class EventSink { + public: + void operator()(T const & x); + }; /*EventSink*/ + } /*namespace sim*/ +} /*namespace xo*/ + +/* end EventSink.hpp */ diff --git a/include/xo/simulator/Simulator.hpp b/include/xo/simulator/Simulator.hpp new file mode 100644 index 00000000..d7a0baa1 --- /dev/null +++ b/include/xo/simulator/Simulator.hpp @@ -0,0 +1,299 @@ +/* @file Simulator.hpp */ + +#pragma once + +#include "xo/reactor/Reactor.hpp" +#include "SourceTimestamp.hpp" +#include "xo/reactor/ReactorSource.hpp" +#include "xo/refcnt/Refcounted.hpp" +//#include "time/Time.hpp" +#include + +namespace xo { + namespace sim { + class TimeSlip; + + /* delay state-changing simulator command while handling + * simulator events. need this to permit reentrancy + */ + struct ReentrantSimulatorCmd { + enum SimulatorCmdEnum { NotifySourcePrimed, CompleteAddSource, CompleteRemoveSource }; + + using ReactorSource = xo::reactor::ReactorSource; + + public: + ReentrantSimulatorCmd() = default; + ReentrantSimulatorCmd(SimulatorCmdEnum cmd, + ref::rp const & src) + : cmd_{cmd}, src_{src} {} + + static ReentrantSimulatorCmd notify_source_primed(ref::rp const & src) { + return ReentrantSimulatorCmd(NotifySourcePrimed, src); + } + + static ReentrantSimulatorCmd complete_add_source(ref::rp const & src) { + return ReentrantSimulatorCmd(CompleteAddSource, src); + } + + static ReentrantSimulatorCmd complete_remove_source(ref::rp const & src) { + return ReentrantSimulatorCmd(CompleteRemoveSource, src); + } + + SimulatorCmdEnum cmd() const { return cmd_; } + ref::rp const & src() const { return src_; } + + private: + /* NotifySourcePrimed: deferred Simulator.notify_source_primed(.src) + * CompleteAddSource: deferred Simulator.complete_add_source(.src) + * CompleteRemoveSource: deferred Simulator.complete_remove_source(.src) + */ + SimulatorCmdEnum cmd_ = NotifySourcePrimed; + /* if .cmd=NotifySourcePrimed|CompleteAddSource|CompleteRemoveSource: reactor source */ + ref::rp src_; + }; /*ReentrantSimulatorCmd*/ + + /* Generic simulator + * + * - time advances monotonically + * - applies a modifiable set of sources + * + * A Simulator isn't an example of a Reactor, + * because it can't work with arbitrary Sources + * (may find it expedient to fake this later, + * so we can easily adopt + * Source.notify_reactor_add() / Source.notify_reactor_remove()) + * in a simulation context + */ + class Simulator : public reactor::Reactor { + public: + using ReactorSourcePtr = xo::reactor::ReactorSourcePtr; + using ReactorSource = xo::reactor::ReactorSource; + using utc_nanos = xo::time::utc_nanos; + using nanos = xo::time::nanos; + + public: + ~Simulator(); + + static ref::rp make(utc_nanos t0); + + /* value of .t0() is estabished in ctor. + * it will not change except across call to .advance_one() + * in particular .add_source() does not change .t0() + */ + utc_nanos t0() const { return t0_; } + + /* timestamp of last event delivered */ + utc_nanos last_tm() const { return last_tm_; } + /* total #of events delivered since sim start */ + uint64_t n_event() const { return n_event_; } + + /* true iff all simulation source are exhausted + * a newly-created simulator is in the exhausted state; + * it may transition to non-exhausted state across + * call to .add_source() + */ + bool is_exhausted() const { return this->src_v_.empty(); } + + /* true iff src has been added to this simulator + * (by .add_source()) + */ + bool is_source_present(ref::brw src) const; + + /* promise: + * .next_tm() > .t0() || .is_exhausted() + * + * .next_tm() may decrease across .add_source() call + * .next_tm() may increase across .advance_one() call + */ + utc_nanos next_tm() const; + + /* returns source that will be used for next simulator event. + * nullptr if no remaining sources + */ + ReactorSource * next_src() const; + + /* cross-reference realtime to simulated time, + * for throttled replay + */ + TimeSlip timeslip() const; + + /* compute throttled real time for next event. + * caller supplies: + * 1. a pair of timesstamps xref_ts = (sim_tm, real_tm) + * - xref_ts.sim_tm is time in simulation coords of last event + * (i.e. most recent available value of .last_tm()) + * - xref_ts.real_tm is wall clock time associated with simtime + * 2. a replay factor, representing desired + * elapsed_simulation_time : elapsed_real_time + * + * return value is realtime delay to apply before next simulated event, + * in order to maintain desired replay factor + * + * The incremental api here is intended to be used from a python thread. + * + * Expect python simulation loop like: + * import pysimulator + * + * replay_factor = 1.0 + * sim = pysimulator.Simulator.make(t0) + * sim.run_one() + * tslip = sim.timeslip() + * while(True): + * dt = sim.throttled_event_dt(tslip, replay_factor) + * sleep(dt) + * sim.run_one() + * + * This allows sleep() to be invoked from python, + * which plays nicely with python threading model + */ + nanos throttled_event_dt(TimeSlip xref_ts, + double replay_factor) const; + + /* current contents of simulation heap, in increasing time order. + * copies heap to drain it in heap order + */ + std::vector heap_contents() const; + + /* print heap contents to *p_scope. intended for diagnostics */ + void log_heap_contents(xo::scope * p_scope) const; + + /* human-readable string identifying this simulator */ + std::string display_string() const; + + /* emit the first available event from a single simulation source. + * resolve ties arbitrarily. + * + * returns the #of events dispatched + * (expect this always = 1) + */ + std::uint64_t advance_one_event(); + + /* run simulation until earliest event time t satisfies t > t1 + */ + void run_until(utc_nanos t1); + + /* run simulation at realtime speed, throttling according to replay_factor, + * until either: + * - simulation exhausted + * - n events handled, if n>0 + * - sim clock reaches t1, if t1>t0 + * + * see also .run_one(), .run_until(), .run_n(), .run() + * + * note: this method not suitable for use from python wrappers; + * would hold GIL until complete. + * for that use case better to implement throttled sim loop + * in python + * + * t1. if > .t0, limit sim to events with t < t1 + * n. if > 0, sim at most n events + * replay_factor. throttle sim to keep + * {elapsed sim time} <= replay_factor * {elapsed real time} + * return. #of events simmed + */ + uint64_t run_throttled_until(utc_nanos t1, + int32_t n, + double replay_factor); + + // ----- inherited from Reactor ----- + + /* notification when nonprimed source becomes primed + */ + virtual void notify_source_primed(ref::brw src) override; + + /* add a new simulation source. + * event that precede .t0 will be discarded. + * + * returns true if src added; false if already present + */ + virtual bool add_source(ref::brw src) override; + + /* remove simulation source. + * returns true if src removed; false if was not present + * + * (not typically needed for simulations) + */ + virtual bool remove_source(ref::brw src) override; + + /* synonym for .advance_one_event() */ + virtual std::uint64_t run_one() override; + + private: + explicit Simulator(utc_nanos t0); + + /* updates source timestamp in simulation heap. + * preserves + * + * Require: + * - src->is_primed() + * - .sim_heap[.sim_heap.size - 1] already refers to src + * + * need_pop_flag, if true, src is at back of heap vector, + * need to pop before re-inserting. + */ + void heap_update_source(ReactorSource * src, + bool need_pop_flag); + + /* insert source into .sim_heap. + * increase sim_heap.size() by +1 + */ + void heap_insert_source(ReactorSource * src); + + /* complete any reentrant work encountered + * while deliverying another event + */ + void complete_delivery_work(); + + /* complete reentrant call to .add_source() */ + void complete_add_source(ref::brw src); + /* complete reentrant call to .remove_source() */ + void complete_remove_source(ref::brw src); + + friend class RaiiDeliveryWork; + + private: + /* simulation heap: + * each unexhausted source appears + * exactly once, in increasing time order of next event + * + * Invariant: + * - all sources s in .sim_heap satisfy: + * - s.is_exhausted() = false + * - s.t0() >= .t0 + */ + std::vector sim_heap_; + + /* initial simulation clock */ + utc_nanos t0_; + + /* time of most recent simulated event */ + utc_nanos last_tm_; + + /* #of simulated events handled */ + uint64_t n_event_ = 0; + + /* simulation sources + * Invariant: + * - all source s in .src_v satisfy: + * EITHER + * 1. s.is_exhausted() = true + * OR + * 2.1 s.is_exhausted() = false + * 2.2 s.t0() >= .t0 + */ + std::vector src_v_; + + /* reentrancy protection. set during .advance_one_event() */ + bool delivery_in_progress_ = false; + + /* when certain Simulator methods are invoked + * while in the midst of delivering another event, + * must defer until delivery has completed + */ + std::vector reentrant_cmd_v_; + }; /*Simulator*/ + + } /*namespace sim*/ +} /*namespace xo*/ + +/* end Simulator.hpp */ diff --git a/include/xo/simulator/SourceTimestamp.hpp b/include/xo/simulator/SourceTimestamp.hpp new file mode 100644 index 00000000..380e81c4 --- /dev/null +++ b/include/xo/simulator/SourceTimestamp.hpp @@ -0,0 +1,107 @@ +/* file SourceTimestamp.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "xo/reactor/ReactorSource.hpp" + +namespace xo { + namespace sim { + /* remember a timestamp for a simulation source; + * use to insert a source into simulation heap. + * don't want to use SimulationSource.t0, so that we can + * promise heap invariants without reying on + * any behavior of SimulationSource. + * + * Note: Need to resolve ties between different sources, + * if they coincide on timestamp of next event. + * For now use SimulationSource address + */ + class SourceTimestamp { + public: + using ReactorSource = xo::reactor::ReactorSource; + using utc_nanos = xo::time::utc_nanos; + + public: + SourceTimestamp(utc_nanos t0, + ReactorSource * src) + : t0_(t0), src_(src) {} + + static int32_t compare(SourceTimestamp const & x, + SourceTimestamp const & y) { + using xo::time::utc_nanos; + using xo::time::nanos; + + nanos dt = x.t0_ - y.t0_; + + if(dt < nanos(0)) + return -1; + else if(dt > nanos(0)) + return +1; + + /* timestamps are equal */ + + std::ptrdiff_t dptr = (x.src() - y.src()); + + return dptr; + } /*compare*/ + + utc_nanos t0() const { return t0_; } + ReactorSource * src() const { return src_; } + + void display(std::ostream & os) const; + std::string display_string() const; + + private: + /* timestamp for this source */ + utc_nanos t0_; + /* simulation source + * promise: + * - src.t0() >= .t0 || src.is_exhausted + */ + ReactorSource * src_ = nullptr; + }; /*SourceTimestamp*/ + + inline bool operator==(SourceTimestamp const & x, + SourceTimestamp const & y) + { + return SourceTimestamp::compare(x, y) == 0; + } /*operator==*/ + + inline bool operator<(SourceTimestamp const & x, + SourceTimestamp const & y) + { + return SourceTimestamp::compare(x, y) < 0; + } /*operator<*/ + + inline bool operator<=(SourceTimestamp const & x, + SourceTimestamp const & y) + { + return SourceTimestamp::compare(x, y) <= 0; + } /*operator<=*/ + + inline bool operator>(SourceTimestamp const & x, + SourceTimestamp const & y) + { + return SourceTimestamp::compare(x, y) > 0; + } /*operator>*/ + + inline bool operator>=(SourceTimestamp const & x, + SourceTimestamp const & y) + { + return SourceTimestamp::compare(x, y) >= 0; + } /*operator>=*/ + + inline std::ostream & + operator<<(std::ostream & os, + SourceTimestamp const & x) + { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace sim*/ +} /*namespace xo*/ + +/* end SourceTimestamp.hpp*/ diff --git a/include/xo/simulator/TimeSlip.hpp b/include/xo/simulator/TimeSlip.hpp new file mode 100644 index 00000000..3399c8ff --- /dev/null +++ b/include/xo/simulator/TimeSlip.hpp @@ -0,0 +1,39 @@ +/* file TimeSlip.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +//#include "time/Time.hpp" + +namespace xo { + namespace sim { + /* helper class for a throttled simulation, + * where we want simulated time to evolve at a constant rate, + * relative to real elapsed time. + * + * A TimeSlip instance pins + * simulation-time coordinates to realtime coordinates + */ + class TimeSlip { + public: + using utc_nanos = xo::time::utc_nanos; + + public: + TimeSlip(utc_nanos sim_tm, utc_nanos real_tm) + : sim_tm_{sim_tm}, real_tm_{real_tm} {} + + utc_nanos sim_tm() const { return sim_tm_; } + utc_nanos real_tm() const { return real_tm_; } + + private: + utc_nanos sim_tm_; + utc_nanos real_tm_; + }; /*TimeSlip*/ + } /*namespace sim*/ + +} /*namespace xo*/ + + +/* end TimeSlip.hpp */ diff --git a/include/xo/simulator/init_simulator.hpp b/include/xo/simulator/init_simulator.hpp new file mode 100644 index 00000000..43137575 --- /dev/null +++ b/include/xo/simulator/init_simulator.hpp @@ -0,0 +1,20 @@ +/* file init_simulator.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "xo/subsys/Subsystem.hpp" + +namespace xo { + enum S_simulator_tag {}; + + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; +} /*namespace xo*/ + +/* end init_simulator.hpp */ diff --git a/src/simulator/CMakeLists.txt b/src/simulator/CMakeLists.txt new file mode 100644 index 00000000..172ee4c4 --- /dev/null +++ b/src/simulator/CMakeLists.txt @@ -0,0 +1,19 @@ +# xo-simulator/src/simulator/CMakeLists.txt + +set(SELF_LIB simulator) +set(SELF_SRCS + Simulator.cpp SourceTimestamp.cpp + init_simulator.cpp) + +xo_add_shared_library3(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) + +# ---------------------------------------------------------------- +# external dependencies + +# note: changes to xo_dependency() calls here +# must coordinate with find_dependency() calls +# in xo-simulator/cmake/simulatorConfig.cmake.in +# +xo_dependency(${SELF_LIB} reactor) +#xo_dependency(${SELF_LIB} webutil) +#xo_dependency(${SELF_LIB} callback) diff --git a/src/simulator/Simulator.cpp b/src/simulator/Simulator.cpp new file mode 100644 index 00000000..9835b1ed --- /dev/null +++ b/src/simulator/Simulator.cpp @@ -0,0 +1,550 @@ +/* @file Simulator.cpp */ + +//#include "time/Time.hpp" /*need this 1st for tag(., time_point)*/ +#include "init_simulator.hpp" +#include "Simulator.hpp" +#include "TimeSlip.hpp" +#include "xo/indentlog/scope.hpp" +#include +#include +#include + +namespace xo { + using xo::reactor::ReactorSource; + using xo::ref::brw; + using xo::time::utc_nanos; + using xo::time::nanos; + + namespace sim { + class RaiiDeliveryWork { + public: + RaiiDeliveryWork(Simulator * sim) : sim_{sim} { + this->sim_->delivery_in_progress_ = true; + } + + ~RaiiDeliveryWork() { + this->sim_->delivery_in_progress_ = false; + this->sim_->complete_delivery_work(); + } + + Simulator * sim_ = nullptr; + }; /*RaiiDeliveryWork*/ + + ref::rp + Simulator::make(utc_nanos t0) { + return new Simulator(t0); + } /*make*/ + + Simulator::Simulator(utc_nanos t0) : t0_(t0) + { + XO_SUBSYSTEM_REQUIRE(simulator); + } /*ctor*/ + + Simulator::~Simulator() { + scope log(XO_ENTER0(verbose), "clear heap.."); + + this->sim_heap_.clear(); + + if (log.enabled()) { + log("visit .src_v", xtag("size", this->src_v_.size())); + for (size_t i=0; isrc_v_.size(); ++i) { + log(":src_v[", i, "] ", this->src_v_[i].get()); + } + } + + log && log("clear .src_v", xtag("size", this->src_v_.size())); + + this->src_v_.clear(); + } /*dtor*/ + + bool + Simulator::is_source_present(brw src) const + { + for (ReactorSourcePtr const & s : this->src_v_) { + if (s == src) + return true; + } + + return false; + } /*is_source_pesent*/ + + utc_nanos + Simulator::next_tm() const { + if(this->sim_heap_.empty()) { + /* 0 remaining events in simulator */ + return this->t0(); + } + + return this->sim_heap_.front().t0(); + } /*next_tm*/ + + ReactorSource* + Simulator::next_src() const { + if (this->sim_heap_.empty()) { + /* 0 remaining events in simulator */ + return nullptr; + } + + return this->sim_heap_.front().src(); + } /*next_src*/ + + void + Simulator::notify_source_primed(brw src) + { + scope log(XO_ENTER1(always, src->debug_sim_flag())); + + brw sim_src + = brw::from(src); + + log && log(xtag("src", (sim_src.get() != nullptr)), + xtag("src.name", src->name())); + + if(!sim_src) + return; + + log && log(xtag("sim.name", sim_src->name()), + xtag("src.current_tm", sim_src->sim_current_tm()), + xtag("sim_heap.size", this->sim_heap_.size())); + + if (this->delivery_in_progress_) { + log && log("reentrant call to .notify_source_primed(), defer", + xtag("src.name", src->name())); + + /* defer reentrant work until delivery completes + * see .complete_delivery_work() + */ + this->reentrant_cmd_v_.push_back + (ReentrantSimulatorCmd::notify_source_primed(src.promote())); + } else { + /* inform Simulator when a source transitions from + * 'notready' to 'ready'. + * + * this means: + * - source knows its next event + * - source should be put back into .sim_heap + */ + this->heap_insert_source(sim_src.get()); + } + + //if (lscope.enabled()) + // this->log_heap_contents(&lscope); + } /*notify_source_primed*/ + + void + Simulator::complete_add_source(brw src) + { + /* also add to simulation heap */ + this->sim_heap_.push_back(SourceTimestamp(src->sim_current_tm(), + src.get())); + + /* use std::greater<> because we need a min-heap; + * smallest timestamp at the front + */ + std::push_heap(this->sim_heap_.begin(), + this->sim_heap_.end(), + std::greater()); + } /*complete_add_source*/ + + bool + Simulator::add_source(brw sim_src) + { + scope log(XO_ENTER1(always, sim_src->debug_sim_flag())); + + log && log("enter", + xtag("src", sim_src.get()), + xtag("src.name", sim_src->name())); + + if(!sim_src || this->is_source_present(sim_src)) + return false; + + log && log("advance to t0", + xtag("t0", this->t0())); + + sim_src->sim_advance_until(this->t0(), false /*!replay_flag*/); + + this->src_v_.push_back(sim_src.promote()); + + if(sim_src->is_exhausted()) { + log && log("source exhausted!"); + } else { + sim_src->notify_reactor_add(this /*reactor*/); + + log && log(xtag("src.sim_current_tm", sim_src->sim_current_tm())); + + if (sim_src->is_empty()) { + log && log("empty source, do not insert into .sim_heap"); + + /* if source is empty, don't add to sim heap yet. + * when source becomes non-empty, source will invoke + * .notify_source_primed() + * which will insert it into .sim_heap[] + */ + ; + } else if (this->delivery_in_progress_) { + log && log("reentrant add non-empty source, delay"); + + /* defer reentrant work until delivery completes + * see .complete_delivery_work() + */ + this->reentrant_cmd_v_.push_back + (ReentrantSimulatorCmd::complete_add_source(sim_src.promote())); + } else { + log && log("non-empty source, add to .sim_heap"); + + this->complete_add_source(sim_src); + } + } + + return true; + } /*add_source*/ + + void + Simulator::complete_remove_source(brw sim_src) + { + /* rebuild .sim_heap, with sim_src removed */ + std::vector sim_heap2; + + for(SourceTimestamp const & item : this->sim_heap_) { + if(item.src() == sim_src.get()) { + /* item refers to the source we are removing -> discard */ + ; + } else { + sim_heap2.push_back(item); + + std::push_heap(sim_heap2.begin(), + sim_heap2.end(), + std::greater()); + } + + /* now discard .sim_heap, replacing with sim_heap2 */ + this->sim_heap_ = std::move(sim_heap2); + } + } /*complete_remove_source*/ + + bool + Simulator::remove_source(brw sim_src) + { + scope log(XO_DEBUG(sim_src->debug_sim_flag())); + + log && log("enter", + xtag("src", sim_src.get()), + xtag("src.name", sim_src->name())); + + //brw sim_src = brw::from(src); + + if(!sim_src || !this->is_source_present(sim_src)) + return false; + + /* WARNING: O(n)implementation here */ + + if (this->delivery_in_progress_) { + /* defer reentrant work until delivery completes. + * see .complete_delivery_work() + */ + this->reentrant_cmd_v_.push_back + (ReentrantSimulatorCmd::complete_remove_source(sim_src.promote())); + } else { + this->complete_remove_source(sim_src); + } + + return true; + } /*remove_source*/ + + std::uint64_t + Simulator::run_one() { + return this->advance_one_event(); + } /*run_one*/ + + void + Simulator::heap_update_source(ReactorSource * src, bool need_pop_flag) + { + /* Require: + * .sim_heap[.sim_heap.size - 1] already refers to src + * just updating timestamp here + */ + + std::size_t simheap_z + = this->sim_heap_.size(); + + scope log(XO_DEBUG(src->debug_sim_flag()), + xtag("src.name", src->name()), + xtag("simheap_z", simheap_z), + xtag("src.sim_current_tm", src->sim_current_tm())); + + if (need_pop_flag) + this->sim_heap_.pop_back(); + /* re-insert at new timestamp */ + this->sim_heap_.push_back(SourceTimestamp(src->sim_current_tm(), src)); + + /* use std::greater<> because we need a min-heap; + * smallest timestamp at the front + */ + std::push_heap(this->sim_heap_.begin(), + this->sim_heap_.end(), + std::greater()); + } /*heap_update_source*/ + + void + Simulator::heap_insert_source(ReactorSource * src) + { + /* santify check -- src should not currently appear in heap */ + for (SourceTimestamp const & src_recd : this->sim_heap_) { + if(src_recd.src() == src) { + /* uh oh. src is already present in heap! */ + assert(false); + } + } + + // don't need this: .heap_update_source() will insert + //this->sim_heap_.push_back(SourceTimestamp(src->sim_current_tm(), src)); + + this->heap_update_source(src, false /*!need_pop_flag*/); + } /*heap_insert_source*/ + + void + Simulator::complete_delivery_work() + { + for (ReentrantSimulatorCmd const & cmd : this->reentrant_cmd_v_) { + scope log(XO_DEBUG(cmd.src() && cmd.src()->debug_sim_flag()), + "complete reentrant work", + xtag("src.name", cmd.src()->name())); + + switch (cmd.cmd()) { + case ReentrantSimulatorCmd::NotifySourcePrimed: + this->notify_source_primed(cmd.src()); + break; + case ReentrantSimulatorCmd::CompleteAddSource: + this->complete_add_source(cmd.src()); + break; + case ReentrantSimulatorCmd::CompleteRemoveSource: + this->complete_remove_source(cmd.src()); + break; + } + + //if (lscope.enabled()) + // this->log_heap_contents(&lscope); + } + + this->reentrant_cmd_v_.clear(); + } /*complete_delivery_work*/ + + TimeSlip + Simulator::timeslip() const + { + utc_nanos real_tm = std::chrono::system_clock::now(); + utc_nanos sim_tm = this->next_tm(); + + return TimeSlip(sim_tm, real_tm); + } /*timeslip*/ + + nanos + Simulator::throttled_event_dt(TimeSlip xref, + double replay_factor) const + { + if (replay_factor <= 0.0) + replay_factor = 1e-6; + + /* hi_sim_tm: simtime for next event to be handled */ + utc_nanos hi_sim_tm = this->next_tm(); + /* desired elapsed /real time/ from start of simulation to + * to when simulation handles event @ hi_sim_tm + */ + nanos sim_dt = (hi_sim_tm - xref.sim_tm()); + auto hi_real_tm = (xref.real_tm() + + std::chrono::duration_cast(sim_dt / replay_factor)); + utc_nanos now_tm = std::chrono::system_clock::now(); + + if (now_tm < hi_real_tm) + return hi_real_tm - now_tm; + else + return nanos(0); + } /*next_throttled_tm*/ + + std::vector + Simulator::heap_contents() const + { + std::vector heap = this->sim_heap_; + std::vector retval; + + while (!heap.empty()) { + retval.push_back(heap.front()); + + std::pop_heap(heap.begin(), heap.end(), + std::greater()); + heap.pop_back(); + } + + return retval; + } /*heap_contents*/ + + void + Simulator::log_heap_contents(scope * p_scope) const + { + std::vector heap = this->sim_heap_; + + p_scope->log("/ sim heap contents:"); + p_scope->log("| t0 name n_in_ev n_queued_out_ev n_out_ev"); + + while(!heap.empty()) { + SourceTimestamp const & ts = heap.front(); + + p_scope->log("|" + , " ", ts.t0() + , " ", ts.src()->name() + , " ", ts.src()->n_queued_out_ev() + , " ", ts.src()->n_out_ev()); + + std::pop_heap(heap.begin(), heap.end(), + std::greater()); + heap.pop_back(); + } + + p_scope->log("\\"); + } /*print_heap_contents*/ + + std::string + Simulator::display_string() const + { + return ""; + } /*display_string*/ + + std::uint64_t + Simulator::advance_one_event() + { + bool debug_flag = (this->loglevel() <= log_level::chatty); + + if(this->sim_heap_.empty()) { + scope log(XO_DEBUG(debug_flag)); + + /* nothing todo */ + return 0; + } + + uint32_t old_heap_z = this->sim_heap_.size(); + + /* *src is source with earliest timestamp */ + ReactorSource * src + = this->sim_heap_.front().src(); + + utc_nanos src_tm = this->sim_heap_.front().t0(); + + scope log(XO_DEBUG(debug_flag), + xtag("threshold-loglevel", this->loglevel()), + xtag("src", src != nullptr), + xtag("src.name", src->name()), + xtag("sim.src_tm", src_tm), + xtag("src.sim_current_tm", src->sim_current_tm()), + xtag("heap_z", old_heap_z)); + + /* NOTE: src.current_tm() isn't preserved across + * call to src.deliver_one() + */ + uint64_t retval = 0; + + { + RaiiDeliveryWork raii_work(this); + + retval = src->deliver_one(); + + this->last_tm_ = src_tm; + this->n_event_ += retval; + + /* note that src.t0 may have advanced */ + + /* moves just-consumed timestamp event for src + * to back of .sim_heap + */ + std::pop_heap(this->sim_heap_.begin(), + this->sim_heap_.end(), + std::greater()); + + /* now .sim_heap[.sim_heap.size() = 1].src() is src, + * with stale timestamp + */ + + if(src->is_exhausted() || src->is_notprimed()) { + /* remove src from .sim_ + * - if src->is_exhausted(), permanently + * - if src->is_notready(), until source calls + * .notify_source_ready() + */ + this->sim_heap_.pop_back(); + } else { + this->heap_update_source(src, true /*need_pop_flag*/); + } + + assert(raii_work.sim_); + } + + return retval; + } /*advance_one_event*/ + + void + Simulator::run_until(utc_nanos t1) + { + assert(!this->delivery_in_progress_); + + while(!this->is_exhausted()) { + utc_nanos t = this->next_tm(); + + if(t > t1) + break; + + this->advance_one_event(); + } /*loop until done*/ + } /*run_until*/ + + uint64_t + Simulator::run_throttled_until(utc_nanos t1, + int32_t n_max, + double replay_factor) + { + Subsystem::verify_all_initialized(); + + scope log(XO_ENTER0(info)); + + assert(!this->delivery_in_progress_); + + uint64_t n = 0; + + if(!this->is_exhausted()) { + n += this->run_one(); + } + + /* cross-reference real time with sim time */ + TimeSlip tslip = this->timeslip(); + + while(!this->is_exhausted()) { + if ((n_max > 0) && (n >= static_cast(n_max))) { + /* reached limit on #of events simmed */ + return n; + } + + if ((t1 > this->t0()) && (this->next_tm() > t1)) { + /* reached limit on sim time */ + return n; + } + + /* if sim time passing faster than realtime (scaled by replay_factor), + * wait for real elapsed time to catch up + */ + nanos wait_dt = this->throttled_event_dt(tslip, replay_factor); + + if (wait_dt > std::chrono::milliseconds(1)) { + log && log(xtag("sleep_dt", wait_dt)); + + std::this_thread::sleep_for(wait_dt); + } else { + /* don't bother throttling for period less than 1ms, linux != rtos */ + } + + n += this->run_one(); + } + + return n; + } /*run_throttled_until*/ + + } /*namespace sim*/ +} /*namespace xo*/ + +/* end Simulator.cpp */ diff --git a/src/simulator/SourceTimestamp.cpp b/src/simulator/SourceTimestamp.cpp new file mode 100644 index 00000000..c3dca025 --- /dev/null +++ b/src/simulator/SourceTimestamp.cpp @@ -0,0 +1,32 @@ +/* file SourceTimestamp.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "SourceTimestamp.hpp" +#include "xo/indentlog/print/tag.hpp" +#include "xo/indentlog/print/tostr.hpp" + +namespace xo { + using xo::xtag; + using xo::tostr; + + namespace sim { + void + SourceTimestamp::display(std::ostream & os) const + { + os << "(src_)); + os << ">"; + } /*display*/ + + std::string + SourceTimestamp::display_string() const + { + return tostr(*this); + } /*display_string*/ + } /*namespace sim*/ +} /*namespace xo*/ + +/* end SourceTimestamp.cpp */ diff --git a/src/simulator/init_simulator.cpp b/src/simulator/init_simulator.cpp new file mode 100644 index 00000000..33d039c3 --- /dev/null +++ b/src/simulator/init_simulator.cpp @@ -0,0 +1,31 @@ +/* file init_simulator.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "init_simulator.hpp" +#include "xo/reactor/init_reactor.hpp" + +namespace xo { + void + InitSubsys::init() + { + } /*init*/ + + InitEvidence + InitSubsys::require() + { + InitEvidence retval; + + /* subsystem dependencies for simulator/ */ + retval ^= InitSubsys::require(); + + /* simulator/'s own initialization code */ + retval ^= XO_SUBSYSTEM_PROVIDE(simulator, &init); + //retval ^= Subsystem::provide("simulator", &init); + + return retval; + } /*require*/ +} /*namespace xo*/ + +/* end init_simulator.cpp */ From ecd9cb5d8554a3f17262444b9e33c4df49422264 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 11:47:53 -0400 Subject: [PATCH 0411/2693] + .gitignore --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..8ea1f615 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# lsp keep state here +.cache +# typical build directories +build +ccov +# for lsp: manual symlink to chosen build directory +compile_commands.json From f472c58591a4fd6a10729851005f45dbf8f6406d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 11:50:29 -0400 Subject: [PATCH 0412/2693] github: + workflow --- .github/workflows/main.yml | 231 +++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..70c00fbf --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,231 @@ +name: build xo-simulator + xo dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + # configure cmake for subsys in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + + - name: Clone reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/reflect + + - name: Configure reflect + # configure cmake for reflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reflect + + - name: Build reflect + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} + + - name: Install reflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reflect + + # ---------------------------------------------------------------- + + - name: Clone callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/callback + + - name: Configure callback + # configure cmake for callback in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_callback -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/callback + + - name: Build callback + run: cmake --build ${{github.workspace}}/build_callback --config ${{env.BUILD_TYPE}} + + - name: Install callback + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_callback + + # ---------------------------------------------------------------- + + - name: Clone webutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-webutil + path: repo/webutil + + - name: Configure webutil + # configure cmake for webutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_webutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/webutil + + - name: Build webutil + run: cmake --build ${{github.workspace}}/build_webutil --config ${{env.BUILD_TYPE}} + + - name: Install webutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_webutil + + # ---------------------------------------------------------------- + + - name: Clone printjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-printjson + path: repo/printjson + + - name: Configure printjson + # configure cmake for printjson in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_printjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/printjson + + - name: Build printjson + run: cmake --build ${{github.workspace}}/build_printjson --config ${{env.BUILD_TYPE}} + + - name: Install printjson + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_printjson + + # ---------------------------------------------------------------- + + - name: Clone randomgen + uses: actions/checkout@v3 + with: + repository: Rconybea/randomgen + path: repo/randomgen + + - name: Configure randomgen + # configure cmake for randomgen in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/randomgen + + - name: Build randomgen + run: cmake --build ${{github.workspace}}/build_randomgen --config ${{env.BUILD_TYPE}} + + - name: Install randomgen + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_randomgen + + # ---------------------------------------------------------------- + + - name: Clone reactor + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-reactor + path: repo/reactor + + - name: Configure reactor + # configure cmake for reactor in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reactor -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reactor + + - name: Build reactor + run: cmake --build ${{github.workspace}}/build_reactor --config ${{env.BUILD_TYPE}} + + - name: Install reactor + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reactor + + # ---------------------------------------------------------------- + + - name: Configure self (simulator) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_simulator -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (simulator) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_simulator --config ${{env.BUILD_TYPE}} + + - name: Test self (simulator) + working-directory: ${{github.workspace}}/build_simulator + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From ebb6378da82e16ce2db42f559906c7ead7497101 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 11:55:27 -0400 Subject: [PATCH 0413/2693] github: supply xo-ordinaltree dep --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 70c00fbf..53837448 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -196,6 +196,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/ordinaltree + + - name: Configure ordinaltree + # configure cmake for ordinaltree in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/ordinaltree + + - name: Build ordinaltree + run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}} + + - name: Install ordinaltree + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_ordinaltree + + # ---------------------------------------------------------------- + - name: Clone reactor uses: actions/checkout@v3 with: From 1078c49269233ebbec285ac5888df85ca6d04137 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:06:07 -0400 Subject: [PATCH 0414/2693] initial implementation --- CMakeLists.txt | 52 +++ README.md | 57 ++++ cmake/processConfig.cmake.in | 13 + include/xo/process/AbstractRealization.hpp | 20 ++ .../xo/process/AbstractStochasticProcess.hpp | 19 ++ include/xo/process/BrownianMotion.hpp | 321 ++++++++++++++++++ include/xo/process/ExpProcess.hpp | 103 ++++++ include/xo/process/LogNormalProcess.hpp | 33 ++ include/xo/process/Realizable2Process.hpp | 58 ++++ include/xo/process/Realization.hpp | 72 ++++ include/xo/process/Realization2.hpp | 91 +++++ include/xo/process/RealizationCallback.hpp | 31 ++ include/xo/process/RealizationSource.hpp | 314 +++++++++++++++++ include/xo/process/RealizationState.hpp | 42 +++ include/xo/process/RealizationTracer.hpp | 112 ++++++ include/xo/process/StochasticProcess.hpp | 73 ++++ include/xo/process/UpxEvent.hpp | 54 +++ include/xo/process/UpxToConsole.hpp | 25 ++ include/xo/process/init_process.hpp | 21 ++ src/process/BrownianMotion.cpp | 102 ++++++ src/process/CMakeLists.txt | 19 ++ src/process/ExpProcess.cpp | 69 ++++ src/process/Realization.cpp | 5 + src/process/UpxEvent.cpp | 45 +++ src/process/UpxToConsole.cpp | 15 + src/process/init_process.cpp | 40 +++ utest/CMakeLists.txt | 28 ++ utest/ProcessReflect.test.cpp | 30 ++ utest/RealizationSource.test.cpp | 261 ++++++++++++++ utest/process_utest_main.cpp | 6 + 30 files changed, 2131 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/processConfig.cmake.in create mode 100644 include/xo/process/AbstractRealization.hpp create mode 100644 include/xo/process/AbstractStochasticProcess.hpp create mode 100644 include/xo/process/BrownianMotion.hpp create mode 100644 include/xo/process/ExpProcess.hpp create mode 100644 include/xo/process/LogNormalProcess.hpp create mode 100644 include/xo/process/Realizable2Process.hpp create mode 100644 include/xo/process/Realization.hpp create mode 100644 include/xo/process/Realization2.hpp create mode 100644 include/xo/process/RealizationCallback.hpp create mode 100644 include/xo/process/RealizationSource.hpp create mode 100644 include/xo/process/RealizationState.hpp create mode 100644 include/xo/process/RealizationTracer.hpp create mode 100644 include/xo/process/StochasticProcess.hpp create mode 100644 include/xo/process/UpxEvent.hpp create mode 100644 include/xo/process/UpxToConsole.hpp create mode 100644 include/xo/process/init_process.hpp create mode 100644 src/process/BrownianMotion.cpp create mode 100644 src/process/CMakeLists.txt create mode 100644 src/process/ExpProcess.cpp create mode 100644 src/process/Realization.cpp create mode 100644 src/process/UpxEvent.cpp create mode 100644 src/process/UpxToConsole.cpp create mode 100644 src/process/init_process.cpp create mode 100644 utest/CMakeLists.txt create mode 100644 utest/ProcessReflect.test.cpp create mode 100644 utest/RealizationSource.test.cpp create mode 100644 utest/process_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..b21be0ba --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,52 @@ +# xo-process/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(process VERSION 1.0) +enable_language(CXX) + +# common XO cmake macros (see proj/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() +# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) +add_code_coverage() +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) + +# ---------------------------------------------------------------- +# c++ settings + +# one-time project-specific c++ flags. usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- + +add_subdirectory(src/process) +add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support for reactor customers + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# install project .hpp files + +xo_install_include_tree() + +# end CMakeLists.txt diff --git a/README.md b/README.md new file mode 100644 index 00000000..154de215 --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# stochastic process library + +constructive, simulation-aware models for stochastic processes + +## Getting Started + +### build + install dependencies + +build+install these first + +- xo-reactor [github.com/Rconybea/xo-reactor](https://github.com/Rconybea/xo-reactor) +- randomgen [github.com/Rconybea/randomgen](https://github.com/Rconybea/randomgen) + +# build + install + +## build +``` +$ cd process +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +## build for unit test coverage +``` +$ cd xo-process +$ mkdir ccov +$ cd ccov +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + +## development + +### LSP support + +LSP looks for compile commands in the root of the source tree; +cmake creates them in the root of its build directory. + +``` +$ cd xo-process +$ ln -s build/compile_commands.json +``` + +## display cmake variables + +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text + +``` +$ cd xo-process/build +$ cmake -LAH +``` diff --git a/cmake/processConfig.cmake.in b/cmake/processConfig.cmake.in new file mode 100644 index 00000000..fa80b6be --- /dev/null +++ b/cmake/processConfig.cmake.in @@ -0,0 +1,13 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# note: changes to find_dependency() calls here +# must coordinate with xo_dependency() calls +# in xo-process/src/process/CMakeLists.txt +# +find_dependency(reflect) +#find_dependency(callback) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/process/AbstractRealization.hpp b/include/xo/process/AbstractRealization.hpp new file mode 100644 index 00000000..e664bb0a --- /dev/null +++ b/include/xo/process/AbstractRealization.hpp @@ -0,0 +1,20 @@ +/* file AbstractRealization.hpp + * + * author: Roland Conybeare, Nov 2022 + */ + +#pragma once + +#include "xo/reflect/SelfTagging.hpp" +#include "AbstractStochasticProcess.hpp" + +namespace xo { + namespace process { + class AbstractRealization : public reflect::SelfTagging { + public: + virtual ref::rp stochastic_process() const = 0; + }; /*AbstractRealization*/ + } /*namespace process*/ +} /*namespace xo*/ + +/* end AbstractRealization.hpp */ diff --git a/include/xo/process/AbstractStochasticProcess.hpp b/include/xo/process/AbstractStochasticProcess.hpp new file mode 100644 index 00000000..b21cc8d6 --- /dev/null +++ b/include/xo/process/AbstractStochasticProcess.hpp @@ -0,0 +1,19 @@ +/* file AbstractStochasticProcess.hpp + * + * author: Roland Conybeare, Nov 2022 + */ + +#pragma once + +#include "xo/reflect/SelfTagging.hpp" + +namespace xo { + namespace process { + class AbstractStochasticProcess : public reflect::SelfTagging { + }; /*AbstractStochasticProcess*/ + } /*namespace process*/ + +} /*namespace xo*/ + + +/* end AbstractStochasticProcess.hpp */ diff --git a/include/xo/process/BrownianMotion.hpp b/include/xo/process/BrownianMotion.hpp new file mode 100644 index 00000000..317b7ec3 --- /dev/null +++ b/include/xo/process/BrownianMotion.hpp @@ -0,0 +1,321 @@ +/* @file BrownianMotion.hpp */ + +#pragma once + +#include "Realizable2Process.hpp" +#include "Realization2.hpp" +#include "RealizationState.hpp" +#include "xo/randomgen/normalgen.hpp" +#include "xo/reflect/StructReflector.hpp" +#include "xo/reflect/TaggedPtr.hpp" +#include +#include + +namespace xo { + namespace process { + class BrownianMotionBase : public Realizable2Process { + public: + using nanos = xo::time::nanos; + + public: + BrownianMotionBase(utc_nanos t0, + double volatility) + : t0_{t0}, + volatility_(volatility), + vol2_day_{(volatility * volatility) * (1.0 / 365.25)} + {} + + /* brownian motion with constant volatility at this level */ + double volatility() const { return volatility_; } + double vol2_day() const { return vol2_day_; } + + /* compute variance that accumulates over time interval dt + * for this brownian motion + */ + double variance_dt(nanos dt) const; + + // ----- needed by Realization2 ----- + + virtual ref::rp> make_realization() override = 0; + + // ----- inherited from StochasticProcess ----- + + virtual utc_nanos t0() const override { return t0_; } + + protected: + /* generate sample given a random number from N(0,1) */ + double exterior_sample_impl(utc_nanos t, + event_type const & lo, + double x0); + + protected: + /* starting time for this process */ + utc_nanos t0_; + /* annual volatility (1-year := 365.25 days) for this process */ + double volatility_ = 0.0; + /* daily variance for this brownian motion */ + double vol2_day_ = 0.0; + }; /*BrownianMotionBase*/ + + /* representation for brownian motion. + * + * starting value of zero at time t0. + * for process with volatility s, variance for horizon dt is + * V = (s^2).dt + * + * ofc this means volatility has units 1/sqrt(t) + * + * event_type: something like std::pair + * value_type: double + */ + template + class BrownianMotion : public BrownianMotionBase { + public: + using self_type = BrownianMotion; + using rstate_type = StochasticProcess::event_type; + using TaggedRcptr = reflect::TaggedRcptr; + using normalgen_type = xo::rng::normalgen; + using nanos = xo::time::nanos; + + public: + /* t0. start time, + * sdev. annual sqrt volatility + * seed. initialize pseudorandom-number generator + */ + template + static ref::rp> make(utc_nanos t0, + double sdev, + Seed const & seed) + { + return new BrownianMotion(t0, sdev, seed); + } /*make*/ + + /* reflect BrownianMotion object representation */ + static void reflect_self() { + reflect::StructReflector> sr; + + if (sr.is_incomplete()) { +#ifdef NOT_USING + REFLECT_MEMBER(sr, t0); + REFLECT_MEMBER(sr, volatility); + REFLECT_MEMBER(sr, vol2_day); +#endif + REFLECT_MEMBER(sr, rng); + } + } /*reflect_self*/ + + virtual ~BrownianMotion() = default; + + /* see .make_realization(); coordinates with that */ + void rstate_sample_inplace(nanos dt, rstate_type * p_rstate) { + utc_nanos t0 = p_rstate->first; + utc_nanos t1 = t0 + dt; + double x1_n01 = this->rng_(); + + value_type x1 = this->exterior_sample(t1, *p_rstate, x1_n01); + + *p_rstate = std::make_pair(t1, x1); + } /*rstate_sample_inplace*/ + + // ----- inherited from BrownianMotionBase ----- + + // ----- inherited from Realizable2Process<> ----- + + virtual ref::rp> make_realization() override { + rstate_type rs0 = std::make_pair(this->t0(), + this->t0_value()); + + return new ProcessRealization2(rs0, this); + } /*make_realization*/ + + virtual std::unique_ptr make_rstate() override { + rstate_type rs0 = std::make_pair(this->t0(), + this->t0_value()); + + return std::unique_ptr(new RealizationState(rs0)); + } /*make_rstate*/ + + // ----- inherited from StochasticProcess<> ----- + + virtual double t0_value() const override { return 0.0; } + + /* sample this process at time t, + * given glb sample for this process lo={t_lo, x_lo}, t>t_lo + */ + virtual value_type exterior_sample(utc_nanos t, + event_type const &lo) override; + /* sample this process at time t, + * given glb sample for this process lo={t_lo, x_lo}, + * and lub sample for this process of hi={t_hi, x_hi}, + * t_lot1} for process hitting value a, + * given preceding known value + * {t1, v1} = {lo.first, lo.second} + */ + virtual utc_nanos hitting_time(double const &a, + event_type const &lo) override; +#endif + + /* return human-readable string identifying this process */ + virtual std::string display_string() const override { + return ""; + } + + // ----- Inherited from SelfTagging ----- + + virtual TaggedRcptr self_tp() override { return reflect::Reflect::make_rctp(this); } + + private: + template + BrownianMotion(utc_nanos t0, double sdev, Seed const & seed) + : BrownianMotionBase(t0, sdev), + rng_{normalgen_type::make(RngEngine(seed), + std::normal_distribution(0.0 /*mean*/, 1.0 /*sdev*/))} { + BrownianMotion::reflect_self(); + } + + private: + /* generates normally-distributed pseudorandom numbers, + * distributed according to N(0,1) + */ + normalgen_type rng_; + }; /*BrownianMotion*/ + + template + double + BrownianMotion::interior_sample(utc_nanos ts, + event_type const & lo, + event_type const & hi) + { + /* suppose we know values of a brownian motion + * at two points t1, t2: + * x1 = B(t1) + * x2 = B(t2) + * + * Want to sample B for some particular time ts in (t1,t2). + * + * First step is to de-drift B: + * + * considered sheared process B'(t): + * B'(dt) = -B(t1) + B(t1+dt) - u.dt, + * where u = (x2-x1).(t2-t1), t in (t1,t2) + * then + * B'(0) = 0 + * E[B'(dt)] = 0 + * V[B'(dt)] ~ dt + * + * so B' is also a brownian motion. + * We want to sample the conditional process + * B''(dt) = {B'(dt) | B'(t2-t1)=0} at some point ts in (0, t2-t1). + * The condition B'(t2-t1)=0 gives us: + * B(t1) + B''(t-t1) = B(t1), t=t1 + * B(t1) + B''(t-t1) = B(t2), t=t2 + * + * At ts: + * - the increment x = B(ts) - B(t1) is normally distributed, + * with variance proportional to (ts - t1). + * - the increment y = B(t2) - B(ts) is normally distributed, + * with variance proportional to (t2 - ts). + * + * Using bivariate normal prob density of two vars x,y + * with sdev sx, sy: + * + * 1 / x^2 y^2 \ + * p(x,y) = ---------- . exp | -(1/2) (------ + ------) | + * 2.pi.sx.sy \ sx^2 sy^2 / + * + * we can condition on y=-x to get conditional probability distribution + * + * 1 / x^2 . sy^2 + y^2 . sx^2 \ + * p(x) = ---------- . exp | -(1/2) --------------------------- | + * 2.pi.sx.sy \ sx^2 . sy^2 / + * + * + * 1 / x^2 . sy^2 + y^2 . sx^2 \ + * = ---------- . exp | -(1/2) --------------------------- | + * 2.pi.sx.sy \ sx^2 . sy^2 / + * + * 1 / x^2 . (sy^2 + sx^2) \ + * = ---------- . exp | -(1/2) --------------------------- | + * 2.pi.sx.sy \ sx^2 . sy^2 / + * + * / sx^2 . sy^2 \ + * let sxy = sqrt | ------------- | + * \ sx^2 + sy^2 / + * + * then + * 1 / x^2 \ + * p(x) = ---------- . exp | -(1/2) ----- | + * 2.pi.sx.sy \ sxy^2 / + * + * which is density for normal distribution with variance sxy^2, + * (scaled by constant 1 / sqrt(sx^2 + sy^2)); + * + * e.g. at midpoint between t1 and t2, is sx^2 = sy^2 = 1/2 : + * sxy^2 = 1/4 + * + * which is 1/2 the variance we'd see at midpoint if not constrained + * to B(t2)=x2 + */ + + utc_nanos lo_tm = lo.first; + double lo_x = lo.second; + utc_nanos hi_tm = hi.first; + double hi_x = hi.second; + + double t_frac = (ts - lo_tm) / (hi_tm - lo_tm); + + /* compute mean value, at t, relative to B(lo), + * of all brownian motions on [lo, hi] that + * start from B(lo) and end at B(hi), + * + * i.e. applying drift u = (x2 - x1)/(t2 - t1) two stationary BM + */ + double mean_dx = (hi_x - lo_x) * t_frac; + + /* t splits the interval [t1,t2] into two subintervals + * [t1,t] and [t,t2]. compute variances of brownian motion + * increments [t1,t], [t,t2]: + */ + double var1 = this->variance_dt(ts - lo_tm); + double var2 = this->variance_dt(hi_tm - ts); + + /* variance for B(ts) is (var1 * var2 / (var1 + var2)) */ + double vars = var1 * var2 / (var1 + var2); + + /* sample from N(0,1) */ + double xs = this->rng_(); + + /* scale for variance of B(ts) */ + double dx = ::sqrt(vars) * xs; + + double x = lo_x + mean_dx + dx; + + return x; + } /*interior_sample*/ + + template + double + BrownianMotion::exterior_sample(utc_nanos t, + event_type const & lo) + { + /* sample brownian motion starting at t0; + * offset by lo.second + */ + + double x0 = this->rng_(); + + return this->exterior_sample_impl(t, lo, x0); + } /*exterior_sample*/ + + + } /*namespace process*/ +} /*namespace xo*/ + +/* end BrownianMotion.hpp */ diff --git a/include/xo/process/ExpProcess.hpp b/include/xo/process/ExpProcess.hpp new file mode 100644 index 00000000..a49313bb --- /dev/null +++ b/include/xo/process/ExpProcess.hpp @@ -0,0 +1,103 @@ +/* @file ExpProcess.hpp */ + +#pragma once + +//#include "time/Time.hpp" +#include "StochasticProcess.hpp" +#include +#include + +namespace xo { + namespace process { + // a stochastic process + // + // S(t) + // P(t) = m.e + // + // where + // - m is a constant scale factor + // - S(t) is some already-defined-and-represented process + // + // In particular, if S(t) is brownian motion, + // then P(t) is log-normal + // + class ExpProcess : public StochasticProcess { + public: + using TaggedRcptr = reflect::TaggedRcptr; + + public: + static ref::rp make(double scale, + ref::brw> exp_proc) { + return new ExpProcess(scale, exp_proc); + } /*make*/ + + /* reflect ExpProcess object representation */ + static void reflect_self(); + + ref::brw> exponent_process() const { return exponent_process_.borrow(); } + + // ----- inherited from StochasticProcess<...> ----- + + virtual ~ExpProcess() = default; + + virtual utc_nanos t0() const override { return this->exponent_process_->t0(); } + + virtual double t0_value() const override { + return this->scale_ * ::exp(this->exponent_process_->t0_value()); + } + + /* note: lo is a sample from the exponentiated process; + * must take log to get sample from the exponent process + */ + virtual value_type exterior_sample(utc_nanos t, + event_type const & lo) override; + + /* note: lo, hi are samples from the exponentiated process; + * must take logs to get samples from the exponent process + */ + virtual value_type interior_sample(utc_nanos t, + event_type const & lo, + event_type const & hi) override { + double m + = this->scale_; + double e + = (this->exponent_process_->interior_sample + (t, + event_type(lo.first, ::log(lo.second)), + event_type(hi.first, ::log(hi.second)))); + + return m * ::exp(e); + } /*interior_sample*/ + + virtual std::string display_string() const override { + // return tostr("display_string(), ">"); + + return ""; + } /*display_string*/ + + // ----- Inherited from SelfTagging ----- + + virtual TaggedRcptr self_tp() override; + + private: + ExpProcess(double scale, ref::brw exp_proc) + : scale_(scale), + exponent_process_{exp_proc.get()} { + ExpProcess::reflect_self(); + } + + private: + /* modeling + * P(t) = m.exp(E(t)) + * where: + * - m is .scale + * - E(t) is .exponent_process + */ + double scale_ = 1.0; + /* exponentiate this process */ + ref::rp> exponent_process_; + }; /*ExpProcess*/ + } /*namespace process*/ +} /*namespace xo*/ + +/* end ExpProcess.hpp */ diff --git a/include/xo/process/LogNormalProcess.hpp b/include/xo/process/LogNormalProcess.hpp new file mode 100644 index 00000000..a7a3ae1a --- /dev/null +++ b/include/xo/process/LogNormalProcess.hpp @@ -0,0 +1,33 @@ +/* LogNormalProcess.hpp */ + +#pragma once + +#include "BrownianMotion.hpp" +#include "ExpProcess.hpp" + +namespace xo { + namespace process { + + /* log-normal process -- i.e. logs follow brownian motion + */ + class LogNormalProcess { + public: + using utc_nanos = xo::time::utc_nanos; + + public: + /* log-normal process starting at (t0, x0) */ + template + static ref::rp make(utc_nanos t0, double x0, + double sdev, Seed const & seed) { + + ref::rp> bm + = BrownianMotion::make(t0, sdev, seed); + + return ExpProcess::make(x0 /*scale*/, bm); + } /*make*/ + }; /*LogNormalProcess*/ + + } /*namespace process*/ +} /*namespace xo*/ + +/* end LogNormalProcess.hpp */ diff --git a/include/xo/process/Realizable2Process.hpp b/include/xo/process/Realizable2Process.hpp new file mode 100644 index 00000000..b05e889f --- /dev/null +++ b/include/xo/process/Realizable2Process.hpp @@ -0,0 +1,58 @@ +/* file Realizable2Process.hpp + * + * author: Roland Conybeare, Nov 2022 + */ + +#pragma once + +#include "StochasticProcess.hpp" +#include "Realization2.hpp" +#include "RealizationState.hpp" + +namespace xo { + namespace process { + /* a stochastic process p that interacts with a Realization2 + * This means: + * - p defines state (Rstate) sufficient to constructively unroll/unfold + * one of its own paths + * - p provides methods to implement such unfolding: + * - .make_realization() :: Realization2 + * create new realization of p. + * - .rstate_sample(t1,rs0) :: time x Rstate -> Rstate + * given runstate rs0 representing process state at some time t0, + * sample process at time t1, with t0<=t1 + * - in general can only sample process at a bounded set of points; + * sometimes useful to be able to generate samples consistently in + * non-monotonically-increasing time order. Algorithm to do this available + * for some, but not all p + * - .rstate_insample(t1,rs0,rs2) :: time x Rstate x Rstate -> Rstate + * given runstates rs0, rs2 representing process state at two times t0 + class Realizable2Process : public StochasticProcess { + public: + virtual ref::rp> make_realization() = 0; + /* make_rstate() will be used to establish nested state when a process is used + * as input to a transforming process (ex: ExpProcess). + * in that context the outer process' realization state will + * need to hold an abstract pointer to nested process' realization state, + * and use this method to establish that state. + */ + virtual std::unique_ptr make_rstate() = 0; + // Rstate rstate_init() const; + // void rstate_sample_implace(utc_nanos t1, Rstate * p_rs0 const; + }; /*Realizable2Process*/ + + } /*namespace process*/ +} /*namespace xo*/ + + +/* end Realizable2Process.hpp */ diff --git a/include/xo/process/Realization.hpp b/include/xo/process/Realization.hpp new file mode 100644 index 00000000..571b7b5a --- /dev/null +++ b/include/xo/process/Realization.hpp @@ -0,0 +1,72 @@ +/* @file Realization.hpp */ + +#pragma once + +#include "StochasticProcess.hpp" +//#include "time/Time.hpp" +//#include +#include +#include + +namespace xo { + namespace process { + +// realization of a stochastic process. +// interface designed to allow for lazy evaluation. +// +// since a process connects a family of random variables, +// a single process can have a generally unbounded number of distinct realizations. +// +// implications: +// - can only realize (or observe) a finite set of instants. +// - given process evolves continuously, +// want ability to revisit intervals that may already contain some realized instants. +// - achieve this by allowing for caching behavior +// + template + class Realization : public ref::Refcount { + public: + using utc_nanos = xo::time::utc_nanos; + using KnownMap = std::map; + using KnownIterator = typename KnownMap::const_iterator; + //using KnownRange = boost::iterator_range; + using KnownRange = decltype(std::views::all(KnownMap())); + + public: + static ref::rp make(ref::brw> p) { + return new Realization(p); + } /*make*/ + + ref::brw> process() const { return process_; } + + utc_nanos t0() const { return process_->t0(); } + + size_t n_known() const { return this->known_map_.size(); } + + /* require: .n_known() > 0 */ + utc_nanos lo_tm() const { return this->known_map_.begin().first(); } + utc_nanos hi_tm() const { return this->known_map_.rbegin().first(); } + + //KnownRange known_range() const { return boost::make_iterator_range(this->known_map_); } + KnownRange known_range() const { return std::views::all(this->known_map_); } + + // concept: + // realized_range() -> iterator_range + + private: + Realization(ref::brw> p) : process_{p} {} + + private: + /* stochastic process from which this realization is sampled */ + ref::rp> process_; + + /* process value (for this realization) has been established (sampled) + * at each time t in {.known_map[].first} + */ + KnownMap known_map_; + }; /*Realization*/ + + } /*namespace process*/ +} /*namespace xo*/ + +/* end Realization.hpp */ diff --git a/include/xo/process/Realization2.hpp b/include/xo/process/Realization2.hpp new file mode 100644 index 00000000..499d815b --- /dev/null +++ b/include/xo/process/Realization2.hpp @@ -0,0 +1,91 @@ +/* file Realization2.hpp + * + * author: Roland Conybeare, Nov 2022 + */ + +#pragma once + +#include "AbstractRealization.hpp" +#include "xo/reflect/Reflect.hpp" +//#include "time/Time.hpp" + +namespace xo { + namespace process { + template + class Realization2 : public AbstractRealization { + }; /*Realization2*/ + + /* Rstate: state needed to trace unfolding of a process + * realization; will be process-specific. + * + * Pattern like: + * StochasticProcess p + * Rstate rs + * Realization2 rz + * + * +----+ +----+ + * | rz +----| rs | + * +--+-+ +----+ + * | ^ + * +----+ | + * | p | <----/ + * +----+ + * + * rz owns rs, sends it to p to be modified as p needs + * p knows type Rstate, initially creates it + * therefore also knows how to create its own realizations + * + * + * Require: + * - Process -isa-> Realizable2Process + */ + template + class ProcessRealization2 : public Realization2 { + public: + using TaggedRcptr = reflect::TaggedRcptr; + using nanos = xo::time::nanos; + + public: + ProcessRealization2(Rstate const & rstate, ref::rp const & process) + : rstate_{rstate}, process_{process} {} + ProcessRealization2(Rstate && rstate, ref::rp const & process) + : rstate_{std::move(rstate)}, process_{process} {} + + Rstate const & rstate() const { return rstate_; } + ref::rp const & process() const { return process_; } + + /* sample process at point .rstate.tk + dt + * Require: + * - dt >= 0 + */ + void advance_dt(nanos dt) { + this->process_->rstate_sample_inplace(dt, &(this->rstate_)); + } /*advance_dt*/ + + // ----- inherited from AbstractRealization ----- + + virtual ref::rp stochastic_process() const override { + return process_; + } /*stochastic_process*/ + + // ----- inherited from SelfTagging ----- + + virtual TaggedRcptr self_tp() override { return reflect::Reflect::make_rctp(this); } + + private: + /* realization state + * this type is determined by .process; + * sufficient state to develop faithful realization + */ + Rstate rstate_; + /* process (set of paths + probability measure); + * *this coordinates with .process to constructively samples one such path + */ + ref::rp process_; + }; /*ProcessRealization2*/ + } /*namespace process*/ + +} /*namespace xo*/ + + +/* end Realization2.hpp */ diff --git a/include/xo/process/RealizationCallback.hpp b/include/xo/process/RealizationCallback.hpp new file mode 100644 index 00000000..314bdc2e --- /dev/null +++ b/include/xo/process/RealizationCallback.hpp @@ -0,0 +1,31 @@ +/* @file RealizationCallback.hpp */ + +#pragma once + +#include "xo/reactor/Sink.hpp" +#include "xo/indentlog/print/pair.hpp" +//#include "time/Time.hpp" +#include + +namespace xo { + namespace process { + /* callback for consuming stochastic process realizations */ + template + class RealizationCallback : public reactor::Sink1> { + public: + using utc_nanos = xo::time::utc_nanos; + + public: + /* notification with process event (std::pair) + * see StochasticProcess::event_type + */ + virtual void notify_ev(std::pair const & ev) override; + + /* CallbackSet invokes these on add/remove events */ + virtual void notify_add_callback() override {} + virtual void notify_remove_callback() override {} + }; /*RealizationCallback*/ + } /*namespace process*/ +} /*namespace xo*/ + +/* end RealizationCallback.hpp */ diff --git a/include/xo/process/RealizationSource.hpp b/include/xo/process/RealizationSource.hpp new file mode 100644 index 00000000..f5087942 --- /dev/null +++ b/include/xo/process/RealizationSource.hpp @@ -0,0 +1,314 @@ +/* @file RealizationSimSource.hpp */ + +#pragma once + +#include "xo/reactor/ReactorSource.hpp" +#include "RealizationTracer.hpp" +#include "RealizationCallback.hpp" +#include "xo/callback/CallbackSet.hpp" +#include "xo/indentlog/scope.hpp" +#include + +namespace xo { + namespace process { + /* use a discrete realization of a continuous stochastic process, + * as a simulation source. + * + * 1. Realization is developed lazily, (see RealizationTracer) + * 2. Use a fixed discretization interval to develop realization + * 3. events are consumed by Sink + * + * Require: + * - std::pair --convertible-to--> EventType + * - EventSink.notify_source_exhausted() + * - invoke EventSink(x), with x :: EventType + */ + template + class RealizationSourceBase : public xo::reactor::ReactorSource { + public: + using event_type = typename RealizationTracer::event_type; + using nanos = xo::time::nanos; + + public: + ~RealizationSourceBase() { + //constexpr char const * c_self = "RealizationSimSource<>::dtor"; + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG(c_logging_enabled), + "delete instance", + xtag("p", this)); + } /*dtor*/ + + static ref::rp + make(ref::rp> const & tracer, + nanos ev_interval_dt, + EventSink const & ev_sink) + { + using xo::scope; + using xo::xtag; + + constexpr bool c_logging_enabled = false; + + auto p = new RealizationSourceBase(tracer, ev_interval_dt, ev_sink); + + scope log(XO_DEBUG(c_logging_enabled), + "create instance", + xtag("p", p), + xtag("bytes", sizeof(RealizationSourceBase))); + + return p; + } /*make*/ + +#ifdef NOT_IN_USE + static ref::rp make(ref::rp> tracer, + nanos ev_interval_dt, + EventSink && ev_sink) + { + return new RealizationSimSource(tracer, ev_interval_dt, ev_sink); + } /*make*/ +#endif + + event_type const & current_ev() const { return this->tracer_->current_ev(); } + nanos ev_interval_dt() const { return ev_interval_dt_; } + + /* supplying this to allow for setting up cyclic pointer references */ + EventSink * ev_sink_addr() { return &(this->ev_sink_); } + + /* deliver current event to sink */ + void sink_one() const { + /* calling .ev_sink() can modify the callback set reentrantly + * (i.e. adding/removing callbacks) + * although this changes the state of .ev_sink, + * we want to treat this as not changing the state of *this + */ + RealizationSourceBase * self = const_cast(this); + + self->ev_sink_(this->tracer_->current_ev()); + } /*sink_one*/ + + // ----- inherited from ReactorSource ----- + + /* process realizations are always primed (at least for now) */ + virtual bool is_empty() const override { return false; } + /* stochastic process api doesn't have an end time; + * will need simulator to impose one + */ + virtual bool is_exhausted() const override { return false; } + + virtual utc_nanos sim_current_tm() const override { return this->tracer_->current_tm(); } + + virtual std::string const & name() const override { return name_; } + virtual void set_name(std::string const & x) override { this->name_ = x; } + virtual bool debug_sim_flag() const override { return debug_sim_flag_; } + virtual void set_debug_sim_flag(bool x) override { this->debug_sim_flag_ = x; } + + /* note: + * with replay_flag=true, treats tm as lower bound + */ + virtual std::uint64_t sim_advance_until(utc_nanos tm, bool replay_flag) override { + std::uint64_t retval = 0ul; + + if(replay_flag) { + while(this->sim_current_tm() < tm) { + retval += this->deliver_one(); + } + } else { + this->tracer_->advance_until(tm); + } + + return retval; + } /*advance_until*/ + + // ----- Inherited from AbstractSource ----- + + virtual TypeDescr source_ev_type() const override { + return reflect::Reflect::require(); + } + + /* Tracer is intended always to deliver non-volatile events */ + virtual bool is_volatile() const override { return false; } + + virtual uint32_t n_out_ev() const override { return n_out_ev_; } + /* no mechanism in RealizationSource to hold onto an outgoing event + * see reactor::SecondarySource for contrary example + */ + virtual uint32_t n_queued_out_ev() const override { return 0; } + + virtual std::uint64_t deliver_one() override { + ++(this->n_out_ev_); + this->sink_one(); + this->tracer_->advance_dt(this->ev_interval_dt_); + + return 1; + } /*deliver_one*/ + + virtual CallbackId attach_sink(ref::rp const & /*sink*/) override { + /* see RealizationSource */ + assert(false); + return CallbackId(); + } + + virtual void detach_sink(CallbackId /*id*/) override { + /* see RealizationSource */ + assert(false); + } + + virtual void display(std::ostream & os) const override { + using xo::xtag; + + os << "name()) + << xtag("n_out_ev", this->n_out_ev()) + //<< xtag("ev_interval_dt", ev_interval_dt_) + << ">"; + } /*display*/ + + virtual void visit_direct_consumers(std::function)> const &) override { + assert(false); + } + + protected: + RealizationSourceBase(ref::rp> const & tracer, + nanos ev_interval_dt, + EventSink const & ev_sink) + : tracer_{tracer}, + ev_sink_{std::move(ev_sink)}, + ev_interval_dt_{ev_interval_dt} {} + RealizationSourceBase(ref::rp> const & tracer, + nanos ev_interval_dt, + EventSink && ev_sink) + : tracer_{tracer}, + ev_sink_{std::move(ev_sink)}, + ev_interval_dt_(ev_interval_dt) {} + + private: + static constexpr std::string_view sc_self_type = xo::reflect::type_name>(); + + private: + /* reporting name for this source -- use when .debug_sim_flag is set */ + std::string name_; + /* if true reactor/simulator to log interaction with this source */ + bool debug_sim_flag_ = false; + /* counts lifetime #of events */ + uint32_t n_out_ev_ = 0; + /* produces events representing realized stochastic-process values */ + ref::rp> tracer_; + /* send stochastic-process events to this sink */ + EventSink ev_sink_; + /* discretize process using this interval: + * consecutive events from this simulation source will be at least + * .ev_interval_dt apart + */ + nanos ev_interval_dt_; + }; /*RealizationSourceBase*/ + + // ----- RealizationSource ----- + + template + class RealizationSource + : public RealizationSourceBase, + decltype(&reactor::Sink1::notify_ev)>> + + { + public: + using TypeDescr = reflect::TypeDescr; + using CallbackId = fn::CallbackId; + using utc_nanos = xo::time::utc_nanos; + using nanos = xo::time::nanos; + + public: + static ref::rp> make(ref::rp> const & tracer, + nanos ev_interval_dt) + { + return new RealizationSource(tracer, ev_interval_dt); + } /*make*/ + + CallbackId add_callback(ref::rp> const & cb) { + return this->ev_sink_addr()->add_callback(cb); + } /*add_callback*/ + + void remove_callback(CallbackId id) { + this->ev_sink_addr()->remove_callback(id); + } /*remove_callback*/ + + // ----- inherited from AbstractSource ----- + + /* alternative naming: + * .add_callback(sink) <--> .attach_sink(sink) + * .remove_callback(sink) <--> .detach_sink(sink) + */ + virtual CallbackId attach_sink(ref::rp const & sink) override { + /* ------- + * WARNING + * ------- + * spent some time chasing down clang behavior here. + * the call to + * reactor::Sink1<...>::require_native() + * fails unexpectedly because the template + * Sink1> + * and RealizationSource may come from different modules. + */ + + //using xo::scope; + //using xo::xtag; + + /* checking that sink handles events of type T + * This is quick-n-dirty. Want reflection here, so we can write + * a runtime type test + * sink->can_consume() + * w/out exploding vtable size + */ + constexpr std::string_view c_self_name + = "RealizationSource::attach_sink"; + + //scope lscope(c_self_name); + //lscope.log(xtag("T", reflect::type_name())); + + ref::rp> event_sink + = reactor::Sink1::require_native(c_self_name, sink); + + return this->add_callback(event_sink); + } /*attach_sink*/ + + virtual void detach_sink(CallbackId id) override { + /* see comment on .attach_sink() */ + + this->remove_callback(id); + } /*detach_sink*/ + + virtual void display(std::ostream & os) const override { + using xo::xtag; + + os << "name()) + << xtag("n_out_ev", this->n_out_ev()) + //<< xtag("ev_interval_dt", this->ev_interval_dt()) + << ">"; + } /*display*/ + + // ----- Inherited from AbstractEventProcessor ----- + + virtual void visit_direct_consumers(std::function)> const & fn) override { + + for(auto const & x : *(this->ev_sink_addr())) + fn(x.fn_.borrow()); + } /*visit_direct_consumers*/ + + private: + RealizationSource(ref::rp> const & tracer, + nanos ev_interval_dt) + : RealizationSourceBase + , + decltype(&reactor::Sink1::notify_ev)> + >(tracer, + ev_interval_dt, + fn::make_notify_cbset(&reactor::Sink1::notify_ev)) + {} + }; /*RealizationSource*/ + + } /*namespace process*/ +} /*namespace xo*/ + +/* end RealizationSource.hpp */ diff --git a/include/xo/process/RealizationState.hpp b/include/xo/process/RealizationState.hpp new file mode 100644 index 00000000..0d43c581 --- /dev/null +++ b/include/xo/process/RealizationState.hpp @@ -0,0 +1,42 @@ +/* file RealizationState.hpp + * + * author: Roland Conybeare, Nov 2022 + */ + +#pragma once + +#include + +/* opaque type representing state of an unfolding + * realization, for a StochasticProcess. + * Needs runtime polymorphism here so we can stack states + * e.g. to represent realization state for a process + * defined by transformation of another process. + * For example see ExpProcess. + * For now we don't refcount these; expect each process-realization + * to create its own stack, managed with unique_ptr<> + * + * See also: + * - ProcessRealization2 + * - Realizable2Process + */ +class AbstractRealizationState { +public: + AbstractRealizationState() = default; + + virtual ~AbstractRealizationState() = default; +}; /*RealizationState*/ + +template +class RealizationState : public AbstractRealizationState { +public: + RealizationState(Rstate const & rs) : rstate_{rs} {} + RealizationState(Rstate && rs) : rstate_{std::move(rs)} {} + + Rstate * p_rstate() { return &rstate_; } + +private: + Rstate rstate_; +}; /*RealizationState*/ + +/* end RealizationState.hpp */ diff --git a/include/xo/process/RealizationTracer.hpp b/include/xo/process/RealizationTracer.hpp new file mode 100644 index 00000000..a654b577 --- /dev/null +++ b/include/xo/process/RealizationTracer.hpp @@ -0,0 +1,112 @@ +/* @file RealizationTracer.hpp */ + +#pragma once + +#include "StochasticProcess.hpp" +#include "xo/refcnt/Refcounted.hpp" + +namespace xo { + namespace process { + //template class RealizationSimSource; + + /* One-way iteration over a realization (i.e. sampled path) + * belonging to a stochastic process. + * has a monotonically increasing 'current time'. + * can be adapted as a simulation source + * + * Example: + * utc_nanos t0 = ...; + * double sdev = 1.0; + * Seed seed; + * auto process = LogNormalProcess::make(t0, sdev, seed); + * auto tracer = RealizationTracer::make(process.get()); + */ + template + class RealizationTracer : public ref::Refcount { + public: + using Process = xo::process::StochasticProcess; + using process_type = Process; + /* something like std::pair */ + using event_type = typename Process::event_type; + using utc_nanos = xo::time::utc_nanos; + using nanos = xo::time::nanos; + + public: + static ref::rp make(ref::rp const & p) { + return new RealizationTracer(p); + } + + event_type const & current_ev() const { return current_; } + utc_nanos current_tm() const { return current_.first; } + /* value of this path at time t */ + T const & current_value() const { return current_.second; } + ref::rp const & process() const { return process_; } + + /* sample with fixed time: + * - advance to time t+dt, where t=.current_tm() + * - return new time and process value + * + * can use .advance_dt(dt) to avoid copying T + */ + std::pair next_dt(nanos dt) { + this->advance_dt(dt); + + return this->current_; + } /*next_dt*/ + + std::pair next_eps(double eps) { + this->advance_eps(eps); + + return this->current_; + } /*next_eps*/ + + /* sample with fixed time: + * - advance to point t+dt, with dt specified. + */ + void advance_dt(nanos dt) { + utc_nanos t1 = this->current_.first + dt; + + this->advance_until(t1); + } /*advance_dt*/ + + void advance_until(utc_nanos t1) { + event_type ev0 = this->current_; + + if(t1 <= ev0.first) { + /* tracer state already past t1 */ + } else { + T x1 = this->process_->exterior_sample(t1, ev0); + + /* careful! may not alter .current until after call to exterior_sample() + * returns + */ + + this->current_.first = t1; + this->current_.second = x1; + } + } /*advance_until*/ + +#ifdef NOT_IN_USE // need StochasticProcess.hitting_time() for this + /* sample with max change in process value eps. + * requires that T defines a norm under which eps + * can be interpreted + */ + virtual void advance_eps(double eps) = 0; +#endif + + private: + RealizationTracer(ref::rp const & p) + : current_(event_type(p->t0(), p->t0_value())), process_(p) {} + + private: + /* current (time, processvalue) associated with this realization */ + event_type current_; + + /* develop a sampled realization of this stochastic process */ + ref::rp process_; + }; /*RealizationTracer*/ + + } /*namespace process*/ +} /*namespace xo*/ + +/* end RealizationTracer.hpp */ diff --git a/include/xo/process/StochasticProcess.hpp b/include/xo/process/StochasticProcess.hpp new file mode 100644 index 00000000..3c6a4101 --- /dev/null +++ b/include/xo/process/StochasticProcess.hpp @@ -0,0 +1,73 @@ +/* @file StochasticProcess.hpp */ + +#pragma once + +#include "AbstractStochasticProcess.hpp" +//#include "refcnt/Refcounted.hpp" +//#include "time/Time.hpp" +#include +#include + +namespace xo { + namespace process { + +// abstraction for a stochastic process. +// - represents a probability space: +// - a collection of paths +// - an associated probability measure on path sapce +// - paths may vary continuously with time +// - need not be continuous +// - want to be able to use in simulation, +// in which case will likely require some discretization +// + template + class StochasticProcess : public AbstractStochasticProcess { + public: + using value_type = T; + using utc_nanos = xo::time::utc_nanos; + using event_type = std::pair; + + public: + virtual ~StochasticProcess() = default; + + /* starting time for this process */ + virtual utc_nanos t0() const = 0; + + /* starting value of this process */ + virtual T t0_value() const = 0; + + /* sample this process at time t, + * given preceding known value + * {t1, v1} + * with t1 < t + */ + virtual value_type exterior_sample(utc_nanos t, + event_type const & lo) = 0; + + /* sample this process at time t, + * given surrounding known values + * {t1, v1}, {t2, v2} + * with t1 < t < t2 + */ + virtual value_type interior_sample(utc_nanos t, + event_type const & lo, + event_type const & hi) = 0; + +#ifdef NOT_IN_USE + /* sample hitting time + * T(a) = inf{t : P(t)=a, t>t1} for process hitting value a, + * given preceding known value + * {t1, v1} = {lo.first, lo.second} + */ + virtual utc_nanos hitting_time(T const & a, + event_type const & lo) = 0; +#endif + + /* human-readable string identifying this process */ + virtual std::string display_string() const = 0; + }; /*StochasticProcess*/ + + } /*namespace process*/ +} /*namespace xo*/ + +/* end StochasticProcess.hpp */ diff --git a/include/xo/process/UpxEvent.hpp b/include/xo/process/UpxEvent.hpp new file mode 100644 index 00000000..51ad768b --- /dev/null +++ b/include/xo/process/UpxEvent.hpp @@ -0,0 +1,54 @@ +/* @file UpxEvent.hpp */ + +#pragma once + +#include "xo/indentlog/timeutil/timeutil.hpp" + +namespace xo { + namespace process { + /* typical representation for events emitted by a stochastic process + * writing this as a non-template class (instead of just template alias) + * because we want typeinfo to be generated + */ + class UpxEvent { + public: + using utc_nanos = xo::time::utc_nanos; + + public: + UpxEvent(); + //UpxEvent(std::pair const & x) : contents_{x} {} + UpxEvent(std::pair const & x) : tm_{x.first}, upx_{x.second} {} + //UpxEvent(utc_nanos tm, double x) : contents_{tm, x} {} + UpxEvent(utc_nanos tm, double x) : tm_{tm}, upx_{x} {} + + /* reflect UpxEvent object representation */ + static void reflect_self(); + + /* convenience -- e.g. so we can use with EventTimeFn */ + //utc_nanos tm() const { return contents_.first; } + utc_nanos tm() const { return tm_; } + //double upx() const { return contents_.second; } + double upx() const { return upx_; } + + void display(std::ostream & os) const; + std::string display_string() const; + + private: + /* note: earlier version inherited std::pair<>, but this exposed + * pybind11 problem when we tried to control printing + */ + utc_nanos tm_; + double upx_; + //std::pair contents_; + }; /*UpxEvent*/ + + inline std::ostream & + operator<<(std::ostream & os, UpxEvent const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace process*/ +} /*namespace xo*/ + +/* end UpxEvent.hpp */ diff --git a/include/xo/process/UpxToConsole.hpp b/include/xo/process/UpxToConsole.hpp new file mode 100644 index 00000000..50c259fd --- /dev/null +++ b/include/xo/process/UpxToConsole.hpp @@ -0,0 +1,25 @@ +/* @file UpxToConsole.hpp */ + +#pragma once + +#include "UpxEvent.hpp" +#include "xo/reactor/Sink.hpp" + +namespace xo { + namespace process { + /* trivial extension of SinkToConsole. + * hoping to workaroudn a typeinfo problem by getting typeinfo for Sink1 + * to appear in the process/ library instead of the process_py/ library. + * + * See FAQ "dynamic_cast *> fails unexpectedly for a template class" + */ + class UpxToConsole : public xo::reactor::SinkToConsole { + public: + UpxToConsole(); + + static ref::rp make(); + }; /*UpxToConsole*/ + } /*namespace process*/ +} /*namespace xo*/ + +/* end UpxToConsole.hpp */ diff --git a/include/xo/process/init_process.hpp b/include/xo/process/init_process.hpp new file mode 100644 index 00000000..5fcba67d --- /dev/null +++ b/include/xo/process/init_process.hpp @@ -0,0 +1,21 @@ +/* file init_process.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "xo/subsys/Subsystem.hpp" + +namespace xo { + /* tag to represent the process/ subsystem within ordered initialization */ + enum S_process_tag {}; + + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; +} /*namespace xo*/ + +/* end init_process.hpp */ diff --git a/src/process/BrownianMotion.cpp b/src/process/BrownianMotion.cpp new file mode 100644 index 00000000..0c000696 --- /dev/null +++ b/src/process/BrownianMotion.cpp @@ -0,0 +1,102 @@ +/* @file BrownianMotion.cpp */ + +#include "xo/reflect/TaggedPtr.hpp" +//#include "time/Time.hpp" +#include "BrownianMotion.hpp" +#include + +namespace xo { + using xo::time::utc_nanos; + using xo::scope; + using xo::xtag; + + namespace process { + double + BrownianMotionBase::variance_dt(nanos dt) const + { + constexpr uint64_t c_sec_per_day = (24L * 3600L); + constexpr double c_day_per_sec = (1.0 / c_sec_per_day); + + /* time-to-horizon in nanos */ + double dt_sec = std::chrono::duration(dt).count(); + double dt_day = dt_sec * c_day_per_sec; + + return this->vol2_day_ * dt_day; + } /*variance_dt*/ + + double + BrownianMotionBase::exterior_sample_impl(utc_nanos t, + BrownianMotionBase::event_type const & lo, + double x0) + { + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG(c_logging_enabled)); + + /* sample brownian motion starting at t0; + * offset by lo.second + */ + + utc_nanos lo_tm = lo.first; + double lo_x = lo.second; + + nanos dt = (t - lo_tm); + + /* variance at horizon t, relative to value at lo.first */ + double var = this->variance_dt(dt); + + /* scale for variance of B(t) - B(lo) */ + double dx = ::sqrt(var) * x0; + + double sample = lo_x + dx; + + log && log("result", + xtag("start-time", this->t0()), + xtag("vol2-day", this->vol2_day()), + xtag("lo.tm", lo_tm), + xtag("lo.x", lo_x), + xtag("dt-us", std::chrono::duration_cast(dt).count()), + xtag("var", var), + xtag("dx", dx)); + + return sample; + } /*exterior_sample_impl*/ + + // ----- BrownianMotion ----- + +#ifdef NOT_IN_USE + utc_nanos + BrownianMotion::hitting_time(double const & a, + event_type const & lo) + { + /* (1) + * probability density function p1(s) + * giving hitting time for brownian motion starting at 0, + * first time to reach a constant barrier a: + * + * a^2 + * - --- + * a 2.s + * p1(s) = ------------- . e + * sqrt(2.pi.s^3) + * + * (2) + * we also know probability density function p2(s) + * giving hitting time for brownian motion starting at 0, + * first time to reach expanding barrier a + ct: + * (i.e. T2 = inf{t : B(t) = c.t + a, t > 0}) + * + * (c.s + a)^2 + * - ----------- + * a 2.s + * p2(s) = -------------- . e + * sqrt(2.pi.s^3) + * + */ + } /*hitting_time*/ +#endif + + } /*namespace process*/ +} /*namespace xo*/ + +/* end BrownianMotion.cpp */ diff --git a/src/process/CMakeLists.txt b/src/process/CMakeLists.txt new file mode 100644 index 00000000..c6033dde --- /dev/null +++ b/src/process/CMakeLists.txt @@ -0,0 +1,19 @@ +# xo-process/src/process/CMakeLists.txt + +set(SELF_LIB process) +set(SELF_SRCS + BrownianMotion.cpp ExpProcess.cpp Realization.cpp UpxEvent.cpp UpxToConsole.cpp + init_process.cpp) + +xo_add_shared_library3(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) + +# ---------------------------------------------------------------- +# external dependencies + +# note: changes to xo_dependency() calls here +# must coordinate with find_dependency() calls +# in xo-process/cmake/processConfig.cmake.in +# +xo_dependency(${SELF_LIB} reflect) +#xo_dependency(${SELF_LIB} webutil) +#xo_dependency(${SELF_LIB} callback) diff --git a/src/process/ExpProcess.cpp b/src/process/ExpProcess.cpp new file mode 100644 index 00000000..7b5d36f7 --- /dev/null +++ b/src/process/ExpProcess.cpp @@ -0,0 +1,69 @@ +/* @file ExpProcess.cpp */ + +#include "xo/reflect/TaggedPtr.hpp" +#include "xo/reflect/StructReflector.hpp" +//#include "time/Time.hpp" +#include "ExpProcess.hpp" + +namespace xo { + using reflect::Reflect; + using reflect::StructReflector; + using reflect::TaggedRcptr; + using xo::scope; + using xo::xtag; + + namespace process { + void + ExpProcess::reflect_self() + { + StructReflector sr; + + if (sr.is_incomplete()) { + REFLECT_MEMBER(sr, scale); + REFLECT_MEMBER(sr, exponent_process); + } + } /*self_reflect*/ + + /* note: lo is a sample from the exponentiated process; + * must take log to get sample from the exponent process + */ + ExpProcess::value_type + ExpProcess::exterior_sample(utc_nanos t, + event_type const & lo) + { + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG(c_logging_enabled)); + + double lo_value = lo.second; + double log_lo_value = ::log(lo.second / this->scale_); + + double e + = (this->exponent_process_->exterior_sample + (t, + event_type(lo.first, log_lo_value))); + + double retval = this->scale_ * ::exp(e); + + log && log("result", + xtag("t", t), + xtag("lo.tm", lo.first), + xtag("lo.value", lo_value), + xtag("log(lo.value/m)", log_lo_value), + xtag("m", this->scale_), + xtag("e", e), + xtag("retval", retval)); + + return retval; + } /*exterior_sample*/ + + TaggedRcptr + ExpProcess::self_tp() + { + return Reflect::make_rctp(this); + } /*self_tp*/ + + } /*namespace process*/ +} /*namespace xo*/ + +/* end ExpProcess.cpp */ diff --git a/src/process/Realization.cpp b/src/process/Realization.cpp new file mode 100644 index 00000000..0697fc39 --- /dev/null +++ b/src/process/Realization.cpp @@ -0,0 +1,5 @@ +/* Realization.cpp */ + +#include "Realization.hpp" + +/* end Realization.cpp */ diff --git a/src/process/UpxEvent.cpp b/src/process/UpxEvent.cpp new file mode 100644 index 00000000..5faa97b8 --- /dev/null +++ b/src/process/UpxEvent.cpp @@ -0,0 +1,45 @@ +/* @file UpxEvent.cpp */ + +#include "UpxEvent.hpp" +#include "xo/reflect/StructReflector.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/tag.hpp" + +namespace xo { + using xo::reflect::StructReflector; + using xo::tostr; + using xo::xtag; + + namespace process { + UpxEvent::UpxEvent() = default; + + void + UpxEvent::reflect_self() + { + StructReflector sr; + + if (sr.is_incomplete()) { + //REFLECT_MEMBER(sr, contents); + REFLECT_MEMBER(sr, tm); + REFLECT_MEMBER(sr, upx); + } + } /*reflect_self*/ + + void + UpxEvent::display(std::ostream & os) const + { + os << "tm()) + << xtag("x", this->upx()) + << ">"; + } /*display*/ + + std::string + UpxEvent::display_string() const { + return tostr(*this); + } /*display_string*/ + + } /*namespace process*/ +} /*namespace xo*/ + +/* end UpxEvent.cpp */ diff --git a/src/process/UpxToConsole.cpp b/src/process/UpxToConsole.cpp new file mode 100644 index 00000000..f78a3499 --- /dev/null +++ b/src/process/UpxToConsole.cpp @@ -0,0 +1,15 @@ +/* @file UpxToConsole.cpp */ + +#include "UpxToConsole.hpp" + +namespace xo { + namespace process { + ref::rp + UpxToConsole::make() + { + return new UpxToConsole(); + } /*make*/ + + UpxToConsole::UpxToConsole() = default; + } /*namespace process*/ +} /*namespace xo*/ diff --git a/src/process/init_process.cpp b/src/process/init_process.cpp new file mode 100644 index 00000000..fead726d --- /dev/null +++ b/src/process/init_process.cpp @@ -0,0 +1,40 @@ +/* file init_process.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "init_process.hpp" +#include "xo/printjson/init_printjson.hpp" + +#include "UpxEvent.hpp" +#include "xo/subsys/Subsystem.hpp" + +namespace xo { + using xo::process::UpxEvent; + + void + InitSubsys::init() + { + UpxEvent::reflect_self(); + } /*init*/ + + InitEvidence + InitSubsys::require() + { + InitEvidence retval; + + /* direct subsystem dependencies for process/ + * + * UpxEventStore --uses-> printjson (via reactor/EventStore.hpp) + */ + retval ^= InitSubsys::require(); + + /* process/'s own initialization code */ + retval ^= Subsystem::provide("process", &init); + + return retval; + } /*require*/ + +} /*namespace xo*/ + +/* end init_process.cpp */ diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..39bd3870 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,28 @@ +# build unittest 'process/unittest' + +# These tests can use the Catch2-provided main + +set(SELF_EXE utest.process) +set(SELF_SRCS + ProcessReflect.test.cpp + RealizationSource.test.cpp + process_utest_main.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) +target_code_coverage(${SELF_EXE} AUTO ALL) + +# ---------------------------------------------------------------- +# internal dependencies (on this codebase) + +xo_self_dependency(${SELF_EXE} process) + +# ---------------------------------------------------------------- +# external dependencies + +xo_dependency(${SELF_EXE} simulator) +xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) + +# end CMakeLists.txt diff --git a/utest/ProcessReflect.test.cpp b/utest/ProcessReflect.test.cpp new file mode 100644 index 00000000..11ac995b --- /dev/null +++ b/utest/ProcessReflect.test.cpp @@ -0,0 +1,30 @@ +/* @file ProcessReflect.test.cpp */ + +#include "xo/process/init_process.hpp" +#include "xo/reflect/Reflect.hpp" +#include + +namespace xo { + using xo::reflect::TypeDescrBase; + + namespace ut { + static InitEvidence s_init = (InitSubsys::require()); + + TEST_CASE("process-reflect", "[reflect]") { + Subsystem::initialize_all(); + + char const * c_self = "TEST_CASE:process-reflect"; + constexpr bool c_logging_enabled = true; + + scope log(XO_DEBUG2(c_logging_enabled, c_self)); + + // this ought to work but doesn't (too much output?)... + //log && log(xo::reflect::reflected_types_printer()); + + xo::reflect::TypeDescrBase::print_reflected_types(std::cout); + } /*TEST_CASE(process-reflect)*/ + + } /*namespace ut*/ +} /*namespace xo*/ + +/* end ProcessReflect.test.cpp */ diff --git a/utest/RealizationSource.test.cpp b/utest/RealizationSource.test.cpp new file mode 100644 index 00000000..5d8e98c1 --- /dev/null +++ b/utest/RealizationSource.test.cpp @@ -0,0 +1,261 @@ +/* @file RealizationSource.test.cpp */ + +//#include "time/Time.hpp" +#include "xo/process/RealizationSource.hpp" +#include "xo/process/LogNormalProcess.hpp" +#include "xo/process/BrownianMotion.hpp" +#include "xo/randomgen/xoshiro256.hpp" +#include "xo/simulator/Simulator.hpp" +#include "xo/indentlog/print/printer.hpp" +#include "xo/indentlog/scope.hpp" +#include + +namespace xo { + using xo::sim::Simulator; + using xo::process::RealizationSourceBase; + using xo::process::RealizationSource; + using xo::process::RealizationTracer; + using xo::process::LogNormalProcess; + using xo::process::ExpProcess; + using xo::process::BrownianMotion; + using xo::rng::xoshiro256ss; + using xo::reactor::SinkToConsole; + using xo::ref::rp; + using xo::time::timeutil; + using xo::time::seconds; + using xo::time::utc_nanos; + //using xo::print::printer; + using xo::scope; + using xo::xtag; + using std::chrono::hours; + using std::chrono::minutes; + + namespace ut { + /* TODO: move this to time/utest/ */ + TEST_CASE("time-formatting", "[time][print]") { + /* TODO: unit test for time conversions */ + + constexpr char const * c_self = "TEST_CASE:time-formatting"; + constexpr bool c_logging_enabled = true; + + utc_nanos t0 = timeutil::ymd_hms_usec(20220610 /*ymd*/, + 162905 /*hms*/, + 123456 /*usec*/); + + std::stringstream ss; + xo::timeutil::print_utc_ymd_hms_usec(t0, ss); + + REQUIRE(ss.str() == "20220610:16:29:05.123456"); + +#ifdef NOT_IN_USE + BrownianMotion bm = BrownianMotion::make(xxx t0, + xxx dev, + xxx seed); +#endif + } /*TEST_CASE(time-formatting)*/ + + /* TODO: move this to simulator/utest/ */ + TEST_CASE("empty-simulation", "[simulation][trivial]") { + constexpr char const * c_self = "TEST_CASE:empty-simulation"; + constexpr bool c_logging_enabled = true; + + /* arbitrary 'starting time' */ + utc_nanos t0 = timeutil::ymd_hms_usec(20220610 /*ymd*/, + 162905 /*hms*/, + 123456 /*usec*/); + + rp sim = Simulator::make(t0); + sim->set_loglevel(log_level::chatty); + + REQUIRE(sim->is_exhausted()); + + utc_nanos t1 = t0 + hours(1); + + sim->run_until(t1); + + REQUIRE((sim->is_exhausted() || (sim->next_tm() > t1))); + } /*TEST_CASE(empty-simulation)*/ + + /* test simulator with a single source */ + TEST_CASE("sim-brownian-motion", "[process][simulation]") { + constexpr char const * c_self = "TEST_CASE:sim-brownian-motion"; + + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG2(c_logging_enabled, c_self)); + + /* arbitrary 'starting time' */ + utc_nanos t0 = timeutil::ymd_hms_usec(20220610 /*ymd*/, + 162905 /*hms*/, + 123456 /*usec*/); + + rp sim = Simulator::make(t0); + sim->set_loglevel(c_logging_enabled + ? log_level::chatty + : log_level::error); + + REQUIRE(sim->is_exhausted()); + + log && log("create brownian motion process 'bm'.."); + + ref::rp> bm + = BrownianMotion::make(t0, + 0.30 /*sdev -- annualized volatility*/, + 12345678UL /*seed*/); + + log && log("..done"); + + + log && log("create realization tracer.."); + + rp> tracer + = RealizationTracer::make(bm); + + log && log("..done"); + + std::vector> sample_v; + + auto sink + = ([&sample_v] + (std::pair const & ev) + { sample_v.push_back(ev); }); + + log && log("create sim source from tracer.."); + + /* what is step dt? */ + rp, double, decltype(sink)>> + sim_source + = RealizationSourceBase, double, decltype(sink)>::make(tracer, + std::chrono::seconds(1) /*ev_interval_dt*/, + sink); + + log && log("..done"); + + log && log("add sim source to simulator.."); + + sim->add_source(sim_source); + + log&& log("..done"); + + utc_nanos t1 = t0 + minutes(1); + + log && log("run sim.."); + + sim->run_until(t1); + + log && log("..done"); + + log && log("verify sample_v.."); + + /* 1-minute simulation with 1-second samples */ + REQUIRE(sample_v.size() == 61); + + utc_nanos sample_t0 = sample_v[0].first; + + for(size_t i = 0; i < sample_v.size(); ++i) { + REQUIRE(sample_v[i].first == t0 + seconds(i)); + } + + log && log("..done"); + + //lscope.log(xtag("sample_v.size", sample_v.size())); + + } /*TEST_CASE("sim-brownian-motion")*/ + + TEST_CASE("sim-brownian-motion-with-sink", "[process][simulation]") { + constexpr char const * c_self = "TEST_CASE:sim-brownian-motion-with-sink"; + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG2(c_logging_enabled, c_self)); + + utc_nanos t0 = timeutil::ymd_hms_usec(20220718 /*ymd*/, + 120000 /*hms*/, + 0 /*usec*/); + + auto bm + = BrownianMotion::make(t0, + 0.50 /*annualized volatility*/, + 65431123UL /*seed*/); + + auto tracer + = RealizationTracer::make(bm); + + auto realization + = RealizationSource, double>::make(tracer, + std::chrono::seconds(1) /*ev_interval_dt*/); + + rp>> sink + = new SinkToConsole>(); + + realization->attach_sink(sink); + } /*TEST_CASE(sim-brownian-motion-with-sink)*/ + + TEST_CASE("sim-lognormal", "[process][simulation]") { + constexpr char const * c_self = "TEST_CASE:sim-lognormal"; + constexpr bool c_logging_enabled = false; + + scope log(XO_LITERAL(log_level::never, c_self, "")); + + /* arbitrary 'starting time' */ + utc_nanos t0 = timeutil::ymd_hms_usec(20220610 /*ymd*/, + 162905 /*hms*/, + 123456 /*usec*/); + + rp sim = Simulator::make(t0); + sim->set_loglevel(c_logging_enabled + ? log_level::chatty + : log_level::error); + + REQUIRE(sim->is_exhausted()); + + rp ebm + (LogNormalProcess::make + (t0, + 1.0 /*x0*/, + 0.30 /*sdev -- annualized volatility*/, + 12345678UL /*seed*/)); + + /* recover the exponentiated process, for testing */ + //StochasticProcess * bm = ebm->exponent_process(); + + rp> tracer + = RealizationTracer::make(ebm.get()); + + /* will be: samples from log-normal brownian motion */ + std::vector> sample_v; + + /* collect process samples as sim runs */ + auto sink + = ([&sample_v] + (std::pair const & ev) + { sample_v.push_back(ev); }); + + rp, double, decltype(sink)>> + sim_source + = RealizationSourceBase, double, decltype(sink)>::make(tracer, + std::chrono::seconds(1) /*ev_interval_dt*/, + sink); + + sim->add_source(sim_source); + + utc_nanos t1 = t0 + minutes(1); + + sim->run_until(t1); + + /* 1-minute simulation with 1-second samples */ + REQUIRE(sample_v.size() == 61); + + utc_nanos sample_t0 = sample_v[0].first; + + for(size_t i = 0; i < sample_v.size(); ++i) { + REQUIRE(sample_v[i].first == t0 + seconds(i)); + /* exponentiated process will have strictly +ve values */ + REQUIRE(sample_v[i].second > 0.0); + } + + log && log(xtag("sample_v.size", sample_v.size())); + } /*TEST_CASE("sim-lognormal")*/ + } /*namespace ut*/ +} /*namespace xo*/ + +/* end RealizationSource.test.cpp */ diff --git a/utest/process_utest_main.cpp b/utest/process_utest_main.cpp new file mode 100644 index 00000000..b1338709 --- /dev/null +++ b/utest/process_utest_main.cpp @@ -0,0 +1,6 @@ +/* @file process_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end process_utest_main.cpp */ From f6a1fe7dc07bc49dad4b1310d2c6af2e12ed0d29 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:07:00 -0400 Subject: [PATCH 0415/2693] + .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..eff45bd2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +build*/* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json From dc723b7ce38d338f69db7baf6228f50f560776ce Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:08:37 -0400 Subject: [PATCH 0416/2693] github: + workflow --- .github/workflows/main.yml | 231 +++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..fd91d47f --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,231 @@ +name: build xo-process + xo dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + # configure cmake for subsys in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + + - name: Clone reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/reflect + + - name: Configure reflect + # configure cmake for reflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reflect + + - name: Build reflect + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} + + - name: Install reflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reflect + + # ---------------------------------------------------------------- + + - name: Clone callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/callback + + - name: Configure callback + # configure cmake for callback in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_callback -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/callback + + - name: Build callback + run: cmake --build ${{github.workspace}}/build_callback --config ${{env.BUILD_TYPE}} + + - name: Install callback + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_callback + + # ---------------------------------------------------------------- + + - name: Clone webutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-webutil + path: repo/webutil + + - name: Configure webutil + # configure cmake for webutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_webutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/webutil + + - name: Build webutil + run: cmake --build ${{github.workspace}}/build_webutil --config ${{env.BUILD_TYPE}} + + - name: Install webutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_webutil + + # ---------------------------------------------------------------- + + - name: Clone printjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-printjson + path: repo/printjson + + - name: Configure printjson + # configure cmake for printjson in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_printjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/printjson + + - name: Build printjson + run: cmake --build ${{github.workspace}}/build_printjson --config ${{env.BUILD_TYPE}} + + - name: Install printjson + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_printjson + + # ---------------------------------------------------------------- + + - name: Clone randomgen + uses: actions/checkout@v3 + with: + repository: Rconybea/randomgen + path: repo/randomgen + + - name: Configure randomgen + # configure cmake for randomgen in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/randomgen + + - name: Build randomgen + run: cmake --build ${{github.workspace}}/build_randomgen --config ${{env.BUILD_TYPE}} + + - name: Install randomgen + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_randomgen + + # ---------------------------------------------------------------- + + - name: Clone reactor + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-reactor + path: repo/reactor + + - name: Configure reactor + # configure cmake for reactor in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reactor -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reactor + + - name: Build reactor + run: cmake --build ${{github.workspace}}/build_reactor --config ${{env.BUILD_TYPE}} + + - name: Install reactor + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reactor + + # ---------------------------------------------------------------- + + - name: Configure self (process) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_process -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (process) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_process --config ${{env.BUILD_TYPE}} + + - name: Test self (process) + working-directory: ${{github.workspace}}/build_process + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From 6cb78eeb2fc2fde463d7cae5849d15f400fe9a89 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:12:12 -0400 Subject: [PATCH 0417/2693] github: + xo-simulator dep --- .github/workflows/main.yml | 19 +++++++++++++++++++ README.md | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fd91d47f..888fce40 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -215,6 +215,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone simulator + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-simulator + path: repo/simulator + + - name: Configure simulator + # configure cmake for simulator in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_simulator -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/simulator + + - name: Build simulator + run: cmake --build ${{github.workspace}}/build_simulator --config ${{env.BUILD_TYPE}} + + - name: Install simulator + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_simulator + + # ---------------------------------------------------------------- + - name: Configure self (process) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type diff --git a/README.md b/README.md index 154de215..53d0b6d0 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ constructive, simulation-aware models for stochastic processes build+install these first -- xo-reactor [github.com/Rconybea/xo-reactor](https://github.com/Rconybea/xo-reactor) +- xo-simulator [github.com/Rconybea/xo-simulator](https://github.com/Rconybea/xo-simulator) - randomgen [github.com/Rconybea/randomgen](https://github.com/Rconybea/randomgen) # build + install From eca9fd9eab2f9708c885a02317259f882a0f6ade Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:19:43 -0400 Subject: [PATCH 0418/2693] github: + xo-ordinaltree dep --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 53837448..c5719ada 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -215,6 +215,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/ordinaltree + + - name: Configure ordinaltree + # configure cmake for ordinaltree in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/ordinaltree + + - name: Build ordinaltree + run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}} + + - name: Install ordinaltree + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_ordinaltree + + # ---------------------------------------------------------------- + - name: Clone reactor uses: actions/checkout@v3 with: From 69d4c89aa60cd8b101b5cb9b19b93d86736a6050 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:19:58 -0400 Subject: [PATCH 0419/2693] doc: + README.md --- README.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..f62f480e --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# simulator library + +in-memory deterministic simulator + +## Getting Started + +### build + install dependencies + +build+install these first + +- xo-reactor [github.com/Rconybea/xo-reactor](https://github.com/Rconybea/xo-reactor) +- xo-ordinaltree [github.com/Rconybea/xo-ordinaltree](https://github.com/Rconybea/xo-ordinaltree) + +### build + install xo-simulator +``` +$ cd xo-simulator +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +### build for unit test coverage +``` +$ cd xo-simulator +$ mkdir ccov +$ cd ccov +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + +## Development + +### LSP support + +LSP looks for compile commands in the root of the source tree; +cmake creates them in the root of its build directory. + +``` +$ cd xo-simulator +$ ln -s build/compile_commands.json +``` + +### display cmake variables + +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text + +``` +$ cd xo-simulator/build +$ cmake -LAH +``` From ee154327ec8631351cd2fbc21bbe0a666eab11e3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:21:42 -0400 Subject: [PATCH 0420/2693] github: + xo-ordinaltree dep --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 888fce40..618a85e4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -196,6 +196,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/ordinaltree + + - name: Configure ordinaltree + # configure cmake for ordinaltree in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/ordinaltree + + - name: Build ordinaltree + run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}} + + - name: Install ordinaltree + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_ordinaltree + + # ---------------------------------------------------------------- + - name: Clone reactor uses: actions/checkout@v3 with: From 64f6d92f0456054ccf6d6ab5615f2ad96ea3232e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:21:53 -0400 Subject: [PATCH 0421/2693] doc: README nit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 53d0b6d0..fe4f6263 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ build+install these first ## build ``` -$ cd process +$ cd xo-process $ mkdir build $ cd build $ INSTALL_PREFIX=/usr/local # or wherever you prefer From c96029fa1bcff13bfa46e4f034d4cb1e44650bdb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:31:38 -0400 Subject: [PATCH 0422/2693] initial implementation --- CMakeLists.txt | 44 +++++++++++++++++++++++ README.md | 46 ++++++++++++++++++++++++ cmake/xo_pyprintjsonConfig.cmake.in | 4 +++ include/xo/pyprintjson/pyprintjson.hpp | 25 +++++++++++++ src/pyprintjson/CMakeLists.txt | 8 +++++ src/pyprintjson/EXAMPLES | 2 ++ src/pyprintjson/pyprintjson.cpp | 50 ++++++++++++++++++++++++++ src/pyprintjson/pyprintjson.hpp.in | 25 +++++++++++++ 8 files changed, 204 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/xo_pyprintjsonConfig.cmake.in create mode 100644 include/xo/pyprintjson/pyprintjson.hpp create mode 100644 src/pyprintjson/CMakeLists.txt create mode 100644 src/pyprintjson/EXAMPLES create mode 100644 src/pyprintjson/pyprintjson.cpp create mode 100644 src/pyprintjson/pyprintjson.hpp.in diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..6f326e13 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,44 @@ +# xo-pyprintjson/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_pyprintjson VERSION 1.0) +enable_language(CXX) + +# common XO cmake macros (see github.com:Rconybea/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() +# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) +add_code_coverage() +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) + +# ---------------------------------------------------------------- +# c++ settings (usually temporary) + +set(PROJECT_CXX_FLAGS "") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- +# sources + +add_subdirectory(src/pyprintjson) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/README.md b/README.md new file mode 100644 index 00000000..68fbdce3 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# python bindings for c++ printjson library (xo-printjson) + +## Getting Started + +### build + install dependencies + +- [github/Rconybea/xo-pyutil](https://github.com/Rconybea/xo-pyutil) +- [github/Rconybea/xo-printjson](https://github.com/Rconybea/xo-printjson) + +### build + install + +``` +$ cd xo-pyprintjson +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer, e.g. ~/local +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +### build for unit test coverage +``` +$ cd xo-pyprintjson +$ mkdir build-ccov +$ cd build-ccov +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCODE_COVERAGE=ON \ + -DCMAKE_BUILD_TYPE=Debug .. +``` + +### LSP (language server) support + +LSP looks for compile commands in the root of the source tree; +while Cmake creates them in the root of its build directory. + +``` +$ cd xo-pyprintjson +$ ln -s build/compile_commands.json # supply compile commands to LSP +``` diff --git a/cmake/xo_pyprintjsonConfig.cmake.in b/cmake/xo_pyprintjsonConfig.cmake.in new file mode 100644 index 00000000..9c15f36a --- /dev/null +++ b/cmake/xo_pyprintjsonConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/pyprintjson/pyprintjson.hpp b/include/xo/pyprintjson/pyprintjson.hpp new file mode 100644 index 00000000..c5fa007d --- /dev/null +++ b/include/xo/pyprintjson/pyprintjson.hpp @@ -0,0 +1,25 @@ +/* @file pyprintjson.hpp + * + * automatically generated from src/pyprintjson/pyprintjson.hpp.in + * see src/pyprintjson/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(PYPRINTJSON_MODULE_NAME(), m) { ... } + */ +#define PYPRINTJSON_MODULE_NAME() pyprintjson + +/* example: + * py::module_::import(PYPRINTJSON_MODULE_NAME_STR) + */ +#define PYPRINTJSON_MODULE_NAME_STR "pyprintjson" + +/* example: + * PYPRINTJSON_IMPORT_MODULE() + * replaces + * py::module_::import("pyprintjson") + */ +#define PYPRINTJSON_IMPORT_MODULE() py::module_::import("pyprintjson") + +/* end pyprintjson.hpp */ diff --git a/src/pyprintjson/CMakeLists.txt b/src/pyprintjson/CMakeLists.txt new file mode 100644 index 00000000..5e77c995 --- /dev/null +++ b/src/pyprintjson/CMakeLists.txt @@ -0,0 +1,8 @@ +# xo_pyprintjson/src/pyprintjson/CMakeLists.txt + +set(SELF_LIB pyprintjson) +set(SELF_SRCS pyprintjson.cpp) + +xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) + +xo_pybind11_dependency(${SELF_LIB} printjson) diff --git a/src/pyprintjson/EXAMPLES b/src/pyprintjson/EXAMPLES new file mode 100644 index 00000000..5965b9b6 --- /dev/null +++ b/src/pyprintjson/EXAMPLES @@ -0,0 +1,2 @@ +import pyprintjson +pj=pyprintjson.PrintJson() diff --git a/src/pyprintjson/pyprintjson.cpp b/src/pyprintjson/pyprintjson.cpp new file mode 100644 index 00000000..f49d4b41 --- /dev/null +++ b/src/pyprintjson/pyprintjson.cpp @@ -0,0 +1,50 @@ +/* @file pyprintjson.cpp */ + +// note: need pyreflect/ here bc pyreflect.hpp is generated, located in build directory +#include "pyprintjson.hpp" +#include "xo/pyreflect/pyreflect.hpp" + +#include "xo/printjson/PrintJson.hpp" +#include "xo/reflect/TaggedRcptr.hpp" +//#include "reflect/SelfTagging.hpp" +//#include "refcnt/Refcounted.hpp" +//#include "refcnt/Unowned.hpp" +#include "xo/pyutil/pyutil.hpp" +//#include +//#include +//#include +//#include + +namespace xo { + namespace py = pybind11; + + namespace json { + using xo::reflect::SelfTagging; + using xo::reflect::TaggedRcptr; + using xo::ref::rp; + using xo::ref::unowned_ptr; + + PYBIND11_MODULE(PYPRINTJSON_MODULE_NAME(), m) { + PYREFLECT_IMPORT_MODULE(); + + py::class_>(m, "PrintJson") + .def_static("instance", &PrintJsonSingleton::instance) + .def("print", + [](PrintJson & pj, TaggedRcptr p) + { + pj.print_tp(p, &std::cout); std::cout << "\n"; + }, + py::arg("value")) + .def("print", + [](PrintJson & pj, rp const & p) + { + pj.print_obj(p, &std::cout); std::cout << "\n"; + }, + py::arg("value")); + + //m.def("print_json", [](){ return PrintJsonSingleton::instance_ptr(); }); + } /*pyprintjson*/ + } /*namespace json*/ +} /*namespace xo*/ + +/* end pyprintjson.cpp */ diff --git a/src/pyprintjson/pyprintjson.hpp.in b/src/pyprintjson/pyprintjson.hpp.in new file mode 100644 index 00000000..d80e5aba --- /dev/null +++ b/src/pyprintjson/pyprintjson.hpp.in @@ -0,0 +1,25 @@ +/* @file pyprintjson.hpp + * + * automatically generated from src/pyprintjson/pyprintjson.hpp.in + * see src/pyprintjson/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(PYPRINTJSON_MODULE_NAME(), m) { ... } + */ +#define PYPRINTJSON_MODULE_NAME() @SELF_LIB@ + +/* example: + * py::module_::import(PYPRINTJSON_MODULE_NAME_STR) + */ +#define PYPRINTJSON_MODULE_NAME_STR "@SELF_LIB@" + +/* example: + * PYPRINTJSON_IMPORT_MODULE() + * replaces + * py::module_::import("pyprintjson") + */ +#define PYPRINTJSON_IMPORT_MODULE() py::module_::import("@SELF_LIB@") + +/* end pyprintjson.hpp */ From b7c2e1d15eded2aba2ff64e110ccdf2d0f82e96a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:32:03 -0400 Subject: [PATCH 0423/2693] + .gitignore --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..8ea1f615 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# lsp keep state here +.cache +# typical build directories +build +ccov +# for lsp: manual symlink to chosen build directory +compile_commands.json From dc253cd388bc53218c01fc2f88fbebf984a61790 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:33:10 -0400 Subject: [PATCH 0424/2693] doc: minor README improvements --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 68fbdce3..34b77909 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ $ make install ``` (also see .github/workflows/main.yml) +## Development + ### build for unit test coverage ``` $ cd xo-pyprintjson @@ -44,3 +46,14 @@ while Cmake creates them in the root of its build directory. $ cd xo-pyprintjson $ ln -s build/compile_commands.json # supply compile commands to LSP ``` + +### display cmake variables + +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text + +``` +$ cd xo-pyprintjson/build +$ cmake -LAH +``` From a1f935579bf0674d974c3124ffb5d8647fbdc095 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:36:12 -0400 Subject: [PATCH 0425/2693] github: + workflow --- .github/workflows/main.yml | 155 +++++++++++++++++++++++++++++ include/xo/pyreflect/pyreflect.hpp | 25 +++++ 2 files changed, 180 insertions(+) create mode 100644 .github/workflows/main.yml create mode 100644 include/xo/pyreflect/pyreflect.hpp diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..289c7040 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,155 @@ +name: build xo-pyreflect + dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + # configure cmake for subsys in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/reflect + + - name: Configure reflect + # configure cmake for reflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reflect + + - name: Build reflect + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} + + - name: Install reflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reflect + + # ---------------------------------------------------------------- + + - name: Clone pyutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyutil + path: repo/pyutil + + - name: Configure pyutil + # configure cmake for pyutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pyutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyutil + + - name: Build pyutil + run: cmake --build ${{github.workspace}}/build_pyutil --config ${{env.BUILD_TYPE}} + + - name: Install pyutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pyutil + + # ---------------------------------------------------------------- + + - name: Configure self (pyreflect) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_pyreflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (pyreflect) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_pyreflect --config ${{env.BUILD_TYPE}} + + - name: Test self (pyreflect) + working-directory: ${{github.workspace}}/build_pyreflect + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} diff --git a/include/xo/pyreflect/pyreflect.hpp b/include/xo/pyreflect/pyreflect.hpp new file mode 100644 index 00000000..bbaef729 --- /dev/null +++ b/include/xo/pyreflect/pyreflect.hpp @@ -0,0 +1,25 @@ +/* @file pyreflect.hpp + * + * automatically generated from src/xo_pyreflect/pyreflect.hpp.in + * see src/xo_pyreflect/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(PYREFLECT_MODULE_NAME(), m) { ... } + */ +#define PYREFLECT_MODULE_NAME() pyreflect + +/* example: + * py::module_::import(PYREFLECT_MODULE_NAME_STR) + */ +#define PYREFLECT_MODULE_NAME_STR "pyreflect" + +/* example: + * PYREFLECT_IMPORT_MODULE() + * replaces + * py::module_::import("pyreflect") + */ +#define PYREFLECT_IMPORT_MODULE() py::module_::import("pyreflect") + +/* end pyreflect.hpp */ From 758b883f0e73c257529984e679925427200b4ba4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:36:40 -0400 Subject: [PATCH 0426/2693] bugfix: remove generated .hpp file --- include/xo/pyreflect/pyreflect.hpp | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 include/xo/pyreflect/pyreflect.hpp diff --git a/include/xo/pyreflect/pyreflect.hpp b/include/xo/pyreflect/pyreflect.hpp deleted file mode 100644 index bbaef729..00000000 --- a/include/xo/pyreflect/pyreflect.hpp +++ /dev/null @@ -1,25 +0,0 @@ -/* @file pyreflect.hpp - * - * automatically generated from src/xo_pyreflect/pyreflect.hpp.in - * see src/xo_pyreflect/CMakeLists.txt - */ - -/* python requires module name = library name - * example: - * PYBIND11_MODULE(PYREFLECT_MODULE_NAME(), m) { ... } - */ -#define PYREFLECT_MODULE_NAME() pyreflect - -/* example: - * py::module_::import(PYREFLECT_MODULE_NAME_STR) - */ -#define PYREFLECT_MODULE_NAME_STR "pyreflect" - -/* example: - * PYREFLECT_IMPORT_MODULE() - * replaces - * py::module_::import("pyreflect") - */ -#define PYREFLECT_IMPORT_MODULE() py::module_::import("pyreflect") - -/* end pyreflect.hpp */ From 711b0a580f74d3f69606f801a056a300b80852b8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:45:47 -0400 Subject: [PATCH 0427/2693] github: install pybind11-dev dep --- .github/workflows/main.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 289c7040..5d9c379b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,6 +25,9 @@ jobs: # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] run: sudo apt-get install -y catch2 + - name: Install pybind11-dev + run: sudo apt-get install -y pybind11-dev + # ---------------------------------------------------------------- - name: Clone xo-cmake From 5f927717b67aa1521cd888a6d4ab391c88f6df41 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:49:06 -0400 Subject: [PATCH 0428/2693] bugfix: compile fix to include path for generated pyreflect.hpp --- src/pyreflect/pyreflect.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyreflect/pyreflect.cpp b/src/pyreflect/pyreflect.cpp index 0b638658..c9b7f47a 100644 --- a/src/pyreflect/pyreflect.cpp +++ b/src/pyreflect/pyreflect.cpp @@ -1,7 +1,7 @@ /* @file pyreflect.cpp */ // note: need pyreflect/ here bc pyreflect.hpp is generated, located in build directory -#include "src/pyreflect/pyreflect.hpp" +#include "xo/pyreflect/pyreflect.hpp" #include "xo/reflect/TypeDescr.hpp" #include "xo/reflect/TaggedRcptr.hpp" #include "xo/reflect/SelfTagging.hpp" From c4ed2e7f22fa983913122ac31d936a3f8e8e277a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:54:16 -0400 Subject: [PATCH 0429/2693] github: + workflow --- .github/workflows/main.yml | 177 +++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..eb51827e --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,177 @@ +name: build xo-pyprintjson + dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + - name: Install pybind11-dev + run: sudo apt-get install -y pybind11-dev + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + # configure cmake for subsys in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/reflect + + - name: Configure reflect + # configure cmake for reflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reflect + + - name: Build reflect + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} + + - name: Install reflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reflect + + # ---------------------------------------------------------------- + + - name: Clone printjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-printjson + path: repo/printjson + + - name: Configure printjson + # configure cmake for printjson in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_printjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/printjson + + - name: Build printjson + run: cmake --build ${{github.workspace}}/build_printjson --config ${{env.BUILD_TYPE}} + + - name: Install printjson + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_printjson + + # ---------------------------------------------------------------- + + - name: Clone pyutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyutil + path: repo/pyutil + + - name: Configure pyutil + # configure cmake for pyutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pyutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyutil + + - name: Build pyutil + run: cmake --build ${{github.workspace}}/build_pyutil --config ${{env.BUILD_TYPE}} + + - name: Install pyutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pyutil + + # ---------------------------------------------------------------- + + - name: Configure self (pyprintjson) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_pyprintjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (pyprintjson) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_pyprintjson --config ${{env.BUILD_TYPE}} + + - name: Test self (pyprintjson) + working-directory: ${{github.workspace}}/build_pyprintjson + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From 70c76bd4a85e4b7bd6dd49c12ce759a68d6ffc13 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 13:29:42 -0400 Subject: [PATCH 0430/2693] xo-cmake: generate .hpp in build directory (src not writable w/ nix) --- cmake/xo_cxx.cmake | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 6a402cca..478cb80b 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -377,8 +377,15 @@ endmacro() # 2. pyfoo/pyfoo.hpp.in -> pyfoo/pyfoo.hpp # macro(xo_pybind11_library target projectTargets source_files) - configure_file(${target}.hpp.in - ${PROJECT_SOURCE_DIR}/include/xo/${target}/${target}.hpp) + configure_file( + ${target}.hpp.in + ${PROJECT_BINARY_DIR}/${target}.hpp) + # was ${PROJECT_SOURCE_DIR}/include/xo/${target}/${target}.hpp) + + install( + FILES ${PROJECT_BINARY_DIR}/${target}.hpp + PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/xo/${target}) # find_package(Python..) finds python in # /Library/Frameworks/Python.framework/... From 36e012934856062dfb8c6f5511ec00dcfcc01819 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 13:34:39 -0400 Subject: [PATCH 0431/2693] github: + pyreflect dep --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index eb51827e..af5526f6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -161,6 +161,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone pyreflect + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyreflect + path: repo/pyreflect + + - name: Configure pyreflect + # configure cmake for pyreflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pyreflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyreflect + + - name: Build pyreflect + run: cmake --build ${{github.workspace}}/build_pyreflect --config ${{env.BUILD_TYPE}} + + - name: Install pyreflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pyreflect + + # ---------------------------------------------------------------- + - name: Configure self (pyprintjson) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 10856b2ed9c55633a6b28238b6490221f3cd7785 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 13:39:08 -0400 Subject: [PATCH 0432/2693] bugfix: track modified path to generated include pyreflect.hpp --- src/pyreflect/pyreflect.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyreflect/pyreflect.cpp b/src/pyreflect/pyreflect.cpp index c9b7f47a..fc6e8fe8 100644 --- a/src/pyreflect/pyreflect.cpp +++ b/src/pyreflect/pyreflect.cpp @@ -1,7 +1,7 @@ /* @file pyreflect.cpp */ // note: need pyreflect/ here bc pyreflect.hpp is generated, located in build directory -#include "xo/pyreflect/pyreflect.hpp" +#include "pyreflect.hpp" #include "xo/reflect/TypeDescr.hpp" #include "xo/reflect/TaggedRcptr.hpp" #include "xo/reflect/SelfTagging.hpp" From 63d63f953f1cdcbb6ddab27ca181973721a953d1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 15:53:41 -0400 Subject: [PATCH 0433/2693] + include/README.md so git checkout creates dir --- include/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 include/README.md diff --git a/include/README.md b/include/README.md new file mode 100644 index 00000000..4454f162 --- /dev/null +++ b/include/README.md @@ -0,0 +1 @@ +placeholder for future pyreflect #include files From 42cd9aedca5834577d122a6f2eba3dcd0730c7d6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 16:06:09 -0400 Subject: [PATCH 0434/2693] initial implementation --- CMakeLists.txt | 43 +++++++++++++++++++++++++++++++ EXAMPLES | 9 +++++++ cmake/xo_pywebutilConfig.cmake.in | 4 +++ include/README.md | 1 + src/pywebutil/CMakeLists.txt | 7 +++++ src/pywebutil/pywebutil.cpp | 34 ++++++++++++++++++++++++ src/pywebutil/pywebutil.hpp.in | 25 ++++++++++++++++++ 7 files changed, 123 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 EXAMPLES create mode 100644 cmake/xo_pywebutilConfig.cmake.in create mode 100644 include/README.md create mode 100644 src/pywebutil/CMakeLists.txt create mode 100644 src/pywebutil/pywebutil.cpp create mode 100644 src/pywebutil/pywebutil.hpp.in diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..ed35dc91 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,43 @@ +# xo-pywebutil/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_pywebutil VERSION 0.1) +enable_language(CXX) + +# common XO cmake macros (see github.com:Rconybea/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() +# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) +add_code_coverage() +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) + +# ---------------------------------------------------------------- +# c++ settings (usually temporary) + +set(PROJECT_CXX_FLAGS "") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- +# sources + +add_subdirectory(src/pywebutil) + +# ---------------------------------------------------------------- +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/EXAMPLES b/EXAMPLES new file mode 100644 index 00000000..a495d316 --- /dev/null +++ b/EXAMPLES @@ -0,0 +1,9 @@ +Creating this pybind11 library to hold low-dependency wrappers +used for interaction between {websock/, pywebsock/} and other subsystems. + +1. This library needs to be a (runtime) dependency of pyxxx libraries that + use reactor::EventStore, for example pyprocess/, to supply wrappers + for EndpointDescr and Alist. + +2. If we chose to put this code in pywebsock/, then pywebsock + libwebsocket + would become runtime dependencies of libraries like pyprocess/. diff --git a/cmake/xo_pywebutilConfig.cmake.in b/cmake/xo_pywebutilConfig.cmake.in new file mode 100644 index 00000000..9c15f36a --- /dev/null +++ b/cmake/xo_pywebutilConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/README.md b/include/README.md new file mode 100644 index 00000000..3df846f2 --- /dev/null +++ b/include/README.md @@ -0,0 +1 @@ +placeholder for future pywebutil #include files diff --git a/src/pywebutil/CMakeLists.txt b/src/pywebutil/CMakeLists.txt new file mode 100644 index 00000000..0a8dc225 --- /dev/null +++ b/src/pywebutil/CMakeLists.txt @@ -0,0 +1,7 @@ +# xo-pywebutil/src/pywebutil/CMakeLists.txt + +set(SELF_LIB pywebutil) +set(SELF_SRCS pywebutil.cpp) + +xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) +xo_pybind11_dependency(${SELF_LIB} webutil) diff --git a/src/pywebutil/pywebutil.cpp b/src/pywebutil/pywebutil.cpp new file mode 100644 index 00000000..dbaa8ef6 --- /dev/null +++ b/src/pywebutil/pywebutil.cpp @@ -0,0 +1,34 @@ +/* @file pywebutil.cpp */ + +#include "pywebutil.hpp" +#include "xo/webutil/HttpEndpointDescr.hpp" +#include "xo/webutil/StreamEndpointDescr.hpp" +#include "xo/pyutil/pyutil.hpp" +#include + +namespace xo { + //using xo::web::Alist; + using xo::web::HttpEndpointDescr; + using xo::ref::rp; + //using xo::time::utc_nanos; + namespace py = pybind11; + + namespace web { + PYBIND11_MODULE(PYWEBUTIL_MODULE_NAME(), m) { + //PYxxx_IMPORT_MODULE(); + + /* module docstring */ + m.doc() = "pybind11 plugin for xo.web_util"; + + py::class_(m, "EndpointDescr") + .def_property_readonly("uri_pattern", &HttpEndpointDescr::uri_pattern) + .def("__repr__", &HttpEndpointDescr::display_string); + + py::class_(m, "StreamEndpointDescr") + .def_property_readonly("uri_pattern", &StreamEndpointDescr::uri_pattern) + .def("__repr__", &StreamEndpointDescr::display_string); + } /*web*/ + } /*namespace web*/ +} /*namespace xo*/ + +/* end pywebutil.cpp */ diff --git a/src/pywebutil/pywebutil.hpp.in b/src/pywebutil/pywebutil.hpp.in new file mode 100644 index 00000000..086701c1 --- /dev/null +++ b/src/pywebutil/pywebutil.hpp.in @@ -0,0 +1,25 @@ +/* @file pywebutil.hpp + * + * automatically generated from src/pywebutil/pywebutil.hpp.in + * see src/pywebutil/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(PYWEBUTIL_MODULE_NAME(), m) { ... } + */ +#define PYWEBUTIL_MODULE_NAME() @SELF_LIB@ + +/* example: + * py::module_::import(PYWEBUTIL_MODULE_NAME_STR) + */ +#define PYWEBUTIL_MODULE_NAME_STR "@SELF_LIB@" + +/* example: + * PYWEBUTIL_IMPORT_MODULE() + * replaces + * py::module_::import("pywebutil") + */ +#define PYWEBUTIL_IMPORT_MODULE() py::module_::import("@SELF_LIB@") + +/* end pywebutil.hpp */ From 368b65465585dfbdbfac9a3f63fbc205eb7b4ccf Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 16:06:41 -0400 Subject: [PATCH 0435/2693] + .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f52f1311 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# lsp keeps state here +.cache +# typical build directory +build +# lsp: symlink to file in build directory (established manually) +compile_commands.json From c3bc9f1b2e6c5cc46987c976fadfc08e6d964f20 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 16:12:48 -0400 Subject: [PATCH 0436/2693] github: + workflow --- .github/workflows/main.yml | 177 +++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..0b2676ed --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,177 @@ +name: build xo-pywebutil + dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + - name: Install pybind11-dev + run: sudo apt-get install -y pybind11-dev + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + +# # ---------------------------------------------------------------- +# +# - name: Clone subsys +# uses: actions/checkout@v3 +# with: +# repository: Rconybea/subsys +# path: repo/subsys +# +# - name: Configure subsys +# # configure cmake for subsys in dedicated build directory. +# run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys +# +# - name: Build subsys +# run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} +# +# - name: Install subsys +# # install into ${{github.workspace}}/local +# run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/callback + + - name: Configure callback + # configure cmake for callback in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_callback -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/callback + + - name: Build callback + run: cmake --build ${{github.workspace}}/build_callback --config ${{env.BUILD_TYPE}} + + - name: Install callback + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_callback + + # ---------------------------------------------------------------- + + - name: Clone webutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-webutil + path: repo/webutil + + - name: Configure webutil + # configure cmake for webutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_webutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/webutil + + - name: Build webutil + run: cmake --build ${{github.workspace}}/build_webutil --config ${{env.BUILD_TYPE}} + + - name: Install webutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_webutil + + # ---------------------------------------------------------------- + + - name: Clone pyutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyutil + path: repo/pyutil + + - name: Configure pyutil + # configure cmake for pyutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pyutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyutil + + - name: Build pyutil + run: cmake --build ${{github.workspace}}/build_pyutil --config ${{env.BUILD_TYPE}} + + - name: Install pyutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pyutil + + # ---------------------------------------------------------------- + + - name: Configure self (pywebutil) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_pywebutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (pywebutil) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_pywebutil --config ${{env.BUILD_TYPE}} + + - name: Test self (pywebutil) + working-directory: ${{github.workspace}}/build_pywebutil + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From 0b5fffc1208327a2162a71ba6f6325b7df60c545 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 16:13:56 -0400 Subject: [PATCH 0437/2693] doc: + README.md --- README.md | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..293fa8a4 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# python bindings for c++ webutil library (xo-webutil) + +## Getting Started + +### build + install dependencies + +- [github/Rconybea/xo-pyutil](https://github.com/Rconybea/xo-pyutil) +- [github/Rconybea/xo-webutil](https://github.com/Rconybea/xo-webutil) + +### build + install + +``` +$ cd xo-pywebutil +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer, e.g. ~/local +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +## Development + +### build for unit test coverage +``` +$ cd xo-pywebutil +$ mkdir build-ccov +$ cd build-ccov +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCODE_COVERAGE=ON \ + -DCMAKE_BUILD_TYPE=Debug .. +``` + +### LSP (language server) support + +LSP looks for compile commands in the root of the source tree; +while Cmake creates them in the root of its build directory. + +``` +$ cd xo-pywebutil +$ ln -s build/compile_commands.json # supply compile commands to LSP +``` + +### display cmake variables + +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text + +``` +$ cd xo-pywebutil/build +$ cmake -LAH +``` From 3487e3780cedf9f13589e6da53089ba9e76ec18d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 17:01:49 -0400 Subject: [PATCH 0438/2693] initial implementation --- CMakeLists.txt | 44 ++++++++++ README.md | 61 +++++++++++++ cmake/xo_pyreactorConfig.cmake.in | 4 + include/README.md | 1 + src/pyreactor/CMakeLists.txt | 8 ++ src/pyreactor/pyreactor.cpp | 141 ++++++++++++++++++++++++++++++ src/pyreactor/pyreactor.hpp.in | 25 ++++++ 7 files changed, 284 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/xo_pyreactorConfig.cmake.in create mode 100644 include/README.md create mode 100644 src/pyreactor/CMakeLists.txt create mode 100644 src/pyreactor/pyreactor.cpp create mode 100644 src/pyreactor/pyreactor.hpp.in diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..aec8d379 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,44 @@ +# xo-pyreactor/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_pyreactor VERSION 1.0) +enable_language(CXX) + +# common XO cmake macros (see github.com:Rconybea/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() +# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) +add_code_coverage() +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) + +# ---------------------------------------------------------------- +# c++ settings (usually temporary) + +set(PROJECT_CXX_FLAGS "") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- +# sources + +add_subdirectory(src/pyreactor) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/README.md b/README.md new file mode 100644 index 00000000..f618c641 --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +# python bindings for c++ reactor library (xo-reactor) + +## Getting Started + +### build + install dependencies + +- [github/Rconybea/xo-reactor](https://github.com/Rconybea/xo-reactor) +- [github/Rconybea/xo-pyutil](https://github.com/Rconybea/xo-pyutil) +- [github/Rconybea/xo-pyreflect](https://github.com/Rconybea/xo-pyreflect) +- [github/Rconybea/xo-pyprintjson](https://github.com/Rconybea/xo-pyprintjson) + +### build + install + +``` +$ cd xo-pyreactor +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer, e.g. ~/local +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +## Development + +### build for unit test coverage +``` +$ cd xo-pyreactor +$ mkdir build-ccov +$ cd build-ccov +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCODE_COVERAGE=ON \ + -DCMAKE_BUILD_TYPE=Debug .. +``` + +### LSP (language server) support + +LSP looks for compile commands in the root of the source tree; +while Cmake creates them in the root of its build directory. + +``` +$ cd xo-pyreactor +$ ln -s build/compile_commands.json # supply compile commands to LSP +``` + +### display cmake variables + +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text + +``` +$ cd xo-pyreactor/build +$ cmake -LAH +``` diff --git a/cmake/xo_pyreactorConfig.cmake.in b/cmake/xo_pyreactorConfig.cmake.in new file mode 100644 index 00000000..9c15f36a --- /dev/null +++ b/cmake/xo_pyreactorConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/README.md b/include/README.md new file mode 100644 index 00000000..4a0ad1c1 --- /dev/null +++ b/include/README.md @@ -0,0 +1 @@ +placeholder for future pyreactor #include files diff --git a/src/pyreactor/CMakeLists.txt b/src/pyreactor/CMakeLists.txt new file mode 100644 index 00000000..ad752e96 --- /dev/null +++ b/src/pyreactor/CMakeLists.txt @@ -0,0 +1,8 @@ +# xo_pyreactor/src/pyreactor/CMakeLists.txt + +set(SELF_LIB pyreactor) +set(SELF_SRCS pyreactor.cpp) + +xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) + +xo_pybind11_dependency(${SELF_LIB} reactor) diff --git a/src/pyreactor/pyreactor.cpp b/src/pyreactor/pyreactor.cpp new file mode 100644 index 00000000..5f8cf77b --- /dev/null +++ b/src/pyreactor/pyreactor.cpp @@ -0,0 +1,141 @@ +/* @file ReactorPy.cpp */ + +#include "pyreactor.hpp" +#include "xo/pyprintjson/pyprintjson.hpp" +#include "xo/pyreflect/pyreflect.hpp" + +#include "xo/reactor/Reactor.hpp" +#include "xo/reactor/ReactorSource.hpp" +#include "xo/reactor/EventStore.hpp" +#include "xo/reactor/Sink.hpp" +#include "xo/webutil/StreamEndpointDescr.hpp" +//#include "time/Time.hpp" + +//#include "xo/pyutil/pytime.hpp" +#include "xo/pyutil/pyutil.hpp" + +//#include +//#include +#include + +namespace xo { + using xo::json::PrintJsonSingleton; + using xo::fn::CallbackId; + using xo::ref::Refcount; + using xo::ref::rp; + using xo::time::utc_nanos; + using xo::tostr; + namespace py = pybind11; + + namespace reactor { + PYBIND11_MODULE(PYREACTOR_MODULE_NAME(), m) { + /* e.g. for TypeDescr */ + PYREFLECT_IMPORT_MODULE(); //py::module_::import("pyreflect"); + PYPRINTJSON_IMPORT_MODULE(); //py::module_::import("pyprintjson"); + + /* module docstring */ + m.doc() = "pybind11 plugin for xo.reactor"; + + m.def("time2str", [](utc_nanos tm) { return tostr(tm); }); + + /* TODO: if we write pycallback/, then CallbackId wrapper belongs there */ + py::class_(m, "CallbackId"); + + py::class_>(m, "AbstractEventProcessor") + .def_property("name", + &AbstractEventProcessor::name, + &AbstractEventProcessor::set_name) + .def("reference_counter", [](AbstractEventProcessor const & x) { return x.reference_counter(); }) + .def("memory_address", [](AbstractEventProcessor const & x) { return (void*)&x; }) + .def("map_network", [](AbstractEventProcessor & x) { return AbstractEventProcessor::map_network(&x); }) + .def("__repr__", &AbstractEventProcessor::display_string); + + py::class_>(m, "AbstractSource") + .def_property_readonly("source_ev_type", &AbstractSource::source_ev_type) + .def_property_readonly("is_volatile", &AbstractSource::is_volatile) + .def_property_readonly("n_out_ev", &AbstractSource::n_out_ev) + .def_property_readonly("n_queued_out_ev", &AbstractSource::n_queued_out_ev) + .def("attach_sink", &AbstractSource::attach_sink) + .def("detach_sink", &AbstractSource::detach_sink) + /* editor bait: websock_endpoint_descr */ + .def("stream_endpoint_descr", &AbstractSource::stream_endpoint_descr) + .def("deliver_one", &AbstractSource::deliver_one) + .def("deliver_n", &AbstractSource::deliver_n, + py::arg("n")); + + py::class_>(m, "AbstractSink") + //.cdef("__repr__", &AbstractSink::display_string) + .def_property_readonly("sink_ev_type", &AbstractSink::sink_ev_type) + .def_property_readonly("n_in_ev", &AbstractSink::n_in_ev) + .def("attach_source", &AbstractSink::attach_source); + + py::class_> + (m, "ReactorSource") + .def_property_readonly("is_empty", &ReactorSource::is_empty) + .def_property_readonly("is_nonempty", &ReactorSource::is_nonempty) + .def_property_readonly("is_exhausted", &ReactorSource::is_exhausted) + .def_property_readonly("sim_current_tm", &ReactorSource::sim_current_tm) + .def_property("debug_sim_flag", + &ReactorSource::debug_sim_flag, + &ReactorSource::set_debug_sim_flag); + + py::class_> + (m, "AbstractEventStore") + .def_property_readonly("empty", &AbstractEventStore::empty) + .def_property_readonly("size", &AbstractEventStore::size) + .def("http_snapshot", + [](AbstractEventStore & self) { + std::stringstream ss; + self.http_snapshot(PrintJsonSingleton::instance(), &ss); + return ss.str(); + }) + .def("http_endpoint_descr", + [](AbstractEventStore & self, std::string const & url_prefix) { + return self.http_endpoint_descr(PrintJsonSingleton::instance(), url_prefix); + }, + py::arg("url_prefix")) + .def("clear", + &AbstractEventStore::clear); + + py::class_> + (m, "Reactor") + .def("add_source", + [](Reactor & self, rp src) { + return self.add_source(src.borrow()); + }) + .def("remove_source", + [](Reactor & self, rp src) { + return self.remove_source(src.borrow()); + }) + .def("run_one", &Reactor::run_one) + .def("run_n", &Reactor::run_n, py::arg("n")); + +#ifdef NOT_IN_USE // trying removed code in ProcessPy.cpp instead for now + /* prints + * std::pair + * pairs + */ + m.def("make_realization_printer", + [] + { + return new SinkToConsole>(); + }); + + py::class_>, + AbstractSink, + xo::ref::rp>>> + (m, "SinkToConsole"); +#endif + } /*pyreactor*/ + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end ReactorPy.cpp */ diff --git a/src/pyreactor/pyreactor.hpp.in b/src/pyreactor/pyreactor.hpp.in new file mode 100644 index 00000000..edbb2e78 --- /dev/null +++ b/src/pyreactor/pyreactor.hpp.in @@ -0,0 +1,25 @@ +/* @file pyreactor.hpp + * + * automatically generated from src/pyreflect/pyreactor.hpp.in + * see src/pyreactor/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(PYREACTOR_MODULE_NAME(), m) { ... } + */ +#define PYREACTOR_MODULE_NAME() @SELF_LIBRARY_NAME@ + +/* example: + * py::module_::import(PYREACTOR_MODULE_NAME_STR) + */ +#define PYREACTOR_MODULE_NAME_STR "@SELF_LIBRARY_NAME@" + +/* example: + * PYREACTOR_IMPORT_MODULE() + * replaces + * py::module_::import("pyreactor") + */ +#define PYREACTOR_IMPORT_MODULE() py::module_::import("@SELF_LIBRARY_NAME@") + +/* end pyreactor.hpp */ From cc1c42b8c35c3a724d933551d34669e79e67af6c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 17:02:09 -0400 Subject: [PATCH 0439/2693] + .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f52f1311 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# lsp keeps state here +.cache +# typical build directory +build +# lsp: symlink to file in build directory (established manually) +compile_commands.json From 0e9ecec2ed83c94bd3a3cb4a638d724e0a6d1185 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 17:10:23 -0400 Subject: [PATCH 0440/2693] initial implementation --- CMakeLists.txt | 44 +++++++ README.md | 48 ++++++++ cmake/cmake | 4 + cmake/xo_pyprocessConfig.cmake.in | 4 + include/README.md | 1 + src/pyprocess/CMakeLists.txt | 8 ++ src/pyprocess/pyprocess.cpp | 183 ++++++++++++++++++++++++++++++ src/pyprocess/pyprocess.hpp.in | 25 ++++ 8 files changed, 317 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/cmake create mode 100644 cmake/xo_pyprocessConfig.cmake.in create mode 100644 include/README.md create mode 100644 src/pyprocess/CMakeLists.txt create mode 100644 src/pyprocess/pyprocess.cpp create mode 100644 src/pyprocess/pyprocess.hpp.in diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..af0bae5b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,44 @@ +# xo-pyprocess/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_pyprocess VERSION 0.1) +enable_language(CXX) + +# common XO cmake macros (see github.com:Rconybea/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() +# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) +add_code_coverage() +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) + +# ---------------------------------------------------------------- +# c++ settings (usually temporary) + +set(PROJECT_CXX_FLAGS "") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- +# sources + +add_subdirectory(src/pyprocess) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/README.md b/README.md new file mode 100644 index 00000000..2ad68ffb --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# python bindings for c++ stochastic process library (xo-process) + +# build + install +``` +$ cd xo-pyprocess +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer, e.g. ~/local +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +# build for unit test coverage +``` +$ cd xo-pyprocess +$ mkdir build-ccov +$ cd build-ccov +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCODE_COVERAGE=ON \ + -DCMAKE_BUILD_TYPE=Debug .. +``` + +# LSP (language server) support + +LSP looks for compile commands in the root of the source tree; +while Cmake creates them in the root of its build directory. + +``` +$ cd xo-pyprocess +$ ln -s build/compile_commands.json # supply compile commands to LSP +``` + +# Examples + +Assumes `xo-pyprocess` installed to `~/local2/lib` + +``` +PYTHONPATH=~/local2/lib python +>>> import pyprocess +>>> dir(pyprocess) +``` diff --git a/cmake/cmake b/cmake/cmake new file mode 100644 index 00000000..b49b4828 --- /dev/null +++ b/cmake/cmake @@ -0,0 +1,4 @@ + /home/roland/proj/xo-pyprocess/cmake: + drwxr-xr-x 2 roland roland 4096 Oct 12 21:49 . + drwxr-xr-x 6 roland roland 4096 Oct 12 21:49 .. + -rw-r--r-- 1 roland roland 125 Oct 12 21:49 xo_pyprocessConfig.cmake.in diff --git a/cmake/xo_pyprocessConfig.cmake.in b/cmake/xo_pyprocessConfig.cmake.in new file mode 100644 index 00000000..9c15f36a --- /dev/null +++ b/cmake/xo_pyprocessConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/README.md b/include/README.md new file mode 100644 index 00000000..aca8853c --- /dev/null +++ b/include/README.md @@ -0,0 +1 @@ +placeholder for future pyprocess #include files diff --git a/src/pyprocess/CMakeLists.txt b/src/pyprocess/CMakeLists.txt new file mode 100644 index 00000000..1b6c5347 --- /dev/null +++ b/src/pyprocess/CMakeLists.txt @@ -0,0 +1,8 @@ +# xo_pyprocess/src/pyprocess/CMakeLists.txt + +set(SELF_LIB pyprocess) +set(SELF_SRCS pyprocess.cpp) + +xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) + +xo_pybind11_dependency(${SELF_LIB} process) diff --git a/src/pyprocess/pyprocess.cpp b/src/pyprocess/pyprocess.cpp new file mode 100644 index 00000000..21c2f287 --- /dev/null +++ b/src/pyprocess/pyprocess.cpp @@ -0,0 +1,183 @@ +/* @file pyprocess.cpp */ + +// note: need pyprocess/ here bc pyprocess.hpp is generated, located in build directory +#include "src/pyprocess/pyprocess.hpp" +#include "xo/pywebutil/pywebutil.hpp" +#include "xo/process/init_process.hpp" +#include "xo/process/UpxToConsole.hpp" +#include "xo/process/StochasticProcess.hpp" +#include "xo/process/BrownianMotion.hpp" +#include "xo/process/ExpProcess.hpp" +#include "xo/process/RealizationSource.hpp" +#include "xo/pyreactor/pyreactor.hpp" +#include "xo/reactor/EventStore.hpp" +#include "xo/reactor/PolyAdapterSink.hpp" +#include "xo/randomgen/random_seed.hpp" +#include "xo/randomgen/xoshiro256.hpp" +#include +#include +#include + +/* xo::ref::intrusive_ptr is an intrusively-reference-counted pointer. + * always safe to create one from a T* p + * (since refcount is directly accessible from p) + */ +PYBIND11_DECLARE_HOLDER_TYPE(T, xo::ref::intrusive_ptr, true); + +namespace xo { + using xo::reactor::AbstractSink; + using xo::reactor::AbstractEventStore; + using xo::reactor::StructEventStore; + using xo::reactor::PolyAdapterSink; + using xo::json::PrintJsonSingleton; + using xo::time::utc_nanos; + using xo::rng::Seed; + using xo::rng::xoshiro256ss; + using xo::ref::rp; + namespace py = pybind11; + + namespace process { + PYBIND11_MODULE(PYPROCESS_MODULE_NAME(), m) { + /* ensure process/ will be initialized */ + InitSubsys::require(); + /* ..and immediately perform init steps */ + Subsystem::initialize_all(); + + /* e.g. py wrapper for xo::reactor::ReactorSource */ + PYREACTOR_IMPORT_MODULE(); + /* e.g. py wrapper for xo::web::EndpointDescr */ + PYWEBUTIL_IMPORT_MODULE(); + + m.doc() = "pybind11 plugin for xo.process"; + + m.def("make_brownian_motion", + [](utc_nanos start_tm, + double annual_volatility) { + Seed seed; + + return BrownianMotion::make(start_tm, + annual_volatility, + seed); + }, + "create new BrownianMotion instance"); + + m.def("make_exponential_brownian_motion", + [](utc_nanos start_tm, + double start_value, + double annual_volatility) { + Seed seed; + + return ExpProcess::make(start_value /*scale*/, + BrownianMotion::make(start_tm, + annual_volatility, + seed)); + }, + py::arg("start_tm"), py::arg("start_value"), py::arg("annual_volatility")); + + py::class_, + xo::ref::rp>>(m, "StochasticProcess") + .def_property_readonly("t0", &StochasticProcess::t0) + .def_property_readonly("t0_value", &StochasticProcess::t0_value) + .def("exterior_sample", &StochasticProcess::exterior_sample) + .def("__repr__", &StochasticProcess::display_string); + + py::class_, + StochasticProcess, + xo::ref::rp>>(m, "BrownianMotion"); + //.def("exterior_sample", &BrownianMotion::exterior_sample) + //.def("__repr__", &BrownianMotion::display_string); + + py::class_, + xo::ref::rp>(m, "ExpProcess") + .def_property_readonly("exponent_process", + [](ExpProcess & self) { + return self.exponent_process().promote(); + }); + + m.def("make_tracer", + &RealizationTracer::make); + + py::class_, + xo::ref::rp>>(m, "RealizationTracer"); + + /* e.g. + * import datetime as dt + * t0=dt.datetime.now() + * ebm=pyprocess.make_exponential_brownian_motion(t0, 0.5) + * s=pyprocess.make_realization_source(ebm, dt.timedelta(seconds=1)) + */ + m.def("make_realization_source", + [](xo::ref::rp> p, + xo::time::nanos sample_dt) + { + auto tracer = RealizationTracer::make(p); + + return RealizationSource::make(tracer, + sample_dt); + }); + + /* note: providing __repr__ changes printing behavior, + * but uses default printer for inherited std::pair<..> + */ + py::class_(m, "UpxEvent") + .def_property_readonly("tm", &UpxEvent::tm) + .def_property_readonly("upx", &UpxEvent::upx) + .def("__repr__", &UpxEvent::display_string); + + py::class_, + reactor::ReactorSource, + xo::ref::rp>>(m, "RealizationSource") + .def_property_readonly("current_ev", &RealizationSource::current_ev); + + py::class_> + (m, "UpxToConsole"); + + using UpxEventStore = StructEventStore; + + /* see also: KalmanFilterStateEventStore in [pyfilter/pyfilter.cpp] + */ + py::class_> + (m, "UpxEventStore") + .def_static("make", &UpxEventStore::make) + .def_property_readonly("empty", &UpxEventStore::empty) + .def_property_readonly("size", &UpxEventStore::size) + .def("last_n", &UpxEventStore::last_n, py::arg("n")) + .def("last_dt", &UpxEventStore::last_dt, py::arg("dt")); + //.def("__repr__", &UpxEventStore::display_string); + + /* temporary -- to reveal compiler errors */ + using UpxAdapterSink = PolyAdapterSink; + + py::class_>(m, "UpxAdapterSink") + .def_static("make", &UpxAdapterSink::make); + + /* prints + * std::pair + * pairs + */ + m.def("make_realization_printer", &UpxToConsole::make); + +#ifdef OBSOLETE + /* this implementation fails -- looks like .so libraries + * have separate typeinfo for std::pair + * and don't find each other. + */ + m.def("make_realization_printer2", + [] + { + return reactor::TemporaryTest::realization_printer(); + }); +#endif + + } /*pyprocess*/ + } /*namespace process*/ +} /*namespace xo*/ + +/* end pyprocess.cpp */ diff --git a/src/pyprocess/pyprocess.hpp.in b/src/pyprocess/pyprocess.hpp.in new file mode 100644 index 00000000..d2f5cec1 --- /dev/null +++ b/src/pyprocess/pyprocess.hpp.in @@ -0,0 +1,25 @@ +/* @file pyprocess.hpp + * + * automatically generated from src/pyprocess/pyprocess.hpp.in + * see src/pyprocess/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(PYPROCESS_MODULE_NAME(), m) { ... } + */ +#define PYPROCESS_MODULE_NAME() @SELF_LIBRARY_NAME@ + +/* example: + * py::module_::import(PYPROCESS_MODULE_NAME_STR) + */ +#define PYPROCESS_MODULE_NAME_STR "@SELF_LIBRARY_NAME@" + +/* example: + * PYPROCESS_IMPORT_MODULE() + * replaces + * py::module_::import("pyprocess") + */ +#define PYPROCESS_IMPORT_MODULE() py::module_::import("@SELF_LIBRARY_NAME@") + +/* end pyprocess.hpp */ From ce5c9145667a842b52c36670ffa1a601b9bc7220 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 17:10:51 -0400 Subject: [PATCH 0441/2693] + .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f52f1311 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# lsp keeps state here +.cache +# typical build directory +build +# lsp: symlink to file in build directory (established manually) +compile_commands.json From 1a1383c724524af5419e9a486b994f7f04e0ec7a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 17:13:32 -0400 Subject: [PATCH 0442/2693] compile fix: include path to pyprocess.hpp --- src/pyprocess/pyprocess.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyprocess/pyprocess.cpp b/src/pyprocess/pyprocess.cpp index 21c2f287..56b5b1f8 100644 --- a/src/pyprocess/pyprocess.cpp +++ b/src/pyprocess/pyprocess.cpp @@ -1,7 +1,7 @@ /* @file pyprocess.cpp */ // note: need pyprocess/ here bc pyprocess.hpp is generated, located in build directory -#include "src/pyprocess/pyprocess.hpp" +#include "pyprocess.hpp" #include "xo/pywebutil/pywebutil.hpp" #include "xo/process/init_process.hpp" #include "xo/process/UpxToConsole.hpp" From 060366684e54c0dd1a692e2237ffea3c3dbfeb38 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 17:16:29 -0400 Subject: [PATCH 0443/2693] github: + workflow --- .github/workflows/main.yml | 234 +++++++++++++++++++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..3bde7ce1 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,234 @@ +name: build xo-pyreactor + dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + - name: Install pybind11-dev + run: sudo apt-get install -y pybind11-dev + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + # configure cmake for subsys in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/reflect + + - name: Configure reflect + # configure cmake for reflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reflect + + - name: Build reflect + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} + + - name: Install reflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reflect + + # ---------------------------------------------------------------- + + - name: Clone reactor + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-reactor + path: repo/reactor + + - name: Configure reactor + # configure cmake for reactor in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reactor -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reactor + + - name: Build reactor + run: cmake --build ${{github.workspace}}/build_reactor --config ${{env.BUILD_TYPE}} + + - name: Install reactor + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reactor + + # ---------------------------------------------------------------- + + - name: Clone printjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-printjson + path: repo/printjson + + - name: Configure printjson + # configure cmake for printjson in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_printjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/printjson + + - name: Build printjson + run: cmake --build ${{github.workspace}}/build_printjson --config ${{env.BUILD_TYPE}} + + - name: Install printjson + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_printjson + + # ---------------------------------------------------------------- + + - name: Clone pyutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyutil + path: repo/pyutil + + - name: Configure pyutil + # configure cmake for pyutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pyutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyutil + + - name: Build pyutil + run: cmake --build ${{github.workspace}}/build_pyutil --config ${{env.BUILD_TYPE}} + + - name: Install pyutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pyutil + + # ---------------------------------------------------------------- + + - name: Clone pyreflect + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyreflect + path: repo/pyreflect + + - name: Configure pyreflect + # configure cmake for pyreflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pyreflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyreflect + + - name: Build pyreflect + run: cmake --build ${{github.workspace}}/build_pyreflect --config ${{env.BUILD_TYPE}} + + - name: Install pyreflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pyreflect + + # ---------------------------------------------------------------- + + - name: Clone pyprintjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyprintjson + path: repo/pyprintjson + + - name: Configure pyprintjson + # configure cmake for pyprintjson in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pyprintjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyprintjson + + - name: Build pyprintjson + run: cmake --build ${{github.workspace}}/build_pyprintjson --config ${{env.BUILD_TYPE}} + + - name: Install pyprintjson + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pyprintjson + + # ---------------------------------------------------------------- + + - name: Configure self (pyreactor) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_pyreactor -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (pyreactor) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_pyreactor --config ${{env.BUILD_TYPE}} + + - name: Test self (pyreactor) + working-directory: ${{github.workspace}}/build_pyreactor + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From 5c4fb100563be70ea675e3b00436c920197720be Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 17:22:20 -0400 Subject: [PATCH 0444/2693] github: + missing webutil dep --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3bde7ce1..c05316bf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -123,6 +123,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone webutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-webutil + path: repo/webutil + + - name: Configure webutil + # configure cmake for webutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_webutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/webutil + + - name: Build webutil + run: cmake --build ${{github.workspace}}/build_webutil --config ${{env.BUILD_TYPE}} + + - name: Install webutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_webutil + + # ---------------------------------------------------------------- + - name: Clone reactor uses: actions/checkout@v3 with: From 0653f4f69ed9a8fd2a80850de8292fd0cd9c1be8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 17:24:36 -0400 Subject: [PATCH 0445/2693] github: + missing callback dep --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c05316bf..ffcf823b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -123,6 +123,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/callback + + - name: Configure callback + # configure cmake for callback in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_callback -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/callback + + - name: Build callback + run: cmake --build ${{github.workspace}}/build_callback --config ${{env.BUILD_TYPE}} + + - name: Install callback + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_callback + + # ---------------------------------------------------------------- + - name: Clone webutil uses: actions/checkout@v3 with: From a97de592dfb37033ad828ced22157049b762c6c0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 17:29:50 -0400 Subject: [PATCH 0446/2693] github: fix dep ordering --- .github/workflows/main.yml | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ffcf823b..7f1a931a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -161,25 +161,6 @@ jobs: # ---------------------------------------------------------------- - - name: Clone reactor - uses: actions/checkout@v3 - with: - repository: Rconybea/xo-reactor - path: repo/reactor - - - name: Configure reactor - # configure cmake for reactor in dedicated build directory. - run: cmake -B ${{github.workspace}}/build_reactor -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reactor - - - name: Build reactor - run: cmake --build ${{github.workspace}}/build_reactor --config ${{env.BUILD_TYPE}} - - - name: Install reactor - # install into ${{github.workspace}}/local - run: cmake --install ${{github.workspace}}/build_reactor - - # ---------------------------------------------------------------- - - name: Clone printjson uses: actions/checkout@v3 with: @@ -199,6 +180,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone reactor + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-reactor + path: repo/reactor + + - name: Configure reactor + # configure cmake for reactor in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reactor -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reactor + + - name: Build reactor + run: cmake --build ${{github.workspace}}/build_reactor --config ${{env.BUILD_TYPE}} + + - name: Install reactor + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reactor + + # ---------------------------------------------------------------- + - name: Clone pyutil uses: actions/checkout@v3 with: From def29aeb9684836066c9d1f5ed8f67d23620e802 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 17:36:39 -0400 Subject: [PATCH 0447/2693] github: + missing dep randomgen --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7f1a931a..6d8ba190 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -180,6 +180,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone randomgen + uses: actions/checkout@v3 + with: + repository: Rconybea/randomgen + path: repo/randomgen + + - name: Configure randomgen + # configure cmake for randomgen in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/randomgen + + - name: Build randomgen + run: cmake --build ${{github.workspace}}/build_randomgen --config ${{env.BUILD_TYPE}} + + - name: Install randomgen + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_randomgen + + # ---------------------------------------------------------------- + - name: Clone reactor uses: actions/checkout@v3 with: From 45d799bd3648a5b507ef5b2598894948aa588837 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 17:42:50 -0400 Subject: [PATCH 0448/2693] github: + missing xo-ordinaltree dep --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6d8ba190..fb304ec2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -199,6 +199,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/ordinaltree + + - name: Configure ordinaltree + # configure cmake for ordinaltree in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/ordinaltree + + - name: Build ordinaltree + run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}} + + - name: Install ordinaltree + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_ordinaltree + + # ---------------------------------------------------------------- + - name: Clone reactor uses: actions/checkout@v3 with: From 20b899fd5ef418ad56f8d5679c4c8ad1ff0cd2f3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 18:05:25 -0400 Subject: [PATCH 0449/2693] github: + workflow --- .github/workflows/main.yml | 386 +++++++++++++++++++++++++++++++++++++ 1 file changed, 386 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..4bfa8333 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,386 @@ +name: build xo-pyprocess + dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + - name: Install pybind11-dev + run: sudo apt-get install -y pybind11-dev + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + # configure cmake for subsys in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/reflect + + - name: Configure reflect + # configure cmake for reflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reflect + + - name: Build reflect + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} + + - name: Install reflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reflect + + # ---------------------------------------------------------------- + + - name: Clone callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/callback + + - name: Configure callback + # configure cmake for callback in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_callback -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/callback + + - name: Build callback + run: cmake --build ${{github.workspace}}/build_callback --config ${{env.BUILD_TYPE}} + + - name: Install callback + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_callback + + # ---------------------------------------------------------------- + + - name: Clone webutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-webutil + path: repo/webutil + + - name: Configure webutil + # configure cmake for webutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_webutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/webutil + + - name: Build webutil + run: cmake --build ${{github.workspace}}/build_webutil --config ${{env.BUILD_TYPE}} + + - name: Install webutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_webutil + + # ---------------------------------------------------------------- + + - name: Clone pywebutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pywebutil + path: repo/pywebutil + + - name: Configure pywebutil + # configure cmake for pywebutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pywebutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pywebutil + + - name: Build pywebutil + run: cmake --build ${{github.workspace}}/build_pywebutil --config ${{env.BUILD_TYPE}} + + - name: Install pywebutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pywebutil + + # ---------------------------------------------------------------- + + - name: Clone printjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-printjson + path: repo/printjson + + - name: Configure printjson + # configure cmake for printjson in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_printjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/printjson + + - name: Build printjson + run: cmake --build ${{github.workspace}}/build_printjson --config ${{env.BUILD_TYPE}} + + - name: Install printjson + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_printjson + + # ---------------------------------------------------------------- + + - name: Clone randomgen + uses: actions/checkout@v3 + with: + repository: Rconybea/randomgen + path: repo/randomgen + + - name: Configure randomgen + # configure cmake for randomgen in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/randomgen + + - name: Build randomgen + run: cmake --build ${{github.workspace}}/build_randomgen --config ${{env.BUILD_TYPE}} + + - name: Install randomgen + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_randomgen + + # ---------------------------------------------------------------- + + - name: Clone ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/ordinaltree + + - name: Configure ordinaltree + # configure cmake for ordinaltree in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/ordinaltree + + - name: Build ordinaltree + run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}} + + - name: Install ordinaltree + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_ordinaltree + + # ---------------------------------------------------------------- + + - name: Clone reactor + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-reactor + path: repo/reactor + + - name: Configure reactor + # configure cmake for reactor in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reactor -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reactor + + - name: Build reactor + run: cmake --build ${{github.workspace}}/build_reactor --config ${{env.BUILD_TYPE}} + + - name: Install reactor + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reactor + + # ---------------------------------------------------------------- + + - name: Clone simulator + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-simulator + path: repo/simulator + + - name: Configure simulator + # configure cmake for simulator in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_simulator -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/simulator + + - name: Build simulator + run: cmake --build ${{github.workspace}}/build_simulator --config ${{env.BUILD_TYPE}} + + - name: Install simulator + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_simulator + + # ---------------------------------------------------------------- + + - name: Clone process + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-process + path: repo/process + + - name: Configure process + # configure cmake for process in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_process -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/process + + - name: Build process + run: cmake --build ${{github.workspace}}/build_process --config ${{env.BUILD_TYPE}} + + - name: Install process + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_process + + # ---------------------------------------------------------------- + + - name: Clone pyutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyutil + path: repo/pyutil + + - name: Configure pyutil + # configure cmake for pyutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pyutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyutil + + - name: Build pyutil + run: cmake --build ${{github.workspace}}/build_pyutil --config ${{env.BUILD_TYPE}} + + - name: Install pyutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pyutil + + # ---------------------------------------------------------------- + + - name: Clone pyreflect + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyreflect + path: repo/pyreflect + + - name: Configure pyreflect + # configure cmake for pyreflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pyreflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyreflect + + - name: Build pyreflect + run: cmake --build ${{github.workspace}}/build_pyreflect --config ${{env.BUILD_TYPE}} + + - name: Install pyreflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pyreflect + + # ---------------------------------------------------------------- + + - name: Clone pyprintjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyprintjson + path: repo/pyprintjson + + - name: Configure pyprintjson + # configure cmake for pyprintjson in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pyprintjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyprintjson + + - name: Build pyprintjson + run: cmake --build ${{github.workspace}}/build_pyprintjson --config ${{env.BUILD_TYPE}} + + - name: Install pyprintjson + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pyprintjson + + # ---------------------------------------------------------------- + + - name: Clone pyreactor + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyreactor + path: repo/pyreactor + + - name: Configure pyreactor + # configure cmake for pyreactor in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pyreactor -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyreactor + + - name: Build pyreactor + run: cmake --build ${{github.workspace}}/build_pyreactor --config ${{env.BUILD_TYPE}} + + - name: Install pyreactor + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pyreactor + + # ---------------------------------------------------------------- + + - name: Configure self (pyprocess) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_pyprocess -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (pyprocess) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_pyprocess --config ${{env.BUILD_TYPE}} + + - name: Test self (pyprocess) + working-directory: ${{github.workspace}}/build_pyprocess + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From 1fe15afb57f26504d9e4d7e1f7ad6d8fd03b0505 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 18:07:50 -0400 Subject: [PATCH 0450/2693] github: fix dep ordering --- .github/workflows/main.yml | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4bfa8333..a760a879 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -161,6 +161,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone pyutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyutil + path: repo/pyutil + + - name: Configure pyutil + # configure cmake for pyutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pyutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyutil + + - name: Build pyutil + run: cmake --build ${{github.workspace}}/build_pyutil --config ${{env.BUILD_TYPE}} + + - name: Install pyutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pyutil + + # ---------------------------------------------------------------- + - name: Clone pywebutil uses: actions/checkout@v3 with: @@ -294,25 +313,6 @@ jobs: # ---------------------------------------------------------------- - - name: Clone pyutil - uses: actions/checkout@v3 - with: - repository: Rconybea/xo-pyutil - path: repo/pyutil - - - name: Configure pyutil - # configure cmake for pyutil in dedicated build directory. - run: cmake -B ${{github.workspace}}/build_pyutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyutil - - - name: Build pyutil - run: cmake --build ${{github.workspace}}/build_pyutil --config ${{env.BUILD_TYPE}} - - - name: Install pyutil - # install into ${{github.workspace}}/local - run: cmake --install ${{github.workspace}}/build_pyutil - - # ---------------------------------------------------------------- - - name: Clone pyreflect uses: actions/checkout@v3 with: From 0068f6c0fda6b270c9e7481a17a7d0556be15f60 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 00:00:41 -0400 Subject: [PATCH 0451/2693] build: concept carveout for clang + std::copyable --- include/xo/randomgen/distribution_concept.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/include/xo/randomgen/distribution_concept.hpp b/include/xo/randomgen/distribution_concept.hpp index 61c764e0..979a64d5 100644 --- a/include/xo/randomgen/distribution_concept.hpp +++ b/include/xo/randomgen/distribution_concept.hpp @@ -24,12 +24,16 @@ namespace xo { // os << dist // is >> dist - } && std::copyable + } +#ifdef __clang__ + // std::copyable apparently not available in clang11 ? +#else + && std::copyable && std::copyable +#endif && std::equality_comparable; } /*namespace rng*/ } /*namespace xo*/ - /* end distribution_concept.hpp */ From a6d0252dd46c19772370fab8e752c15497f0ca64 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 12:15:18 -0400 Subject: [PATCH 0452/2693] build: clang16 fixes (c++20 concept headers available) --- include/xo/randomgen/distribution_concept.hpp | 3 ++- include/xo/randomgen/engine_concept.hpp | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/include/xo/randomgen/distribution_concept.hpp b/include/xo/randomgen/distribution_concept.hpp index 979a64d5..c2343255 100644 --- a/include/xo/randomgen/distribution_concept.hpp +++ b/include/xo/randomgen/distribution_concept.hpp @@ -30,8 +30,9 @@ namespace xo { #else && std::copyable && std::copyable + && std::equality_comparable #endif - && std::equality_comparable; + ; } /*namespace rng*/ } /*namespace xo*/ diff --git a/include/xo/randomgen/engine_concept.hpp b/include/xo/randomgen/engine_concept.hpp index 38b65cd7..2b3a2e53 100644 --- a/include/xo/randomgen/engine_concept.hpp +++ b/include/xo/randomgen/engine_concept.hpp @@ -7,6 +7,8 @@ namespace std { #ifdef __clang__ + +# if __clang_major__ <= 11 template < class T > concept integral = std::is_integral_v; @@ -31,8 +33,11 @@ namespace std { && requires { { G::min() } -> std::same_as>; { G::max() } -> std::same_as>; requires std::bool_constant<(G::min() < G::max())>::value; }; +# endif + #else /* uniform_random_bit_generator provided by gcc 12.3.2 */ + /* uniform_random_bit_generator provided by clang 16 */ #endif } /*namespace std*/ From a9c73175e2a8f8e1cd9993dad3d0779c9b67f751 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 12:21:14 -0400 Subject: [PATCH 0453/2693] build: fix deps -- need reactor + printjson --- src/process/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/process/CMakeLists.txt b/src/process/CMakeLists.txt index c6033dde..9be5b87b 100644 --- a/src/process/CMakeLists.txt +++ b/src/process/CMakeLists.txt @@ -14,6 +14,6 @@ xo_add_shared_library3(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 $ # must coordinate with find_dependency() calls # in xo-process/cmake/processConfig.cmake.in # -xo_dependency(${SELF_LIB} reflect) -#xo_dependency(${SELF_LIB} webutil) +xo_dependency(${SELF_LIB} reactor) +xo_dependency(${SELF_LIB} printjson) #xo_dependency(${SELF_LIB} callback) From 450b17807ab191611014b04954ed7a90e8d5768e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 15:25:52 -0400 Subject: [PATCH 0454/2693] + XO_SYMLINK_INSTALL + symlink-only install variations --- cmake/xo_cxx.cmake | 95 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 4 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 478cb80b..bcb6f7b7 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -187,14 +187,85 @@ macro(xo_compile_options target) target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) endmacro() +# ---------------------------------------------------------------- +# +# establish default value for XO_SYMLINK_INSTALL. +# +# may want to use this for a nested build, +# where we want to run cmake for nested codebase using externalproject_add(). +# +# in this case: +# 1. will need to build+install nested project foo to temporary location in build tree, +# so that build artifacts (such as fooConfig.cmake) are available to depending +# projects +# 2. bona fide install with (for example) copied .hpp files interferes with +# cross-codebase development. +# 2a. want changes to original .hpp files to trigger rebuild of depending codebases. +# This won't happen if depending project relies on a copy +# 2b. want IDE that observes compiler commands (i.e. LSP) to visit .hpp files +# in their original codebase, since that's the correct place to make any edits. +# +# see +# - xo_install_include_tree() +# +macro(xo_establish_symlink_install) + if(NOT DEFINED XO_SYMLINK_INSTALL) + set(XO_SYMLINK_INSTALL False) + endif() +endmacro() + # ---------------------------------------------------------------- # use this to install typical include file subtree # macro(xo_install_include_tree) - install( - DIRECTORY ${PROJECT_SOURCE_DIR}/include/ - FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ - DESTINATION ${CMAKE_INSTALL_PREFIX}/include) + xo_establish_symlink_install() + + if(XO_SYMLINK_INSTALL) + message(FATAL_ERROR "symlink install not implemented for ${PROJECT_SOURCE_DIR}/include -- use xo_install_include_tree3()") + else() + install( + DIRECTORY ${PROJECT_SOURCE_DIR}/include/ + FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_PREFIX}/include) + endif() +endmacro() + +# create symlink from ${symlink_path} -> ${dest_path}, +# from +# make install +# +macro(xo_install_make_symlink dest_path symlink_dir symlink_name) + install(CODE "message(\"make_directory: ${symlink_dir}\")") + install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${symlink_dir})") + + install(CODE "message(\"symlink: ${symlink_dir}/${symlink_name} -> ${dest_path}/${symlink_name}\")") + install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink ${dest_path}/${symlink_name} ${symlink_dir}/${symlink_name})") +endmacro() + +# e.g. path = include/xo/foo to install xo-foo/include/xo/foo +# +macro(xo_install_include_tree3 subdir_path) + xo_establish_symlink_install() + + if(XO_SYMLINK_INSTALL) + # ugh. cmake doesn't allow input path argument to cmake_path() + # to be a macro variable. + set(_xo_install_include_tree3_subdir_path ${subdir_path}) + set(_xo_install_include_tree3_dirname "") + set(_xo_install_include_tree3_basename "") + cmake_path(GET _xo_install_include_tree3_subdir_path PARENT_PATH _xo_install_include_tree3_dirname) + cmake_path(GET _xo_install_include_tree3_subdir_path FILENAME _xo_install_include_tree3_basename) + + xo_install_make_symlink( + ${PROJECT_SOURCE_DIR}/${_xo_install_include_tree3_dirname} + ${CMAKE_INSTALL_PREFIX}/${_xo_install_include_tree3_dirname} + ${_xo_install_include_tree3_basename}) + else() + install( + DIRECTORY ${PROJECT_SOURCE_DIR}/${subdir_path} + FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_PREFIX}/${subdir_path}) + endif() endmacro() # ---------------------------------------------------------------- @@ -227,6 +298,22 @@ macro(xo_install_library3 target projectTargets) xo_install_include_tree() endmacro() +macro(xo_install_library4 target projectTargets) + install( + TARGETS ${target} + EXPORT ${projectTargets} + LIBRARY DESTINATION lib COMPONENT Runtime + ARCHIVE DESTINATION lib COMPONENT Development + RUNTIME DESTINATION bin COMPONENT Runtime + PUBLIC_HEADER DESTINATION include COMPONENT Development + BUNDLE DESTINATION bin COMPONENT Runtime + ) + + xo_install_include_tree3(include/xo/${target}) + + #xo_install_include_tree() -- use xo_install_include_tree3() separately +endmacro() + # ---------------------------------------------------------------- # for projectname=foo, require: From f2224d5005a16308044f3917be70cfe5f8e8e3d4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 15:27:37 -0400 Subject: [PATCH 0455/2693] indentlog: support symlink-only install --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1cce6e83..c6799082 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,7 @@ xo_add_headeronly_library(indentlog) #xo_install_library2(indentlog) #xo_install_include_tree() -xo_install_library3(indentlog ${PROJECT_NAME}Targets) +xo_install_library4(indentlog ${PROJECT_NAME}Targets) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- From 9e680badecb2281c50112e342d6b27168de7b8aa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 15:27:54 -0400 Subject: [PATCH 0456/2693] doc: README extensions --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 9bc7e7aa..18c7c984 100644 --- a/README.md +++ b/README.md @@ -43,17 +43,17 @@ $ make $ make install ``` -alternatively, if you're a nix user: -``` -$ git clone git@github.com:rconybea/indentlog-nix.git -$ ls -d indentlog-nix -indentlog-nix -$ cd indentlog-nix -$ nix-build -``` - For some more detail see [BUILD.md](BUILD.md) +### LSP support + +lsp will look for `compile_commands.json` in the root of the source tree; cmake creates it in build directory + +``` +$ cd xo-indentlog +$ ln -s build/compile_commands.json +``` + ## Examples ### 1 From 9dfe2477298c0d77f5d13ece5bc82a7f0557dfc7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 15:41:29 -0400 Subject: [PATCH 0457/2693] xo-cmake: + xo_add_shared_library4() (symlink-hpp-enabled) --- cmake/xo_cxx.cmake | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index bcb6f7b7..51268ea3 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -83,9 +83,38 @@ macro(xo_include_headeronly_options2 target) endmacro() # ---------------------------------------------------------------- -# use this for a shared library. +# use this to introduce a shared library. +# - has symlink-enabled .hpp install +# +macro(xo_add_shared_library4 target projectTargets targetversion soversion sources) + add_library(${target} SHARED ${sources}) + foreach(arg IN ITEMS ${ARGN}) + #message("target=${target}; arg=${arg}") + + # to use PUBLIC here would need to split: + # $ + # $ + # but shouldn't need that, since we arrange to install includes via + # xo_include_options2() below + # + target_sources(${target} PRIVATE ${arg}) + endforeach() + set_target_properties( + ${target} + PROPERTIES + VERSION ${targetversion} + SOVERSION ${soversion}) + xo_compile_options(${target}) + xo_include_options2(${target}) + xo_install_library4(${target} ${projectTargets}) +endmacro() + +# ---------------------------------------------------------------- +# OBSOLETE. prefer xo_add_shared_library4() # macro(xo_add_shared_library3 target projectTargets targetversion soversion sources) + message(WARNING "obsolete call to xo_add_shared_library3(); prefer xo_add_shared_library4()") + add_library(${target} SHARED ${sources}) foreach(arg IN ITEMS ${ARGN}) #message("target=${target}; arg=${arg}") @@ -112,6 +141,8 @@ endmacro() # OBSOLETE. prefer xo_add_shared_library3() # macro(xo_add_shared_library target targetversion soversion sources) + message(WARNING "obsolete call to xo_add_shared_library(); prefer xo_add_shared_library4()") + add_library(${target} SHARED ${sources}) foreach(arg IN ITEMS ${ARGN}) #message("target=${target}; arg=${arg}") From 49844da0e674d3f25f50d154d3ae7566cc1ea700 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 15:46:05 -0400 Subject: [PATCH 0458/2693] minor: +warning on deprecated xo_install_include_tree() --- cmake/xo_cxx.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 51268ea3..380ef28b 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -249,6 +249,8 @@ endmacro() # use this to install typical include file subtree # macro(xo_install_include_tree) + message(WARNING "deprecated xo_install_include_tree(); prefer xo_install_include_tree3()") + xo_establish_symlink_install() if(XO_SYMLINK_INSTALL) From 6bb448708be7e616bff3676a7fa2100b84449df4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 15:46:37 -0400 Subject: [PATCH 0459/2693] build: symlink-aware install --- CMakeLists.txt | 3 ++- src/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 655a14a4..bcb080d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,7 @@ xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets # ---------------------------------------------------------------- # install .hpp -xo_install_include_tree() +#xo_install_include_tree() +xo_install_include_tree3(include/xo/cxxutil) # end CMakeLists.txt diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9d35b21d..4622ad44 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,5 @@ set(SELF_LIB refcnt) set(SELF_SRCS Refcounted.cpp Displayable.cpp) -xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) xo_dependency(${SELF_LIB} indentlog) From 7ccc752b00d102cf31a0482294e192e82ed9d604 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 16:02:33 -0400 Subject: [PATCH 0460/2693] build: support symlink-only install --- CMakeLists.txt | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 189db40a..766058b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,25 +17,15 @@ add_code_coverage() # add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) -set(XO_PROJECT_NAME subsys) +#set(XO_PROJECT_NAME subsys) xo_toplevel_compile_options() # ---------------------------------------------------------------- # installing header-only library -add_library(subsys INTERFACE) -xo_include_headeronly_options2(subsys) -xo_install_library2(subsys) - -# ---------------------------------------------------------------- -# provide find_package() support - +xo_add_headeronly_library(subsys) +xo_install_library4(subsys ${PROJECT_NAME}Targets) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -# ---------------------------------------------------------------- -# install .hpp - -xo_install_include_tree() - # end CMakeLists.txt From 6d5a14b33557e2ed9abf8a51682bf18ceb77288b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 16:20:33 -0400 Subject: [PATCH 0461/2693] bugfix: XO_PROJECT_NAME -> PROJECT_NAME in .cmake.in --- cmake/subsysConfig.cmake.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/subsysConfig.cmake.in b/cmake/subsysConfig.cmake.in index e13a2c54..9c15f36a 100644 --- a/cmake/subsysConfig.cmake.in +++ b/cmake/subsysConfig.cmake.in @@ -1,4 +1,4 @@ @PACKAGE_INIT@ -include("${CMAKE_CURRENT_LIST_DIR}/@XO_PROJECT_NAME@Targets.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") From ea36e50ea07a256dbadeeb243b1654793e69520b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 16:26:27 -0400 Subject: [PATCH 0462/2693] minor tweaks : doc , build messaging --- CMakeLists.txt | 6 +++--- README.md | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6bd4ca7a..e7c1b6eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,8 +5,8 @@ cmake_minimum_required(VERSION 3.10) project(reflect VERSION 0.1) enable_language(CXX) -message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") -message(STATUS "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}") +#message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +#message(STATUS "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}") # common XO cmake macros (see proj/xo-cmake) include(xo_macros/xo_cxx) @@ -50,4 +50,4 @@ xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets # ---------------------------------------------------------------- # install .hpp files -xo_install_include_tree() +#xo_install_include_tree() diff --git a/README.md b/README.md index 40032c62..ecbafe0c 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ ### build + install dependencies -- [github/Rconybea/xo-refcnt](https://github.com/Rconybea/xo-refcnt) -- [github/Rconybea/xo-subsys](https://github.com/Rconybea/xo-subsys) +- [github/Rconybea/refcnt](https://github.com/Rconybea/refcnt) +- [github/Rconybea/subsys](https://github.com/Rconybea/subsys) ### build + install ``` From ee6eb49354e5769cd98dbb1308da0cb0e307e133 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 16:26:49 -0400 Subject: [PATCH 0463/2693] build: make symlink-aware --- src/reflect/CMakeLists.txt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index 27485780..00d0b520 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -1,20 +1,21 @@ # reflect/CMakeLists.txt -set(SELF_LIBRARY_NAME reflect) -set(SELF_SOURCE_FILES TypeDescr.cpp TypeDescrExtra.cpp TaggedRcptr.cpp atomic/AtomicTdx.cpp pointer/PointerTdx.cpp vector/VectorTdx.cpp struct/StructTdx.cpp struct/StructMember.cpp init_reflect.cpp) +set(SELF_LIB reflect) +set(SELF_SRCS TypeDescr.cpp TypeDescrExtra.cpp TaggedRcptr.cpp atomic/AtomicTdx.cpp pointer/PointerTdx.cpp vector/VectorTdx.cpp struct/StructTdx.cpp struct/StructMember.cpp init_reflect.cpp) -xo_add_shared_library(${SELF_LIBRARY_NAME} ${PROJECT_VERSION} 1 ${SELF_SOURCE_FILES}) +#xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- # dependencies: indentlog, ... -xo_dependency(${SELF_LIBRARY_NAME} refcnt) -xo_dependency(${SELF_LIBRARY_NAME} indentlog) -xo_dependency(${SELF_LIBRARY_NAME} subsys) +xo_dependency(${SELF_LIB} refcnt) +xo_dependency(${SELF_LIB} indentlog) +xo_dependency(${SELF_LIB} subsys) # ---------------------------------------------------------------- # 3rd party dependency: boost: -#xo_boost_dependency(${SELF_LIBRARY_NAME}) +#xo_boost_dependency(${SELF_LIB}) # end CMakeLists.txt From e3f6cda58fe00283ca880eb37b33ba44a3a73948 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 16:30:55 -0400 Subject: [PATCH 0464/2693] build: make symlink-aware --- CMakeLists.txt | 5 ----- src/printjson/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index af42e3e7..99aa239f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,9 +44,4 @@ add_subdirectory(utest) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -# ---------------------------------------------------------------- -# install .hpp files - -xo_install_include_tree() - # end CMakeLists.txt diff --git a/src/printjson/CMakeLists.txt b/src/printjson/CMakeLists.txt index b0a4d532..2acf74cf 100644 --- a/src/printjson/CMakeLists.txt +++ b/src/printjson/CMakeLists.txt @@ -3,7 +3,7 @@ set(SELF_LIB printjson) set(SELF_SRCS PrintJson.cpp init_printjson.cpp) -xo_add_shared_library3(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- # dependencies: indentlog, ... From 36a6edc51f217ca1f89ce24dd3776f3f6d9877f9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 16:34:28 -0400 Subject: [PATCH 0465/2693] build: allow symlink-preferred install --- CMakeLists.txt | 9 +++++---- README.md | 7 ++++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c5413d7a..3f68effb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,14 +51,15 @@ add_subdirectory(example) # output targets set(SELF_LIB randomgen) -add_library(${SELF_LIB} INTERFACE) -xo_include_headeronly_options2(${SELF_LIB}) +xo_add_headeronly_library(${SELF_LIB}) +#add_library(${SELF_LIB} INTERFACE) +#xo_include_headeronly_options2(${SELF_LIB}) # ---------------------------------------------------------------- # standard install + provide find_package() support -xo_install_library2(${SELF_LIB}) -xo_install_include_tree() +xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) +#xo_install_include_tree() xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- diff --git a/README.md b/README.md index 73f13f1b..414a8218 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,12 @@ # random number generators -# to build + install locally +## Getting Started +### build + install dependencies + +- see [github/Rconybea/cmake](https://github.com/Rconybea/xo-cmake) -- cmake modules + +### to build + install locally ``` $ cd randomgen $ mkdir build From f48208072a9d186441989603d03a8a609f69716d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 16:52:08 -0400 Subject: [PATCH 0466/2693] xo-cmake: + xo_install_library5() --- cmake/xo_cxx.cmake | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 380ef28b..68ed519c 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -347,6 +347,22 @@ macro(xo_install_library4 target projectTargets) #xo_install_include_tree() -- use xo_install_include_tree3() separately endmacro() +macro(xo_install_library5 target nxo_target projectTargets) + install( + TARGETS ${target} + EXPORT ${projectTargets} + LIBRARY DESTINATION lib COMPONENT Runtime + ARCHIVE DESTINATION lib COMPONENT Development + RUNTIME DESTINATION bin COMPONENT Runtime + PUBLIC_HEADER DESTINATION include COMPONENT Development + BUNDLE DESTINATION bin COMPONENT Runtime + ) + + xo_install_include_tree3(include/xo/${nxo_target}) + + #xo_install_include_tree() -- use xo_install_include_tree3() separately +endmacro() + # ---------------------------------------------------------------- # for projectname=foo, require: From 5b3f712e0fb01151c7b36038faee1a94652ef672 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 16:52:29 -0400 Subject: [PATCH 0467/2693] build: + symlink-centric install --- CMakeLists.txt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c7e32668..b5894f9a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,15 +50,16 @@ xo_toplevel_compile_options() # ---------------------------------------------------------------- # output targets -set(SELF_LIB xo_pyutil) -add_library(${SELF_LIB} INTERFACE) -xo_include_headeronly_options2(${SELF_LIB}) +set(SELF_SHORTNAME pyutil) +set(SELF_LIB xo_${SELF_SHORTNAME}) +xo_add_headeronly_library(${SELF_LIB}) +#xo_include_headeronly_options2(${SELF_LIB}) # ---------------------------------------------------------------- # standard install + provide find_package() support -xo_install_library2(${SELF_LIB}) -xo_install_include_tree() +xo_install_library5(${SELF_LIB} ${SELF_SHORTNAME} ${PROJECT_NAME}Targets) +#xo_install_include_tree() xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- From aa24e1890956a97e8397a56fc6b170cd628d11a0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 16:56:14 -0400 Subject: [PATCH 0468/2693] build: support symlink-centric variation --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a7b8377..727883e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,8 +41,8 @@ xo_add_headeronly_library(${SELF_LIB}) # ---------------------------------------------------------------- # -xo_install_library3(${SELF_LIB} ${PROJECT_NAME}Targets) -xo_install_include_tree() +xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) +#xo_install_include_tree() # (note: ..Targets from xo_install_library2()) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) From c0c9cf279eb51299997dcee22f0904defd54354f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 17:03:47 -0400 Subject: [PATCH 0469/2693] github: nit: name for workflow yaml --- .github/workflows/main.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0971d8de..38eee8cf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,4 @@ -# This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage. -# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml -name: CMake on a single platform +name: build xo-callback + xo dependencies on: push: From dd01874b2ea023202f3084f92c31199e7fa03e2c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 17:04:09 -0400 Subject: [PATCH 0470/2693] build: support symlink-enabled variation --- CMakeLists.txt | 5 ----- src/callback/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d67984af..52296781 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,9 +45,4 @@ add_subdirectory(src/callback) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -# ---------------------------------------------------------------- -# install .hpp files - -xo_install_include_tree() - # end CMakeLists.txt diff --git a/src/callback/CMakeLists.txt b/src/callback/CMakeLists.txt index 114a89e2..cfeaf08c 100644 --- a/src/callback/CMakeLists.txt +++ b/src/callback/CMakeLists.txt @@ -4,7 +4,7 @@ set(SELF_LIB callback) #set(SELF_SRCS CallbackSet.cpp) xo_add_headeronly_library(${SELF_LIB}) -xo_install_library3(${SELF_LIB} ${PROJECT_NAME}Targets) +xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- # external dependencies: From 7c2d01c5f437ed01bdf72681036b33f40551bb6e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 17:10:28 -0400 Subject: [PATCH 0471/2693] build: support symlink-enabled variation --- CMakeLists.txt | 7 ------- src/webutil/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 04cc53f2..b63e290e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,10 +42,3 @@ add_subdirectory(src/webutil) # provide find_package() support xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) - -# ---------------------------------------------------------------- -# install bespoke targets, if any - -xo_install_include_tree() - -#install(TARGETS example DESTINATION bin/xo-webutil/example) diff --git a/src/webutil/CMakeLists.txt b/src/webutil/CMakeLists.txt index 7d9f5665..54fe2a88 100644 --- a/src/webutil/CMakeLists.txt +++ b/src/webutil/CMakeLists.txt @@ -4,7 +4,7 @@ set(SELF_LIB webutil) set(SELF_SRCS StreamEndpointDescr.cpp HttpEndpointDescr.cpp Alist.cpp) # reminder: can't be header-only library, because depends on non-header-only callback (bc of non-header-only refcnt) -xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- # external dependencies From f26fb23255d49544a6c437ad64b7b0257f50fa64 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 17:18:21 -0400 Subject: [PATCH 0472/2693] build: support symlink-enabled variation --- CMakeLists.txt | 5 ----- src/reactor/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e0be181..b24e139a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,9 +44,4 @@ add_subdirectory(utest) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -# ---------------------------------------------------------------- -# install project .hpp files - -xo_install_include_tree() - # end CMakeLists.txt diff --git a/src/reactor/CMakeLists.txt b/src/reactor/CMakeLists.txt index 884e466c..e50e7016 100644 --- a/src/reactor/CMakeLists.txt +++ b/src/reactor/CMakeLists.txt @@ -7,7 +7,7 @@ set(SELF_SRCS Reactor.cpp PollingReactor.cpp init_reactor.cpp) -xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- # external dependencies From 32a027e610339794e2a0aab1871a8d1a54f1711c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 17:18:32 -0400 Subject: [PATCH 0473/2693] github: nit: copypasta for name --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index aaef0421..30ac4a91 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: build xo-callback + xo dependencies +name: build xo-reactor + xo dependencies on: push: From fe268c12b29800e5b45872a6dc78f6a49eda2318 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 17:18:48 -0400 Subject: [PATCH 0474/2693] doc: nits in README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8ab8e106..8136c1da 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ build+install these first ### build + install xo-reactor ``` -$ cd reactor +$ cd xo-reactor $ mkdir build $ cd build $ INSTALL_PREFIX=/usr/local # or wherever you prefer @@ -24,6 +24,8 @@ $ make install ``` (also see .github/workflows/main.yml) +## Development + ### build for unit test coverage ``` $ cd xo-reactor @@ -32,8 +34,6 @@ $ cd ccov $ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. ``` -## Development - ### LSP support LSP looks for compile commands in the root of the source tree; @@ -51,6 +51,6 @@ $ ln -s build/compile_commands.json - `-H` include help text ``` -$ cd reactor/build +$ cd xo-reactor/build $ cmake -LAH ``` From 20fbb27afc0c8be1fbd119772883018af7fe8687 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 17:29:24 -0400 Subject: [PATCH 0475/2693] build: symlink-aware install variation for pyxxx libraries --- cmake/xo_cxx.cmake | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 68ed519c..2c169b77 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -518,10 +518,19 @@ macro(xo_pybind11_library target projectTargets source_files) ${PROJECT_BINARY_DIR}/${target}.hpp) # was ${PROJECT_SOURCE_DIR}/include/xo/${target}/${target}.hpp) - install( - FILES ${PROJECT_BINARY_DIR}/${target}.hpp - PERMISSIONS OWNER_READ GROUP_READ WORLD_READ - DESTINATION ${CMAKE_INSTALL_PREFIX}/include/xo/${target}) + xo_establish_symlink_install() + + if(XO_SYMLINK_INSTALL) + xo_install_make_symlink( + ${PROJECT_BINARY_DIR} + ${CMAKE_INSTALL_PREFIX}/include/xo/${target} + ${target}.hpp) + else() + install( + FILES ${PROJECT_BINARY_DIR}/${target}.hpp + PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/xo/${target}) + endif() # find_package(Python..) finds python in # /Library/Frameworks/Python.framework/... From 0119cdfe1c2a884455bc54343128196f56f9c903 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 17:35:05 -0400 Subject: [PATCH 0476/2693] symlink policy for pybind11 libs --- cmake/xo_cxx.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 2c169b77..0b054171 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -559,7 +559,7 @@ macro(xo_pybind11_library target projectTargets source_files) xo_pybind11_link_flags() xo_include_options2(${target}) - xo_install_library3(${target} ${projectTargets}) + xo_install_library4(${target} ${projectTargets}) endmacro() # ---------------------------------------------------------------- From 71416fcd259b60bd72f0f338859484461c1100e8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 17:42:09 -0400 Subject: [PATCH 0477/2693] xo-cmake: provide canoncial include dir for generated pyfoo.hpp --- cmake/xo_cxx.cmake | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 0b054171..6932c3e8 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -513,21 +513,23 @@ endmacro() # 2. pyfoo/pyfoo.hpp.in -> pyfoo/pyfoo.hpp # macro(xo_pybind11_library target projectTargets source_files) + file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/include/xo/${target}) + configure_file( ${target}.hpp.in - ${PROJECT_BINARY_DIR}/${target}.hpp) + ${PROJECT_BINARY_DIR}/include/xo/${target}/${target}.hpp) # was ${PROJECT_SOURCE_DIR}/include/xo/${target}/${target}.hpp) xo_establish_symlink_install() if(XO_SYMLINK_INSTALL) xo_install_make_symlink( - ${PROJECT_BINARY_DIR} + ${PROJECT_BINARY_DIR}/include/xo/${target} ${CMAKE_INSTALL_PREFIX}/include/xo/${target} ${target}.hpp) else() install( - FILES ${PROJECT_BINARY_DIR}/${target}.hpp + FILES ${PROJECT_BINARY_DIR}/include/xo/${target}/${target}.hpp PERMISSIONS OWNER_READ GROUP_READ WORLD_READ DESTINATION ${CMAKE_INSTALL_PREFIX}/include/xo/${target}) endif() From 2dbda84ef1ff5004418bac8c379e58cb0d6b4a38 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 20 Oct 2023 12:25:10 -0400 Subject: [PATCH 0478/2693] fix include symlink handling for pybind11 libraries --- cmake/xo_cxx.cmake | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 6932c3e8..eb9fad7f 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -305,6 +305,8 @@ endmacro() # use this for a subdir that builds a library # and supports find_package() # +# note: used deliberately in xo_pybind11_library() below +# macro(xo_install_library2 target) install( TARGETS ${target} @@ -347,6 +349,18 @@ macro(xo_install_library4 target projectTargets) #xo_install_include_tree() -- use xo_install_include_tree3() separately endmacro() +macro(xo_install_library4_noincludes target projectTargets) + install( + TARGETS ${target} + EXPORT ${projectTargets} + LIBRARY DESTINATION lib COMPONENT Runtime + ARCHIVE DESTINATION lib COMPONENT Development + RUNTIME DESTINATION bin COMPONENT Runtime + PUBLIC_HEADER DESTINATION include COMPONENT Development + BUNDLE DESTINATION bin COMPONENT Runtime + ) +endmacro() + macro(xo_install_library5 target nxo_target projectTargets) install( TARGETS ${target} @@ -524,9 +538,9 @@ macro(xo_pybind11_library target projectTargets source_files) if(XO_SYMLINK_INSTALL) xo_install_make_symlink( - ${PROJECT_BINARY_DIR}/include/xo/${target} - ${CMAKE_INSTALL_PREFIX}/include/xo/${target} - ${target}.hpp) + ${PROJECT_BINARY_DIR}/include/xo + ${CMAKE_INSTALL_PREFIX}/include/xo + ${target}) else() install( FILES ${PROJECT_BINARY_DIR}/include/xo/${target}/${target}.hpp @@ -561,7 +575,9 @@ macro(xo_pybind11_library target projectTargets source_files) xo_pybind11_link_flags() xo_include_options2(${target}) - xo_install_library4(${target} ${projectTargets}) + # don't want to symlink include tree, because lives in build dir. + # see install for generated .hpp above + xo_install_library4_noincludes(${target} ${projectTargets}) endmacro() # ---------------------------------------------------------------- From 729975b14559f8a70ac7b9cf96aa68d4d8f0a29c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 20 Oct 2023 12:32:19 -0400 Subject: [PATCH 0479/2693] tidy: drop dead comments --- CMakeLists.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ccf4b129..b2e5ca94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,8 +43,3 @@ add_subdirectory(src/pyreflect) # provide find_package() support xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) - -# ---------------------------------------------------------------- -# install .hpp files - -#xo_install_include_tree() From 3194fb35f78c94cc825d90cc5f2c5122d9c49e2d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 20 Oct 2023 12:34:06 -0400 Subject: [PATCH 0480/2693] fix include path for generated .hpp --- src/pyreflect/pyreflect.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyreflect/pyreflect.cpp b/src/pyreflect/pyreflect.cpp index fc6e8fe8..c9b7f47a 100644 --- a/src/pyreflect/pyreflect.cpp +++ b/src/pyreflect/pyreflect.cpp @@ -1,7 +1,7 @@ /* @file pyreflect.cpp */ // note: need pyreflect/ here bc pyreflect.hpp is generated, located in build directory -#include "pyreflect.hpp" +#include "xo/pyreflect/pyreflect.hpp" #include "xo/reflect/TypeDescr.hpp" #include "xo/reflect/TaggedRcptr.hpp" #include "xo/reflect/SelfTagging.hpp" From a480e40afe636fa94ce4150f555b0c2014bf9faa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 20 Oct 2023 12:39:29 -0400 Subject: [PATCH 0481/2693] build: adopt builddir/include/xo/target in include path --- cmake/xo_cxx.cmake | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index eb9fad7f..54072862 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -65,10 +65,11 @@ macro(xo_include_headeronly_options2 target) ${target} INTERFACE $ $ - $ # e.g. for #include "indentlog/scope.hpp" - $ # e.g. for #include "Refcounted.hpp" in refcnt/src when ${target}=refcnt [DEPRECATED] + $ # e.g. for #include "indentlog/scope.hpp" + #$ # e.g. for #include "Refcounted.hpp" in refcnt/src when ${target}=refcnt [DEPRECATED] $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect - $ # e.g. for generated .hpp files + #$ # e.g. for generated .hpp files + $ # e.g. for generated .hpp files ) # ---------------------------------------------------------------- @@ -192,10 +193,11 @@ macro(xo_include_options2 target) ${target} PUBLIC $ $ - $ # e.g. for #include "indentlog/scope.hpp" - $ # e.g. for #include "Refcounted.hpp" in refcnt/src [DEPRECATED] + $ # e.g. for #include "indentlog/scope.hpp" + #$ # e.g. for #include "Refcounted.hpp" in refcnt/src [DEPRECATED] $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect - $ # e.g. for generated .hpp files + #$ # e.g. for generated .hpp files + $ # e.g. for generated .hpp files ) # ---------------------------------------------------------------- From 35820ef0dadbe75f70617b7c0ee5108f499a1ea7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:29:03 -0400 Subject: [PATCH 0482/2693] xo-cmake: sub-module build w/out relying on cmake external-project --- CMakeLists.txt | 6 +- cmake/{ => xo_macros}/code-coverage.cmake | 0 cmake/{ => xo_macros}/xo_cxx.cmake | 322 +++++++++++++++++++--- 3 files changed, 293 insertions(+), 35 deletions(-) rename cmake/{ => xo_macros}/code-coverage.cmake (100%) rename cmake/{ => xo_macros}/xo_cxx.cmake (64%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c9aa85f..197416e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,8 +12,8 @@ set(XO_PROJECT_NAME xo_macros) install( FILES - "cmake/xo_cxx.cmake" - "cmake/code-coverage.cmake" + "cmake/xo_macros/xo_cxx.cmake" + "cmake/xo_macros/code-coverage.cmake" PERMISSIONS OWNER_READ GROUP_READ WORLD_READ - DESTINATION share/cmake/${XO_PROJECT_NAME} + DESTINATION share/cmake/xo_macros ) diff --git a/cmake/code-coverage.cmake b/cmake/xo_macros/code-coverage.cmake similarity index 100% rename from cmake/code-coverage.cmake rename to cmake/xo_macros/code-coverage.cmake diff --git a/cmake/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake similarity index 64% rename from cmake/xo_cxx.cmake rename to cmake/xo_macros/xo_cxx.cmake index 54072862..2ab628be 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -1,5 +1,21 @@ macro(xo_toplevel_compile_options) + define_property( + TARGET + PROPERTY xo_deps + BRIEF_DOCS "transitive completion of xo-dependencies for a target. Used with XO_SUBMODULE_BUILD" + ) + define_property( + TARGET + PROPERTY xo_srcdir + BRIEF_DOCS "snapshot of PROJECT_SOURCE_DIR asof when this target introduced" + ) + define_property( + TARGET + PROPERTY xo_bindir + BRIEF_DOCS "snapshot of PROJECT_BINARY_DIR asof when this target introduced" + ) + if(NOT DEFINED CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 20) endif() @@ -47,7 +63,11 @@ macro(xo_toplevel_compile_options) endif() endmacro() -macro(xo_include_headeronly_options2 target) +# e.g. +# - xo_target = xo_pyutil +# - nxo_target = pyutil +# +macro(xo_include_headeronly_options5 target nxo_target) # ---------------------------------------------------------------- # PROJECT_SOURCE_DIR: # so we can for example write @@ -64,12 +84,12 @@ macro(xo_include_headeronly_options2 target) target_include_directories( ${target} INTERFACE $ - $ + $ $ # e.g. for #include "indentlog/scope.hpp" #$ # e.g. for #include "Refcounted.hpp" in refcnt/src when ${target}=refcnt [DEPRECATED] - $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect + $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect #$ # e.g. for generated .hpp files - $ # e.g. for generated .hpp files + $ # e.g. for generated .hpp files ) # ---------------------------------------------------------------- @@ -83,12 +103,26 @@ macro(xo_include_headeronly_options2 target) endif() endmacro() +macro(xo_include_headeronly_options2 target) + xo_include_headeronly_options5(${target} ${target}) +endmacro() + # ---------------------------------------------------------------- # use this to introduce a shared library. # - has symlink-enabled .hpp install # macro(xo_add_shared_library4 target projectTargets targetversion soversion sources) add_library(${target} SHARED ${sources}) + set_property( + TARGET ${target} + PROPERTY xo_deps "${target}") + set_property( + TARGET ${target} + PROPERTY xo_srcdir ${PROJECT_SOURCE_DIR}) + set_property( + TARGET ${target} + PROPERTY xo_bindir ${PROJECT_BINARY_DIR}) + foreach(arg IN ITEMS ${ARGN}) #message("target=${target}; arg=${arg}") @@ -114,7 +148,7 @@ endmacro() # OBSOLETE. prefer xo_add_shared_library4() # macro(xo_add_shared_library3 target projectTargets targetversion soversion sources) - message(WARNING "obsolete call to xo_add_shared_library3(); prefer xo_add_shared_library4()") + message(WARNING "${target}: obsolete call to xo_add_shared_library3(); prefer xo_add_shared_library4()") add_library(${target} SHARED ${sources}) foreach(arg IN ITEMS ${ARGN}) @@ -142,7 +176,7 @@ endmacro() # OBSOLETE. prefer xo_add_shared_library3() # macro(xo_add_shared_library target targetversion soversion sources) - message(WARNING "obsolete call to xo_add_shared_library(); prefer xo_add_shared_library4()") + message(WARNING "${target}: obsolete call to xo_add_shared_library(); prefer xo_add_shared_library4()") add_library(${target} SHARED ${sources}) foreach(arg IN ITEMS ${ARGN}) @@ -169,9 +203,41 @@ endmacro() # ---------------------------------------------------------------- # use this for a header-only library # -macro(xo_add_headeronly_library target) +# e.g. +# - target=xo_pyutil cmake target name for this library +# - nxo_target=pyutil directory for this target under include/xo +# +macro(xo_add_headeronly_library5 target nxo_target) add_library(${target} INTERFACE) - xo_include_headeronly_options2(${target}) + + set_property( + TARGET ${target} + PROPERTY xo_deps "${target}") + set_property( + TARGET ${target} + PROPERTY xo_srcdir ${PROJECT_SOURCE_DIR}) + set_property( + TARGET ${target} + PROPERTY xo_bindir ${PROJECT_BINARY_DIR}) + + xo_include_headeronly_options5(${target} ${nxo_target}) +endmacro() + +macro(xo_add_headeronly_library target) + xo_add_headeronly_library5(${target} ${target}) +endmacro() + +# ---------------------------------------------------------------- +# +# in submodule build each xo codebases cmake files are incorporated +# directly (via add_subdirectory()) into a single umbrella build. +# +# In such case we don't use find_package for xo dependencies +# +macro(xo_establish_submodule_build) + if(NOT DEFINED XO_SUBMODULE_BUILD) + set(XO_SUBMODULE_BUILD False) + endif() endmacro() # ---------------------------------------------------------------- @@ -179,6 +245,10 @@ endmacro() # do not use for header-only subsystems; see xo_include_headeronly_options2() # macro(xo_include_options2 target) + xo_establish_submodule_build() + + #message("xo_include_options2: XO_SUBMODULE_BUILD=${XO_SUBMODULE_BUILD}") + # ---------------------------------------------------------------- # PROJECT_SOURCE_DIR: # so we can for example write @@ -414,6 +484,134 @@ macro(xo_export_cmake_config projectname projectversion projecttargets) ) endmacro() +# ---------------------------------------------------------------- +# helper macro for xo_dependency_helper() see below +# +macro(xo_dependency_helper1 target visibility dep_include_subdir) + target_include_directories(${target} ${visibility} + $) + target_include_directories(${target} ${visibility} + $) + + # note: header directories owned by dep get added to target. + # however, header directories _used_ by dep don't get automatically added to target, + # (for example if referred to from a dep-owned .hpp file) + # +endmacro() + +# ---------------------------------------------------------------- +# dependency helper when depending on an xo codebase +# +# Two flavors: +# - depended-on codebase separately built+installed. +# In this case behaves like any other best-practice cmake dep: +# use find_package() to rely on install .cmake config files +# in PREFIX/lib/cmake +# - depended-on codebase in same umbrella source tree as codebase +# invoking this helper. +# 1. XO_UMBRELLA_SOURCE_DIR gives top of umbrella source tree +# 2. depended-on codebase foo in one of +# XO_UMBRELLA_SOURCE_DIR/repo/{foo, xo-foo} +# +# target: cmake target for which to supply a dependency +# visibility: INTERFACE|PUBLIC +# - INTERFACE must be used when supplying a dependency to a header-only target +# dep: cmake target on which to depend (e.g. xo_pyutil) +# nxo_dep: cmake target without any xo_ prefix. (e.g. pyutil) +# +macro(xo_dependency_helper target visibility dep) + string(REGEX REPLACE "^xo_" "" _nxo_dep ${dep}) + string(REGEX REPLACE "^xo-" "" _nxo_dep ${_nxo_dep}) + + xo_establish_submodule_build() + + if(XO_SUBMODULE_BUILD) + if(EXISTS ${XO_UMBRELLA_SOURCE_DIR}/repo/xo-${_nxo_dep}) + xo_dependency_helper1(${target} ${visibility} repo/xo-${_nxo_dep}/include) + endif() + + if(EXISTS ${XO_UMBRELLA_SOURCE_DIR}/repo/${_nxo_dep}) + xo_dependency_helper1(${target} ${visibility} repo/${_nxo_dep}/include) + endif() + else() + find_package(${dep} CONFIG REQUIRED) + endif() + + if (XO_SUBMODULE_BUILD) + get_target_property(_tmp ${target} LINK_LIBRARIES) + message("xo_dependency_helper: ${target} -> ${dep}: ${target}.LINK_LIBRARIES=${_tmp}") + get_target_property(_tmp ${dep} LINK_LIBRARIES) + message("xo_dependency_helper: ${target} -> ${dep}: ${dep}.LINK_LIBRARIES=${_tmp}") + + get_target_property(_tmp ${target} INTERFACE_LINK_LIBRARIES) + message("xo_dependency_helper: ${target} -> ${dep}: ${target}.INTERFACE_LINK_LIBRARIES=${_tmp}") + get_target_property(_tmp ${dep} INTERFACE_LINK_LIBRARIES) + message("xo_dependency_helper: ${target} -> ${dep}: ${dep}.INTERFACE_LINK_LIBRARIES=${_tmp}") + + # add INCLUDE_DIRECTORIES from ${dep} to ${target}. + # + # 1. we only need this for a submodule build + # 1a. cmake automatically adds ${dep}'s directly-set include directories + # (i.e. attached via target_include_directories(${dep} ..) etc. + # 1b. furthermore, cmake adds transitive deps when we use generated + # cmake support + # 1c. cmake doesn't seem automatically to arrange transitive include directories + # when we have a chain of target_link_libraries() calls. + # (NOTE: it does incorporate include paths from direct dependencies) + # 2. because of 1a, need to exclude ${dep}'s own dirs here (to avoid too-long include path) + # + get_target_property(_depsrcdir ${dep} xo_srcdir) + get_target_property(_depbindir ${dep} xo_bindir) + get_target_property(_tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES) + if(_tmp) + message("xo_dependency_helper: ${target}: + ${dep}.INCLUDE_DIRECTORIES: ${_tmp}") + foreach(dir ${_tmp}) + # want to add these to compile line for $target}. + # this will happen automatically for ${dep}'s own directories; + # those will have been added by + # xo_include_options2() / xo_include_headeronly_options2() + # however we also need directories for ${dep}'s transitive dependencies + # + if(${dir} MATCHES "BUILD_INTERFACE") + message("xo_dependency_helper: ${target} -> ${dep}: consider dir=${dir}") + if(${dir} MATCHES ${_depsrcdir}) + #message(" skip ${dir}") + elseif(${dir} MATCHES ${_depbindir}) + #message(" skip ${dir}") + else() + message(" KEEP ${dir}") + target_include_directories(${target} ${visibility} ${dir}) + endif() + endif() + +# set_property( +# TARGET ${target} +# APPEND +# PROPERTY INCLUDE_DIRECTORIES ${dir}) + endforeach() + get_target_property(_tmp ${target} INTERFACE_INCLUDE_DIRECTORIES) + list(REMOVE_DUPLICATES _tmp) + set_property( + TARGET ${target} + PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${_tmp}) + #message("xo_dependency_helper: ${target}.INCLUDE_DIRECTORIES: ${_tmp}") + endif() + endif() + + # want ${target}.xo_deps to contain union of previous value, and ${dep}.xo_deps + set_property( + TARGET ${target} + APPEND + PROPERTY xo_deps ${dep}) + get_target_property(_tmp ${target} xo_deps) + list(REMOVE_DUPLICATES _tmp) + set_property( + TARGET ${target} + PROPERTY xo_deps ${_tmp}) + + #list(REMOVE_DUPLICATES _xo_dependency_tmp1) +endmacro() + # ---------------------------------------------------------------- # # dependency on an xo library (including header-only libraries) @@ -429,37 +627,64 @@ endmacro() # dep: name of required dependency, e.g. indentlog # macro(xo_dependency target dep) - find_package(${dep} CONFIG REQUIRED) + xo_establish_submodule_build() + + #message("xo_dependency: XO_SUBMODULE_BUILD=${XO_SUBMODULE_BUILD}") + #message("xo_dependency: XO_UMBRELLA_SOURCE_DIR=${XO_UMBRELLA_SOURCE_DIR}") + #message("xo_dependency: PROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}") + + xo_dependency_helper(${target} PUBLIC ${dep}) + + message("----------------------------------------------------------------") + #message("xo_dependency: ${target}.xo_deps.pre=${_xo_dependency_tmp0}") + #message("xo_dependency: ${dep}.xo_deps=${_xo_dependency_tmp2}") + get_target_property(_tmp ${target} xo_deps) + message("xo_dependency: ${target}.xo_deps=${_tmp}") + #get_target_property(_tmp ${target} INCLUDE_DIRECTORIES) + #message("xo_dependency: ${target}.INCLUDE_DIRECTORIES=${_tmp} before target_link_libraries with ${dep}") + target_link_libraries(${target} PUBLIC ${dep}) + #target_link_libraries(${target} ${dep}) + + #get_target_property(_tmp ${target} INCLUDE_DIRECTORIES) + #message("xo_dependency: ${target}.INCLUDE_DIRECTORIES=${_tmp} after target_link_libraries with ${dep}") + #get_target_property(_tmp ${dep} INCLUDE_DIRECTORIES) + #message("xo_dependency: ${dep}.INCLUDE_DIRECTORIES=${_tmp}") + #get_target_property(_tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES) + #message("xo_dependency: ${dep}.INTERFACE_INCLUDE_DIRECTORIES=${_tmp}") + message("----------------------------------------------------------------") endmacro() # dependency of a header-only library on another header-only library # macro(xo_headeronly_dependency target dep) - find_package(${dep} CONFIG REQUIRED) - # Conflict here between PUBLIC and INTERFACE - # - # PUBLIC ensures that include directories required by ${dep} will also be included in compilation of ${target}; - # i.e. will appear in property ${target}.INCLUDE_DIRECTORIES - # - # INTERFACE mandatory when depending on a header-only library (created with add_library(foo INTERFACE)). - # otherwise get error: - # INTERFACE library can only be used with the INTERFACE keyword of - # target_link_libraries - # Unfortunately target_link_libraries() does not copy dependent's INTERFACE_INCLUDE_DIRECTORIES property - # (at least asof cmake 3.25.3). Dependent's INCLUDE_DIRECTORIES property will be empty, since it's header-only. - # - # Workaround by copying property explicity, which we do below - # - target_link_libraries(${target} INTERFACE ${dep}) + xo_establish_submodule_build() -# get_target_property(xo_dependency_headeronly__tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES) -# set_property( -# TARGET ${target} -# APPEND PROPERTY INCLUDE_DIRECTORIES ${xo_dependency_headeronly__tmp}) + xo_dependency_helper(${target} INTERFACE ${dep}) + + # Conflict here between PUBLIC and INTERFACE + # + # PUBLIC ensures that include directories required by ${dep} will also be included in compilation of ${target}; + # i.e. will appear in property ${target}.INCLUDE_DIRECTORIES + # + # INTERFACE mandatory when depending on a header-only library (created with add_library(foo INTERFACE)). + # otherwise get error: + # INTERFACE library can only be used with the INTERFACE keyword of + # target_link_libraries + # Unfortunately target_link_libraries() does not copy dependent's INTERFACE_INCLUDE_DIRECTORIES property + # (at least asof cmake 3.25.3). Dependent's INCLUDE_DIRECTORIES property will be empty, since it's header-only. + # + # Workaround by copying property explicity, which we do below + # + target_link_libraries(${target} INTERFACE ${dep}) + + # get_target_property(xo_dependency_headeronly__tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES) + # set_property( + # TARGET ${target} + # APPEND PROPERTY INCLUDE_DIRECTORIES ${xo_dependency_headeronly__tmp}) endmacro() -# dependency on namespaced target +# dependency on external (non-xo) namespaced target # e.g. # add_library(foo ..) or add_executable(foo ...) # then @@ -471,6 +696,13 @@ endmacro() macro(xo_external_target_dependency target pkg pkgtarget) find_package(${pkg} CONFIG REQUIRED) target_link_libraries(${target} PUBLIC ${pkgtarget}) + #target_link_libraries(${target} ${pkgtarget}) +endmacro() + +# dependency on external (non-xo) target +# +macro(xo_external_dependency target pkg) + xo_external_target_dependency(${target} ${pkg} ${target}) endmacro() # dependency on target provided from this codebase. @@ -575,6 +807,16 @@ macro(xo_pybind11_library target projectTargets source_files) # pybind11_add_module(${target} MODULE ${source_files}) + set_property( + TARGET ${target} + PROPERTY xo_deps "${target}") + set_property( + TARGET ${target} + PROPERTY xo_srcdir ${PROJECT_SOURCE_DIR}) + set_property( + TARGET ${target} + PROPERTY xo_bindir ${PROJECT_BINARY_DIR}) + xo_pybind11_link_flags() xo_include_options2(${target}) # don't want to symlink include tree, because lives in build dir. @@ -613,10 +855,26 @@ endmacro() # -- conceivably true if libfoo.so has RUNPATH etc. # macro(xo_pybind11_dependency target dep) - find_package(${dep} CONFIG REQUIRED) + xo_establish_submodule_build() + + xo_dependency_helper(${target} PUBLIC ${dep}) # clobber secondary dependencies, as discussed above - set_property(TARGET ${dep} PROPERTY INTERFACE_LINK_LIBRARIES "") + if(XO_SUBMODULE_BUILD) + # ok to keep dep libraries on link line in submodule build + message("xo_pybind11_dependency: ${target}: don't clobber ${dep}.INTERFACE_LINK_LIBRARIES") + else() + message("xo_pybind11_dependency: ${target}: clobbering ${dep}.INTERFACE_LINK_LIBRARIES") + set_property(TARGET ${dep} PROPERTY INTERFACE_LINK_LIBRARIES "") + endif() # now that secondary deps are gone, attach to target pybind11 library # skip xo_dependency() here, that would repeat the find_package() expansion target_link_libraries(${target} PUBLIC ${dep}) endmacro() + +# use when one xo pybind library imports another. +# for example +# pyprintjson -> pyreflect +# +macro(xo_pybind11_header_dependency target dep) + xo_dependency_helper(${target} PUBLIC ${dep}) +endmacro() From 3831e387a9d1102da1bad9eabbc80de384d45fe6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:45:18 -0400 Subject: [PATCH 0483/2693] indentlog: need unique cmake targets across xo for submodule build --- CMakeLists.txt | 8 ++++---- example/ex1/CMakeLists.txt | 4 ++-- example/ex2/CMakeLists.txt | 6 ++++-- example/ex3/CMakeLists.txt | 4 ++-- example/ex4/CMakeLists.txt | 4 ++-- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c6799082..65572cc6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,7 +46,7 @@ xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets # ---------------------------------------------------------------- install(TARGETS hello DESTINATION bin/indentlog/example) -install(TARGETS ex1 DESTINATION bin/indentlog/example) -install(TARGETS ex2 DESTINATION bin/indentlog/example) -install(TARGETS ex3 DESTINATION bin/indentlog/example) -install(TARGETS ex4 DESTINATION bin/indentlog/example) +install(TARGETS indentlog_ex1 DESTINATION bin/indentlog/example) +install(TARGETS indentlog_ex2 DESTINATION bin/indentlog/example) +install(TARGETS indentlog_ex3 DESTINATION bin/indentlog/example) +install(TARGETS indentlog_ex4 DESTINATION bin/indentlog/example) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt index a9d1b27d..f42c56de 100644 --- a/example/ex1/CMakeLists.txt +++ b/example/ex1/CMakeLists.txt @@ -1,2 +1,2 @@ -add_executable(ex1 ex1.cpp) -xo_include_options2(ex1) +add_executable(indentlog_ex1 ex1.cpp) +xo_include_options2(indentlog_ex1) diff --git a/example/ex2/CMakeLists.txt b/example/ex2/CMakeLists.txt index 20026b03..2dc39192 100644 --- a/example/ex2/CMakeLists.txt +++ b/example/ex2/CMakeLists.txt @@ -1,2 +1,4 @@ -add_executable(ex2 ex2.cpp) -xo_include_options2(ex2) +# NOTE: need target names to be globally unique within the xo umbrella + +add_executable(indentlog_ex2 ex2.cpp) +xo_include_options2(indentlog_ex2) diff --git a/example/ex3/CMakeLists.txt b/example/ex3/CMakeLists.txt index 9791a931..bb5110ce 100644 --- a/example/ex3/CMakeLists.txt +++ b/example/ex3/CMakeLists.txt @@ -1,2 +1,2 @@ -add_executable(ex3 ex3.cpp) -xo_include_options2(ex3) +add_executable(indentlog_ex3 ex3.cpp) +xo_include_options2(indentlog_ex3) diff --git a/example/ex4/CMakeLists.txt b/example/ex4/CMakeLists.txt index 2a6b9c31..6ff720f7 100644 --- a/example/ex4/CMakeLists.txt +++ b/example/ex4/CMakeLists.txt @@ -1,2 +1,2 @@ -add_executable(ex4 ex4.cpp) -xo_include_options2(ex4) +add_executable(indentlog_ex4 ex4.cpp) +xo_include_options2(indentlog_ex4) From fa5e4216a8c5f1bb7ea14a91ef1b10d8cfa085fe Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:49:21 -0400 Subject: [PATCH 0484/2693] globally-unique cmake target names for submodule build --- CMakeLists.txt | 2 +- example/ex1/CMakeLists.txt | 4 ++-- example/ex2/CMakeLists.txt | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f68effb..8506e5b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,4 +65,4 @@ xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets # ---------------------------------------------------------------- # install additional components -install(TARGETS ex1 DESTINATION bin/randomgen/example) +install(TARGETS randomgen_ex1 DESTINATION bin/randomgen/example) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt index a9d1b27d..0c809818 100644 --- a/example/ex1/CMakeLists.txt +++ b/example/ex1/CMakeLists.txt @@ -1,2 +1,2 @@ -add_executable(ex1 ex1.cpp) -xo_include_options2(ex1) +add_executable(randomgen_ex1 ex1.cpp) +xo_include_options2(randomgen_ex1) diff --git a/example/ex2/CMakeLists.txt b/example/ex2/CMakeLists.txt index 78016d45..877fae36 100644 --- a/example/ex2/CMakeLists.txt +++ b/example/ex2/CMakeLists.txt @@ -1,3 +1,3 @@ -add_executable(ex2 ex2.cpp) -xo_include_options2(ex2) -xo_dependency(ex2 indentlog) +add_executable(randomgen_ex2 ex2.cpp) +xo_include_options2(randomgen_ex2) +xo_dependency(randomgen_ex2 indentlog) From 7b433e3f4ec0a162ac8bca7058b9b68156f3c9fc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:52:39 -0400 Subject: [PATCH 0485/2693] build: .cmake code layout --- src/reflect/CMakeLists.txt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index 00d0b520..397876fa 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -1,9 +1,14 @@ # reflect/CMakeLists.txt set(SELF_LIB reflect) -set(SELF_SRCS TypeDescr.cpp TypeDescrExtra.cpp TaggedRcptr.cpp atomic/AtomicTdx.cpp pointer/PointerTdx.cpp vector/VectorTdx.cpp struct/StructTdx.cpp struct/StructMember.cpp init_reflect.cpp) +set(SELF_SRCS + TypeDescr.cpp TypeDescrExtra.cpp TaggedRcptr.cpp + atomic/AtomicTdx.cpp + pointer/PointerTdx.cpp + vector/VectorTdx.cpp + struct/StructTdx.cpp struct/StructMember.cpp + init_reflect.cpp) -#xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- From e3e50aca2c70d724cf16f3ecd1a5d4bd10b2047e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:54:07 -0400 Subject: [PATCH 0486/2693] build: streamline include-tree install --- CMakeLists.txt | 5 ----- src/process/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b21be0ba..8d562914 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,9 +44,4 @@ add_subdirectory(utest) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -# ---------------------------------------------------------------- -# install project .hpp files - -xo_install_include_tree() - # end CMakeLists.txt diff --git a/src/process/CMakeLists.txt b/src/process/CMakeLists.txt index 9be5b87b..c7bb3abe 100644 --- a/src/process/CMakeLists.txt +++ b/src/process/CMakeLists.txt @@ -5,7 +5,7 @@ set(SELF_SRCS BrownianMotion.cpp ExpProcess.cpp Realization.cpp UpxEvent.cpp UpxToConsole.cpp init_process.cpp) -xo_add_shared_library3(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- # external dependencies From 66a668703951dda9abcd6ecbba45e8b47ecbef22 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:54:40 -0400 Subject: [PATCH 0487/2693] pyprintjson: + pyreflect dep --- src/pyprintjson/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pyprintjson/CMakeLists.txt b/src/pyprintjson/CMakeLists.txt index 5e77c995..d73baad1 100644 --- a/src/pyprintjson/CMakeLists.txt +++ b/src/pyprintjson/CMakeLists.txt @@ -6,3 +6,4 @@ set(SELF_SRCS pyprintjson.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} printjson) +xo_pybind11_header_dependency(${SELF_LIB} pyreflect) From 5553fe5f706eb87bf2af158002720e355dbde910 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:55:03 -0400 Subject: [PATCH 0488/2693] pyreactor: + pyprintjson dep --- src/pyreactor/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pyreactor/CMakeLists.txt b/src/pyreactor/CMakeLists.txt index ad752e96..b7d5695f 100644 --- a/src/pyreactor/CMakeLists.txt +++ b/src/pyreactor/CMakeLists.txt @@ -6,3 +6,4 @@ set(SELF_SRCS pyreactor.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} reactor) +xo_pybind11_header_dependency(${SELF_LIB} pyprintjson) From 74a23f1132b54155567a9629b19b17d4a9feb2d6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:55:32 -0400 Subject: [PATCH 0489/2693] pyreflect: + xo-pyutil dep --- src/pyreflect/CMakeLists.txt | 2 +- src/pyreflect/pyreflect.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyreflect/CMakeLists.txt b/src/pyreflect/CMakeLists.txt index 94a71814..64be5e08 100644 --- a/src/pyreflect/CMakeLists.txt +++ b/src/pyreflect/CMakeLists.txt @@ -9,4 +9,4 @@ set(SELF_SRCS pyreflect.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} reflect) - +xo_pybind11_dependency(${SELF_LIB} xo_pyutil) diff --git a/src/pyreflect/pyreflect.cpp b/src/pyreflect/pyreflect.cpp index c9b7f47a..fc6e8fe8 100644 --- a/src/pyreflect/pyreflect.cpp +++ b/src/pyreflect/pyreflect.cpp @@ -1,7 +1,7 @@ /* @file pyreflect.cpp */ // note: need pyreflect/ here bc pyreflect.hpp is generated, located in build directory -#include "xo/pyreflect/pyreflect.hpp" +#include "pyreflect.hpp" #include "xo/reflect/TypeDescr.hpp" #include "xo/reflect/TaggedRcptr.hpp" #include "xo/reflect/SelfTagging.hpp" From e548a3e8ed063767d36d095a527620082421e94b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:56:52 -0400 Subject: [PATCH 0490/2693] build: handle cmake target called xo_pyutil instead of pyutil --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b5894f9a..f85dd952 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,7 +52,7 @@ xo_toplevel_compile_options() set(SELF_SHORTNAME pyutil) set(SELF_LIB xo_${SELF_SHORTNAME}) -xo_add_headeronly_library(${SELF_LIB}) +xo_add_headeronly_library5(${SELF_LIB} ${SELF_SHORTNAME}) #xo_include_headeronly_options2(${SELF_LIB}) # ---------------------------------------------------------------- From 8036f787e16541a03b7bc48a70b9524ecba04527 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:57:48 -0400 Subject: [PATCH 0491/2693] build: + pyutil dep --- src/pywebutil/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pywebutil/CMakeLists.txt b/src/pywebutil/CMakeLists.txt index 0a8dc225..95f8df26 100644 --- a/src/pywebutil/CMakeLists.txt +++ b/src/pywebutil/CMakeLists.txt @@ -5,3 +5,4 @@ set(SELF_SRCS pywebutil.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} webutil) +xo_pybind11_dependency(${SELF_LIB} xo_pyutil) From a115153dd0a2bd20be42548ef53c8ff6870391d2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:58:12 -0400 Subject: [PATCH 0492/2693] build: + missing deps (noticed in submodule build) --- src/reactor/CMakeLists.txt | 1 + utest/CMakeLists.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/src/reactor/CMakeLists.txt b/src/reactor/CMakeLists.txt index e50e7016..f65772c1 100644 --- a/src/reactor/CMakeLists.txt +++ b/src/reactor/CMakeLists.txt @@ -21,3 +21,4 @@ xo_dependency(${SELF_LIB} reflect) xo_dependency(${SELF_LIB} webutil) xo_dependency(${SELF_LIB} printjson) xo_dependency(${SELF_LIB} callback) +xo_dependency(${SELF_LIB} ordinaltree) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 1ab4b440..1f538cd2 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -13,6 +13,7 @@ target_code_coverage(${SELF_EXE} AUTO ALL) # internal dependency (on this codebase) xo_self_dependency(${SELF_EXE} reactor) +xo_dependency(${SELF_EXE} randomgen) # ---------------------------------------------------------------- # external dependencies From c09dfcc4f526ba1cdc45bccef4761ec903ba13b8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:58:37 -0400 Subject: [PATCH 0493/2693] build: streamling include-tree install --- CMakeLists.txt | 5 ----- src/simulator/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f99f0a2..82d94e6b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,9 +44,4 @@ add_subdirectory(src/simulator) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -# ---------------------------------------------------------------- -# install project .hpp files - -xo_install_include_tree() - # end CMakeLists.txt diff --git a/src/simulator/CMakeLists.txt b/src/simulator/CMakeLists.txt index 172ee4c4..3a7879f5 100644 --- a/src/simulator/CMakeLists.txt +++ b/src/simulator/CMakeLists.txt @@ -5,7 +5,7 @@ set(SELF_SRCS Simulator.cpp SourceTimestamp.cpp init_simulator.cpp) -xo_add_shared_library3(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- # external dependencies From fa0444daa1534dd10ff30b7224768e3b094db868 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:59:00 -0400 Subject: [PATCH 0494/2693] build: streamline include-tree install --- CMakeLists.txt | 5 ----- src/websock/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 51006eed..2178a12d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,9 +44,4 @@ add_subdirectory(src/websock) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -# ---------------------------------------------------------------- -# install .hpp files - -xo_install_include_tree() - # end CMakeLists.txt diff --git a/src/websock/CMakeLists.txt b/src/websock/CMakeLists.txt index a1f15e18..42f37a7d 100644 --- a/src/websock/CMakeLists.txt +++ b/src/websock/CMakeLists.txt @@ -3,7 +3,7 @@ set(SELF_LIB websock) set(SELF_SRCS EndpointUtil.cpp DynamicEndpoint.cpp WebsockUtil.cpp WebsocketSink.cpp Webserver.cpp) -xo_add_shared_library3(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- # external dependencies From 7add6297923d4e92a0d55ef14d29ec36c6498e2e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 15:13:26 -0400 Subject: [PATCH 0495/2693] build: cmake target must match cmake config templates --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 727883e5..d148b38e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,7 +36,7 @@ add_subdirectory(utest) # ---------------------------------------------------------------- # header-only library -set(SELF_LIB ordinaltree) +set(SELF_LIB xo_ordinaltree) xo_add_headeronly_library(${SELF_LIB}) # ---------------------------------------------------------------- From 020f72c375908e5ddb3b80b81c229efc25376499 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 15:13:55 -0400 Subject: [PATCH 0496/2693] build: track chnage to xo-ordinaltree cmake target name --- src/reactor/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reactor/CMakeLists.txt b/src/reactor/CMakeLists.txt index f65772c1..f84c1ca3 100644 --- a/src/reactor/CMakeLists.txt +++ b/src/reactor/CMakeLists.txt @@ -21,4 +21,4 @@ xo_dependency(${SELF_LIB} reflect) xo_dependency(${SELF_LIB} webutil) xo_dependency(${SELF_LIB} printjson) xo_dependency(${SELF_LIB} callback) -xo_dependency(${SELF_LIB} ordinaltree) +xo_dependency(${SELF_LIB} xo_ordinaltree) From 4e933b9a7c6a29ff86bf40e535cdb87cf59b0561 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 15:18:24 -0400 Subject: [PATCH 0497/2693] build: fix include paths --- CMakeLists.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d148b38e..f2a60a57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,8 +36,9 @@ add_subdirectory(utest) # ---------------------------------------------------------------- # header-only library -set(SELF_LIB xo_ordinaltree) -xo_add_headeronly_library(${SELF_LIB}) +set(SELF_SHORTNAME ordinaltree) +set(SELF_LIB xo_${SELF_SHORTNAME}) +xo_add_headeronly_library5(${SELF_LIB} ${SELF_SHORTNAME}) # ---------------------------------------------------------------- # From ff0c4f20198a517f70075525d29ecd13d291a6e8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 15:23:59 -0400 Subject: [PATCH 0498/2693] build: fix include-tree install --- CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f2a60a57..c6fcc268 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,8 +42,7 @@ xo_add_headeronly_library5(${SELF_LIB} ${SELF_SHORTNAME}) # ---------------------------------------------------------------- # -xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) -#xo_install_include_tree() +xo_install_library5(${SELF_LIB} ${SELF_SHORTNAME} ${PROJECT_NAME}Targets) # (note: ..Targets from xo_install_library2()) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) From 8ad546083132b44f362bc97011d873ab31610a5c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 15:50:42 -0400 Subject: [PATCH 0499/2693] build: patch xo- or xo_ prefixes --- cmake/xo_macros/xo_cxx.cmake | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index 2ab628be..94a9cb27 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -247,6 +247,9 @@ endmacro() macro(xo_include_options2 target) xo_establish_submodule_build() + string(REGEX REPLACE "^xo_" "" _nxo_target ${target}) + string(REGEX REPLACE "^xo-" "" _nxo_target ${_nxo_target}) + #message("xo_include_options2: XO_SUBMODULE_BUILD=${XO_SUBMODULE_BUILD}") # ---------------------------------------------------------------- @@ -262,12 +265,12 @@ macro(xo_include_options2 target) target_include_directories( ${target} PUBLIC $ - $ - $ # e.g. for #include "indentlog/scope.hpp" - #$ # e.g. for #include "Refcounted.hpp" in refcnt/src [DEPRECATED] - $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect - #$ # e.g. for generated .hpp files - $ # e.g. for generated .hpp files + $ + $ # e.g. for #include "indentlog/scope.hpp" + #$ # e.g. for #include "Refcounted.hpp" in refcnt/src [DEPRECATED] + $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect + #$ # e.g. for generated .hpp files + $ # e.g. for generated .hpp files ) # ---------------------------------------------------------------- @@ -761,11 +764,14 @@ endmacro() # 2. pyfoo/pyfoo.hpp.in -> pyfoo/pyfoo.hpp # macro(xo_pybind11_library target projectTargets source_files) - file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/include/xo/${target}) + string(REGEX REPLACE "^xo_" "" _nxo_target ${target}) + string(REGEX REPLACE "^xo-" "" _nxo_target ${_nxo_target}) + + file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/include/xo/${_nxo_target}) configure_file( - ${target}.hpp.in - ${PROJECT_BINARY_DIR}/include/xo/${target}/${target}.hpp) + ${_nxo_target}.hpp.in + ${PROJECT_BINARY_DIR}/include/xo/${_nxo_target}/${_nxo_target}.hpp) # was ${PROJECT_SOURCE_DIR}/include/xo/${target}/${target}.hpp) xo_establish_symlink_install() @@ -774,12 +780,12 @@ macro(xo_pybind11_library target projectTargets source_files) xo_install_make_symlink( ${PROJECT_BINARY_DIR}/include/xo ${CMAKE_INSTALL_PREFIX}/include/xo - ${target}) + ${_nxo_target}) else() install( - FILES ${PROJECT_BINARY_DIR}/include/xo/${target}/${target}.hpp + FILES ${PROJECT_BINARY_DIR}/include/xo/${_nxo_target}/${_nxo_target}.hpp PERMISSIONS OWNER_READ GROUP_READ WORLD_READ - DESTINATION ${CMAKE_INSTALL_PREFIX}/include/xo/${target}) + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/xo/${_nxo_target}) endif() # find_package(Python..) finds python in From 098c979f2dfbc3ebce880f5ffdb874370d39dc04 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 15:53:04 -0400 Subject: [PATCH 0500/2693] cmake target pyreflect -> xo_pyreflect --- src/pyreflect/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyreflect/CMakeLists.txt b/src/pyreflect/CMakeLists.txt index 64be5e08..d4acdbfa 100644 --- a/src/pyreflect/CMakeLists.txt +++ b/src/pyreflect/CMakeLists.txt @@ -1,6 +1,6 @@ # xo_pyreflect/src/pyreflect/CMakeLists.txt -set(SELF_LIB pyreflect) +set(SELF_LIB xo_pyreflect) set(SELF_SRCS pyreflect.cpp) # ---------------------------------------------------------------- From a55be719146df74e3bf27bdb21750f91e2eb432d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 15:54:01 -0400 Subject: [PATCH 0501/2693] build: pyreflect -> xo_pyreflect --- src/pyprintjson/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyprintjson/CMakeLists.txt b/src/pyprintjson/CMakeLists.txt index d73baad1..ad68a137 100644 --- a/src/pyprintjson/CMakeLists.txt +++ b/src/pyprintjson/CMakeLists.txt @@ -6,4 +6,4 @@ set(SELF_SRCS pyprintjson.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} printjson) -xo_pybind11_header_dependency(${SELF_LIB} pyreflect) +xo_pybind11_header_dependency(${SELF_LIB} xo_pyreflect) From 5f9942cf0309961b8e90c6b19a48fddbacdaf22a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 16:18:45 -0400 Subject: [PATCH 0502/2693] build: bugfix: ordinaltree<-randomgen dependency in cmake config --- CMakeLists.txt | 3 ++- cmake/xo_ordinaltreeConfig.cmake.in | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c6fcc268..745aacc9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,7 @@ xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets # ---------------------------------------------------------------- # input dependencies +# NOTE: dependency set here must be kept consistent with ordinaltree/cmake/xo_ordinaltreeConfig.cmake.in + # xo-ordinaltree is also header-only -# xo_headeronly_dependency(${SELF_LIB} randomgen) diff --git a/cmake/xo_ordinaltreeConfig.cmake.in b/cmake/xo_ordinaltreeConfig.cmake.in index 8fbf8cb5..7e308d14 100644 --- a/cmake/xo_ordinaltreeConfig.cmake.in +++ b/cmake/xo_ordinaltreeConfig.cmake.in @@ -1,5 +1,6 @@ @PACKAGE_INIT@ +include(CMakeFindDependencyMacro) +find_dependency(randomgen) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") - From 16306684d3c9c2b9433f23863f5bc851df4ce83d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 17:05:05 -0400 Subject: [PATCH 0503/2693] cosmetic: comments --- cmake/xo_macros/xo_cxx.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index 94a9cb27..27e3d02c 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -834,7 +834,7 @@ endmacro() # use this for a dependency of a pybind11 library, # e.g. that was introduced by xo_pybind11_library() # -# Working around the following problem (cmake 3.25.3, pybind11 2.10.4).N +# Working around the following problem (cmake 3.25.3, pybind11 2.10.4) # if: # 1. we have pybind11 library pyfoo, depending on c++ native library foo. # 2. foo depends on other libraries foodep1, foodep2; @@ -849,7 +849,7 @@ endmacro() # get compile instructions like: # g++ -o pyfoo.cpython-311-x86_64-linux-gnu.so path/to/pyfoo.cpp.o path/to/libfoo.so.x.y -lfoodep1 -lfoodep2 # -# 1. This is broken for foodep2, since there no libfoodep2.so exists +# 1. This is broken for foodep2, since in this case no libfoodep2.so exists # 2. Also broken for nix build, because directory containing libfoodep1.so doesn't appear on the compile line. # (It's likely possible to extract this from the .cmake package in lib/cmake/foo/fooTargets.cmake, # but I don't know how to do that yet) @@ -871,6 +871,8 @@ macro(xo_pybind11_dependency target dep) else() message("xo_pybind11_dependency: ${target}: clobbering ${dep}.INTERFACE_LINK_LIBRARIES") set_property(TARGET ${dep} PROPERTY INTERFACE_LINK_LIBRARIES "") + + # also have to clobber libraries for endif() # now that secondary deps are gone, attach to target pybind11 library # skip xo_dependency() here, that would repeat the find_package() expansion From 24a145b48936688a4493a09b6e29d1ba0abde249 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 17:05:37 -0400 Subject: [PATCH 0504/2693] cosmetic: warning comment --- src/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4622ad44..1b90ed07 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,4 +2,8 @@ set(SELF_LIB refcnt) set(SELF_SRCS Refcounted.cpp Displayable.cpp) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) + +# NOTE: +# dependency set here must be kept consistent with refcnt/cmake/refcntConfig.cmake.in +# xo_dependency(${SELF_LIB} indentlog) From 4ccb726cca80ebaee41056a87fb24191aa47e66f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 17:26:00 -0400 Subject: [PATCH 0505/2693] xo-cmake: simplify api -- drop nxo_ args --- cmake/xo_macros/xo_cxx.cmake | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index 27e3d02c..80429258 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -65,9 +65,11 @@ endmacro() # e.g. # - xo_target = xo_pyutil -# - nxo_target = pyutil # -macro(xo_include_headeronly_options5 target nxo_target) +macro(xo_include_headeronly_options target) + string(REGEX REPLACE "^xo_" "" _nxo_target ${target}) + string(REGEX REPLACE "^xo-" "" _nxo_target ${_nxo_target}) + # ---------------------------------------------------------------- # PROJECT_SOURCE_DIR: # so we can for example write @@ -84,12 +86,10 @@ macro(xo_include_headeronly_options5 target nxo_target) target_include_directories( ${target} INTERFACE $ - $ - $ # e.g. for #include "indentlog/scope.hpp" - #$ # e.g. for #include "Refcounted.hpp" in refcnt/src when ${target}=refcnt [DEPRECATED] - $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect - #$ # e.g. for generated .hpp files - $ # e.g. for generated .hpp files + $ + $ # e.g. for #include "indentlog/scope.hpp" + $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect + $ # e.g. for generated .hpp files ) # ---------------------------------------------------------------- @@ -104,7 +104,7 @@ macro(xo_include_headeronly_options5 target nxo_target) endmacro() macro(xo_include_headeronly_options2 target) - xo_include_headeronly_options5(${target} ${target}) + xo_include_headeronly_options(${target}) endmacro() # ---------------------------------------------------------------- @@ -205,9 +205,8 @@ endmacro() # # e.g. # - target=xo_pyutil cmake target name for this library -# - nxo_target=pyutil directory for this target under include/xo # -macro(xo_add_headeronly_library5 target nxo_target) +macro(xo_add_headeronly_library target) add_library(${target} INTERFACE) set_property( @@ -220,11 +219,7 @@ macro(xo_add_headeronly_library5 target nxo_target) TARGET ${target} PROPERTY xo_bindir ${PROJECT_BINARY_DIR}) - xo_include_headeronly_options5(${target} ${nxo_target}) -endmacro() - -macro(xo_add_headeronly_library target) - xo_add_headeronly_library5(${target} ${target}) + xo_include_headeronly_options(${target}) endmacro() # ---------------------------------------------------------------- From ef1e0b037135af6129f5214d835af05a9f7b71fc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 17:26:35 -0400 Subject: [PATCH 0506/2693] build: use streamlined xo-cmake api --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 745aacc9..263db2ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,7 +38,7 @@ add_subdirectory(utest) set(SELF_SHORTNAME ordinaltree) set(SELF_LIB xo_${SELF_SHORTNAME}) -xo_add_headeronly_library5(${SELF_LIB} ${SELF_SHORTNAME}) +xo_add_headeronly_library(${SELF_LIB}) # ---------------------------------------------------------------- # From 6f1b0aca760a719a173fe4a60ba97c8c798cd8e5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 17:27:05 -0400 Subject: [PATCH 0507/2693] build: use streamlined xo-cmake api --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f85dd952..b5894f9a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,7 +52,7 @@ xo_toplevel_compile_options() set(SELF_SHORTNAME pyutil) set(SELF_LIB xo_${SELF_SHORTNAME}) -xo_add_headeronly_library5(${SELF_LIB} ${SELF_SHORTNAME}) +xo_add_headeronly_library(${SELF_LIB}) #xo_include_headeronly_options2(${SELF_LIB}) # ---------------------------------------------------------------- From b2d7a04a09f9224618d96c3faf2da638a11ddeee Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 17:36:21 -0400 Subject: [PATCH 0508/2693] build: extend xo_install_library4 + retire xo_install_library5P --- cmake/xo_macros/xo_cxx.cmake | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index 80429258..a90820f7 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -404,6 +404,9 @@ macro(xo_install_library3 target projectTargets) endmacro() macro(xo_install_library4 target projectTargets) + string(REGEX REPLACE "^xo_" "" _nxo_target ${target}) + string(REGEX REPLACE "^xo-" "" _nxo_target ${_nxo_target}) + install( TARGETS ${target} EXPORT ${projectTargets} @@ -414,7 +417,7 @@ macro(xo_install_library4 target projectTargets) BUNDLE DESTINATION bin COMPONENT Runtime ) - xo_install_include_tree3(include/xo/${target}) + xo_install_include_tree3(include/xo/${_nxo_target}) #xo_install_include_tree() -- use xo_install_include_tree3() separately endmacro() @@ -431,22 +434,6 @@ macro(xo_install_library4_noincludes target projectTargets) ) endmacro() -macro(xo_install_library5 target nxo_target projectTargets) - install( - TARGETS ${target} - EXPORT ${projectTargets} - LIBRARY DESTINATION lib COMPONENT Runtime - ARCHIVE DESTINATION lib COMPONENT Development - RUNTIME DESTINATION bin COMPONENT Runtime - PUBLIC_HEADER DESTINATION include COMPONENT Development - BUNDLE DESTINATION bin COMPONENT Runtime - ) - - xo_install_include_tree3(include/xo/${nxo_target}) - - #xo_install_include_tree() -- use xo_install_include_tree3() separately -endmacro() - # ---------------------------------------------------------------- # for projectname=foo, require: From f880bc66598db2b9fcb57cd6ca3c0b67edd561a6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 17:37:10 -0400 Subject: [PATCH 0509/2693] build: simplify cmake macro api --- CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b5894f9a..8b8e6720 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,15 +50,14 @@ xo_toplevel_compile_options() # ---------------------------------------------------------------- # output targets -set(SELF_SHORTNAME pyutil) -set(SELF_LIB xo_${SELF_SHORTNAME}) +set(SELF_LIB xo_pyutil) xo_add_headeronly_library(${SELF_LIB}) #xo_include_headeronly_options2(${SELF_LIB}) # ---------------------------------------------------------------- # standard install + provide find_package() support -xo_install_library5(${SELF_LIB} ${SELF_SHORTNAME} ${PROJECT_NAME}Targets) +xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) #xo_install_include_tree() xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) From b5dc259687ff5a19dcf49a57c4a8edac56ed6f0d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 17:37:39 -0400 Subject: [PATCH 0510/2693] build: use simplified cmake macro api --- CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 263db2ca..f336712c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,13 +36,12 @@ add_subdirectory(utest) # ---------------------------------------------------------------- # header-only library -set(SELF_SHORTNAME ordinaltree) -set(SELF_LIB xo_${SELF_SHORTNAME}) +set(SELF_LIB xo_ordinaltree) xo_add_headeronly_library(${SELF_LIB}) # ---------------------------------------------------------------- # -xo_install_library5(${SELF_LIB} ${SELF_SHORTNAME} ${PROJECT_NAME}Targets) +xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) # (note: ..Targets from xo_install_library2()) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) From 6360cd7297d726534a9b1402343804360ccb85a4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 17:45:52 -0400 Subject: [PATCH 0511/2693] build: + xo_strip_xo_prefix() + use to consolidate --- cmake/xo_macros/xo_cxx.cmake | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index a90820f7..cba39ed2 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -63,12 +63,20 @@ macro(xo_toplevel_compile_options) endif() endmacro() +# xo_strip_xo_prefix(xo_foo tmp) --> tmp=foo +# xo_strip_xo_prefix(xo-foo tmp) --> tmp=foo +# xo_strip_xo_prefix(foo tmp) --> tmp=foo +# +macro(xo_strip_xo_prefix str outputvar) + string(REGEX REPLACE "^xo_" "" _tmp ${str}) + string(REGEX REPLACE "^xo-" "" ${outputvar} ${_tmp}) +endmacro() + # e.g. # - xo_target = xo_pyutil # macro(xo_include_headeronly_options target) - string(REGEX REPLACE "^xo_" "" _nxo_target ${target}) - string(REGEX REPLACE "^xo-" "" _nxo_target ${_nxo_target}) + xo_strip_xo_prefix(${target} _nxo_target) # ---------------------------------------------------------------- # PROJECT_SOURCE_DIR: @@ -242,8 +250,7 @@ endmacro() macro(xo_include_options2 target) xo_establish_submodule_build() - string(REGEX REPLACE "^xo_" "" _nxo_target ${target}) - string(REGEX REPLACE "^xo-" "" _nxo_target ${_nxo_target}) + xo_strip_xo_prefix(${target} _nxo_target) #message("xo_include_options2: XO_SUBMODULE_BUILD=${XO_SUBMODULE_BUILD}") @@ -404,8 +411,7 @@ macro(xo_install_library3 target projectTargets) endmacro() macro(xo_install_library4 target projectTargets) - string(REGEX REPLACE "^xo_" "" _nxo_target ${target}) - string(REGEX REPLACE "^xo-" "" _nxo_target ${_nxo_target}) + xo_strip_xo_prefix(${target} _nxo_target) install( TARGETS ${target} @@ -505,8 +511,7 @@ endmacro() # nxo_dep: cmake target without any xo_ prefix. (e.g. pyutil) # macro(xo_dependency_helper target visibility dep) - string(REGEX REPLACE "^xo_" "" _nxo_dep ${dep}) - string(REGEX REPLACE "^xo-" "" _nxo_dep ${_nxo_dep}) + xo_strip_xo_prefix(${dep} _nxo_dep) xo_establish_submodule_build() @@ -746,8 +751,7 @@ endmacro() # 2. pyfoo/pyfoo.hpp.in -> pyfoo/pyfoo.hpp # macro(xo_pybind11_library target projectTargets source_files) - string(REGEX REPLACE "^xo_" "" _nxo_target ${target}) - string(REGEX REPLACE "^xo-" "" _nxo_target ${_nxo_target}) + xo_strip_xo_prefix(${target} _nxo_target) file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/include/xo/${_nxo_target}) From cd969000466a7b6a91c7fab806f0db70635032ee Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 17:52:50 -0400 Subject: [PATCH 0512/2693] build: drop debug log messages --- cmake/xo_macros/xo_cxx.cmake | 45 ++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index cba39ed2..2f9560db 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -528,15 +528,15 @@ macro(xo_dependency_helper target visibility dep) endif() if (XO_SUBMODULE_BUILD) - get_target_property(_tmp ${target} LINK_LIBRARIES) - message("xo_dependency_helper: ${target} -> ${dep}: ${target}.LINK_LIBRARIES=${_tmp}") - get_target_property(_tmp ${dep} LINK_LIBRARIES) - message("xo_dependency_helper: ${target} -> ${dep}: ${dep}.LINK_LIBRARIES=${_tmp}") + #get_target_property(_tmp ${target} LINK_LIBRARIES) + #message("xo_dependency_helper: ${target} -> ${dep}: ${target}.LINK_LIBRARIES=${_tmp}") + #get_target_property(_tmp ${dep} LINK_LIBRARIES) + #message("xo_dependency_helper: ${target} -> ${dep}: ${dep}.LINK_LIBRARIES=${_tmp}") - get_target_property(_tmp ${target} INTERFACE_LINK_LIBRARIES) - message("xo_dependency_helper: ${target} -> ${dep}: ${target}.INTERFACE_LINK_LIBRARIES=${_tmp}") - get_target_property(_tmp ${dep} INTERFACE_LINK_LIBRARIES) - message("xo_dependency_helper: ${target} -> ${dep}: ${dep}.INTERFACE_LINK_LIBRARIES=${_tmp}") + #get_target_property(_tmp ${target} INTERFACE_LINK_LIBRARIES) + #message("xo_dependency_helper: ${target} -> ${dep}: ${target}.INTERFACE_LINK_LIBRARIES=${_tmp}") + #get_target_property(_tmp ${dep} INTERFACE_LINK_LIBRARIES) + #message("xo_dependency_helper: ${target} -> ${dep}: ${dep}.INTERFACE_LINK_LIBRARIES=${_tmp}") # add INCLUDE_DIRECTORIES from ${dep} to ${target}. # @@ -554,7 +554,7 @@ macro(xo_dependency_helper target visibility dep) get_target_property(_depbindir ${dep} xo_bindir) get_target_property(_tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES) if(_tmp) - message("xo_dependency_helper: ${target}: + ${dep}.INCLUDE_DIRECTORIES: ${_tmp}") + #message("xo_dependency_helper: ${target}: + ${dep}.INCLUDE_DIRECTORIES: ${_tmp}") foreach(dir ${_tmp}) # want to add these to compile line for $target}. # this will happen automatically for ${dep}'s own directories; @@ -563,21 +563,16 @@ macro(xo_dependency_helper target visibility dep) # however we also need directories for ${dep}'s transitive dependencies # if(${dir} MATCHES "BUILD_INTERFACE") - message("xo_dependency_helper: ${target} -> ${dep}: consider dir=${dir}") + #message("xo_dependency_helper: ${target} -> ${dep}: consider dir=${dir}") if(${dir} MATCHES ${_depsrcdir}) #message(" skip ${dir}") elseif(${dir} MATCHES ${_depbindir}) #message(" skip ${dir}") else() - message(" KEEP ${dir}") + #message(" KEEP ${dir}") target_include_directories(${target} ${visibility} ${dir}) endif() endif() - -# set_property( -# TARGET ${target} -# APPEND -# PROPERTY INCLUDE_DIRECTORIES ${dir}) endforeach() get_target_property(_tmp ${target} INTERFACE_INCLUDE_DIRECTORIES) list(REMOVE_DUPLICATES _tmp) @@ -625,13 +620,13 @@ macro(xo_dependency target dep) xo_dependency_helper(${target} PUBLIC ${dep}) - message("----------------------------------------------------------------") - #message("xo_dependency: ${target}.xo_deps.pre=${_xo_dependency_tmp0}") - #message("xo_dependency: ${dep}.xo_deps=${_xo_dependency_tmp2}") - get_target_property(_tmp ${target} xo_deps) - message("xo_dependency: ${target}.xo_deps=${_tmp}") - #get_target_property(_tmp ${target} INCLUDE_DIRECTORIES) - #message("xo_dependency: ${target}.INCLUDE_DIRECTORIES=${_tmp} before target_link_libraries with ${dep}") + #message("----------------------------------------------------------------") + ##message("xo_dependency: ${target}.xo_deps.pre=${_xo_dependency_tmp0}") + ##message("xo_dependency: ${dep}.xo_deps=${_xo_dependency_tmp2}") + #get_target_property(_tmp ${target} xo_deps) + #message("xo_dependency: ${target}.xo_deps=${_tmp}") + ##get_target_property(_tmp ${target} INCLUDE_DIRECTORIES) + ##message("xo_dependency: ${target}.INCLUDE_DIRECTORIES=${_tmp} before target_link_libraries with ${dep}") target_link_libraries(${target} PUBLIC ${dep}) #target_link_libraries(${target} ${dep}) @@ -642,7 +637,7 @@ macro(xo_dependency target dep) #message("xo_dependency: ${dep}.INCLUDE_DIRECTORIES=${_tmp}") #get_target_property(_tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES) #message("xo_dependency: ${dep}.INTERFACE_INCLUDE_DIRECTORIES=${_tmp}") - message("----------------------------------------------------------------") + #message("----------------------------------------------------------------") endmacro() # dependency of a header-only library on another header-only library @@ -853,7 +848,7 @@ macro(xo_pybind11_dependency target dep) # clobber secondary dependencies, as discussed above if(XO_SUBMODULE_BUILD) # ok to keep dep libraries on link line in submodule build - message("xo_pybind11_dependency: ${target}: don't clobber ${dep}.INTERFACE_LINK_LIBRARIES") + #message("xo_pybind11_dependency: ${target}: don't clobber ${dep}.INTERFACE_LINK_LIBRARIES") else() message("xo_pybind11_dependency: ${target}: clobbering ${dep}.INTERFACE_LINK_LIBRARIES") set_property(TARGET ${dep} PROPERTY INTERFACE_LINK_LIBRARIES "") From db01f8cefc350e9053f9f01cff9909e966e059a4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 20:26:27 -0400 Subject: [PATCH 0513/2693] bugfix: varname in .hpp template --- src/pyreactor/pyreactor.hpp.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pyreactor/pyreactor.hpp.in b/src/pyreactor/pyreactor.hpp.in index edbb2e78..140ded1b 100644 --- a/src/pyreactor/pyreactor.hpp.in +++ b/src/pyreactor/pyreactor.hpp.in @@ -8,18 +8,18 @@ * example: * PYBIND11_MODULE(PYREACTOR_MODULE_NAME(), m) { ... } */ -#define PYREACTOR_MODULE_NAME() @SELF_LIBRARY_NAME@ +#define PYREACTOR_MODULE_NAME() @SELF_LIB@ /* example: * py::module_::import(PYREACTOR_MODULE_NAME_STR) */ -#define PYREACTOR_MODULE_NAME_STR "@SELF_LIBRARY_NAME@" +#define PYREACTOR_MODULE_NAME_STR "@SELF_LIB@" /* example: * PYREACTOR_IMPORT_MODULE() * replaces * py::module_::import("pyreactor") */ -#define PYREACTOR_IMPORT_MODULE() py::module_::import("@SELF_LIBRARY_NAME@") +#define PYREACTOR_IMPORT_MODULE() py::module_::import("@SELF_LIB@") /* end pyreactor.hpp */ From 86fb63c6794e9dd9d4abfb10a247b48f737bb8b9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 20:39:54 -0400 Subject: [PATCH 0514/2693] bugfix: drop std::move to allow copy elisison --- 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 d7bec9ae..29ad0dda 100644 --- a/include/xo/indentlog/log_state.hpp +++ b/include/xo/indentlog/log_state.hpp @@ -347,7 +347,7 @@ namespace xo { ss << code_location(this->file_, this->line_, log_config::code_location_color); - std::string ss_str = std::move(ss.str()); /*c++20*/ + std::string ss_str = ss.str(); /*hoping for copy elision here*/ sbuf2->sputn(ss_str.c_str(), ss_str.size()); this->location_flag_ = false; From 8454b48956dcaf7b8884588595ef52d230067b4f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 20:51:11 -0400 Subject: [PATCH 0515/2693] initial implementation --- CMakeLists.txt.old | 37 ++++ EXAMPLES | 164 +++++++++++++++ EigenUtil.cpp | 157 ++++++++++++++ EigenUtil.hpp | 29 +++ KalmanFilter.cpp | 78 +++++++ KalmanFilter.hpp | 128 ++++++++++++ KalmanFilterEngine.cpp | 360 +++++++++++++++++++++++++++++++++ KalmanFilterEngine.hpp | 185 +++++++++++++++++ KalmanFilterInput.cpp | 118 +++++++++++ KalmanFilterInput.hpp | 121 +++++++++++ KalmanFilterInputCallback.hpp | 14 ++ KalmanFilterInputSource.hpp | 28 +++ KalmanFilterInputToConsole.cpp | 25 +++ KalmanFilterInputToConsole.hpp | 30 +++ KalmanFilterObservable.cpp | 71 +++++++ KalmanFilterObservable.hpp | 110 ++++++++++ KalmanFilterOutputCallback.hpp | 15 ++ KalmanFilterSpec.cpp | 27 +++ KalmanFilterSpec.hpp | 86 ++++++++ KalmanFilterState.cpp | 231 +++++++++++++++++++++ KalmanFilterState.hpp | 163 +++++++++++++++ KalmanFilterStateToConsole.cpp | 25 +++ KalmanFilterStateToConsole.hpp | 30 +++ KalmanFilterStep.cpp | 84 ++++++++ KalmanFilterStep.hpp | 128 ++++++++++++ KalmanFilterSvc.cpp | 44 ++++ KalmanFilterSvc.hpp | 64 ++++++ KalmanFilterTransition.cpp | 55 +++++ KalmanFilterTransition.hpp | 62 ++++++ init_filter.cpp | 51 +++++ init_filter.hpp | 20 ++ print_eigen.hpp | 41 ++++ 32 files changed, 2781 insertions(+) create mode 100644 CMakeLists.txt.old create mode 100644 EXAMPLES create mode 100644 EigenUtil.cpp create mode 100644 EigenUtil.hpp create mode 100644 KalmanFilter.cpp create mode 100644 KalmanFilter.hpp create mode 100644 KalmanFilterEngine.cpp create mode 100644 KalmanFilterEngine.hpp create mode 100644 KalmanFilterInput.cpp create mode 100644 KalmanFilterInput.hpp create mode 100644 KalmanFilterInputCallback.hpp create mode 100644 KalmanFilterInputSource.hpp create mode 100644 KalmanFilterInputToConsole.cpp create mode 100644 KalmanFilterInputToConsole.hpp create mode 100644 KalmanFilterObservable.cpp create mode 100644 KalmanFilterObservable.hpp create mode 100644 KalmanFilterOutputCallback.hpp create mode 100644 KalmanFilterSpec.cpp create mode 100644 KalmanFilterSpec.hpp create mode 100644 KalmanFilterState.cpp create mode 100644 KalmanFilterState.hpp create mode 100644 KalmanFilterStateToConsole.cpp create mode 100644 KalmanFilterStateToConsole.hpp create mode 100644 KalmanFilterStep.cpp create mode 100644 KalmanFilterStep.hpp create mode 100644 KalmanFilterSvc.cpp create mode 100644 KalmanFilterSvc.hpp create mode 100644 KalmanFilterTransition.cpp create mode 100644 KalmanFilterTransition.hpp create mode 100644 init_filter.cpp create mode 100644 init_filter.hpp create mode 100644 print_eigen.hpp diff --git a/CMakeLists.txt.old b/CMakeLists.txt.old new file mode 100644 index 00000000..e85d245f --- /dev/null +++ b/CMakeLists.txt.old @@ -0,0 +1,37 @@ +# filter/CMakeLists.txt + +set(SELF_LIBRARY_NAME filter) + +set(SELF_SOURCE_FILES KalmanFilterSvc.cpp KalmanFilter.cpp KalmanFilterState.cpp KalmanFilterTransition.cpp KalmanFilterObservable.cpp KalmanFilterInput.cpp KalmanFilterStep.cpp KalmanFilterSpec.cpp KalmanFilterEngine.cpp KalmanFilterInputToConsole.cpp KalmanFilterStateToConsole.cpp EigenUtil.cpp init_filter.cpp) + +# build shared liburary 'filter' +add_library(${SELF_LIBRARY_NAME} SHARED ${SELF_SOURCE_FILES}) + +set_target_properties(${SELF_LIBRARY_NAME} + PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION 1 + PUBLIC_HEADER init_filter.hpp) + +# ---------------------------------------------------------------- +# all the warnings! +# +xo_compile_options(${SELF_LIBRARY_NAME}) +xo_include_options(${SELF_LIBRARY_NAME}) + +# ---------------------------------------------------------------- +# internal dependencies: reactor, ... + +#target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC process) +target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC reactor) +target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC printjson) +target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC refcnt) + +# ---------------------------------------------------------------- +# 3rd party dependency: eigen: + +xo_eigen_dependency(${SELF_LIBRARY_NAME}) + +xo_install_library(${SELF_LIBRARY_NAME}) + +# end CMakeLists.txt diff --git a/EXAMPLES b/EXAMPLES new file mode 100644 index 00000000..8f69ea73 --- /dev/null +++ b/EXAMPLES @@ -0,0 +1,164 @@ +scaffold kalman filter implementation here + +notation: + +x_(k) : n x 1 : true system state that we want to estimate +F(k) : n x n : state transition matrix at time t(k) +w_(k) : n x 1 : system noise, with mean 0, covariance Q(k) +Q(k) : n x n : covariance of systen noise + +v_(k) : m x 1 : observation errors at time t(k) +R(k) : m x m : observation error covariance matrix at time t(k) + +x(k) : n x 1 : state vector estimate for time t(k) +P(k) : n x n : covariance matrix for x(k) +z(k) : m x 1 : observation vector at time t(k) +H(k) : m x n : observation matrix at time t(k) + +K(k) : : kalman gain - measures information gain from observation z(k) + relative to prior P(k | k-1) + +use shorthand: + xT, F(k)T for transpose(x), transpose(F(k)) + x(k+1 | k) for "estimate x(k+1) given information known at t(k)" + +A. Linear Kalman Filter +----------------------- + +1. system model: + x_(k+1) = F(k).x_(k) + w_(k), w_(k) ~ N(0,Q) + + i.e. expected behavior of system from t(k) -> t(k+1), + absent system noise, is given by linear transformation F(k) + + ofc model is not directly observable, + since we don't know x_(k) or w_(k), + instead we will be estimating it. + +2. measurement model: + z(k+1) = H(k+1).x_(k+1) + v_(k), v_(k) ~ N(0, R) + +3. prior: + x(0), P(0) + + must be supplied as initial input + +4. pre-estimate for t(k+1) system state: + (before incorporating z(k+1) and accounting for system noise) + x(k+1|k) := F(k).x(k) + + in other words propagate t(k) estimate to t(k+1), + using F(k) + +5. pre-estimate for t(k+1) estimate covariance + (before incorporating z(k+1) and accounting for system noise) + P(k+1|k) := F(k).P(k).F(k)T + Q(k) + +6. kalman gain matrix + K(k+1) := P(k+1|k).H(k)T.inverse(H(k).P(k+1|k).H(k)T + R(k)) + + note that the matrix-to-be-inverted in the gain expression + is symmetric and positive-definite (so can use cholesky decomposition) + +7. innovation: difference between actual observation vector and + observation predicted from state estimate x(k+1|k): + z(k+1) - H(k+1).x(k+1|k) + +8. corrected state estimate for t(k+1): + x(k+1) := x(k+1|k) + K(k+1)[z(k+1) - H(k+1).x(k+1|k)] + +9. correct state covariance for t(k+1): + P(k+1) := [I - K(k+1).H(k+1)].P(k+1|k) + + +B. Extended Kalman Filter +------------------------- +Must write in continuous form, to deal with non-linearities + +notation: + +x_(t) : n x 1 : true system state that we want to estimate, + continuously evolves with t +f(x_(t),t) : n x 1 : expected derivative (d/dt)(x_(t)) +h(x_(t),t) : m x 1 : observation function +x(k) : n x 1 : state vector estimate for x(t) at time t=t(k) +F(x(t),t) : n x n : jacobian of f(x_(t),t) evaluated at x_(t)=x(t) +H(x(t),t) : m x n : jacobian of h(x_(t),t) evaluated at x_(t)=x(t) + +1. system model: + (d/dt)x_(t) = f(x_(t),t) + w(t), + where + w(t) is a white noise with + + / t2 + | + | w(t).dt ~ N(0, Q(t1,t2)) + | + / t1 + + editorial: + note that we exclude more general model + (d/dt)x_(t) = f(x_(t),t)) + G(x_(t),t).w(t) + because would be unable to assume RHS mean-square integrable. + The form chosen effectively band-limits the frequencies at which + w(t) acts, since it's independent of x_(t). This allows mean-square + rules to be applied as in linear kalman filter + +2. measurement model: + z(k+1) = h(x_(t(k+1)), t(k+1)) + v(k), v(k) ~ N(0, Rk) + +3. prior: + x(0), P(0) + + (same as for linear kalman filter) + +(3a). state estimate propagation model: + (d/dt)x(t) = f(x(t),t) + + editorial: + this step is *sketchy*: x(t) is intended to be minimum-variance + estimate for system state, but that property is lost when solving + differential equation (d/dt)x(t) = f(x(t),t) with non-linear f. + +(3b). covariance estimate propagation model: + (d/dt)P(t) = F(x(t),t).P(t) + P(t).F(x(t),t)T + Q(t) + +4. pre-estimate (extrapolation) for t(k+1) system state: + + can use first order approx: + x(k+1|k) = x(k) + F(x(tk),tk).(t(k+1) - t(k)) + or multiple timesteps if t(k+1) - t(k) is too large + + i.e. approximately solving (3a) + +5. pre-estimate (extrapolation) for t(k+1) error covariance: + + P(k+1|k) = P(k) + [F(x(tk),tk).P(k) + P(k)F(x(tk),tk)T + Q(tk)].(t(k+1) - t(k)) + or multiple timesteps if t(k+1) - t(k) is too large + + i.e. approximately solving (3b) + +6. kalman gain matrix + K(k+1) = P(k+1|k) + .H(k+1)T(x(k+1|k)) + .inverse[H(k+1)(x(k+1|k)).P(k+1|k).H(k+1)T(x(k+1|k)) + R(k+1)] + +7. innovation: + z(k+1) - h(x(k+1|k), t(k+1)) + +8. corrected state estimate for t(k+1): + x(k+1) = x(k+1|k) + K(k+1).(z(k+1) - h(k+1)(x(k+1|k)) + +9. corrected error covariance for t(k+1): + P(k+1) = [I - K(k+1).H(k+1)(x(k+1|k))].P(k+1|k) + + editorial: + This step is *sketchy*, to the extend h() is non-linear, linearizing around + x(k+1|k) may give poor estimate of state covariance + +10. linearization (1st order term for taylor series of f() around x(t)) + jacobian: + F(x(t),t) = (df/dx_) evaluated at x_(t)=x(t) + + H(k+1)(x(k+1|k)) = (dh/dx_) evaluated at x_(t)=x(k+1|k) + diff --git a/EigenUtil.cpp b/EigenUtil.cpp new file mode 100644 index 00000000..f1d3c4a3 --- /dev/null +++ b/EigenUtil.cpp @@ -0,0 +1,157 @@ +/* file EigenUtil.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "EigenUtil.hpp" +#include "printjson/PrintJson.hpp" +#include "reflect/Reflect.hpp" +#include +#include +#include +#include + +namespace xo { + using xo::json::PrintJson; + using xo::json::JsonPrinter; + using xo::reflect::Reflect; + using xo::reflect::TypeDescr; + using VectorXb = Eigen::Array; + using Eigen::VectorXd; + using Eigen::MatrixXd; + +#ifdef NOT_YET + namespace reflect { + template + using EigenVectorX_Tdx = xo::reflect::StlVectorTdx>; + + /* probably need this to appear before decl for class xo::reflect::Reflect */ + template + class EstablishTdx> { + public: + static std::unique_ptr make() { + return EigenVectorX_Tdx::make(); + } /*make*/ + }; /*EstablishTdx*/ + } /*reflect*/ +#endif + + namespace eigen { + + namespace { + /* prints a VectorXd as json, in the obvious format, e.g. + * [1,2,3] + */ + template + class EigenVectorJsonPrinter : public JsonPrinter { + public: + EigenVectorJsonPrinter(PrintJson const * pjson) : JsonPrinter(pjson) {} + + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override + { + EigenVectorType * pv = this->check_recover_native(tp, p_os); + + if (pv) { + /* EigenVectorType (VectorXb, VectorXd, ..) + * is reflected as atomic for now, out of expedience. + * + * as soon as we reflect as mt_vector, will not need this helper. + */ + *p_os << "["; + + for (std::uint32_t i = 0, n = pv->size(); i < n; ++i) { + if (i > 0) + *p_os << ","; + + /* note: need to dispatch via json printer for vector elements, + * to get special treatment for non-finite values + */ + this->pjson()->print((*pv)[i], p_os); + //*p_os << jsonp((*pv)[i], this->pjson()); + } + + *p_os << "]"; + } + } /*print_json*/ + }; /*EigenVectorJsonPrinter*/ + + /* prints a MatrixXd as json, in row-major format, e.g. + * [[1,2,3], [4,5,6], [7,8,9]] + */ + class MatrixXdJsonPrinter : public JsonPrinter { + public: + MatrixXdJsonPrinter(PrintJson const * pjson) : JsonPrinter(pjson) {} + + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override + { + MatrixXd * pm = this->check_recover_native(tp, p_os); + + if (pm) { + /* MatrixXd is reflected as atomic for now, out of expedience */ + *p_os << "["; + + for(std::uint32_t i=0, m=pm->rows(); i 0) + *p_os << ", "; + *p_os << "["; + for(std::uint32_t j=0, n=pm->cols(); j 0) + *p_os << ","; + + /* note: need to dispatch via json printer for matrix elements, + * to get special treatment for non-finite values + */ + this->pjson()->print((*pm)(i, j), p_os); + //*p_os << jsonp((*pm)(i, j), this->pjson()); + } + *p_os << "]"; + } + + *p_os << "]"; + } + } /*print_json*/ + }; /*MatrixXdJsonPrinter*/ + + template + void + provide_eigen_vector_printer(PrintJson * p_pjson) + { + TypeDescr td = Reflect::require(); + std::unique_ptr pr(new EigenVectorJsonPrinter(p_pjson)); + + p_pjson->provide_printer(td, std::move(pr)); + } /*provide_eigen_vector_printer*/ + } /*namespace*/ + + void + EigenUtil::reflect_eigen() + { +#ifdef NOT_YET + Reflect::require(); + Reflect::require(); +#endif + } /*reflect_eigen*/ + + void + EigenUtil::provide_json_printers(PrintJson * p_pjson) + { + assert(p_pjson); + + provide_eigen_vector_printer(p_pjson); + provide_eigen_vector_printer(p_pjson); + + { + TypeDescr td = Reflect::require(); + std::unique_ptr pr(new MatrixXdJsonPrinter(p_pjson)); + + p_pjson->provide_printer(td, std::move(pr)); + } + } /*provide_json_printers*/ + } /*namespace eigen*/ +} /*namespace xo*/ + +/* end EigenUtil.cpp */ diff --git a/EigenUtil.hpp b/EigenUtil.hpp new file mode 100644 index 00000000..07a86d48 --- /dev/null +++ b/EigenUtil.hpp @@ -0,0 +1,29 @@ +/* file EigenUtil.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +namespace xo { + namespace json { class PrintJson; } + + namespace eigen { + class EigenUtil { + public: + /* reflection for + * Eigen::VectorXd + * VectorXb (= Eigen::Array; by analogy with Eigen::VectorXd) + */ + static void reflect_eigen(); + + /* json printers for: + * Eigen::VectorXd + * Eigen::MatrixXd + */ + static void provide_json_printers(json::PrintJson * pjson); + }; /*EigenUtil*/ + } /*namespace eigen*/ +} /*namespace xo*/ + +/* end EigenUtil.hpp */ diff --git a/KalmanFilter.cpp b/KalmanFilter.cpp new file mode 100644 index 00000000..bf94fde2 --- /dev/null +++ b/KalmanFilter.cpp @@ -0,0 +1,78 @@ +/* @file KalmanFilter.cpp */ + +#include "KalmanFilter.hpp" +#include "KalmanFilterEngine.hpp" +#include "print_eigen.hpp" +#include "indentlog/scope.hpp" +#include "Eigen/src/Core/Matrix.h" + +namespace xo { + using xo::time::utc_nanos; + //using logutil::matrix; + using xo::scope; + using xo::tostr; + using xo::xtag; + using Eigen::MatrixXd; + using Eigen::VectorXd; + + namespace kalman { + // ----- KalmanFilter ----- + + KalmanFilter::KalmanFilter(KalmanFilterSpec spec) + : filter_spec_{std::move(spec)}, + state_ext_{filter_spec_.start_ext()} + {} /*ctor*/ + + void + KalmanFilter::notify_input(ref::rp const & input_kp1) + { + scope log(XO_ENTER0(info)); + + /* on entry: + * .state_ext refers to t(k) + * on exit: + * .step refers to t(k+1) + * .state_ext refers to t(k+1) + */ + + log && log(xtag("step_dt", + input_kp1->tkp1() - this->state_ext_->tm())); + + /* establish step inputs for this filter step: + * F(k+1) (system transition matrix) + * Q(k+1) (system noise covariance matrix) + * H(k+1) (observation coupling matrix) + * R(k+1) (observation noise covariance matrix) + * z(k+1) (observation vector) + */ + this->step_ = this->filter_spec_.make_step(this->state_ext_, input_kp1); + + //if (lscope.enabled()) { lscope.log(xtag("step", this->step_)); } + + /* extrapolate filter state to t(k+1), + * and correct based on z(k+1) + */ + this->state_ext_ = KalmanFilterEngine::step(this->step_); + + //if (lscope.enabled()) { lscope.log(xtag("state_ext", this->state_ext_)); } + } /*notify_input*/ + + void + KalmanFilter::display(std::ostream & os) const + { + os << ""; + } /*display*/ + + std::string + KalmanFilter::display_string() const + { + return tostr(*this); + } /*display_string*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilter.cpp */ diff --git a/KalmanFilter.hpp b/KalmanFilter.hpp new file mode 100644 index 00000000..295a4f7a --- /dev/null +++ b/KalmanFilter.hpp @@ -0,0 +1,128 @@ +/* @file KalmanFilter.hpp */ + +#pragma once + +#include "filter/KalmanFilterSpec.hpp" + +namespace xo { + namespace kalman { + /* Specification for an ordinary discrete linear kalman filter. + * + * The filter generates estimates for a process observed at a discrete + * set of times tk in {t0, t1, .., tn} + * + * At each time tk we have the following: + * + * 0. x(0) initial estimate at t(0) + * P(0) initial priors: error covariance matrix for x(0) + * + * 1. x_(k), [n x 1] vector: + * system state, denoted by vector. + * (state is not directly observable, + * filter will attempt to estimate it) + * + * 2. w_(k), [n x 1] vector + * Q(k), [n x n] matrix + * + * w_(k) denotes system noise, + * gaussian with covariance Q(k). + * noise w_(k) is not directly observable. + * + * 3. z(k), [m x 1] vector: + * + * observation vector for time tk + * + * 4. v_(k), [m x 1] vector + * R(k), [m x m] matrix + * + * v_(k) denotes observation errors, + * gaussian with covariance R(k). + * noise v_(k) is not directly observable. + * + * 5. F(k), [n x n] matrix + * state transition matrix + * model system evolves according to: + * + * x_(k+1) = F(x).x_(k) + w_(k) + * + * 6. observations z(k) depend on system state: + * + * z(k) = H(k).x_(k) + v_(k) + * + * 7. Kalman filter outputs: + * x(k), [n x 1] vector + * Q(k), [n x n] matrix + * + * x(k) is optimal estimate for system state x_(k) + * P(k) is covariance matrix specifying confidence intervals + * for pairs (x(k)[i], x(k)[j]) + * + * filter specification consists of: + * n, m, x(0), P(0), F(k), Q(k), H(k), R(k) + * The cardinality of observations z(k) can vary over time, + * so to be precise, m can vary with tk; write as m(k) + * + * More details: + * - avoid having to specify t(k) in advance; + * instead defer until observation available + * so t(k) can be taken from polling timestamp + */ + + /* encapsulate a (linear) kalman filter + * together with event publishing + */ + class KalmanFilter { + public: + using MatrixXd = Eigen::MatrixXd; + using VectorXd = Eigen::VectorXd; + using utc_nanos = xo::time::utc_nanos; + + public: + /* create filter with specification given by spec, and initial state s0 */ + explicit KalmanFilter(KalmanFilterSpec spec); + + uint32_t step_no() const { return state_ext_->step_no(); } + utc_nanos tm() const { return state_ext_->tm(); } + KalmanFilterSpec const & filter_spec() const { return filter_spec_; } + KalmanFilterStep const & step() const { return step_; } + ref::rp const & state_ext() const { return state_ext_; } + + /* notify kalman filter with input for time t(k+1) = input_kp1.tkp1() + * Require: input.tkp1() >= .current_tm() + * Promise: + * - .tm() = input_kp1.tkp1() + * - .step_no() = old .step_no() + 1 + * - .filter_spec_k, .step_k, .state_k updated + * for observations in input_kp1 + */ + void notify_input(ref::rp const & input_kp1); + + void display(std::ostream & os) const; + std::string display_string() const; + + private: + /* specification for kalman filter; + * produces process/observation matrices on demand + */ + KalmanFilterSpec filter_spec_; + + /* filter step for most recent observation */ + KalmanFilterStep step_; + + /* filter state as of most recent observation; + * result of applying KalmanFilterEngine::step() to contents of .step + */ + ref::rp state_ext_; + }; /*KalmanFilter*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilter const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace kalman*/ +} /*namespace xo*/ + + +/* end KalmanFilter.hpp */ diff --git a/KalmanFilterEngine.cpp b/KalmanFilterEngine.cpp new file mode 100644 index 00000000..e80f7497 --- /dev/null +++ b/KalmanFilterEngine.cpp @@ -0,0 +1,360 @@ +/* @file KalmanFilterEngine.cpp + * + */ + +#include "KalmanFilterEngine.hpp" +#include "print_eigen.hpp" +#include "indentlog/scope.hpp" +#include "Eigen/src/Core/Matrix.h" + +namespace xo { + using xo::time::utc_nanos; + using logutil::matrix; + using xo::scope; + using xo::xtag; + using Eigen::LDLT; + using Eigen::MatrixXd; + using Eigen::VectorXd; + + namespace kalman { + // ----- KalmanFilterEngine ----- + + ref::rp + KalmanFilterEngine::extrapolate(utc_nanos tkp1, + ref::rp const & s, + KalmanFilterTransition const & f) + { + //constexpr char const * c_self_name + // = "KalmanFilterEngine::extrapolate"; + + /* prior estimates at t(k) */ + VectorXd const & x = s->state_v(); + MatrixXd const & P = s->state_cov(); + + /* model change from t(k) -> t(k+1) */ + MatrixXd const & F = f.transition_mat(); + MatrixXd const & Q = f.transition_cov(); + + if(F.cols() != x.rows()) { + scope log(XO_DEBUG(true /*debug_flag*/)); + + log("error: F*x: expected F.cols=x.rows", + xtag("F.cols", F.cols()), xtag("x.rows", x.rows())); + } + + /* x(k+1|k) */ + VectorXd x_ext = F * x; + + /* P(k+1|k) */ + MatrixXd P_ext = (F * P * F.transpose()) + Q; + + /* creating new state object here + * allows KalmanFilterSvc.is_volatile()=false + */ + + return KalmanFilterState::make(s->step_no() + 1, + tkp1, + std::move(x_ext), + std::move(P_ext), + f); + } /*extrapolate*/ + + VectorXd + KalmanFilterEngine::kalman_gain1(ref::rp const & skp1_ext, + KalmanFilterObservable const & h, + uint32_t j) + { + constexpr bool c_debug_enabled = false; + + scope log(XO_DEBUG(c_debug_enabled)); + + /* P(k+1|k) :: [n x n] */ + MatrixXd const & P_ext = skp1_ext->state_cov(); + + /* H(k) :: [m x n] */ + MatrixXd const & H = h.observable(); + /* R(k) :: [m x m] */ + MatrixXd const & R = h.observable_cov(); + + /* i'th col of H couples element #i of filter state to each member of input z(k); + * j'th row of H couples filter state to j'th observable + * + * Hj :: [1 x n] Hj is a row-vector + */ + auto Hj = H.row(j); + + /* Rjj is the j'th diagonal element of R */ + double Rjj = R(j, j); + + /* T + * M(k) = Hj * P(k+1|k) * Hj + Rjj + * + * M(k) is a [1 x 1] matrix + */ + double m = Hj * (P_ext * Hj.transpose()) + Rjj; + + /* -1 + * M(k) trivial, since M is [1 x 1] + */ + double m_inv = 1.0 / m; + + /* K :: [n x 1] */ + VectorXd K = P_ext * Hj.transpose() * m_inv; + + log && log("result", + xtag("P(k+1|k)", matrix(P_ext)), + xtag("R", matrix(R)), + xtag("m", m)); + + return K; + } /*kalman_gain1*/ + + MatrixXd + KalmanFilterEngine::kalman_gain(ref::rp const & skp1_ext, + KalmanFilterObservable const & h) + { + scope log(XO_DEBUG(false /*debug_enabled*/)); + + /* P(k+1|k) */ + MatrixXd const & P_ext = skp1_ext->state_cov(); + + MatrixXd const & H = h.observable(); + MatrixXd const & R = h.observable_cov(); + + uint32_t m = H.rows(); + uint32_t n = H.cols(); + + if ((P_ext.rows() != n) || (P_ext.cols() != n)) { + std::string err_msg + = tostr("kalman_gain: with dim(H) = [m x n] expect dim(P) = [n x n]", + xtag("m", m), xtag("n", n), + xtag("P.rows", P_ext.rows()), + xtag("P.cols", P_ext.cols())); + + throw std::runtime_error(err_msg); + } + + if ((R.rows() != m) || (R.cols() != m)) { + std::string err_msg + = tostr("kalman_gain: with dim(H) = [m x n] expect dim(R) = [m x m]", + xtag("m", m), xtag("n", n), + xtag("R.rows", R.rows()), xtag("R.cols", R.cols())); + + throw std::runtime_error(err_msg); + } + + /* kalman gain: + * T -1 + * K(k+1) = P(k+1|k).H(k) .M + * + * T / T \ -1 + * = P(k+1|k).H(k) .| H(k).P(k+1|k).H(k) + R(k) | + * \ / + * + * Notes: + * 1. the matrix M being inverted is symmetric, since represents covariances. + * 2. if diagonal of R(k) has no zeroes (i.e. all measurements are subject to error), + * then it must be non-negative definite + * 3. unless observation errors are perfectly correlated, M(k) + * is positive definite. + * 4. even though 3. holds, there may be a nearby non-positive-definite matrix M+dM, + * Factoring M with finite-precision arithmetic solves for M+dM instead of M; + * which may run into difficulty if M is only 'slighlty' +ve definite. + * If necessary add small diagonal correction D to M, + * sufficient to make M+D positive definite. + * This is equivalent to introducing additional + * uncorrelated observation error, so benign from a robustness perspective + * 5. In generally we usually want to avoid fully realizing a matrix inverse. + * In this case need to explicitly compute K as ingredient used to + * correct state covariance later. + * 6. However, if R is diagonal (which is in practice quite likely), + * then it's easy to decompose a suite of vector observations z(k+1) = [z1, ..zm]T + * into separate zi, with dt=0 separating them. + * Can use this to avoid computing the inverse. + * See .kalman_gain1(), .correct1() + * 7. .kalman_gain() works unaltered when H, R have been reindexed + * to exclude outliers/errors; this is true because .kalman_gain() does not + * use the observation vector z[], i.e. operates entirely in the reduced + * reindexed space. + */ + + MatrixXd M = H * P_ext * H.transpose() + R; + + /* will use to write M as: + * + * T T + * M = P .L.D.L .P + * + * where: + * P is a permutation matrix + * L is lower triangular, with unit diagonal + * D is diagonal + */ + LDLT ldlt = M.ldlt(); + + /* solve for the identity matrix to realize the inverse this way */ + MatrixXd I = MatrixXd::Identity(M.rows(), M.cols()); + + /* -1 + * M + */ + MatrixXd M_inv = ldlt.solve(I); + + /* K(k+1) */ + MatrixXd K = P_ext * H.transpose() * M_inv; + + log && log("result", + xtag("k", skp1_ext->step_no()), + xtag("P(k+1|k)", matrix(P_ext)), + xtag("H", matrix(H)), + xtag("R", matrix(R)), + xtag("M", matrix(M)), + xtag("K", matrix(K))); + + return K; + } /*kalman_gain*/ + + ref::rp + KalmanFilterEngine::correct1(ref::rp const & skp1_ext, + KalmanFilterObservable const & h, + ref::rp const & zkp1, + uint32_t j) + { + uint32_t n = skp1_ext->n_state(); + /* Kj :: [n x 1] */ + VectorXd Kj = kalman_gain1(skp1_ext, h, j); + /* H :: [m x n] */ + MatrixXd const & H = h.observable(); + VectorXd const & z = zkp1->z(); + + /* Hj :: [1 x n] the j'th row of H */ + auto const & Hj = H.row(j); + + + /* x(k+1|x) :: [n x 1] */ + VectorXd const & x_ext = skp1_ext->state_v(); + + /* P(k+1|k) :: [n x n] */ + MatrixXd const & P_ext = skp1_ext->state_cov(); + + /* innovj : difference between jth 'actual observation' + * and jth 'predicted observation' + */ + double innovj = z[j] - (Hj * x_ext); + + /* x(k+1) */ + VectorXd xkp1 = x_ext + (Kj * innovj); + + MatrixXd I = MatrixXd::Identity(n, n); + /* note: Kj [n x 1], Hj [1 x n], + * so Kj * Hj [n x n], with rank 1 + */ + MatrixXd Pkp1 = (I - (Kj * Hj)) * P_ext; + + return KalmanFilterStateExt::make(skp1_ext->step_no(), + skp1_ext->tm(), + xkp1, + Pkp1, + skp1_ext->transition(), + Kj, + j, + zkp1); + } /*correct1*/ + + ref::rp + KalmanFilterEngine::correct(ref::rp const & skp1_ext, + KalmanFilterObservable const & h, + ref::rp const & zkp1) + { + uint32_t n = skp1_ext->n_state(); + /* K :: [n x m] */ + MatrixXd K = kalman_gain(skp1_ext, h); + MatrixXd const & H = h.observable(); + /* z_orig[] is original observation vector before reindexing */ + VectorXd const & z_orig = zkp1->z(); + /* reindex z_orig, keeping only elements that appear in + */ + VectorXd z = z_orig(h.keep()); + + /* 'ext' short for 'extrapolated' */ + VectorXd const & x_ext = skp1_ext->state_v(); + MatrixXd const & P_ext = skp1_ext->state_cov(); + + /* innov: difference between 'actual observations' + * and 'predicted observations' + */ + VectorXd innov = z - (H * x_ext); + + /* x(k+1) :: [n x 1] */ + VectorXd xkp1 = x_ext + K * innov; + MatrixXd I = MatrixXd::Identity(n, n); + MatrixXd Pkp1 = (I - K * H) * P_ext; + + return KalmanFilterStateExt::make(skp1_ext->step_no(), + skp1_ext->tm(), + xkp1, + Pkp1, + skp1_ext->transition(), + K, + -1 /*j: not used*/, + zkp1); + } /*correct*/ + + ref::rp + KalmanFilterEngine::step(utc_nanos tkp1, + ref::rp const & sk, + KalmanFilterTransition const & Fk, + KalmanFilterObservable const & Hkp1, + ref::rp const & zkp1) + { + ref::rp skp1_ext + = KalmanFilterEngine::extrapolate(tkp1, sk, Fk); + + ref::rp skp1 + = KalmanFilterEngine::correct(skp1_ext, Hkp1, zkp1); + + return skp1; + } /*step*/ + + ref::rp + KalmanFilterEngine::step(KalmanFilterStep const & step_spec) + { + return step(step_spec.tkp1(), + step_spec.state(), + step_spec.model(), + step_spec.obs(), + step_spec.input()); + } /*step*/ + + ref::rp + KalmanFilterEngine::step1(utc_nanos tkp1, + ref::rp const & sk, + KalmanFilterTransition const & Fk, + KalmanFilterObservable const & Hkp1, + ref::rp const & zkp1, + uint32_t j) + { + ref::rp skp1_ext + = KalmanFilterEngine::extrapolate(tkp1, sk, Fk); + + ref::rp skp1 + = KalmanFilterEngine::correct1(skp1_ext, Hkp1, zkp1, j); + + return skp1; + } /*step1*/ + + ref::rp + KalmanFilterEngine::step1(KalmanFilterStep const & step_spec, + uint32_t j) + { + return step1(step_spec.tkp1(), + step_spec.state(), + step_spec.model(), + step_spec.obs(), + step_spec.input(), + j); + } /*step1*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterEngine.cpp */ diff --git a/KalmanFilterEngine.hpp b/KalmanFilterEngine.hpp new file mode 100644 index 00000000..b8c2f949 --- /dev/null +++ b/KalmanFilterEngine.hpp @@ -0,0 +1,185 @@ +/* @file KalmanFilterEngine.hpp */ + +#pragma once + +#include "filter/KalmanFilterStep.hpp" +#include "filter/KalmanFilterState.hpp" +#include "filter/KalmanFilterTransition.hpp" +#include "filter/KalmanFilterObservable.hpp" +#include "filter/KalmanFilterInput.hpp" + +namespace xo { + namespace kalman { + class KalmanFilterEngine { + public: + using MatrixXd = Eigen::MatrixXd; + using VectorXd = Eigen::VectorXd; + using utc_nanos = xo::time::utc_nanos; + + public: + /* evolution of system state + account for system noise, + * before incorporating effect of observations z(k+1) + * x(k) --> x(k+1|k) + * P(k) --> P(k+1|k) + * + * tkp1. time t(k+1) assoc'd with step k+1 + * sk. filter state at time tk: + * sk.k = k step # (starts from 0) + * sk.tk = t(k) time t(k) assoc'd with step #k + * sk.x = x(k) estimated state at time tk + * sk.P = P(k) quality of state estimate x(k) + * (error covariance matrix) + * Fk. state transition: + * Fk.F = F(k) state transition matrix + * Fk.Q = Q(k) covariance matrix for system noise + * + * returns propagated state estimate for t(k+1): + * retval.k = k+1 + * retval.tk = t(k+1) = tkp1 + * retval.x = x(k+1|k) + * retval.P = P(k+1|k) + */ + static ref::rp extrapolate(utc_nanos tkp1, + ref::rp const & sk, + KalmanFilterTransition const & Fk); + + /* compute kalman gain matrix for filter step t(k) -> t(k+1) + * Expensive implementation using matrix inversion + * + * T + * M(k+1) = H(k).P(k+1|k).H(k) + R(k) + * + * T -1 + * K(k+1) = P(k+1|k).H(k) .M(k+1) + * + * Require: + * - skp1_ext.n_state() = Hkp1.n_state() + * + * skp1_ext. extrapolated filter state at t(k+1) + * Hkp1. relates model state to observable variables, + * for step t(k) -> t(k+1) + */ + static MatrixXd kalman_gain(ref::rp const & skp1_ext, + KalmanFilterObservable const & Hkp1); + + /* compute kalman gain for a single observation z(k)[j]. + * This is useful iff the observation error matrix R is diagonal. + * For diagonal R we can present a set of observations z(k) serially + * instead of all at once, with lower time complexity + * + * Kalman Filter specifies some space with m observables. + * j identifies one of those observables, indexing from 0. + * This corresponds to row #j of H(k), and element R[j,j] of R. + * + * Effectively, we are projecting the kalman filter assoc'd with + * {skp1_ext, Hkp1} to a filter with a single observable variable z(k)[j], + * then computing the (scalar) kalman gain for this 1-variable filter + * + * The gain vector tells us for each member of filter state, + * how much to adjust our optimal estimate for that member for a unit + * amount of innovation in observable #j, i.e. for difference between + * expected and actual value for that observable. + */ + static VectorXd kalman_gain1(ref::rp const & skp1_ext, + KalmanFilterObservable const & Hkp1, + uint32_t j); + + /* correct extrapolated state+cov estimate; + * also computes kalman gain + * + * Require: + * - skp1_ext.n_state() = Hkp1.n_state() + * - zkp1.n_obs() == Hkp1.n_observable() + * + * skp1_ext. extrapolated kalman state + covaraince at t(k+1) + * Hkp1. relates model state to observable variables + * for step t(k) -> t(k+1) + * zkp1. observations for time t(k+1) + * + * return new filter state+cov + */ + static ref::rp correct(ref::rp const & skp1_ext, + KalmanFilterObservable const & Hkp1, + ref::rp const & zkp1); + + /* correct extrapolated filter state for observation + * of j'th filter observable z(k+1)[j] + * + * Can use this when observation errors are uncorrelated + * (i.e. observation error matrix R is diagonal) + */ + static ref::rp correct1(ref::rp const & skp1_ext, + KalmanFilterObservable const & Hkp1, + ref::rp const & zkp1, + uint32_t j); + + /* step filter from t(k) -> t(k+1) + * + * sk. filter state from previous step: + * x (state vector), P (state covar matrix) + * Fk. transition-related params: + * F (transition matrix), Q (system noise covar matrix) + * Hkp1. observation-related params: + * H (coupling matrix), R (error covar matrix) + * zkp1. observations z(k+1) for time t(k+1) + */ + static ref::rp step(utc_nanos tkp1, + ref::rp const & sk, + KalmanFilterTransition const & Fk, + KalmanFilterObservable const & Hkp1, + ref::rp const & zkp1); + + /* step filter from t(k) -> tk(k+1) + * same as + * .step(tkp1, sk, step_spec.model(), step_spec.obs(), zkp1); + * + * step_spec. encapsulates Fk (transition-related params) + * and Q (system noise covar matrix) + */ + static ref::rp step(KalmanFilterStep const & step_spec); + + /* step filter from t(k) -> t(k+1) + * + * sk. filter state from previous step: + * x (state vector), P (state covar matrix) + * Fk. transition-related params: + * F (transition matrix), Q (system noise covar matrix) + * Hkp1. observation-related params: + * H (coupling matrix), R (error covar matrix) + * zkp1. observations z(k+1) for time t(k+1) + * j. identifies a single filter observable -- + * step will only consume observation z(k+1)[j] + */ + static ref::rp step1(utc_nanos tkp1, + ref::rp const & sk, + KalmanFilterTransition const & Fk, + KalmanFilterObservable const & Hkp1, + ref::rp const & zkp1, + uint32_t j); + + /* step filter from t(k) -> t(k+1) + * + * same as + * .step1(step_spec.tkp1(), + * step_spec.state(), + * step_spec.model(), + * step_spec.obs(), + * step_spec.input(), + * j); + * + * step_spec. encapsulates + * x (state vector), + * P (state covar matrix), + * Fk (transition-related params), + * Q (system noise covar matrix) + * z (z(k+1), observations at time t(k+1)) + * j. identifies a single filter observable -- + * step will only consume observation z(k+1)[j] + */ + static ref::rp step1(KalmanFilterStep const & step_spec, + uint32_t j); + }; /*KalmanFilterEngine*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterEngine.hpp */ diff --git a/KalmanFilterInput.cpp b/KalmanFilterInput.cpp new file mode 100644 index 00000000..03b5330f --- /dev/null +++ b/KalmanFilterInput.cpp @@ -0,0 +1,118 @@ +/* @file KalmanFilterInput.cpp */ + +#include "KalmanFilterInput.hpp" +#include "reflect/StructReflector.hpp" +#include "Eigen/src/Core/Matrix.h" +#include "print_eigen.hpp" +#include "indentlog/scope.hpp" +#include "reflect/TaggedRcptr.hpp" + +namespace xo { + using xo::reflect::Reflect; + using xo::reflect::TaggedRcptr; + using xo::reflect::StructReflector; + using xo::scope; + using logutil::matrix; + using xo::xtag; + using Eigen::MatrixXd; + using Eigen::VectorXi; + using std::uint32_t; + + namespace kalman { + ref::rp + KalmanFilterInput::make(utc_nanos tkp1, + VectorXb const & presence, + VectorXd const & z, + VectorXd const & Rd) + { + return new KalmanFilterInput(tkp1, presence, z, Rd); + } /*make*/ + + ref::rp + KalmanFilterInput::make_present(utc_nanos tkp1, + VectorXd const & z) + { + VectorXb presence = VectorXb::Ones(z.cols()); + + return new KalmanFilterInput(tkp1, + presence, + z, + VectorXd(0) /*Rd - not using*/); + } /*make*/ + + VectorXi + KalmanFilterInput::make_kept_index() const + { + scope log(XO_DEBUG(false /*!debug_flag*/)); + + log && log(xtag("presence", matrix(presence_))); + + /* count truth values */ + uint32_t mstar = 0; + + for (uint32_t j = 0, m = this->presence_.rows(); jpresence_[j]) + ++mstar; + } + + log && log(xtag("m*", mstar)); + + VectorXi keep(mstar); + + /* 2nd pass, populate keep[] */ + + uint32_t jstar = 0; + + for (uint32_t j = 0, m = this->presence_.rows(); jpresence_[j]) { + keep[jstar] = j; + ++jstar; + } + } + + log && log("keep", matrix(keep)); + + return keep; + } /*make_kept_index*/ + + void + KalmanFilterInput::reflect_self() + { + StructReflector sr; + + if (sr.is_incomplete()) { + REFLECT_MEMBER(sr, tkp1); + REFLECT_MEMBER(sr, presence); + REFLECT_MEMBER(sr, z); + REFLECT_MEMBER(sr, Rd); + } + } /*reflect_self*/ + + reflect::TaggedRcptr + KalmanFilterInput::self_tp() + { + return Reflect::make_rctp(this); + } /*self_tp*/ + + void + KalmanFilterInput::display(std::ostream & os) const + { + os << ""; + } /*display*/ + + std::string + KalmanFilterInput::display_string() const + { + std::stringstream ss; + this->display(ss); + return ss.str(); + } /*display_string*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterInput.cpp */ diff --git a/KalmanFilterInput.hpp b/KalmanFilterInput.hpp new file mode 100644 index 00000000..a73a72d0 --- /dev/null +++ b/KalmanFilterInput.hpp @@ -0,0 +1,121 @@ +/* @file KalmanFilterInput.hpp */ + +#pragma once + +#include "reflect/SelfTagging.hpp" +#include "time/Time.hpp" +#include "refcnt/Refcounted.hpp" +#include +#include + +namespace xo { + /* FIXME. hack here to get member access working for reflection */ + namespace vf { class StrikesetKfinput; } + + namespace kalman { + /* represents a single kalman filter input event + * comprising: + * - time tkp1 + * - observation vector z[] + * - presence bits presence[] for z + * - observation errors Rd[] + */ + class KalmanFilterInput : public reflect::SelfTagging { + public: + using TaggedRcptr = xo::reflect::TaggedRcptr; + using utc_nanos = xo::time::utc_nanos; + using VectorXb = Eigen::Array; + using VectorXi = Eigen::VectorXi; + using VectorXd = Eigen::VectorXd; + using uint32_t = std::uint32_t; + + public: + KalmanFilterInput() = default; + + static ref::rp make(utc_nanos tkp1, + VectorXb const & presence, + VectorXd const & z, + VectorXd const & Rd); + + /* create input, with all presence bits set + not using Rd */ + static ref::rp make_present(utc_nanos tkp1, + VectorXd const & z); + + /* reflect KalmanFilterInput object representation */ + static void reflect_self(); + + /* alt name -- concession to reactor::DirectSource + * when event type is KalmanFilterInput + */ + utc_nanos tm() const { return tkp1_; } + + utc_nanos tkp1() const { return tkp1_; } + uint32_t n_obs() const { return z_.size(); } + VectorXb const & presence() const { return presence_; } + VectorXd const & z() const { return z_; } + VectorXd const & Rd() const { return Rd_; } + + /* computes reindexer keep[]: + * .presence[keep[j*]] + * is the j*'th true value in .presence + */ + VectorXi make_kept_index() const; + + virtual void display(std::ostream & os) const; + std::string display_string() const; + + // ----- inherited from SelfTagging ----- + + virtual TaggedRcptr self_tp() override; + + protected: + KalmanFilterInput(utc_nanos tkp1, VectorXb presence, VectorXd z, VectorXd Rd) + : tkp1_(tkp1), + presence_{std::move(presence)}, + z_{std::move(z)}, + Rd_{std::move(Rd)} {} + + friend class xo::vf::StrikesetKfinput; + + private: + /* t(k+1) - asof time for observations .z */ + utc_nanos tkp1_ = xo::time::timeutil::epoch(); + /* [m x 1] presence vector. + * an observation z[j] is present iff .presence[j] is true. + * rows/columns for an absent observation are removed from filter matrices + */ + VectorXb presence_; + /* [m x 1] observation vector z(k) */ + VectorXd z_; + + /* [m x 1] observation error vector Rd(k). + * This represents a side channel for passing the diagonal of + * observation matrix R(k), when both: + * (a) error of different observations are assumed to be uncorrelated (likely) + * (b) error variance is derived from input data, e.g. because of + * some input preprocessing. + * + * It's up to KalmanFilterSpec::MkStepFn to opt-in to using this information, + * which requires agreement with any input preparation step. + * + * This variable could just as well provide observation error matrix R + * + * NOTE: perhaps-cleaner alternative would be to inherit KalmanFilterInput to + * introduce additional state, then MkStepFn can dynamic_cast + */ + VectorXd Rd_; + }; /*KalmanFilterInput*/ + + using KalmanFilterInputPtr = ref::rp; + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterInput const & x) + { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterInput.hpp */ diff --git a/KalmanFilterInputCallback.hpp b/KalmanFilterInputCallback.hpp new file mode 100644 index 00000000..d9b2f213 --- /dev/null +++ b/KalmanFilterInputCallback.hpp @@ -0,0 +1,14 @@ +/* @file KalmanFilterInputCallback.hpp */ + +#pragma once + +#include "refcnt/Refcounted.hpp" +#include "filter/KalmanFilter.hpp" + +namespace xo { + namespace kalman { + using KalmanFilterInputCallback = reactor::Sink1>; + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterInputCallback.hpp */ diff --git a/KalmanFilterInputSource.hpp b/KalmanFilterInputSource.hpp new file mode 100644 index 00000000..30c5cbf1 --- /dev/null +++ b/KalmanFilterInputSource.hpp @@ -0,0 +1,28 @@ +/* @file KalmanFilterInputSource.hpp */ + +#pragma once + +#include "reactor/EventSource.hpp" +#include "filter/KalmanFilterInputCallback.hpp" + +namespace xo { + namespace kalman { + /* Use: + * rp src = ...; + * rp in_cb = ...; + * + * src->add_callback(in_cb); + * ... + * // src invokes in_cb->notify_input( + * src->remove_callback(in_cb); + */ + using KalmanFilterInputSource = reactor::EventSource< + /*KalmanFilterInput,*/ + KalmanFilterInputCallback + /*&KalmanFilterInputCallback::notify_filter*/ + >; + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterInputSource.hpp */ + diff --git a/KalmanFilterInputToConsole.cpp b/KalmanFilterInputToConsole.cpp new file mode 100644 index 00000000..a0a848ca --- /dev/null +++ b/KalmanFilterInputToConsole.cpp @@ -0,0 +1,25 @@ +/* @file KalmanFilterInputToConsole.cpp */ + +#include "KalmanFilterInputToConsole.hpp" +#include "indentlog/print/tag.hpp" + +namespace xo { + using xo::xtag; + + namespace kalman { + ref::rp + KalmanFilterInputToConsole::make() { + return new KalmanFilterInputToConsole(); + } /*make*/ + + void + KalmanFilterInputToConsole::display(std::ostream & os) const + { + os << ""; + } /*display*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterInputToConsole.cpp */ diff --git a/KalmanFilterInputToConsole.hpp b/KalmanFilterInputToConsole.hpp new file mode 100644 index 00000000..7d48ef7e --- /dev/null +++ b/KalmanFilterInputToConsole.hpp @@ -0,0 +1,30 @@ +/* @file KalmanFilterInputToConsole.hpp */ + +#pragma once + +#include "reactor/Sink.hpp" +#include "filter/KalmanFilterInput.hpp" + +namespace xo { + namespace kalman { + class KalmanFilterInputToConsole + : public xo::reactor::SinkToConsole> + { + public: + KalmanFilterInputToConsole() = default; + + static ref::rp make(); + + virtual void display(std::ostream & os) const; + //virtual std::string display_string() const; + }; /*KalmanFilterInputToConsole*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterInputToConsole const & x) { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace option*/ +} /*namespace xo*/ + +/* end KalmanFilterInputToConsole.hpp */ diff --git a/KalmanFilterObservable.cpp b/KalmanFilterObservable.cpp new file mode 100644 index 00000000..90f31460 --- /dev/null +++ b/KalmanFilterObservable.cpp @@ -0,0 +1,71 @@ +/* @file KalmanFilterObservable.cpp */ + +#include "KalmanFilterObservable.hpp" +#include "print_eigen.hpp" +#include "indentlog/scope.hpp" + +namespace xo { + using xo::scope; + using logutil::matrix; + using xo::xtag; + + namespace kalman { + KalmanFilterObservable + KalmanFilterObservable::keep_all(MatrixXd H, + MatrixXd R) + { + VectorXi keep(H.rows()); + + for (uint32_t j=0; j"; + } /*display*/ + + std::string + KalmanFilterObservable::display_string() const + { + std::stringstream ss; + this->display(ss); + return ss.str(); + } /*display_string*/ + + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterObservable.cpp */ diff --git a/KalmanFilterObservable.hpp b/KalmanFilterObservable.hpp new file mode 100644 index 00000000..87c4f9d1 --- /dev/null +++ b/KalmanFilterObservable.hpp @@ -0,0 +1,110 @@ +/* @file KalmanFilterObservable.hpp */ + +#pragma once + +#include "time/Time.hpp" +#include +#include + +namespace xo { + namespace kalman { + class KalmanFilterObservable { + public: + using MatrixXd = Eigen::MatrixXd; + using VectorXi = Eigen::VectorXi; + + public: + KalmanFilterObservable() = default; + + /* keep maps back to canonical observations z(j). + * H, R have been re-indexed + * + * If all m observations are included, then keep will be: + * [0, .., m-1] + */ + KalmanFilterObservable(VectorXi keep, MatrixXd H, MatrixXd R) + : keep_{std::move(keep)}, H_{std::move(H)}, R_{std::move(R)} { + assert(this->check_ok()); + } /*ctor*/ + + /* build KF observable object where keeping all observations */ + static KalmanFilterObservable keep_all(MatrixXd H, MatrixXd R); + + /* build KF observable object. replace H, R with reindexed versions H', R' + * according to indexing vector keep[]. keep[] indexes members of + * observation vector z(k)[j]. Reindexed z', H', R' as follows: + * + * z'[j*] = z[keep[j*]] + * H'[j*, i] = H[keep[j*], i] + * R'[j1*, j2*] = R[keep[j1*], keep[j2*]] + */ + static KalmanFilterObservable reindex(VectorXi keep, MatrixXd H, MatrixXd R); + + uint32_t n_state() const { return H_.cols(); } + uint32_t n_observable() const { return H_.rows(); } + VectorXi const & keep() const { return keep_; } + MatrixXd const & observable() const { return H_; } + MatrixXd const & observable_cov() const { return R_; } + + bool check_ok() const { + uint32_t m = H_.rows(); + bool keep_is_mx1 = (keep_.rows() == m); + bool r_is_mxm = ((R_.cols() == m) && (R_.rows() == m)); + + bool keep_is_well_ordered = true; + + /* members of .keep are non-negative integers, + * in strictly increasing order + */ + int64_t keep_jm1 = -1; + + for (uint32_t j = 0; j < keep_.rows(); ++j) { + if (keep_[j] < 0) + keep_is_well_ordered = false; + if (keep_[j] <= keep_jm1) + keep_is_well_ordered = false; + } + + /* also would like to require: R is +ve definite */ + + return keep_is_mx1 && keep_is_well_ordered && r_is_mxm; + } /*check_ok*/ + + void display(std::ostream & os) const; + std::string display_string() const; + + private: + + private: + /* m: #of observations that survived sanity/error checks + * + * H, R here will have been re-indexed to exclude rejected observations. + * observations z will also have been re-indexed. + * + * If an observation z[j] is excluded, then also exclude: + * - j'th row of H + * - j'th row and j'th column of R + * - j'th element of z + * + * Given re-indexed H, R, the j*'th row of H goes with z[keep[j*]] + */ + + /* [m x 1] maps back to indices in original observation vector */ + VectorXi keep_; + /* [m x n] observation matrix */ + MatrixXd H_; + /* [m x m] covariance matrix for observation noise */ + MatrixXd R_; + }; /*KalmanFilterObservable*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterObservable const & x) + { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterObservable.hpp */ + diff --git a/KalmanFilterOutputCallback.hpp b/KalmanFilterOutputCallback.hpp new file mode 100644 index 00000000..69fb8484 --- /dev/null +++ b/KalmanFilterOutputCallback.hpp @@ -0,0 +1,15 @@ +/* @file KalmanFilterOutputCallback.hpp */ + +#pragma once + +#include "reactor/Sink.hpp" +#include "filter/KalmanFilter.hpp" + +namespace xo { + namespace kalman { + /* callback for consuming kalman filter output */ + using KalmanFilterOutputCallback = reactor::Sink1>; + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterOutputCallback.hpp */ diff --git a/KalmanFilterSpec.cpp b/KalmanFilterSpec.cpp new file mode 100644 index 00000000..296aeb99 --- /dev/null +++ b/KalmanFilterSpec.cpp @@ -0,0 +1,27 @@ +/* @file KalmanFilterSpec.cpp */ + +#include "KalmanFilterSpec.hpp" +#include "indentlog/scope.hpp" + +namespace xo { + using xo::tostr; + using xo::xtag; + + namespace kalman { + void + KalmanFilterSpec::display(std::ostream & os) const + { + os << ""; + } /*display*/ + + std::string + KalmanFilterSpec::display_string() const + { + return tostr(*this); + } /*display_string*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterSpec.cpp */ diff --git a/KalmanFilterSpec.hpp b/KalmanFilterSpec.hpp new file mode 100644 index 00000000..56ffd783 --- /dev/null +++ b/KalmanFilterSpec.hpp @@ -0,0 +1,86 @@ +/* @file KalmanFilterSpec.hpp */ + +#pragma once + +#include "filter/KalmanFilterStep.hpp" + +namespace xo { + namespace kalman { + /* full specification for a kalman filter. + * + * For a textbook linear filter, expect a KalmanFilterStep + * instance to be independent of KalmanFilterState/KalmanFilterInput. + * + * Relaxing this requirement for two reasons: + * 1. (proper) want to allow filter with variable timing between observations, + * expecially if observations are event-driven. + * In that case it's likely that state transition matrices are a function + * of elapsed time between observations. Providing filter state sk + * allows MkStepFn to use sk.tm() + * 2. (sketchy) when observations represent market data, + * desirable to treat an observation as giving one-sided information + * about true value. For example treat a bid price as evidence + * true value is higher than that bid, but don't want to constrain + * "how much higher". Certainly no reason to think that + * bid price is normally distributed around fair value. + * Allow for hack in which we + * and modulate error distribution "as if it were normal" to assess + * a non-gaussian error distribution + */ + class KalmanFilterSpec { + public: + /* typical implementation will look something like: + * mk_step(KalmanFilterState const & sk, + * KalmanFilterInputPtr const & zkp1) + * { + * KalmanFilterTransition model = ...; + * KalmanFilterObservable obs = ...; + * + * return KalmanFilterStep(sk, model, obs, zkp1); + * } + */ + using MkStepFn = std::function const & sk, + KalmanFilterInputPtr const & zkp1)>; + + public: + explicit KalmanFilterSpec(ref::rp s0, MkStepFn mkstepfn) + : start_ext_{std::move(s0)}, + mk_step_fn_{std::move(mkstepfn)} {} + + ref::rp const & start_ext() const { return start_ext_; } + /* get step parameters (i.e. matrices F, Q, H, R) + * for step t(k) -> t(k+1). + * + * We supply t(k) state s and t(k+1) observation z(k+1): + * - to allow time stepping to be observation-driven + * - to allow for selective outlier removal + */ + KalmanFilterStep make_step(ref::rp const & sk, + ref::rp const & zkp1) { + return this->mk_step_fn_(sk, zkp1); + } /*make_step*/ + + void display(std::ostream & os) const; + std::string display_string() const; + + private: + /* starting state */ + ref::rp start_ext_; + + /* creates kalman filter step object on demand; + * step object specifies inputs to 1 step in discrete + * linear kalman filter + */ + MkStepFn mk_step_fn_; + }; /*KalmanFilterSpec*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterSpec const & x) { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterSpec.hpp */ diff --git a/KalmanFilterState.cpp b/KalmanFilterState.cpp new file mode 100644 index 00000000..7656908c --- /dev/null +++ b/KalmanFilterState.cpp @@ -0,0 +1,231 @@ +/* @file KalmanFilterState.cpp */ + +#include "KalmanFilterState.hpp" +#include "print_eigen.hpp" +#include "reflect/StructReflector.hpp" +#include "reflect/TaggedPtr.hpp" +#include "indentlog/scope.hpp" +#include "Eigen/src/Core/Matrix.h" +#include +#include + +namespace xo { + using xo::reflect::Reflect; + using xo::reflect::TaggedRcptr; + using xo::reflect::StructReflector; + using xo::time::utc_nanos; + using xo::ref::rp; + using logutil::matrix; + using logutil::vector; + //using xo::scope; + using xo::xtag; + using xo::tostr; + //using Eigen::LDLT; + using Eigen::MatrixXd; + using Eigen::VectorXd; + + namespace kalman { + // ----- KalmanFilterState ----- + + rp + KalmanFilterState::make() + { + return new KalmanFilterState(); + } /*make*/ + + rp + KalmanFilterState::make(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition) + { + return new KalmanFilterState(k, tk, + std::move(x), + std::move(P), + std::move(transition)); + } /*make*/ + + void + KalmanFilterState::reflect_self() + { + StructReflector sr; + + if (sr.is_incomplete()) { + REFLECT_MEMBER(sr, k); + REFLECT_MEMBER(sr, tk); + REFLECT_MEMBER(sr, x); + REFLECT_MEMBER(sr, P); + } + } /*reflect_self*/ + + KalmanFilterState::KalmanFilterState() = default; + + KalmanFilterState::KalmanFilterState(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition) + : k_{k}, tk_{tk}, + x_{std::move(x)}, P_{std::move(P)}, + transition_{std::move(transition)} + {} + + TaggedRcptr + KalmanFilterState::self_tp() + { + return Reflect::make_rctp(this); + } /*self_tp*/ + + // ----- KalmanFilterExt ----- + + rp + KalmanFilterStateExt::make() + { + return new KalmanFilterStateExt(); + } /*make*/ + + rp + KalmanFilterStateExt::make(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition, + MatrixXd K, + int32_t j, + rp zk) + { + return new KalmanFilterStateExt(k, + tk, + std::move(x), + std::move(P), + std::move(transition), + std::move(K), + j, + std::move(zk)); + } /*make*/ + + void + KalmanFilterStateExt::reflect_self() + { + StructReflector sr; + + if (sr.is_incomplete()) { + /* TODO: use sr.adopt_ancestors() */ + + REFLECT_EXPLICIT_MEMBER(sr, "k", &KalmanFilterState::k_); + REFLECT_EXPLICIT_MEMBER(sr, "tk", &KalmanFilterState::tk_); + REFLECT_EXPLICIT_MEMBER(sr, "x", &KalmanFilterState::x_); + REFLECT_EXPLICIT_MEMBER(sr, "P", &KalmanFilterState::P_); + REFLECT_EXPLICIT_MEMBER(sr, "transition", &KalmanFilterState::transition_); + REFLECT_MEMBER(sr, j); + REFLECT_MEMBER(sr, K); + REFLECT_MEMBER(sr, zk); + } + } /*reflect_self*/ + + KalmanFilterStateExt::KalmanFilterStateExt(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition, + MatrixXd K, + int32_t j, + rp zk) + : KalmanFilterState(k, tk, + std::move(x), + std::move(P), + std::move(transition)), + j_{j}, + K_{std::move(K)}, + zk_{std::move(zk)} + { + uint32_t n = x.size(); + + if (n != P.rows() || n != P.cols()) { + std::string err_msg + = tostr("with n=x.size expect [n x n] covar matrix P", + xtag("n", x.size()), + xtag("P.rows", P.rows()), + xtag("P.cols", P.cols())); + + throw std::runtime_error(err_msg); + } + + if ((K.rows() > 0) && (K.rows() > 0)) { + if (n != K.rows()) { + std::string err_msg + = tostr("with n=x.size expect [m x n] gain matrix K", + xtag("n", x.size()), + xtag("K.rows", K.rows()), + xtag("K.cols", K.cols())); + + throw std::runtime_error(err_msg); + } + } else { + /* bypass test with [0 x 0] matrix K; + * normal for initial filter state + */ + } + } /*ctor*/ + + void + KalmanFilterState::display(std::ostream & os) const + { + os << ""; + } /*display*/ + + std::string + KalmanFilterState::display_string() const + { + std::stringstream ss; + ss << *this; + return ss.str(); + } /*display_string*/ + + // ----- KalmanFilterStateExt ----- + + ref::rp + KalmanFilterStateExt::initial(utc_nanos t0, + VectorXd x0, + MatrixXd P0) + { + return KalmanFilterStateExt::make + (0 /*k*/, + t0, + std::move(x0), + std::move(P0), + KalmanFilterTransition(MatrixXd() /*F - not used for initial step*/, + MatrixXd() /*Q - not used for initial step*/), + MatrixXd() /*K - not used for initial step*/, + -1 /*j - not used for initial step*/, + nullptr /*zk - not defined for initial step*/); + } /*initial*/ + + void + KalmanFilterStateExt::display(std::ostream & os) const + { + os << "step_no()) + << xtag("tk", this->tm()) + << xtag("x", matrix(this->state_v())) + << xtag("P", matrix(this->state_cov())) + << xtag("K", matrix(K_)) + << xtag("j", j_) + << ">"; + } /*display*/ + + TaggedRcptr + KalmanFilterStateExt::self_tp() + { + return Reflect::make_rctp(this); + } /*self_tp*/ + } /*namespace filter*/ +} /*namespace xo*/ + +/* end KalmanFilterState.cpp */ diff --git a/KalmanFilterState.hpp b/KalmanFilterState.hpp new file mode 100644 index 00000000..113c4c6b --- /dev/null +++ b/KalmanFilterState.hpp @@ -0,0 +1,163 @@ +/* @file KalmanFilterState.hpp */ + +#pragma once + +#include "reflect/SelfTagging.hpp" +#include "filter/KalmanFilterInput.hpp" +#include "filter/KalmanFilterTransition.hpp" +#include "time/Time.hpp" +#include +#include +#include + +namespace xo { + namespace kalman { + /* encapsulate state (i.e. initial state, and output after each step) + * for a kalman filter + */ + class KalmanFilterState : public reflect::SelfTagging { + public: + using TaggedRcptr = reflect::TaggedRcptr; + using utc_nanos = xo::time::utc_nanos; + using VectorXd = Eigen::VectorXd; + using MatrixXd = Eigen::MatrixXd; + using uint32_t = std::uint32_t; + + public: + static ref::rp make(); + static ref::rp make(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition); + virtual ~KalmanFilterState() = default; + + /* reflect KalmanFilterState object representation */ + static void reflect_self(); + + uint32_t step_no() const { return k_; } + utc_nanos tm() const { return tk_; } + /* with n = .n_state(): + * x_ is [n x 1] vector + * P_ is [n x n] matrix, + */ + uint32_t n_state() const { return x_.size(); } + VectorXd const & state_v() const { return x_; } + MatrixXd const & state_cov() const { return P_; } + + KalmanFilterTransition const & transition() const { return transition_; } + + virtual void display(std::ostream & os) const; + std::string display_string() const; + + // ----- inherited from SelfTagging ----- + + virtual TaggedRcptr self_tp() override; + + private: + KalmanFilterState(); + KalmanFilterState(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition); + + friend class KalmanFilterStateExt; + + private: + /* step# k, advances by +1 on each filter step */ + uint32_t k_ = 0; + /* time t(k) */ + utc_nanos tk_; + /* [n x 1] (estimated) system state xk = x(k) */ + VectorXd x_; + /* [n x n] covariance matrix for error assoc'd with with x(k) + * P(i,j) is the covariance of (ek[i], ek[j]), + * where ex(k) is the difference (x(k) - x_(k)) + * between estimated state x(k) + * (= this->x_) and model state x_(k) + */ + MatrixXd P_; + + /* F, Q matrices driving .x, .P */ + KalmanFilterTransition transition_; + }; /*KalmanFilterState*/ + + inline std::ostream & operator<<(std::ostream & os, + KalmanFilterState const & s) + { + s.display(os); + return os; + } /*operator<<*/ + + /* KalmanFilterStateExt: + * adds additional details from filter step to KalmanFilterState + */ + class KalmanFilterStateExt : public KalmanFilterState { + public: + using TaggedRcptr = reflect::TaggedRcptr; + using MatrixXd = Eigen::MatrixXd; + using int32_t = std::int32_t; + + public: + static ref::rp make(); + static ref::rp make(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition, + MatrixXd K, + int32_t j, + ref::rp zk); + + /* create state object for initial filter state */ + static ref::rp initial(utc_nanos t0, + VectorXd x0, + MatrixXd P0); + + /* reflect KalmanFilterStateExt object representation */ + static void reflect_self(); + + int32_t observable() const { return j_; } + MatrixXd const & gain() const { return K_; } + ref::rp const & zk() const { return zk_; } + + virtual void display(std::ostream & os) const override; + + // ----- inherited from SelfTagging ----- + + virtual TaggedRcptr self_tp() override; + + private: + KalmanFilterStateExt() = default; + KalmanFilterStateExt(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition, + MatrixXd K, + int32_t j, + ref::rp zk); + + private: + /* if -1: not used; + * if >= 0: identifies j'th of m observables; + * gain .K applies just to information obtainable from + * observing that scalar variable + */ + int32_t j_ = -1; + /* if .j is -1: + * [n x n] kalman gain + * if .j >= 0: + * [n x 1] kalman gain for observable #j + */ + MatrixXd K_; + /* input leading to state k. + * empty for initial state (i.e. when .k is 0) + */ + ref::rp zk_; + }; /*KalamnFilterStateExt*/ + } /*namespace filter*/ +} /*namespace xo*/ + +/* end KalmanFilterState.hpp */ diff --git a/KalmanFilterStateToConsole.cpp b/KalmanFilterStateToConsole.cpp new file mode 100644 index 00000000..8e76ce84 --- /dev/null +++ b/KalmanFilterStateToConsole.cpp @@ -0,0 +1,25 @@ +/* @file KalmanFilterStateToConsole.cpp */ + +#include "KalmanFilterStateToConsole.hpp" +#include "indentlog/print/tag.hpp" + +namespace xo { + using xo::xtag; + + namespace kalman { + ref::rp + KalmanFilterStateToConsole::make() { + return new KalmanFilterStateToConsole(); + } /*make*/ + + void + KalmanFilterStateToConsole::display(std::ostream & os) const + { + os << ""; + } /*display*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterStateToConsole.cpp */ diff --git a/KalmanFilterStateToConsole.hpp b/KalmanFilterStateToConsole.hpp new file mode 100644 index 00000000..0d525cea --- /dev/null +++ b/KalmanFilterStateToConsole.hpp @@ -0,0 +1,30 @@ +/* @file KalmanFilterStateToConsole.hpp */ + +#pragma once + +#include "reactor/Sink.hpp" +#include "filter/KalmanFilterState.hpp" + +namespace xo { + namespace kalman { + class KalmanFilterStateToConsole + : public xo::reactor::SinkToConsole + { + public: + KalmanFilterStateToConsole() = default; + + static ref::rp make(); + + virtual void display(std::ostream & os) const; + //virtual std::string display_string() const; + }; /*KalmanFilterStateToConsole*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterStateToConsole const & x) { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace option*/ +} /*namespace xo*/ + +/* end KalmanFilterStateToConsole.hpp */ diff --git a/KalmanFilterStep.cpp b/KalmanFilterStep.cpp new file mode 100644 index 00000000..f62f2d21 --- /dev/null +++ b/KalmanFilterStep.cpp @@ -0,0 +1,84 @@ +/* @file KalmanFilterStep.cpp */ + +#include "KalmanFilterStep.hpp" +#include "filter/KalmanFilterEngine.hpp" +#include "filter/KalmanFilterState.hpp" +#include "indentlog/scope.hpp" + +namespace xo { + using xo::scope; + using xo::tostr; + using xo::xtag; + using Eigen::MatrixXd; + using Eigen::VectorXd; + + namespace kalman { + ref::rp + KalmanFilterStep::extrapolate() const + { + return KalmanFilterEngine::extrapolate(this->tkp1(), + this->state(), + this->model() /*transition*/); + } /*extrapolate*/ + + MatrixXd + KalmanFilterStep::gain(ref::rp const & skp1_ext) const + { + return KalmanFilterEngine::kalman_gain(skp1_ext, + this->obs()); + } /*gain*/ + + VectorXd + KalmanFilterStep::gain1(ref::rp const & skp1_ext, + uint32_t j) const + { + return KalmanFilterEngine::kalman_gain1(skp1_ext, + this->obs(), + j); + + } /*gain1*/ + + ref::rp + KalmanFilterStep::correct(ref::rp const & skp1_ext) + { + return KalmanFilterEngine::correct(skp1_ext, + this->obs(), + this->input()); + } /*correct*/ + + ref::rp + KalmanFilterStep::correct1(ref::rp const & skp1_ext, + uint32_t j) + { + return KalmanFilterEngine::correct1(skp1_ext, + this->obs(), + this->input(), + j); + } /*correct1*/ + + void + KalmanFilterStep::display(std::ostream & os) const + { + //scope lscope("KalmanFilterStep::display"); + + os << "model()); + //lscope.log("obs:"); + os << xtag("obs", this->obs()); + //lscope.log("input:"); + os << xtag("input", this->input()); + os << ">"; + } /*display*/ + + std::string + KalmanFilterStep::display_string() const + { + return tostr(*this); + } /*display_string*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterStep.cpp */ diff --git a/KalmanFilterStep.hpp b/KalmanFilterStep.hpp new file mode 100644 index 00000000..89813cc2 --- /dev/null +++ b/KalmanFilterStep.hpp @@ -0,0 +1,128 @@ +/* @file KalmanFilterStep.hpp */ + +#pragma once + +#include "KalmanFilterState.hpp" +#include "KalmanFilterInput.hpp" +#include "KalmanFilterTransition.hpp" +#include "KalmanFilterObservable.hpp" + +namespace xo { + namespace kalman { + /* encapsulate {state + observation} models for a single time step t(k). + * Emitted by KalmanFilterSpec, q.v. + */ + class KalmanFilterStepBase { + public: + KalmanFilterStepBase() = default; + KalmanFilterStepBase(KalmanFilterTransition model, + KalmanFilterObservable obs) + : model_{std::move(model)}, + obs_{std::move(obs)} {} + + /* aka system_model() */ + KalmanFilterTransition const & model() const { return model_; } + KalmanFilterObservable const & obs() const { return obs_; } + + private: + /* model for process being observed (state transition + noise) */ + KalmanFilterTransition model_; + /* what can be observed (observables + noise) */ + KalmanFilterObservable obs_; + }; /*KalmanFilterStepBase*/ + + /* encapsulate {state + observation} models for a single time step t(k). + * Emitted by KalmanFilterSpec, q.v. + * + * holds: + * x(k) + * P(k) + * F(k) + * H(k+1) + * z(k+1) + * + * contains all the inputs needed to compute: + * x(k+1) + * P(k+1) + * + * does not provide that result + */ + class KalmanFilterStep : public KalmanFilterStepBase { + public: + using utc_nanos = xo::time::utc_nanos; + using MatrixXd = Eigen::MatrixXd; + using VectorXd = Eigen::VectorXd; + + public: + KalmanFilterStep() = default; + KalmanFilterStep(ref::rp state, + KalmanFilterTransition model, + KalmanFilterObservable obs, + ref::rp zkp1) + : KalmanFilterStepBase(model, obs), + state_{std::move(state)}, + input_{std::move(zkp1)} {} + + ref::rp const & state() const { return state_; } + ref::rp const & input() const { return input_; } + + utc_nanos tkp1() const { return input_->tkp1(); } + + /* extrapolate kalman filter state forward to time + * .tkp1() (i.e. to t(k+1)); computes + * x(k+1|k) + * P(k+1|k) + * does not use the t(k+1) observations .input.z + */ + ref::rp extrapolate() const; + + /* compute kalman gain matrix K(k+1) + * given extrapolated t(k+1) state skp1_ext = {x(k+1|k), P(k+1|k)} + * + * note that .state() != skp1_ext; .state() reports {x(k), P(k)} + */ + MatrixXd gain(ref::rp const & skp1_ext) const; + + /* compute kalman gain vector K(k+1) + * given extrapolated t(k+1) state skp1_ext = {x(k+1|k), P(k+1|k)}, + * on behalf of a single observation z[j]. + * actual observation z[j] is not given here, + * just computing the gain vector. i'th member of gain vector + * gives effect of innovation on i'th member of kalman filter state. + */ + VectorXd gain1(ref::rp const & skp1_ext, + uint32_t j) const; + + /* compute correction to extrapolated filter state {x(k+1|k), P(k+1|k)}, + * for observation z(k+1) = .input.z() + */ + ref::rp correct(ref::rp const & skp1_ext); + + /* compute correction to extrapolated filter state skp1_ext = {x(k+1|k), P(k+1|k)}, + * for a single observation z(k+1, j) = .input.z()[j] + */ + ref::rp correct1(ref::rp const & skp1_ext, + uint32_t j); + + void display(std::ostream & os) const; + std::string display_string() const; + + private: + /* system state: timestamp, estimated process state, process covariance + * asof beginning of this step + */ + ref::rp state_; + /* input: observations at time t(k+1) */ + KalmanFilterInputPtr input_; + }; /*KalmanFilterStep*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterStep const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterStep.hpp */ diff --git a/KalmanFilterSvc.cpp b/KalmanFilterSvc.cpp new file mode 100644 index 00000000..5e32b102 --- /dev/null +++ b/KalmanFilterSvc.cpp @@ -0,0 +1,44 @@ +/* @file KalmanFilterSvc.cpp */ + +#include "KalmanFilterSvc.hpp" + +namespace xo { + using xo::ref::rp; + using xo::scope; + using xo::xtag; + + namespace kalman { + rp + KalmanFilterSvc::make(KalmanFilterSpec spec) + { + return new KalmanFilterSvc(std::move(spec)); + } /*make*/ + + KalmanFilterSvc::KalmanFilterSvc(KalmanFilterSpec spec) + : filter_{std::move(spec)} + {} + + void + KalmanFilterSvc::notify_ev(ref::rp const & input_kp1) + { + this->filter_.notify_input(input_kp1); + + ++(this->n_in_ev_); + this->notify_secondary_event(this->filter_.state_ext()); + } /*notify_input*/ + + void + KalmanFilterSvc::display(std::ostream & os) const + { + os << "name()) + << xtag("n_in_ev", this->n_in_ev()) + << xtag("n_queued_out_ev", this->n_queued_out_ev()) + << xtag("n_out_ev", this->n_out_ev()) + //<< xtag("filter", this->filter_) + << ">"; + } /*display*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterSvc.cpp */ diff --git a/KalmanFilterSvc.hpp b/KalmanFilterSvc.hpp new file mode 100644 index 00000000..33d25514 --- /dev/null +++ b/KalmanFilterSvc.hpp @@ -0,0 +1,64 @@ +/* @file KalmanFilterSvc.hpp */ + +#include "reactor/Sink.hpp" +#include "reactor/DirectSourcePtr.hpp" +#include "filter/KalmanFilter.hpp" +#include "filter/KalmanFilterInputSource.hpp" +#include "filter/KalmanFilterOutputCallback.hpp" +#include "callback/CallbackSet.hpp" + +namespace xo { + namespace kalman { + /* encapsulate a passive KalmanFilter + * instance as an active event consumer+producer + * + * sinks that want to consume KalmanFilterSvc events will use + * .attach_sink() (or .add_callback()) + */ + class KalmanFilterSvc : public xo::reactor::Sink1>, + public xo::reactor::DirectSourcePtr> { + public: + using AbstractSource = xo::reactor::AbstractSource; + + public: + /* named ctor idiom */ + static ref::rp make(KalmanFilterSpec spec); + + KalmanFilter const & filter() const { return filter_; } + + /* notify incoming observations; will trigger kalman filter step */ + void notify_ev(ref::rp const & input_kp1) override; + + // ----- inherited from reactor::AbstractSink ----- + + /* filter captures KF input pointer */ + virtual bool allow_volatile_source() const override { return false; } + virtual uint32_t n_in_ev() const override { return n_in_ev_; } + virtual void display(std::ostream & os) const override; + + // ----- inherited from reactor::AbstractSource ----- + + /* note: correct since KalmanFilterEngine.extrapolate() + * always creates new state object + */ + virtual bool is_volatile() const override { return false; } + + // ----- Inherited from AbstractEventProcessor ----- + + private: + KalmanFilterSvc(KalmanFilterSpec spec); + + private: + /* passive kalman filter */ + KalmanFilter filter_; + /* receive filter input from this source; see .attach_input() */ + ref::rp input_src_; + /* counts lifetime #of input events (see .notify_ev()) */ + uint32_t n_in_ev_ = 0; + /* publish filter state updates to these callbacks */ + fn::RpCallbackSet pub_; + }; /*KalmanFilterSvc*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* KalmanFilterSvc.hpp */ diff --git a/KalmanFilterTransition.cpp b/KalmanFilterTransition.cpp new file mode 100644 index 00000000..65be627b --- /dev/null +++ b/KalmanFilterTransition.cpp @@ -0,0 +1,55 @@ +/* @file KalmanFilterTransition.cpp */ + +#include "KalmanFilterTransition.hpp" +#include "reflect/StructReflector.hpp" +#include "print_eigen.hpp" +#include "indentlog/scope.hpp" + +namespace xo { + using xo::reflect::StructReflector; + using logutil::matrix; + using xo::xtag; + + namespace kalman { + void + KalmanFilterTransition::reflect_self() + { + StructReflector sr; + + if (sr.is_incomplete()) { + REFLECT_MEMBER(sr, F); + REFLECT_MEMBER(sr, Q); + } + } /*reflect_self*/ + + uint32_t + KalmanFilterTransition::n_state() const + { + /* we know F.rows() == F.cols() = Q.cols() == Q.rows(), + * see .check_ok() + */ + + return F_.rows(); + } /*n_state*/ + + void + KalmanFilterTransition::display(std::ostream & os) const + { + os << ""; + } /*display*/ + + std::string + KalmanFilterTransition::display_string() const + { + std::stringstream ss; + this->display(ss); + return ss.str(); + } /*display_string*/ + + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterTransition.cpp */ diff --git a/KalmanFilterTransition.hpp b/KalmanFilterTransition.hpp new file mode 100644 index 00000000..fc777c00 --- /dev/null +++ b/KalmanFilterTransition.hpp @@ -0,0 +1,62 @@ +/* @file KalmanFilterTransition.hpp */ + +#pragma once + +#include "time/Time.hpp" +#include +#include + +namespace xo { + namespace kalman { + + /* encapsulate transition behavior for a kalman filter + * before taking observations into account + */ + class KalmanFilterTransition { + public: + using MatrixXd = Eigen::MatrixXd; + using uint32_t = std::uint32_t; + + public: + KalmanFilterTransition() = default; + KalmanFilterTransition(MatrixXd F, + MatrixXd Q) + : F_{std::move(F)}, Q_{std::move(Q)} { assert(this->check_ok()); } + + static void reflect_self(); + + /* n: cardinality of state vector */ + uint32_t n_state() const; + + MatrixXd const & transition_mat() const { return F_; } + MatrixXd const & transition_cov() const { return Q_; } + + bool check_ok() const { + uint32_t n = F_.rows(); + bool f_is_nxn = ((F_.rows() == n) && (F_.cols() == n)); + bool q_is_nxn = ((Q_.rows() == n) && (Q_.cols() == n)); + + /* also would like to require: Q is +ve definite */ + + return f_is_nxn && q_is_nxn; + } /*check_ok*/ + + void display(std::ostream & os) const; + std::string display_string() const; + + private: + /* [n x n] state transition matrix */ + MatrixXd F_; + /* [n x n] covariance matrix for system noise */ + MatrixXd Q_; + }; /*KalmanFilterTransition*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterTransition const & x) { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterTransition.hpp */ diff --git a/init_filter.cpp b/init_filter.cpp new file mode 100644 index 00000000..80407cc4 --- /dev/null +++ b/init_filter.cpp @@ -0,0 +1,51 @@ +/* file init_filter.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "init_filter.hpp" +#include "reactor/init_reactor.hpp" +#include "KalmanFilterState.hpp" +#include "EigenUtil.hpp" +#include "printjson/PrintJson.hpp" + +namespace xo { + using xo::kalman::KalmanFilterInput; + using xo::kalman::KalmanFilterTransition; + using xo::kalman::KalmanFilterState; + using xo::kalman::KalmanFilterStateExt; + using xo::eigen::EigenUtil; + using xo::json::PrintJsonSingleton; + using xo::json::PrintJson; + + void + InitSubsys::init() + { + PrintJson * pjson = PrintJsonSingleton::instance().get(); + + EigenUtil::reflect_eigen(); + EigenUtil::provide_json_printers(pjson); + + KalmanFilterInput::reflect_self(); + KalmanFilterTransition::reflect_self(); + KalmanFilterState::reflect_self(); + KalmanFilterStateExt::reflect_self(); + + } /*init*/ + + InitEvidence + InitSubsys::require() + { + InitEvidence retval; + + /* subsystem dependencies for filter/ */ + retval ^= InitSubsys::require(); + + /* filter/'s own initialization code */ + retval ^= Subsystem::provide("filter", &init); + + return retval; + } /*require*/ +} /*namespace xo*/ + +/* end init_filter.cpp */ diff --git a/init_filter.hpp b/init_filter.hpp new file mode 100644 index 00000000..79cfc6d0 --- /dev/null +++ b/init_filter.hpp @@ -0,0 +1,20 @@ +/* file init_filter.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "subsys/Subsystem.hpp" + +namespace xo { + enum S_filter_tag {}; + + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; +} /*namespace xo*/ + +/* end init_filter.hpp */ diff --git a/print_eigen.hpp b/print_eigen.hpp new file mode 100644 index 00000000..953c5904 --- /dev/null +++ b/print_eigen.hpp @@ -0,0 +1,41 @@ +/* @file print_eigen.hpp */ + +#include +#include + +namespace logutil { + template + class matrix { + public: + matrix(T x) : x_{std::move(x)} {} + + /* print this value */ + T x_; + }; /*matrix*/ + + template + using vector = matrix; + + template + inline std::ostream & + operator<<(std::ostream & s, matrix const & mat) + { + s << "["; + for(std::uint32_t i = 0, m = mat.x_.rows(); i 0) + s << "; "; + + for(std::uint32_t j = 0, n = mat.x_.cols(); j 0) + s << ' '; + + s << mat.x_(i, j); + } + } + s << "]"; + + return s; + } /*operator<<*/ +} /*namespace logutil*/ + +/* end print_eigen.hpp */ From 49d2fd37572db5dfb844ad160b8989bac71ef5e7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 11:10:55 -0400 Subject: [PATCH 0516/2693] initial implementation --- CMakeLists.txt | 64 +++++++ cmake/xo_statisticsConfig.cmake.in | 4 + include/statistics/Accumulator.hpp | 10 ++ include/statistics/Histogram.hpp | 217 ++++++++++++++++++++++++ include/statistics/SampleStatistics.hpp | 130 ++++++++++++++ 5 files changed, 425 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/xo_statisticsConfig.cmake.in create mode 100644 include/statistics/Accumulator.hpp create mode 100644 include/statistics/Histogram.hpp create mode 100644 include/statistics/SampleStatistics.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..68eb1445 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,64 @@ +# xo-statistics/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_statistics VERSION 1.0) +enable_language(CXX) + +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() +# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) +add_code_coverage() +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) + +# ---------------------------------------------------------------- +# bespoke (usually temporary) c++ settings + +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- +# common include paths etc. + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- +# external dependencies +# +# set CMAKE_INSTALL_PREFIX to analog of /usr +# to use .cmake assistants from /usr/lib/cmake/indentlog +# +# xo_dependency(..) + +# ---------------------------------------------------------------- + +#add_subdirectory(example) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# output targets + +set(SELF_LIB xo_statistics) +xo_add_headeronly_library(${SELF_LIB}) + +# ---------------------------------------------------------------- +# standard install + provide find_package() support + +xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# install additional components + +#install(TARGETS statistics_ex1 DESTINATION bin/xo-statistics/example) diff --git a/cmake/xo_statisticsConfig.cmake.in b/cmake/xo_statisticsConfig.cmake.in new file mode 100644 index 00000000..9c15f36a --- /dev/null +++ b/cmake/xo_statisticsConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/statistics/Accumulator.hpp b/include/statistics/Accumulator.hpp new file mode 100644 index 00000000..69763e38 --- /dev/null +++ b/include/statistics/Accumulator.hpp @@ -0,0 +1,10 @@ +/* @file Accumulator.hpp */ + +namespace xo { + nmaespace statistics { + class Accumulator { + }; /*Accumulator*/ + } /*namespace statistics*/ +} /*namespace xo*/ + +/* end Accumulator.hpp */ diff --git a/include/statistics/Histogram.hpp b/include/statistics/Histogram.hpp new file mode 100644 index 00000000..d2219f23 --- /dev/null +++ b/include/statistics/Histogram.hpp @@ -0,0 +1,217 @@ +/* @file Histogram.hpp */ + +#pragma once + +#include "statistics/SampleStatistics.hpp" +#include "logutil/scope.hpp" +#include +#include +#include + +namespace xo { + namespace statistics { + /* sample statistics for a histogram bucket + * (editorial: compare with distribution::Counter) + */ + class Bucket { + public: + Bucket() = default; + Bucket(uint32_t n_sample, double sum, double mean, double mom2) + : n_sample_(n_sample), sum_(sum), mean_(mean), moment2_(mom2) {} + + uint32_t n_sample() const { return n_sample_; } + double sum() const { return sum_; } + double mean() const { return mean_; } + double sample_variance() const { return (n_sample_ > 1) ? moment2_ / (n_sample_ - 1) : 0.0; } + double standard_error() const { return ::sqrt(this->sample_variance()); } + + /* to estimate standard error of the mean: + * 0. let nk = .n_sample be the #of samples falling into this bin. + * n is the total #of samples across all bins. + * (i.e. Histogram.n_sample) + * 1. imagine probability of a sample falling in this bin + * is the observed frequency p = (.n_sample / n) + * 2. imagine a Bernoulli random variable Bp(i) associated with each sample x(i) + * {1, with probability p; 0 with probability q=1-p}) + * 3. each Bp(i) has mean p, variance p(1-p) + * 4. sum of the Bp(1) .. Bp(n) has mean n.p = nk, + * variance + * n.p.(1-p) + * = n.(nk/n).(1 - nk/n) + * = nk.(1 - nk/n) + * (by central limit theorem we can treat this as approximately normal + * for sufficiently large n) + * 5. standard error of Sum{Bp(i)} + * will be + * sqrt(nk.(1 - nk/n)) + */ + double n_sample_stderr(uint32_t n) const { + double nr = 1.0 / n; + uint32_t nk = this->n_sample_; + + return ::sqrt(nk * (1.0 - nk * nr)); + } /*n_sample_stderr*/ + + /* add one sample, x, to this bucket */ + void include_sample(double x) { + using logutil::scope; + using logutil::xtag; + + constexpr char const * c_self = "Bucket::include_sample"; + constexpr bool c_logging_enabled = false; + + /* size of sample _before_ adding x */ + int n = this->n_sample_; + + this->n_sample_ = n+1; + this->sum_ += x; + + double mean_n = this->mean_; + double mom2_n = this->moment2_; + double mean_np1 = SampleStatistics::update_online_mean(x, n, mean_n); + double mom2_np1 = SampleStatistics::update_online_moment2(x, + mean_np1, mean_n, + mom2_n); + scope lscope(c_self, c_logging_enabled); + if(c_logging_enabled) { + lscope.log("update", + xtag("x", x), xtag("n", n), + xtag("sum", sum_), + xtag("mean(n)", mean_n), + xtag("mom2(n)", mom2_n), + xtag("mean(n+1)", mean_np1), + xtag("mom2(n+1)", mom2_np1)); + } + + this->mean_ = mean_np1; + this->moment2_ = mom2_np1; + } /*include_sample*/ + + private: + /* #of samples in this bucket (will be #of times .sample() has been called) */ + uint32_t n_sample_ = 0; + /* sum of samples in this bucket */ + double sum_ = 0.0; + /* mean of values in this bucket + * -- use online algo to avoid catastrophic errors for large #samples + */ + double mean_ = 0.0; + double moment2_ = 0.0; + }; /*Bucket*/ + + /* accumulate histogram on sampled data */ + class Histogram { + public: + using const_iterator = std::vector::const_iterator; + + public: + Histogram(uint32_t n_interior_bucket, double lo_bucket, double hi_bucket) + : n_interior_bucket_(n_interior_bucket), + lo_bucket_(lo_bucket), + hi_bucket_(hi_bucket), + bucket_v_(n_interior_bucket + 2) + {} + + uint32_t n_sample() const { return n_sample_; } + uint32_t n_bucket() const { return n_interior_bucket_ + 2; } + + double bucket_width() const { return (this->hi_bucket_ - this->lo_bucket_) / this->n_interior_bucket_; } + + const_iterator begin() const { return bucket_v_.begin(); } + const_iterator end() const { return bucket_v_.end(); } + Bucket const & lookup(uint32_t ix) const { return this->bucket_v_[ix]; } + + /* compute bucket representing pooled sample combining + * contents of buckets [lo .. hi) + */ + Bucket pooled(uint32_t lo, uint32_t hi) const { + /* NOTE: for pooled bucket, may want to compute "reliability variance", + * i.e. report + * M2 / (N - (sum(nk^2) / N)) + * instead of + * M2 / (N - 1) + */ + + uint32_t n_sample = 0; + double sum = 0.0; + double mean = 0.0; + double mom2 = 0.0; + + for(uint32_t i = lo; ilookup(i); + + n_sample += bucket.n_sample(); + /* note that sum is not numerically well-behaved if summing + * over a large #of buckets + */ + sum += bucket.sum(); + + double prev_mean = mean; + /* relative weight of bucket b(i) relative to pooled statistics + * from buckets b(lo) .. b(i-1) + */ + double wt = (bucket.n_sample() / static_cast(n_sample)); + + /* similar to SampleStatistics::update_online_mean() */ + mean = prev_mean + wt * (bucket.mean() - prev_mean); + /* similar to SampleStatistics::update_online_moment2() */ + mom2 = (mom2 + (bucket.n_sample() + * (bucket.mean() - prev_mean) + * (bucket.mean() - mean))); + } + + return Bucket(n_sample, sum, mean, mom2); + } /*pooled*/ + + double bucket_lo_edge(uint32_t ix) const { + if(ix == 0) { + return -std::numeric_limits::infinity(); + } else { + return this->lo_bucket_ + (ix - 1) * this->bucket_width(); + } + } /*bucket_lo_edge*/ + + double bucket_hi_edge(uint32_t ix) const { + if(ix < n_interior_bucket_ + 1) + return this->lo_bucket_ + ix * this->bucket_width(); + else + return std::numeric_limits::infinity(); + } /*bucket_hi_edge*/ + + /* index (into .bucket_v[]) of bucket to use for a sample with value x */ + uint32_t bucket_ix(double x) const { + if(x < this->lo_bucket_) + return 0; + + if(x < this->hi_bucket_) + return 1 + static_cast((x - this->lo_bucket_) / this->bucket_width()); + + return this->n_interior_bucket_ + 1; + } /*bucket_ix*/ + + void include_sample(double x) { + uint32_t ix = this->bucket_ix(x); + + ++(this->n_sample_); + this->bucket_v_[ix].include_sample(x); + } /*include_sample*/ + + private: + /* #of samples across all buckets */ + uint32_t n_sample_ = 0; + /* #of interior buckets: split [.lo_bucket, .hi_bucket] into + * equally-spaced intervals of width (.hi_bucket - .lo_bucket) / .n_bucket + */ + uint32_t n_interior_bucket_ = 0; + /* right edge of first bucket (left edge is -oo) */ + double lo_bucket_ = 0.0; + /* left edge of last bucket (right edge is +oo) */ + double hi_bucket_ = 0.0; + + /* hisogram buckets */ + std::vector bucket_v_; + }; /*Histogram*/ + } /*namespace statistics*/ +} /*namespace xo*/ + +/* end Histogram.hpp */ diff --git a/include/statistics/SampleStatistics.hpp b/include/statistics/SampleStatistics.hpp new file mode 100644 index 00000000..a7d595b0 --- /dev/null +++ b/include/statistics/SampleStatistics.hpp @@ -0,0 +1,130 @@ +/* @file SampleStatistics.hpp */ + +#pragma once + +#include + +namespace xo { + namespace statistics { + /* accumlate statistics online for a sample */ + class SampleStatistics { + public: + SampleStatistics() = default; + + /* given we have a sample S(n) of size n with given mean, + * compute mean of sample with one event x added + * + * n. #of samples *preceding* x + */ + static double update_online_mean(double x, uint32_t n, double mean) { + /* to update mean in a numerically stable way: + * avoid computing running sample sum, to avoid + * adding floating point numbers with distant magnitudes; + * instead compute correction to the mean directly + * + * n / x(i) \ + * mean(Sn) := Sum | ----- | + * i=1 \ n / + * + * so + * n+1 / x(i) \ + * mean(S(n+1)) = Sum | ----- | + * i=1 \ n+1 / + * + * n n+1 / x(i) \ + * = --- Sum | ----- | + * n+1 i=1 \ n / + * + * n / x(n+1) n x(i) \ + * = --- | ------ + Sum ---- | + * n+1 \ n i=1 n / + * + * x(n+1) / n \ + * = ------ + | --- . mean(S(n)) | + * n+1 \ n+1 / + * + * x(n+1) / -1 \ + * = ------ + mean(S(n)) + | --- . mean(S(n)) | + * n+1 \ n+1 / + * + * = mean(S(n)) + (x(n+1) - mean(S(n))) / (n+1) + */ + return mean + ((1.0 / (n+1)) * (x - mean)); + } /*update_online_mean*/ + + /* + * with S(n) = Sn = {set of n samples}, + * u(n) = mean(Sn) + * + * (with mean, variance meaning "estimate for") + * + * 1 n / 2 \ / 1 \ 2 + * variance(Sn) := --- . Sum | (x(i) | - | --- . Sum x(i) | + * n i=1 \ / \ n i=1 / + * + * using Welford's recurrence for 2nd moment: + * + * define + * M2(n+1) := M2(n) + (x(n+1) - mean(S(n))) + * . (x(n+1) - mean(S(n+1)) + * + * then unbiased variance estimate for S(n+1) is: + * + * M2(n+1) + * ------- + * n + * + * x. new sample value + * mean_np1. mean estimate for S(n+1) + * mean_n. mean estimate for S(n) + * moment2. 2nd moment for S(n) + */ + static double update_online_moment2(double x, + double mean_np1, double mean_n, + double moment2) + { + return moment2 + (x - mean_n) * (x - mean_np1); + } /*update_online_moment2*/ + + uint32_t n_sample() const { return n_sample_; } + double mean() const { return mean_; } + double moment2() const { return moment2_; } + /* 'sample variance' = variance estimate, + * applying Bessel correction for sample bias + * + * require: n_sample >= 2 + */ + double sample_variance() const { return moment2_ / (n_sample_ - 1); } + + /* biased variance estimate + * = (1 - 1/(n+1)) * .sample_variance() + * + * .variance() -> .sample_variance() as sample size -> +oo + * + * require: n_sample >= 1 + */ + double variance() const { return moment2_ / n_sample_; } + + void include_sample(double x) { + /* n+1 */ + uint32_t np1 = this->n_sample_ + 1; + + double mean_np1 = update_online_mean(x, this->n_sample_, this->mean_); + double moment2_np1 = update_online_moment2(x, this->mean_, mean_np1, this->moment2_); + + this->n_sample_ = np1; + this->mean_ = mean_np1; + this->moment2_ = moment2_np1; + } /*include_sample*/ + + private: + uint32_t n_sample_ = 0; + /* estimated mean */ + double mean_ = 0.0; + /* estimated 2nd moment E[X^2] */ + double moment2_ = 0.0; + }; /*SampleStatistics*/ + } /*namespace statistics*/ +} /*namespace xo*/ + +/* end SampleStatistics.hpp */ From e1f39b7b0de413a6522905a971f843e9cee91dcb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 11:11:56 -0400 Subject: [PATCH 0517/2693] initial implementation --- CMakeLists.txt | 47 ++ EigenUtil.cpp | 157 ---- KalmanFilter.cpp | 78 -- KalmanFilter.hpp | 128 ---- KalmanFilterEngine.cpp | 360 --------- KalmanFilterEngine.hpp | 185 ----- KalmanFilterInput.cpp | 118 --- KalmanFilterInput.hpp | 121 --- KalmanFilterInputCallback.hpp | 14 - KalmanFilterInputSource.hpp | 28 - KalmanFilterInputToConsole.hpp | 30 - KalmanFilterObservable.hpp | 110 --- KalmanFilterOutputCallback.hpp | 15 - KalmanFilterSpec.cpp | 27 - KalmanFilterSpec.hpp | 86 --- KalmanFilterState.cpp | 231 ------ KalmanFilterState.hpp | 163 ----- KalmanFilterStateToConsole.hpp | 30 - KalmanFilterStep.cpp | 84 --- KalmanFilterSvc.hpp | 64 -- KalmanFilterTransition.cpp | 55 -- KalmanFilterTransition.hpp | 62 -- cmake/xo_kalmanfilterConfig.cmake.in | 17 + .../xo/kalmanfilter/EigenUtil.hpp | 0 include/xo/kalmanfilter/KalmanFilter.hpp | 127 ++++ .../xo/kalmanfilter/KalmanFilterEngine.hpp | 185 +++++ include/xo/kalmanfilter/KalmanFilterInput.hpp | 121 +++ .../KalmanFilterInputCallback.hpp | 14 + .../kalmanfilter/KalmanFilterInputSource.hpp | 27 + .../KalmanFilterInputToConsole.hpp | 30 + .../kalmanfilter/KalmanFilterObservable.hpp | 109 +++ .../KalmanFilterOutputCallback.hpp | 15 + include/xo/kalmanfilter/KalmanFilterSpec.hpp | 86 +++ include/xo/kalmanfilter/KalmanFilterState.hpp | 163 +++++ .../KalmanFilterStateToConsole.hpp | 30 + .../xo/kalmanfilter/KalmanFilterStep.hpp | 0 include/xo/kalmanfilter/KalmanFilterSvc.hpp | 64 ++ .../kalmanfilter/KalmanFilterTransition.hpp | 62 ++ include/xo/kalmanfilter/init_filter.hpp | 20 + include/xo/kalmanfilter/print_eigen.hpp | 41 ++ init_filter.cpp | 51 -- init_filter.hpp | 20 - print_eigen.hpp | 41 -- src/kalmanfilter/CMakeLists.txt | 31 + src/kalmanfilter/EigenUtil.cpp | 157 ++++ src/kalmanfilter/KalmanFilter.cpp | 78 ++ src/kalmanfilter/KalmanFilterEngine.cpp | 360 +++++++++ src/kalmanfilter/KalmanFilterInput.cpp | 118 +++ .../KalmanFilterInputToConsole.cpp | 2 +- .../kalmanfilter/KalmanFilterObservable.cpp | 2 +- src/kalmanfilter/KalmanFilterSpec.cpp | 27 + src/kalmanfilter/KalmanFilterState.cpp | 231 ++++++ .../KalmanFilterStateToConsole.cpp | 2 +- src/kalmanfilter/KalmanFilterStep.cpp | 84 +++ .../kalmanfilter/KalmanFilterSvc.cpp | 0 src/kalmanfilter/KalmanFilterTransition.cpp | 55 ++ src/kalmanfilter/init_filter.cpp | 52 ++ utest/CMakeLists.txt | 54 ++ utest/KalmanFilter.test.cpp | 690 ++++++++++++++++++ utest/filter_utest_main.cpp | 6 + 60 files changed, 3104 insertions(+), 2261 deletions(-) create mode 100644 CMakeLists.txt delete mode 100644 EigenUtil.cpp delete mode 100644 KalmanFilter.cpp delete mode 100644 KalmanFilter.hpp delete mode 100644 KalmanFilterEngine.cpp delete mode 100644 KalmanFilterEngine.hpp delete mode 100644 KalmanFilterInput.cpp delete mode 100644 KalmanFilterInput.hpp delete mode 100644 KalmanFilterInputCallback.hpp delete mode 100644 KalmanFilterInputSource.hpp delete mode 100644 KalmanFilterInputToConsole.hpp delete mode 100644 KalmanFilterObservable.hpp delete mode 100644 KalmanFilterOutputCallback.hpp delete mode 100644 KalmanFilterSpec.cpp delete mode 100644 KalmanFilterSpec.hpp delete mode 100644 KalmanFilterState.cpp delete mode 100644 KalmanFilterState.hpp delete mode 100644 KalmanFilterStateToConsole.hpp delete mode 100644 KalmanFilterStep.cpp delete mode 100644 KalmanFilterSvc.hpp delete mode 100644 KalmanFilterTransition.cpp delete mode 100644 KalmanFilterTransition.hpp create mode 100644 cmake/xo_kalmanfilterConfig.cmake.in rename EigenUtil.hpp => include/xo/kalmanfilter/EigenUtil.hpp (100%) create mode 100644 include/xo/kalmanfilter/KalmanFilter.hpp create mode 100644 include/xo/kalmanfilter/KalmanFilterEngine.hpp create mode 100644 include/xo/kalmanfilter/KalmanFilterInput.hpp create mode 100644 include/xo/kalmanfilter/KalmanFilterInputCallback.hpp create mode 100644 include/xo/kalmanfilter/KalmanFilterInputSource.hpp create mode 100644 include/xo/kalmanfilter/KalmanFilterInputToConsole.hpp create mode 100644 include/xo/kalmanfilter/KalmanFilterObservable.hpp create mode 100644 include/xo/kalmanfilter/KalmanFilterOutputCallback.hpp create mode 100644 include/xo/kalmanfilter/KalmanFilterSpec.hpp create mode 100644 include/xo/kalmanfilter/KalmanFilterState.hpp create mode 100644 include/xo/kalmanfilter/KalmanFilterStateToConsole.hpp rename KalmanFilterStep.hpp => include/xo/kalmanfilter/KalmanFilterStep.hpp (100%) create mode 100644 include/xo/kalmanfilter/KalmanFilterSvc.hpp create mode 100644 include/xo/kalmanfilter/KalmanFilterTransition.hpp create mode 100644 include/xo/kalmanfilter/init_filter.hpp create mode 100644 include/xo/kalmanfilter/print_eigen.hpp delete mode 100644 init_filter.cpp delete mode 100644 init_filter.hpp delete mode 100644 print_eigen.hpp create mode 100644 src/kalmanfilter/CMakeLists.txt create mode 100644 src/kalmanfilter/EigenUtil.cpp create mode 100644 src/kalmanfilter/KalmanFilter.cpp create mode 100644 src/kalmanfilter/KalmanFilterEngine.cpp create mode 100644 src/kalmanfilter/KalmanFilterInput.cpp rename KalmanFilterInputToConsole.cpp => src/kalmanfilter/KalmanFilterInputToConsole.cpp (93%) rename KalmanFilterObservable.cpp => src/kalmanfilter/KalmanFilterObservable.cpp (98%) create mode 100644 src/kalmanfilter/KalmanFilterSpec.cpp create mode 100644 src/kalmanfilter/KalmanFilterState.cpp rename KalmanFilterStateToConsole.cpp => src/kalmanfilter/KalmanFilterStateToConsole.cpp (93%) create mode 100644 src/kalmanfilter/KalmanFilterStep.cpp rename KalmanFilterSvc.cpp => src/kalmanfilter/KalmanFilterSvc.cpp (100%) create mode 100644 src/kalmanfilter/KalmanFilterTransition.cpp create mode 100644 src/kalmanfilter/init_filter.cpp create mode 100644 utest/CMakeLists.txt create mode 100644 utest/KalmanFilter.test.cpp create mode 100644 utest/filter_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..f4120636 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,47 @@ +# xo-kalmanfilter/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_kalmanfilter VERSION 1.0) +enable_language(CXX) + +# common XO cmake macros (see proj/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() +# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) +add_code_coverage() +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) + +# ---------------------------------------------------------------- +# c++ settings + +# one-time project-specific c++ flags. usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- + +add_subdirectory(src/kalmanfilter) +add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support for reactor customers + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# end CMakeLists.txt diff --git a/EigenUtil.cpp b/EigenUtil.cpp deleted file mode 100644 index f1d3c4a3..00000000 --- a/EigenUtil.cpp +++ /dev/null @@ -1,157 +0,0 @@ -/* file EigenUtil.cpp - * - * author: Roland Conybeare, Sep 2022 - */ - -#include "EigenUtil.hpp" -#include "printjson/PrintJson.hpp" -#include "reflect/Reflect.hpp" -#include -#include -#include -#include - -namespace xo { - using xo::json::PrintJson; - using xo::json::JsonPrinter; - using xo::reflect::Reflect; - using xo::reflect::TypeDescr; - using VectorXb = Eigen::Array; - using Eigen::VectorXd; - using Eigen::MatrixXd; - -#ifdef NOT_YET - namespace reflect { - template - using EigenVectorX_Tdx = xo::reflect::StlVectorTdx>; - - /* probably need this to appear before decl for class xo::reflect::Reflect */ - template - class EstablishTdx> { - public: - static std::unique_ptr make() { - return EigenVectorX_Tdx::make(); - } /*make*/ - }; /*EstablishTdx*/ - } /*reflect*/ -#endif - - namespace eigen { - - namespace { - /* prints a VectorXd as json, in the obvious format, e.g. - * [1,2,3] - */ - template - class EigenVectorJsonPrinter : public JsonPrinter { - public: - EigenVectorJsonPrinter(PrintJson const * pjson) : JsonPrinter(pjson) {} - - virtual void print_json(TaggedPtr tp, - std::ostream * p_os) const override - { - EigenVectorType * pv = this->check_recover_native(tp, p_os); - - if (pv) { - /* EigenVectorType (VectorXb, VectorXd, ..) - * is reflected as atomic for now, out of expedience. - * - * as soon as we reflect as mt_vector, will not need this helper. - */ - *p_os << "["; - - for (std::uint32_t i = 0, n = pv->size(); i < n; ++i) { - if (i > 0) - *p_os << ","; - - /* note: need to dispatch via json printer for vector elements, - * to get special treatment for non-finite values - */ - this->pjson()->print((*pv)[i], p_os); - //*p_os << jsonp((*pv)[i], this->pjson()); - } - - *p_os << "]"; - } - } /*print_json*/ - }; /*EigenVectorJsonPrinter*/ - - /* prints a MatrixXd as json, in row-major format, e.g. - * [[1,2,3], [4,5,6], [7,8,9]] - */ - class MatrixXdJsonPrinter : public JsonPrinter { - public: - MatrixXdJsonPrinter(PrintJson const * pjson) : JsonPrinter(pjson) {} - - virtual void print_json(TaggedPtr tp, - std::ostream * p_os) const override - { - MatrixXd * pm = this->check_recover_native(tp, p_os); - - if (pm) { - /* MatrixXd is reflected as atomic for now, out of expedience */ - *p_os << "["; - - for(std::uint32_t i=0, m=pm->rows(); i 0) - *p_os << ", "; - *p_os << "["; - for(std::uint32_t j=0, n=pm->cols(); j 0) - *p_os << ","; - - /* note: need to dispatch via json printer for matrix elements, - * to get special treatment for non-finite values - */ - this->pjson()->print((*pm)(i, j), p_os); - //*p_os << jsonp((*pm)(i, j), this->pjson()); - } - *p_os << "]"; - } - - *p_os << "]"; - } - } /*print_json*/ - }; /*MatrixXdJsonPrinter*/ - - template - void - provide_eigen_vector_printer(PrintJson * p_pjson) - { - TypeDescr td = Reflect::require(); - std::unique_ptr pr(new EigenVectorJsonPrinter(p_pjson)); - - p_pjson->provide_printer(td, std::move(pr)); - } /*provide_eigen_vector_printer*/ - } /*namespace*/ - - void - EigenUtil::reflect_eigen() - { -#ifdef NOT_YET - Reflect::require(); - Reflect::require(); -#endif - } /*reflect_eigen*/ - - void - EigenUtil::provide_json_printers(PrintJson * p_pjson) - { - assert(p_pjson); - - provide_eigen_vector_printer(p_pjson); - provide_eigen_vector_printer(p_pjson); - - { - TypeDescr td = Reflect::require(); - std::unique_ptr pr(new MatrixXdJsonPrinter(p_pjson)); - - p_pjson->provide_printer(td, std::move(pr)); - } - } /*provide_json_printers*/ - } /*namespace eigen*/ -} /*namespace xo*/ - -/* end EigenUtil.cpp */ diff --git a/KalmanFilter.cpp b/KalmanFilter.cpp deleted file mode 100644 index bf94fde2..00000000 --- a/KalmanFilter.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/* @file KalmanFilter.cpp */ - -#include "KalmanFilter.hpp" -#include "KalmanFilterEngine.hpp" -#include "print_eigen.hpp" -#include "indentlog/scope.hpp" -#include "Eigen/src/Core/Matrix.h" - -namespace xo { - using xo::time::utc_nanos; - //using logutil::matrix; - using xo::scope; - using xo::tostr; - using xo::xtag; - using Eigen::MatrixXd; - using Eigen::VectorXd; - - namespace kalman { - // ----- KalmanFilter ----- - - KalmanFilter::KalmanFilter(KalmanFilterSpec spec) - : filter_spec_{std::move(spec)}, - state_ext_{filter_spec_.start_ext()} - {} /*ctor*/ - - void - KalmanFilter::notify_input(ref::rp const & input_kp1) - { - scope log(XO_ENTER0(info)); - - /* on entry: - * .state_ext refers to t(k) - * on exit: - * .step refers to t(k+1) - * .state_ext refers to t(k+1) - */ - - log && log(xtag("step_dt", - input_kp1->tkp1() - this->state_ext_->tm())); - - /* establish step inputs for this filter step: - * F(k+1) (system transition matrix) - * Q(k+1) (system noise covariance matrix) - * H(k+1) (observation coupling matrix) - * R(k+1) (observation noise covariance matrix) - * z(k+1) (observation vector) - */ - this->step_ = this->filter_spec_.make_step(this->state_ext_, input_kp1); - - //if (lscope.enabled()) { lscope.log(xtag("step", this->step_)); } - - /* extrapolate filter state to t(k+1), - * and correct based on z(k+1) - */ - this->state_ext_ = KalmanFilterEngine::step(this->step_); - - //if (lscope.enabled()) { lscope.log(xtag("state_ext", this->state_ext_)); } - } /*notify_input*/ - - void - KalmanFilter::display(std::ostream & os) const - { - os << ""; - } /*display*/ - - std::string - KalmanFilter::display_string() const - { - return tostr(*this); - } /*display_string*/ - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilter.cpp */ diff --git a/KalmanFilter.hpp b/KalmanFilter.hpp deleted file mode 100644 index 295a4f7a..00000000 --- a/KalmanFilter.hpp +++ /dev/null @@ -1,128 +0,0 @@ -/* @file KalmanFilter.hpp */ - -#pragma once - -#include "filter/KalmanFilterSpec.hpp" - -namespace xo { - namespace kalman { - /* Specification for an ordinary discrete linear kalman filter. - * - * The filter generates estimates for a process observed at a discrete - * set of times tk in {t0, t1, .., tn} - * - * At each time tk we have the following: - * - * 0. x(0) initial estimate at t(0) - * P(0) initial priors: error covariance matrix for x(0) - * - * 1. x_(k), [n x 1] vector: - * system state, denoted by vector. - * (state is not directly observable, - * filter will attempt to estimate it) - * - * 2. w_(k), [n x 1] vector - * Q(k), [n x n] matrix - * - * w_(k) denotes system noise, - * gaussian with covariance Q(k). - * noise w_(k) is not directly observable. - * - * 3. z(k), [m x 1] vector: - * - * observation vector for time tk - * - * 4. v_(k), [m x 1] vector - * R(k), [m x m] matrix - * - * v_(k) denotes observation errors, - * gaussian with covariance R(k). - * noise v_(k) is not directly observable. - * - * 5. F(k), [n x n] matrix - * state transition matrix - * model system evolves according to: - * - * x_(k+1) = F(x).x_(k) + w_(k) - * - * 6. observations z(k) depend on system state: - * - * z(k) = H(k).x_(k) + v_(k) - * - * 7. Kalman filter outputs: - * x(k), [n x 1] vector - * Q(k), [n x n] matrix - * - * x(k) is optimal estimate for system state x_(k) - * P(k) is covariance matrix specifying confidence intervals - * for pairs (x(k)[i], x(k)[j]) - * - * filter specification consists of: - * n, m, x(0), P(0), F(k), Q(k), H(k), R(k) - * The cardinality of observations z(k) can vary over time, - * so to be precise, m can vary with tk; write as m(k) - * - * More details: - * - avoid having to specify t(k) in advance; - * instead defer until observation available - * so t(k) can be taken from polling timestamp - */ - - /* encapsulate a (linear) kalman filter - * together with event publishing - */ - class KalmanFilter { - public: - using MatrixXd = Eigen::MatrixXd; - using VectorXd = Eigen::VectorXd; - using utc_nanos = xo::time::utc_nanos; - - public: - /* create filter with specification given by spec, and initial state s0 */ - explicit KalmanFilter(KalmanFilterSpec spec); - - uint32_t step_no() const { return state_ext_->step_no(); } - utc_nanos tm() const { return state_ext_->tm(); } - KalmanFilterSpec const & filter_spec() const { return filter_spec_; } - KalmanFilterStep const & step() const { return step_; } - ref::rp const & state_ext() const { return state_ext_; } - - /* notify kalman filter with input for time t(k+1) = input_kp1.tkp1() - * Require: input.tkp1() >= .current_tm() - * Promise: - * - .tm() = input_kp1.tkp1() - * - .step_no() = old .step_no() + 1 - * - .filter_spec_k, .step_k, .state_k updated - * for observations in input_kp1 - */ - void notify_input(ref::rp const & input_kp1); - - void display(std::ostream & os) const; - std::string display_string() const; - - private: - /* specification for kalman filter; - * produces process/observation matrices on demand - */ - KalmanFilterSpec filter_spec_; - - /* filter step for most recent observation */ - KalmanFilterStep step_; - - /* filter state as of most recent observation; - * result of applying KalmanFilterEngine::step() to contents of .step - */ - ref::rp state_ext_; - }; /*KalmanFilter*/ - - inline std::ostream & - operator<<(std::ostream & os, KalmanFilter const & x) { - x.display(os); - return os; - } /*operator<<*/ - - } /*namespace kalman*/ -} /*namespace xo*/ - - -/* end KalmanFilter.hpp */ diff --git a/KalmanFilterEngine.cpp b/KalmanFilterEngine.cpp deleted file mode 100644 index e80f7497..00000000 --- a/KalmanFilterEngine.cpp +++ /dev/null @@ -1,360 +0,0 @@ -/* @file KalmanFilterEngine.cpp - * - */ - -#include "KalmanFilterEngine.hpp" -#include "print_eigen.hpp" -#include "indentlog/scope.hpp" -#include "Eigen/src/Core/Matrix.h" - -namespace xo { - using xo::time::utc_nanos; - using logutil::matrix; - using xo::scope; - using xo::xtag; - using Eigen::LDLT; - using Eigen::MatrixXd; - using Eigen::VectorXd; - - namespace kalman { - // ----- KalmanFilterEngine ----- - - ref::rp - KalmanFilterEngine::extrapolate(utc_nanos tkp1, - ref::rp const & s, - KalmanFilterTransition const & f) - { - //constexpr char const * c_self_name - // = "KalmanFilterEngine::extrapolate"; - - /* prior estimates at t(k) */ - VectorXd const & x = s->state_v(); - MatrixXd const & P = s->state_cov(); - - /* model change from t(k) -> t(k+1) */ - MatrixXd const & F = f.transition_mat(); - MatrixXd const & Q = f.transition_cov(); - - if(F.cols() != x.rows()) { - scope log(XO_DEBUG(true /*debug_flag*/)); - - log("error: F*x: expected F.cols=x.rows", - xtag("F.cols", F.cols()), xtag("x.rows", x.rows())); - } - - /* x(k+1|k) */ - VectorXd x_ext = F * x; - - /* P(k+1|k) */ - MatrixXd P_ext = (F * P * F.transpose()) + Q; - - /* creating new state object here - * allows KalmanFilterSvc.is_volatile()=false - */ - - return KalmanFilterState::make(s->step_no() + 1, - tkp1, - std::move(x_ext), - std::move(P_ext), - f); - } /*extrapolate*/ - - VectorXd - KalmanFilterEngine::kalman_gain1(ref::rp const & skp1_ext, - KalmanFilterObservable const & h, - uint32_t j) - { - constexpr bool c_debug_enabled = false; - - scope log(XO_DEBUG(c_debug_enabled)); - - /* P(k+1|k) :: [n x n] */ - MatrixXd const & P_ext = skp1_ext->state_cov(); - - /* H(k) :: [m x n] */ - MatrixXd const & H = h.observable(); - /* R(k) :: [m x m] */ - MatrixXd const & R = h.observable_cov(); - - /* i'th col of H couples element #i of filter state to each member of input z(k); - * j'th row of H couples filter state to j'th observable - * - * Hj :: [1 x n] Hj is a row-vector - */ - auto Hj = H.row(j); - - /* Rjj is the j'th diagonal element of R */ - double Rjj = R(j, j); - - /* T - * M(k) = Hj * P(k+1|k) * Hj + Rjj - * - * M(k) is a [1 x 1] matrix - */ - double m = Hj * (P_ext * Hj.transpose()) + Rjj; - - /* -1 - * M(k) trivial, since M is [1 x 1] - */ - double m_inv = 1.0 / m; - - /* K :: [n x 1] */ - VectorXd K = P_ext * Hj.transpose() * m_inv; - - log && log("result", - xtag("P(k+1|k)", matrix(P_ext)), - xtag("R", matrix(R)), - xtag("m", m)); - - return K; - } /*kalman_gain1*/ - - MatrixXd - KalmanFilterEngine::kalman_gain(ref::rp const & skp1_ext, - KalmanFilterObservable const & h) - { - scope log(XO_DEBUG(false /*debug_enabled*/)); - - /* P(k+1|k) */ - MatrixXd const & P_ext = skp1_ext->state_cov(); - - MatrixXd const & H = h.observable(); - MatrixXd const & R = h.observable_cov(); - - uint32_t m = H.rows(); - uint32_t n = H.cols(); - - if ((P_ext.rows() != n) || (P_ext.cols() != n)) { - std::string err_msg - = tostr("kalman_gain: with dim(H) = [m x n] expect dim(P) = [n x n]", - xtag("m", m), xtag("n", n), - xtag("P.rows", P_ext.rows()), - xtag("P.cols", P_ext.cols())); - - throw std::runtime_error(err_msg); - } - - if ((R.rows() != m) || (R.cols() != m)) { - std::string err_msg - = tostr("kalman_gain: with dim(H) = [m x n] expect dim(R) = [m x m]", - xtag("m", m), xtag("n", n), - xtag("R.rows", R.rows()), xtag("R.cols", R.cols())); - - throw std::runtime_error(err_msg); - } - - /* kalman gain: - * T -1 - * K(k+1) = P(k+1|k).H(k) .M - * - * T / T \ -1 - * = P(k+1|k).H(k) .| H(k).P(k+1|k).H(k) + R(k) | - * \ / - * - * Notes: - * 1. the matrix M being inverted is symmetric, since represents covariances. - * 2. if diagonal of R(k) has no zeroes (i.e. all measurements are subject to error), - * then it must be non-negative definite - * 3. unless observation errors are perfectly correlated, M(k) - * is positive definite. - * 4. even though 3. holds, there may be a nearby non-positive-definite matrix M+dM, - * Factoring M with finite-precision arithmetic solves for M+dM instead of M; - * which may run into difficulty if M is only 'slighlty' +ve definite. - * If necessary add small diagonal correction D to M, - * sufficient to make M+D positive definite. - * This is equivalent to introducing additional - * uncorrelated observation error, so benign from a robustness perspective - * 5. In generally we usually want to avoid fully realizing a matrix inverse. - * In this case need to explicitly compute K as ingredient used to - * correct state covariance later. - * 6. However, if R is diagonal (which is in practice quite likely), - * then it's easy to decompose a suite of vector observations z(k+1) = [z1, ..zm]T - * into separate zi, with dt=0 separating them. - * Can use this to avoid computing the inverse. - * See .kalman_gain1(), .correct1() - * 7. .kalman_gain() works unaltered when H, R have been reindexed - * to exclude outliers/errors; this is true because .kalman_gain() does not - * use the observation vector z[], i.e. operates entirely in the reduced - * reindexed space. - */ - - MatrixXd M = H * P_ext * H.transpose() + R; - - /* will use to write M as: - * - * T T - * M = P .L.D.L .P - * - * where: - * P is a permutation matrix - * L is lower triangular, with unit diagonal - * D is diagonal - */ - LDLT ldlt = M.ldlt(); - - /* solve for the identity matrix to realize the inverse this way */ - MatrixXd I = MatrixXd::Identity(M.rows(), M.cols()); - - /* -1 - * M - */ - MatrixXd M_inv = ldlt.solve(I); - - /* K(k+1) */ - MatrixXd K = P_ext * H.transpose() * M_inv; - - log && log("result", - xtag("k", skp1_ext->step_no()), - xtag("P(k+1|k)", matrix(P_ext)), - xtag("H", matrix(H)), - xtag("R", matrix(R)), - xtag("M", matrix(M)), - xtag("K", matrix(K))); - - return K; - } /*kalman_gain*/ - - ref::rp - KalmanFilterEngine::correct1(ref::rp const & skp1_ext, - KalmanFilterObservable const & h, - ref::rp const & zkp1, - uint32_t j) - { - uint32_t n = skp1_ext->n_state(); - /* Kj :: [n x 1] */ - VectorXd Kj = kalman_gain1(skp1_ext, h, j); - /* H :: [m x n] */ - MatrixXd const & H = h.observable(); - VectorXd const & z = zkp1->z(); - - /* Hj :: [1 x n] the j'th row of H */ - auto const & Hj = H.row(j); - - - /* x(k+1|x) :: [n x 1] */ - VectorXd const & x_ext = skp1_ext->state_v(); - - /* P(k+1|k) :: [n x n] */ - MatrixXd const & P_ext = skp1_ext->state_cov(); - - /* innovj : difference between jth 'actual observation' - * and jth 'predicted observation' - */ - double innovj = z[j] - (Hj * x_ext); - - /* x(k+1) */ - VectorXd xkp1 = x_ext + (Kj * innovj); - - MatrixXd I = MatrixXd::Identity(n, n); - /* note: Kj [n x 1], Hj [1 x n], - * so Kj * Hj [n x n], with rank 1 - */ - MatrixXd Pkp1 = (I - (Kj * Hj)) * P_ext; - - return KalmanFilterStateExt::make(skp1_ext->step_no(), - skp1_ext->tm(), - xkp1, - Pkp1, - skp1_ext->transition(), - Kj, - j, - zkp1); - } /*correct1*/ - - ref::rp - KalmanFilterEngine::correct(ref::rp const & skp1_ext, - KalmanFilterObservable const & h, - ref::rp const & zkp1) - { - uint32_t n = skp1_ext->n_state(); - /* K :: [n x m] */ - MatrixXd K = kalman_gain(skp1_ext, h); - MatrixXd const & H = h.observable(); - /* z_orig[] is original observation vector before reindexing */ - VectorXd const & z_orig = zkp1->z(); - /* reindex z_orig, keeping only elements that appear in - */ - VectorXd z = z_orig(h.keep()); - - /* 'ext' short for 'extrapolated' */ - VectorXd const & x_ext = skp1_ext->state_v(); - MatrixXd const & P_ext = skp1_ext->state_cov(); - - /* innov: difference between 'actual observations' - * and 'predicted observations' - */ - VectorXd innov = z - (H * x_ext); - - /* x(k+1) :: [n x 1] */ - VectorXd xkp1 = x_ext + K * innov; - MatrixXd I = MatrixXd::Identity(n, n); - MatrixXd Pkp1 = (I - K * H) * P_ext; - - return KalmanFilterStateExt::make(skp1_ext->step_no(), - skp1_ext->tm(), - xkp1, - Pkp1, - skp1_ext->transition(), - K, - -1 /*j: not used*/, - zkp1); - } /*correct*/ - - ref::rp - KalmanFilterEngine::step(utc_nanos tkp1, - ref::rp const & sk, - KalmanFilterTransition const & Fk, - KalmanFilterObservable const & Hkp1, - ref::rp const & zkp1) - { - ref::rp skp1_ext - = KalmanFilterEngine::extrapolate(tkp1, sk, Fk); - - ref::rp skp1 - = KalmanFilterEngine::correct(skp1_ext, Hkp1, zkp1); - - return skp1; - } /*step*/ - - ref::rp - KalmanFilterEngine::step(KalmanFilterStep const & step_spec) - { - return step(step_spec.tkp1(), - step_spec.state(), - step_spec.model(), - step_spec.obs(), - step_spec.input()); - } /*step*/ - - ref::rp - KalmanFilterEngine::step1(utc_nanos tkp1, - ref::rp const & sk, - KalmanFilterTransition const & Fk, - KalmanFilterObservable const & Hkp1, - ref::rp const & zkp1, - uint32_t j) - { - ref::rp skp1_ext - = KalmanFilterEngine::extrapolate(tkp1, sk, Fk); - - ref::rp skp1 - = KalmanFilterEngine::correct1(skp1_ext, Hkp1, zkp1, j); - - return skp1; - } /*step1*/ - - ref::rp - KalmanFilterEngine::step1(KalmanFilterStep const & step_spec, - uint32_t j) - { - return step1(step_spec.tkp1(), - step_spec.state(), - step_spec.model(), - step_spec.obs(), - step_spec.input(), - j); - } /*step1*/ - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterEngine.cpp */ diff --git a/KalmanFilterEngine.hpp b/KalmanFilterEngine.hpp deleted file mode 100644 index b8c2f949..00000000 --- a/KalmanFilterEngine.hpp +++ /dev/null @@ -1,185 +0,0 @@ -/* @file KalmanFilterEngine.hpp */ - -#pragma once - -#include "filter/KalmanFilterStep.hpp" -#include "filter/KalmanFilterState.hpp" -#include "filter/KalmanFilterTransition.hpp" -#include "filter/KalmanFilterObservable.hpp" -#include "filter/KalmanFilterInput.hpp" - -namespace xo { - namespace kalman { - class KalmanFilterEngine { - public: - using MatrixXd = Eigen::MatrixXd; - using VectorXd = Eigen::VectorXd; - using utc_nanos = xo::time::utc_nanos; - - public: - /* evolution of system state + account for system noise, - * before incorporating effect of observations z(k+1) - * x(k) --> x(k+1|k) - * P(k) --> P(k+1|k) - * - * tkp1. time t(k+1) assoc'd with step k+1 - * sk. filter state at time tk: - * sk.k = k step # (starts from 0) - * sk.tk = t(k) time t(k) assoc'd with step #k - * sk.x = x(k) estimated state at time tk - * sk.P = P(k) quality of state estimate x(k) - * (error covariance matrix) - * Fk. state transition: - * Fk.F = F(k) state transition matrix - * Fk.Q = Q(k) covariance matrix for system noise - * - * returns propagated state estimate for t(k+1): - * retval.k = k+1 - * retval.tk = t(k+1) = tkp1 - * retval.x = x(k+1|k) - * retval.P = P(k+1|k) - */ - static ref::rp extrapolate(utc_nanos tkp1, - ref::rp const & sk, - KalmanFilterTransition const & Fk); - - /* compute kalman gain matrix for filter step t(k) -> t(k+1) - * Expensive implementation using matrix inversion - * - * T - * M(k+1) = H(k).P(k+1|k).H(k) + R(k) - * - * T -1 - * K(k+1) = P(k+1|k).H(k) .M(k+1) - * - * Require: - * - skp1_ext.n_state() = Hkp1.n_state() - * - * skp1_ext. extrapolated filter state at t(k+1) - * Hkp1. relates model state to observable variables, - * for step t(k) -> t(k+1) - */ - static MatrixXd kalman_gain(ref::rp const & skp1_ext, - KalmanFilterObservable const & Hkp1); - - /* compute kalman gain for a single observation z(k)[j]. - * This is useful iff the observation error matrix R is diagonal. - * For diagonal R we can present a set of observations z(k) serially - * instead of all at once, with lower time complexity - * - * Kalman Filter specifies some space with m observables. - * j identifies one of those observables, indexing from 0. - * This corresponds to row #j of H(k), and element R[j,j] of R. - * - * Effectively, we are projecting the kalman filter assoc'd with - * {skp1_ext, Hkp1} to a filter with a single observable variable z(k)[j], - * then computing the (scalar) kalman gain for this 1-variable filter - * - * The gain vector tells us for each member of filter state, - * how much to adjust our optimal estimate for that member for a unit - * amount of innovation in observable #j, i.e. for difference between - * expected and actual value for that observable. - */ - static VectorXd kalman_gain1(ref::rp const & skp1_ext, - KalmanFilterObservable const & Hkp1, - uint32_t j); - - /* correct extrapolated state+cov estimate; - * also computes kalman gain - * - * Require: - * - skp1_ext.n_state() = Hkp1.n_state() - * - zkp1.n_obs() == Hkp1.n_observable() - * - * skp1_ext. extrapolated kalman state + covaraince at t(k+1) - * Hkp1. relates model state to observable variables - * for step t(k) -> t(k+1) - * zkp1. observations for time t(k+1) - * - * return new filter state+cov - */ - static ref::rp correct(ref::rp const & skp1_ext, - KalmanFilterObservable const & Hkp1, - ref::rp const & zkp1); - - /* correct extrapolated filter state for observation - * of j'th filter observable z(k+1)[j] - * - * Can use this when observation errors are uncorrelated - * (i.e. observation error matrix R is diagonal) - */ - static ref::rp correct1(ref::rp const & skp1_ext, - KalmanFilterObservable const & Hkp1, - ref::rp const & zkp1, - uint32_t j); - - /* step filter from t(k) -> t(k+1) - * - * sk. filter state from previous step: - * x (state vector), P (state covar matrix) - * Fk. transition-related params: - * F (transition matrix), Q (system noise covar matrix) - * Hkp1. observation-related params: - * H (coupling matrix), R (error covar matrix) - * zkp1. observations z(k+1) for time t(k+1) - */ - static ref::rp step(utc_nanos tkp1, - ref::rp const & sk, - KalmanFilterTransition const & Fk, - KalmanFilterObservable const & Hkp1, - ref::rp const & zkp1); - - /* step filter from t(k) -> tk(k+1) - * same as - * .step(tkp1, sk, step_spec.model(), step_spec.obs(), zkp1); - * - * step_spec. encapsulates Fk (transition-related params) - * and Q (system noise covar matrix) - */ - static ref::rp step(KalmanFilterStep const & step_spec); - - /* step filter from t(k) -> t(k+1) - * - * sk. filter state from previous step: - * x (state vector), P (state covar matrix) - * Fk. transition-related params: - * F (transition matrix), Q (system noise covar matrix) - * Hkp1. observation-related params: - * H (coupling matrix), R (error covar matrix) - * zkp1. observations z(k+1) for time t(k+1) - * j. identifies a single filter observable -- - * step will only consume observation z(k+1)[j] - */ - static ref::rp step1(utc_nanos tkp1, - ref::rp const & sk, - KalmanFilterTransition const & Fk, - KalmanFilterObservable const & Hkp1, - ref::rp const & zkp1, - uint32_t j); - - /* step filter from t(k) -> t(k+1) - * - * same as - * .step1(step_spec.tkp1(), - * step_spec.state(), - * step_spec.model(), - * step_spec.obs(), - * step_spec.input(), - * j); - * - * step_spec. encapsulates - * x (state vector), - * P (state covar matrix), - * Fk (transition-related params), - * Q (system noise covar matrix) - * z (z(k+1), observations at time t(k+1)) - * j. identifies a single filter observable -- - * step will only consume observation z(k+1)[j] - */ - static ref::rp step1(KalmanFilterStep const & step_spec, - uint32_t j); - }; /*KalmanFilterEngine*/ - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterEngine.hpp */ diff --git a/KalmanFilterInput.cpp b/KalmanFilterInput.cpp deleted file mode 100644 index 03b5330f..00000000 --- a/KalmanFilterInput.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/* @file KalmanFilterInput.cpp */ - -#include "KalmanFilterInput.hpp" -#include "reflect/StructReflector.hpp" -#include "Eigen/src/Core/Matrix.h" -#include "print_eigen.hpp" -#include "indentlog/scope.hpp" -#include "reflect/TaggedRcptr.hpp" - -namespace xo { - using xo::reflect::Reflect; - using xo::reflect::TaggedRcptr; - using xo::reflect::StructReflector; - using xo::scope; - using logutil::matrix; - using xo::xtag; - using Eigen::MatrixXd; - using Eigen::VectorXi; - using std::uint32_t; - - namespace kalman { - ref::rp - KalmanFilterInput::make(utc_nanos tkp1, - VectorXb const & presence, - VectorXd const & z, - VectorXd const & Rd) - { - return new KalmanFilterInput(tkp1, presence, z, Rd); - } /*make*/ - - ref::rp - KalmanFilterInput::make_present(utc_nanos tkp1, - VectorXd const & z) - { - VectorXb presence = VectorXb::Ones(z.cols()); - - return new KalmanFilterInput(tkp1, - presence, - z, - VectorXd(0) /*Rd - not using*/); - } /*make*/ - - VectorXi - KalmanFilterInput::make_kept_index() const - { - scope log(XO_DEBUG(false /*!debug_flag*/)); - - log && log(xtag("presence", matrix(presence_))); - - /* count truth values */ - uint32_t mstar = 0; - - for (uint32_t j = 0, m = this->presence_.rows(); jpresence_[j]) - ++mstar; - } - - log && log(xtag("m*", mstar)); - - VectorXi keep(mstar); - - /* 2nd pass, populate keep[] */ - - uint32_t jstar = 0; - - for (uint32_t j = 0, m = this->presence_.rows(); jpresence_[j]) { - keep[jstar] = j; - ++jstar; - } - } - - log && log("keep", matrix(keep)); - - return keep; - } /*make_kept_index*/ - - void - KalmanFilterInput::reflect_self() - { - StructReflector sr; - - if (sr.is_incomplete()) { - REFLECT_MEMBER(sr, tkp1); - REFLECT_MEMBER(sr, presence); - REFLECT_MEMBER(sr, z); - REFLECT_MEMBER(sr, Rd); - } - } /*reflect_self*/ - - reflect::TaggedRcptr - KalmanFilterInput::self_tp() - { - return Reflect::make_rctp(this); - } /*self_tp*/ - - void - KalmanFilterInput::display(std::ostream & os) const - { - os << ""; - } /*display*/ - - std::string - KalmanFilterInput::display_string() const - { - std::stringstream ss; - this->display(ss); - return ss.str(); - } /*display_string*/ - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterInput.cpp */ diff --git a/KalmanFilterInput.hpp b/KalmanFilterInput.hpp deleted file mode 100644 index a73a72d0..00000000 --- a/KalmanFilterInput.hpp +++ /dev/null @@ -1,121 +0,0 @@ -/* @file KalmanFilterInput.hpp */ - -#pragma once - -#include "reflect/SelfTagging.hpp" -#include "time/Time.hpp" -#include "refcnt/Refcounted.hpp" -#include -#include - -namespace xo { - /* FIXME. hack here to get member access working for reflection */ - namespace vf { class StrikesetKfinput; } - - namespace kalman { - /* represents a single kalman filter input event - * comprising: - * - time tkp1 - * - observation vector z[] - * - presence bits presence[] for z - * - observation errors Rd[] - */ - class KalmanFilterInput : public reflect::SelfTagging { - public: - using TaggedRcptr = xo::reflect::TaggedRcptr; - using utc_nanos = xo::time::utc_nanos; - using VectorXb = Eigen::Array; - using VectorXi = Eigen::VectorXi; - using VectorXd = Eigen::VectorXd; - using uint32_t = std::uint32_t; - - public: - KalmanFilterInput() = default; - - static ref::rp make(utc_nanos tkp1, - VectorXb const & presence, - VectorXd const & z, - VectorXd const & Rd); - - /* create input, with all presence bits set + not using Rd */ - static ref::rp make_present(utc_nanos tkp1, - VectorXd const & z); - - /* reflect KalmanFilterInput object representation */ - static void reflect_self(); - - /* alt name -- concession to reactor::DirectSource - * when event type is KalmanFilterInput - */ - utc_nanos tm() const { return tkp1_; } - - utc_nanos tkp1() const { return tkp1_; } - uint32_t n_obs() const { return z_.size(); } - VectorXb const & presence() const { return presence_; } - VectorXd const & z() const { return z_; } - VectorXd const & Rd() const { return Rd_; } - - /* computes reindexer keep[]: - * .presence[keep[j*]] - * is the j*'th true value in .presence - */ - VectorXi make_kept_index() const; - - virtual void display(std::ostream & os) const; - std::string display_string() const; - - // ----- inherited from SelfTagging ----- - - virtual TaggedRcptr self_tp() override; - - protected: - KalmanFilterInput(utc_nanos tkp1, VectorXb presence, VectorXd z, VectorXd Rd) - : tkp1_(tkp1), - presence_{std::move(presence)}, - z_{std::move(z)}, - Rd_{std::move(Rd)} {} - - friend class xo::vf::StrikesetKfinput; - - private: - /* t(k+1) - asof time for observations .z */ - utc_nanos tkp1_ = xo::time::timeutil::epoch(); - /* [m x 1] presence vector. - * an observation z[j] is present iff .presence[j] is true. - * rows/columns for an absent observation are removed from filter matrices - */ - VectorXb presence_; - /* [m x 1] observation vector z(k) */ - VectorXd z_; - - /* [m x 1] observation error vector Rd(k). - * This represents a side channel for passing the diagonal of - * observation matrix R(k), when both: - * (a) error of different observations are assumed to be uncorrelated (likely) - * (b) error variance is derived from input data, e.g. because of - * some input preprocessing. - * - * It's up to KalmanFilterSpec::MkStepFn to opt-in to using this information, - * which requires agreement with any input preparation step. - * - * This variable could just as well provide observation error matrix R - * - * NOTE: perhaps-cleaner alternative would be to inherit KalmanFilterInput to - * introduce additional state, then MkStepFn can dynamic_cast - */ - VectorXd Rd_; - }; /*KalmanFilterInput*/ - - using KalmanFilterInputPtr = ref::rp; - - inline std::ostream & - operator<<(std::ostream & os, KalmanFilterInput const & x) - { - x.display(os); - return os; - } /*operator<<*/ - - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterInput.hpp */ diff --git a/KalmanFilterInputCallback.hpp b/KalmanFilterInputCallback.hpp deleted file mode 100644 index d9b2f213..00000000 --- a/KalmanFilterInputCallback.hpp +++ /dev/null @@ -1,14 +0,0 @@ -/* @file KalmanFilterInputCallback.hpp */ - -#pragma once - -#include "refcnt/Refcounted.hpp" -#include "filter/KalmanFilter.hpp" - -namespace xo { - namespace kalman { - using KalmanFilterInputCallback = reactor::Sink1>; - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterInputCallback.hpp */ diff --git a/KalmanFilterInputSource.hpp b/KalmanFilterInputSource.hpp deleted file mode 100644 index 30c5cbf1..00000000 --- a/KalmanFilterInputSource.hpp +++ /dev/null @@ -1,28 +0,0 @@ -/* @file KalmanFilterInputSource.hpp */ - -#pragma once - -#include "reactor/EventSource.hpp" -#include "filter/KalmanFilterInputCallback.hpp" - -namespace xo { - namespace kalman { - /* Use: - * rp src = ...; - * rp in_cb = ...; - * - * src->add_callback(in_cb); - * ... - * // src invokes in_cb->notify_input( - * src->remove_callback(in_cb); - */ - using KalmanFilterInputSource = reactor::EventSource< - /*KalmanFilterInput,*/ - KalmanFilterInputCallback - /*&KalmanFilterInputCallback::notify_filter*/ - >; - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterInputSource.hpp */ - diff --git a/KalmanFilterInputToConsole.hpp b/KalmanFilterInputToConsole.hpp deleted file mode 100644 index 7d48ef7e..00000000 --- a/KalmanFilterInputToConsole.hpp +++ /dev/null @@ -1,30 +0,0 @@ -/* @file KalmanFilterInputToConsole.hpp */ - -#pragma once - -#include "reactor/Sink.hpp" -#include "filter/KalmanFilterInput.hpp" - -namespace xo { - namespace kalman { - class KalmanFilterInputToConsole - : public xo::reactor::SinkToConsole> - { - public: - KalmanFilterInputToConsole() = default; - - static ref::rp make(); - - virtual void display(std::ostream & os) const; - //virtual std::string display_string() const; - }; /*KalmanFilterInputToConsole*/ - - inline std::ostream & - operator<<(std::ostream & os, KalmanFilterInputToConsole const & x) { - x.display(os); - return os; - } /*operator<<*/ - } /*namespace option*/ -} /*namespace xo*/ - -/* end KalmanFilterInputToConsole.hpp */ diff --git a/KalmanFilterObservable.hpp b/KalmanFilterObservable.hpp deleted file mode 100644 index 87c4f9d1..00000000 --- a/KalmanFilterObservable.hpp +++ /dev/null @@ -1,110 +0,0 @@ -/* @file KalmanFilterObservable.hpp */ - -#pragma once - -#include "time/Time.hpp" -#include -#include - -namespace xo { - namespace kalman { - class KalmanFilterObservable { - public: - using MatrixXd = Eigen::MatrixXd; - using VectorXi = Eigen::VectorXi; - - public: - KalmanFilterObservable() = default; - - /* keep maps back to canonical observations z(j). - * H, R have been re-indexed - * - * If all m observations are included, then keep will be: - * [0, .., m-1] - */ - KalmanFilterObservable(VectorXi keep, MatrixXd H, MatrixXd R) - : keep_{std::move(keep)}, H_{std::move(H)}, R_{std::move(R)} { - assert(this->check_ok()); - } /*ctor*/ - - /* build KF observable object where keeping all observations */ - static KalmanFilterObservable keep_all(MatrixXd H, MatrixXd R); - - /* build KF observable object. replace H, R with reindexed versions H', R' - * according to indexing vector keep[]. keep[] indexes members of - * observation vector z(k)[j]. Reindexed z', H', R' as follows: - * - * z'[j*] = z[keep[j*]] - * H'[j*, i] = H[keep[j*], i] - * R'[j1*, j2*] = R[keep[j1*], keep[j2*]] - */ - static KalmanFilterObservable reindex(VectorXi keep, MatrixXd H, MatrixXd R); - - uint32_t n_state() const { return H_.cols(); } - uint32_t n_observable() const { return H_.rows(); } - VectorXi const & keep() const { return keep_; } - MatrixXd const & observable() const { return H_; } - MatrixXd const & observable_cov() const { return R_; } - - bool check_ok() const { - uint32_t m = H_.rows(); - bool keep_is_mx1 = (keep_.rows() == m); - bool r_is_mxm = ((R_.cols() == m) && (R_.rows() == m)); - - bool keep_is_well_ordered = true; - - /* members of .keep are non-negative integers, - * in strictly increasing order - */ - int64_t keep_jm1 = -1; - - for (uint32_t j = 0; j < keep_.rows(); ++j) { - if (keep_[j] < 0) - keep_is_well_ordered = false; - if (keep_[j] <= keep_jm1) - keep_is_well_ordered = false; - } - - /* also would like to require: R is +ve definite */ - - return keep_is_mx1 && keep_is_well_ordered && r_is_mxm; - } /*check_ok*/ - - void display(std::ostream & os) const; - std::string display_string() const; - - private: - - private: - /* m: #of observations that survived sanity/error checks - * - * H, R here will have been re-indexed to exclude rejected observations. - * observations z will also have been re-indexed. - * - * If an observation z[j] is excluded, then also exclude: - * - j'th row of H - * - j'th row and j'th column of R - * - j'th element of z - * - * Given re-indexed H, R, the j*'th row of H goes with z[keep[j*]] - */ - - /* [m x 1] maps back to indices in original observation vector */ - VectorXi keep_; - /* [m x n] observation matrix */ - MatrixXd H_; - /* [m x m] covariance matrix for observation noise */ - MatrixXd R_; - }; /*KalmanFilterObservable*/ - - inline std::ostream & - operator<<(std::ostream & os, KalmanFilterObservable const & x) - { - x.display(os); - return os; - } /*operator<<*/ - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterObservable.hpp */ - diff --git a/KalmanFilterOutputCallback.hpp b/KalmanFilterOutputCallback.hpp deleted file mode 100644 index 69fb8484..00000000 --- a/KalmanFilterOutputCallback.hpp +++ /dev/null @@ -1,15 +0,0 @@ -/* @file KalmanFilterOutputCallback.hpp */ - -#pragma once - -#include "reactor/Sink.hpp" -#include "filter/KalmanFilter.hpp" - -namespace xo { - namespace kalman { - /* callback for consuming kalman filter output */ - using KalmanFilterOutputCallback = reactor::Sink1>; - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterOutputCallback.hpp */ diff --git a/KalmanFilterSpec.cpp b/KalmanFilterSpec.cpp deleted file mode 100644 index 296aeb99..00000000 --- a/KalmanFilterSpec.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/* @file KalmanFilterSpec.cpp */ - -#include "KalmanFilterSpec.hpp" -#include "indentlog/scope.hpp" - -namespace xo { - using xo::tostr; - using xo::xtag; - - namespace kalman { - void - KalmanFilterSpec::display(std::ostream & os) const - { - os << ""; - } /*display*/ - - std::string - KalmanFilterSpec::display_string() const - { - return tostr(*this); - } /*display_string*/ - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterSpec.cpp */ diff --git a/KalmanFilterSpec.hpp b/KalmanFilterSpec.hpp deleted file mode 100644 index 56ffd783..00000000 --- a/KalmanFilterSpec.hpp +++ /dev/null @@ -1,86 +0,0 @@ -/* @file KalmanFilterSpec.hpp */ - -#pragma once - -#include "filter/KalmanFilterStep.hpp" - -namespace xo { - namespace kalman { - /* full specification for a kalman filter. - * - * For a textbook linear filter, expect a KalmanFilterStep - * instance to be independent of KalmanFilterState/KalmanFilterInput. - * - * Relaxing this requirement for two reasons: - * 1. (proper) want to allow filter with variable timing between observations, - * expecially if observations are event-driven. - * In that case it's likely that state transition matrices are a function - * of elapsed time between observations. Providing filter state sk - * allows MkStepFn to use sk.tm() - * 2. (sketchy) when observations represent market data, - * desirable to treat an observation as giving one-sided information - * about true value. For example treat a bid price as evidence - * true value is higher than that bid, but don't want to constrain - * "how much higher". Certainly no reason to think that - * bid price is normally distributed around fair value. - * Allow for hack in which we - * and modulate error distribution "as if it were normal" to assess - * a non-gaussian error distribution - */ - class KalmanFilterSpec { - public: - /* typical implementation will look something like: - * mk_step(KalmanFilterState const & sk, - * KalmanFilterInputPtr const & zkp1) - * { - * KalmanFilterTransition model = ...; - * KalmanFilterObservable obs = ...; - * - * return KalmanFilterStep(sk, model, obs, zkp1); - * } - */ - using MkStepFn = std::function const & sk, - KalmanFilterInputPtr const & zkp1)>; - - public: - explicit KalmanFilterSpec(ref::rp s0, MkStepFn mkstepfn) - : start_ext_{std::move(s0)}, - mk_step_fn_{std::move(mkstepfn)} {} - - ref::rp const & start_ext() const { return start_ext_; } - /* get step parameters (i.e. matrices F, Q, H, R) - * for step t(k) -> t(k+1). - * - * We supply t(k) state s and t(k+1) observation z(k+1): - * - to allow time stepping to be observation-driven - * - to allow for selective outlier removal - */ - KalmanFilterStep make_step(ref::rp const & sk, - ref::rp const & zkp1) { - return this->mk_step_fn_(sk, zkp1); - } /*make_step*/ - - void display(std::ostream & os) const; - std::string display_string() const; - - private: - /* starting state */ - ref::rp start_ext_; - - /* creates kalman filter step object on demand; - * step object specifies inputs to 1 step in discrete - * linear kalman filter - */ - MkStepFn mk_step_fn_; - }; /*KalmanFilterSpec*/ - - inline std::ostream & - operator<<(std::ostream & os, KalmanFilterSpec const & x) { - x.display(os); - return os; - } /*operator<<*/ - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterSpec.hpp */ diff --git a/KalmanFilterState.cpp b/KalmanFilterState.cpp deleted file mode 100644 index 7656908c..00000000 --- a/KalmanFilterState.cpp +++ /dev/null @@ -1,231 +0,0 @@ -/* @file KalmanFilterState.cpp */ - -#include "KalmanFilterState.hpp" -#include "print_eigen.hpp" -#include "reflect/StructReflector.hpp" -#include "reflect/TaggedPtr.hpp" -#include "indentlog/scope.hpp" -#include "Eigen/src/Core/Matrix.h" -#include -#include - -namespace xo { - using xo::reflect::Reflect; - using xo::reflect::TaggedRcptr; - using xo::reflect::StructReflector; - using xo::time::utc_nanos; - using xo::ref::rp; - using logutil::matrix; - using logutil::vector; - //using xo::scope; - using xo::xtag; - using xo::tostr; - //using Eigen::LDLT; - using Eigen::MatrixXd; - using Eigen::VectorXd; - - namespace kalman { - // ----- KalmanFilterState ----- - - rp - KalmanFilterState::make() - { - return new KalmanFilterState(); - } /*make*/ - - rp - KalmanFilterState::make(uint32_t k, - utc_nanos tk, - VectorXd x, - MatrixXd P, - KalmanFilterTransition transition) - { - return new KalmanFilterState(k, tk, - std::move(x), - std::move(P), - std::move(transition)); - } /*make*/ - - void - KalmanFilterState::reflect_self() - { - StructReflector sr; - - if (sr.is_incomplete()) { - REFLECT_MEMBER(sr, k); - REFLECT_MEMBER(sr, tk); - REFLECT_MEMBER(sr, x); - REFLECT_MEMBER(sr, P); - } - } /*reflect_self*/ - - KalmanFilterState::KalmanFilterState() = default; - - KalmanFilterState::KalmanFilterState(uint32_t k, - utc_nanos tk, - VectorXd x, - MatrixXd P, - KalmanFilterTransition transition) - : k_{k}, tk_{tk}, - x_{std::move(x)}, P_{std::move(P)}, - transition_{std::move(transition)} - {} - - TaggedRcptr - KalmanFilterState::self_tp() - { - return Reflect::make_rctp(this); - } /*self_tp*/ - - // ----- KalmanFilterExt ----- - - rp - KalmanFilterStateExt::make() - { - return new KalmanFilterStateExt(); - } /*make*/ - - rp - KalmanFilterStateExt::make(uint32_t k, - utc_nanos tk, - VectorXd x, - MatrixXd P, - KalmanFilterTransition transition, - MatrixXd K, - int32_t j, - rp zk) - { - return new KalmanFilterStateExt(k, - tk, - std::move(x), - std::move(P), - std::move(transition), - std::move(K), - j, - std::move(zk)); - } /*make*/ - - void - KalmanFilterStateExt::reflect_self() - { - StructReflector sr; - - if (sr.is_incomplete()) { - /* TODO: use sr.adopt_ancestors() */ - - REFLECT_EXPLICIT_MEMBER(sr, "k", &KalmanFilterState::k_); - REFLECT_EXPLICIT_MEMBER(sr, "tk", &KalmanFilterState::tk_); - REFLECT_EXPLICIT_MEMBER(sr, "x", &KalmanFilterState::x_); - REFLECT_EXPLICIT_MEMBER(sr, "P", &KalmanFilterState::P_); - REFLECT_EXPLICIT_MEMBER(sr, "transition", &KalmanFilterState::transition_); - REFLECT_MEMBER(sr, j); - REFLECT_MEMBER(sr, K); - REFLECT_MEMBER(sr, zk); - } - } /*reflect_self*/ - - KalmanFilterStateExt::KalmanFilterStateExt(uint32_t k, - utc_nanos tk, - VectorXd x, - MatrixXd P, - KalmanFilterTransition transition, - MatrixXd K, - int32_t j, - rp zk) - : KalmanFilterState(k, tk, - std::move(x), - std::move(P), - std::move(transition)), - j_{j}, - K_{std::move(K)}, - zk_{std::move(zk)} - { - uint32_t n = x.size(); - - if (n != P.rows() || n != P.cols()) { - std::string err_msg - = tostr("with n=x.size expect [n x n] covar matrix P", - xtag("n", x.size()), - xtag("P.rows", P.rows()), - xtag("P.cols", P.cols())); - - throw std::runtime_error(err_msg); - } - - if ((K.rows() > 0) && (K.rows() > 0)) { - if (n != K.rows()) { - std::string err_msg - = tostr("with n=x.size expect [m x n] gain matrix K", - xtag("n", x.size()), - xtag("K.rows", K.rows()), - xtag("K.cols", K.cols())); - - throw std::runtime_error(err_msg); - } - } else { - /* bypass test with [0 x 0] matrix K; - * normal for initial filter state - */ - } - } /*ctor*/ - - void - KalmanFilterState::display(std::ostream & os) const - { - os << ""; - } /*display*/ - - std::string - KalmanFilterState::display_string() const - { - std::stringstream ss; - ss << *this; - return ss.str(); - } /*display_string*/ - - // ----- KalmanFilterStateExt ----- - - ref::rp - KalmanFilterStateExt::initial(utc_nanos t0, - VectorXd x0, - MatrixXd P0) - { - return KalmanFilterStateExt::make - (0 /*k*/, - t0, - std::move(x0), - std::move(P0), - KalmanFilterTransition(MatrixXd() /*F - not used for initial step*/, - MatrixXd() /*Q - not used for initial step*/), - MatrixXd() /*K - not used for initial step*/, - -1 /*j - not used for initial step*/, - nullptr /*zk - not defined for initial step*/); - } /*initial*/ - - void - KalmanFilterStateExt::display(std::ostream & os) const - { - os << "step_no()) - << xtag("tk", this->tm()) - << xtag("x", matrix(this->state_v())) - << xtag("P", matrix(this->state_cov())) - << xtag("K", matrix(K_)) - << xtag("j", j_) - << ">"; - } /*display*/ - - TaggedRcptr - KalmanFilterStateExt::self_tp() - { - return Reflect::make_rctp(this); - } /*self_tp*/ - } /*namespace filter*/ -} /*namespace xo*/ - -/* end KalmanFilterState.cpp */ diff --git a/KalmanFilterState.hpp b/KalmanFilterState.hpp deleted file mode 100644 index 113c4c6b..00000000 --- a/KalmanFilterState.hpp +++ /dev/null @@ -1,163 +0,0 @@ -/* @file KalmanFilterState.hpp */ - -#pragma once - -#include "reflect/SelfTagging.hpp" -#include "filter/KalmanFilterInput.hpp" -#include "filter/KalmanFilterTransition.hpp" -#include "time/Time.hpp" -#include -#include -#include - -namespace xo { - namespace kalman { - /* encapsulate state (i.e. initial state, and output after each step) - * for a kalman filter - */ - class KalmanFilterState : public reflect::SelfTagging { - public: - using TaggedRcptr = reflect::TaggedRcptr; - using utc_nanos = xo::time::utc_nanos; - using VectorXd = Eigen::VectorXd; - using MatrixXd = Eigen::MatrixXd; - using uint32_t = std::uint32_t; - - public: - static ref::rp make(); - static ref::rp make(uint32_t k, - utc_nanos tk, - VectorXd x, - MatrixXd P, - KalmanFilterTransition transition); - virtual ~KalmanFilterState() = default; - - /* reflect KalmanFilterState object representation */ - static void reflect_self(); - - uint32_t step_no() const { return k_; } - utc_nanos tm() const { return tk_; } - /* with n = .n_state(): - * x_ is [n x 1] vector - * P_ is [n x n] matrix, - */ - uint32_t n_state() const { return x_.size(); } - VectorXd const & state_v() const { return x_; } - MatrixXd const & state_cov() const { return P_; } - - KalmanFilterTransition const & transition() const { return transition_; } - - virtual void display(std::ostream & os) const; - std::string display_string() const; - - // ----- inherited from SelfTagging ----- - - virtual TaggedRcptr self_tp() override; - - private: - KalmanFilterState(); - KalmanFilterState(uint32_t k, - utc_nanos tk, - VectorXd x, - MatrixXd P, - KalmanFilterTransition transition); - - friend class KalmanFilterStateExt; - - private: - /* step# k, advances by +1 on each filter step */ - uint32_t k_ = 0; - /* time t(k) */ - utc_nanos tk_; - /* [n x 1] (estimated) system state xk = x(k) */ - VectorXd x_; - /* [n x n] covariance matrix for error assoc'd with with x(k) - * P(i,j) is the covariance of (ek[i], ek[j]), - * where ex(k) is the difference (x(k) - x_(k)) - * between estimated state x(k) - * (= this->x_) and model state x_(k) - */ - MatrixXd P_; - - /* F, Q matrices driving .x, .P */ - KalmanFilterTransition transition_; - }; /*KalmanFilterState*/ - - inline std::ostream & operator<<(std::ostream & os, - KalmanFilterState const & s) - { - s.display(os); - return os; - } /*operator<<*/ - - /* KalmanFilterStateExt: - * adds additional details from filter step to KalmanFilterState - */ - class KalmanFilterStateExt : public KalmanFilterState { - public: - using TaggedRcptr = reflect::TaggedRcptr; - using MatrixXd = Eigen::MatrixXd; - using int32_t = std::int32_t; - - public: - static ref::rp make(); - static ref::rp make(uint32_t k, - utc_nanos tk, - VectorXd x, - MatrixXd P, - KalmanFilterTransition transition, - MatrixXd K, - int32_t j, - ref::rp zk); - - /* create state object for initial filter state */ - static ref::rp initial(utc_nanos t0, - VectorXd x0, - MatrixXd P0); - - /* reflect KalmanFilterStateExt object representation */ - static void reflect_self(); - - int32_t observable() const { return j_; } - MatrixXd const & gain() const { return K_; } - ref::rp const & zk() const { return zk_; } - - virtual void display(std::ostream & os) const override; - - // ----- inherited from SelfTagging ----- - - virtual TaggedRcptr self_tp() override; - - private: - KalmanFilterStateExt() = default; - KalmanFilterStateExt(uint32_t k, - utc_nanos tk, - VectorXd x, - MatrixXd P, - KalmanFilterTransition transition, - MatrixXd K, - int32_t j, - ref::rp zk); - - private: - /* if -1: not used; - * if >= 0: identifies j'th of m observables; - * gain .K applies just to information obtainable from - * observing that scalar variable - */ - int32_t j_ = -1; - /* if .j is -1: - * [n x n] kalman gain - * if .j >= 0: - * [n x 1] kalman gain for observable #j - */ - MatrixXd K_; - /* input leading to state k. - * empty for initial state (i.e. when .k is 0) - */ - ref::rp zk_; - }; /*KalamnFilterStateExt*/ - } /*namespace filter*/ -} /*namespace xo*/ - -/* end KalmanFilterState.hpp */ diff --git a/KalmanFilterStateToConsole.hpp b/KalmanFilterStateToConsole.hpp deleted file mode 100644 index 0d525cea..00000000 --- a/KalmanFilterStateToConsole.hpp +++ /dev/null @@ -1,30 +0,0 @@ -/* @file KalmanFilterStateToConsole.hpp */ - -#pragma once - -#include "reactor/Sink.hpp" -#include "filter/KalmanFilterState.hpp" - -namespace xo { - namespace kalman { - class KalmanFilterStateToConsole - : public xo::reactor::SinkToConsole - { - public: - KalmanFilterStateToConsole() = default; - - static ref::rp make(); - - virtual void display(std::ostream & os) const; - //virtual std::string display_string() const; - }; /*KalmanFilterStateToConsole*/ - - inline std::ostream & - operator<<(std::ostream & os, KalmanFilterStateToConsole const & x) { - x.display(os); - return os; - } /*operator<<*/ - } /*namespace option*/ -} /*namespace xo*/ - -/* end KalmanFilterStateToConsole.hpp */ diff --git a/KalmanFilterStep.cpp b/KalmanFilterStep.cpp deleted file mode 100644 index f62f2d21..00000000 --- a/KalmanFilterStep.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/* @file KalmanFilterStep.cpp */ - -#include "KalmanFilterStep.hpp" -#include "filter/KalmanFilterEngine.hpp" -#include "filter/KalmanFilterState.hpp" -#include "indentlog/scope.hpp" - -namespace xo { - using xo::scope; - using xo::tostr; - using xo::xtag; - using Eigen::MatrixXd; - using Eigen::VectorXd; - - namespace kalman { - ref::rp - KalmanFilterStep::extrapolate() const - { - return KalmanFilterEngine::extrapolate(this->tkp1(), - this->state(), - this->model() /*transition*/); - } /*extrapolate*/ - - MatrixXd - KalmanFilterStep::gain(ref::rp const & skp1_ext) const - { - return KalmanFilterEngine::kalman_gain(skp1_ext, - this->obs()); - } /*gain*/ - - VectorXd - KalmanFilterStep::gain1(ref::rp const & skp1_ext, - uint32_t j) const - { - return KalmanFilterEngine::kalman_gain1(skp1_ext, - this->obs(), - j); - - } /*gain1*/ - - ref::rp - KalmanFilterStep::correct(ref::rp const & skp1_ext) - { - return KalmanFilterEngine::correct(skp1_ext, - this->obs(), - this->input()); - } /*correct*/ - - ref::rp - KalmanFilterStep::correct1(ref::rp const & skp1_ext, - uint32_t j) - { - return KalmanFilterEngine::correct1(skp1_ext, - this->obs(), - this->input(), - j); - } /*correct1*/ - - void - KalmanFilterStep::display(std::ostream & os) const - { - //scope lscope("KalmanFilterStep::display"); - - os << "model()); - //lscope.log("obs:"); - os << xtag("obs", this->obs()); - //lscope.log("input:"); - os << xtag("input", this->input()); - os << ">"; - } /*display*/ - - std::string - KalmanFilterStep::display_string() const - { - return tostr(*this); - } /*display_string*/ - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterStep.cpp */ diff --git a/KalmanFilterSvc.hpp b/KalmanFilterSvc.hpp deleted file mode 100644 index 33d25514..00000000 --- a/KalmanFilterSvc.hpp +++ /dev/null @@ -1,64 +0,0 @@ -/* @file KalmanFilterSvc.hpp */ - -#include "reactor/Sink.hpp" -#include "reactor/DirectSourcePtr.hpp" -#include "filter/KalmanFilter.hpp" -#include "filter/KalmanFilterInputSource.hpp" -#include "filter/KalmanFilterOutputCallback.hpp" -#include "callback/CallbackSet.hpp" - -namespace xo { - namespace kalman { - /* encapsulate a passive KalmanFilter - * instance as an active event consumer+producer - * - * sinks that want to consume KalmanFilterSvc events will use - * .attach_sink() (or .add_callback()) - */ - class KalmanFilterSvc : public xo::reactor::Sink1>, - public xo::reactor::DirectSourcePtr> { - public: - using AbstractSource = xo::reactor::AbstractSource; - - public: - /* named ctor idiom */ - static ref::rp make(KalmanFilterSpec spec); - - KalmanFilter const & filter() const { return filter_; } - - /* notify incoming observations; will trigger kalman filter step */ - void notify_ev(ref::rp const & input_kp1) override; - - // ----- inherited from reactor::AbstractSink ----- - - /* filter captures KF input pointer */ - virtual bool allow_volatile_source() const override { return false; } - virtual uint32_t n_in_ev() const override { return n_in_ev_; } - virtual void display(std::ostream & os) const override; - - // ----- inherited from reactor::AbstractSource ----- - - /* note: correct since KalmanFilterEngine.extrapolate() - * always creates new state object - */ - virtual bool is_volatile() const override { return false; } - - // ----- Inherited from AbstractEventProcessor ----- - - private: - KalmanFilterSvc(KalmanFilterSpec spec); - - private: - /* passive kalman filter */ - KalmanFilter filter_; - /* receive filter input from this source; see .attach_input() */ - ref::rp input_src_; - /* counts lifetime #of input events (see .notify_ev()) */ - uint32_t n_in_ev_ = 0; - /* publish filter state updates to these callbacks */ - fn::RpCallbackSet pub_; - }; /*KalmanFilterSvc*/ - } /*namespace kalman*/ -} /*namespace xo*/ - -/* KalmanFilterSvc.hpp */ diff --git a/KalmanFilterTransition.cpp b/KalmanFilterTransition.cpp deleted file mode 100644 index 65be627b..00000000 --- a/KalmanFilterTransition.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* @file KalmanFilterTransition.cpp */ - -#include "KalmanFilterTransition.hpp" -#include "reflect/StructReflector.hpp" -#include "print_eigen.hpp" -#include "indentlog/scope.hpp" - -namespace xo { - using xo::reflect::StructReflector; - using logutil::matrix; - using xo::xtag; - - namespace kalman { - void - KalmanFilterTransition::reflect_self() - { - StructReflector sr; - - if (sr.is_incomplete()) { - REFLECT_MEMBER(sr, F); - REFLECT_MEMBER(sr, Q); - } - } /*reflect_self*/ - - uint32_t - KalmanFilterTransition::n_state() const - { - /* we know F.rows() == F.cols() = Q.cols() == Q.rows(), - * see .check_ok() - */ - - return F_.rows(); - } /*n_state*/ - - void - KalmanFilterTransition::display(std::ostream & os) const - { - os << ""; - } /*display*/ - - std::string - KalmanFilterTransition::display_string() const - { - std::stringstream ss; - this->display(ss); - return ss.str(); - } /*display_string*/ - - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterTransition.cpp */ diff --git a/KalmanFilterTransition.hpp b/KalmanFilterTransition.hpp deleted file mode 100644 index fc777c00..00000000 --- a/KalmanFilterTransition.hpp +++ /dev/null @@ -1,62 +0,0 @@ -/* @file KalmanFilterTransition.hpp */ - -#pragma once - -#include "time/Time.hpp" -#include -#include - -namespace xo { - namespace kalman { - - /* encapsulate transition behavior for a kalman filter - * before taking observations into account - */ - class KalmanFilterTransition { - public: - using MatrixXd = Eigen::MatrixXd; - using uint32_t = std::uint32_t; - - public: - KalmanFilterTransition() = default; - KalmanFilterTransition(MatrixXd F, - MatrixXd Q) - : F_{std::move(F)}, Q_{std::move(Q)} { assert(this->check_ok()); } - - static void reflect_self(); - - /* n: cardinality of state vector */ - uint32_t n_state() const; - - MatrixXd const & transition_mat() const { return F_; } - MatrixXd const & transition_cov() const { return Q_; } - - bool check_ok() const { - uint32_t n = F_.rows(); - bool f_is_nxn = ((F_.rows() == n) && (F_.cols() == n)); - bool q_is_nxn = ((Q_.rows() == n) && (Q_.cols() == n)); - - /* also would like to require: Q is +ve definite */ - - return f_is_nxn && q_is_nxn; - } /*check_ok*/ - - void display(std::ostream & os) const; - std::string display_string() const; - - private: - /* [n x n] state transition matrix */ - MatrixXd F_; - /* [n x n] covariance matrix for system noise */ - MatrixXd Q_; - }; /*KalmanFilterTransition*/ - - inline std::ostream & - operator<<(std::ostream & os, KalmanFilterTransition const & x) { - x.display(os); - return os; - } /*operator<<*/ - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterTransition.hpp */ diff --git a/cmake/xo_kalmanfilterConfig.cmake.in b/cmake/xo_kalmanfilterConfig.cmake.in new file mode 100644 index 00000000..366e45ba --- /dev/null +++ b/cmake/xo_kalmanfilterConfig.cmake.in @@ -0,0 +1,17 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# note: changes to find_dependency() calls here +# must coordinate with xo_dependency() calls +# in xo-reactor/src/reactor/CMakeLists.txt +# +find_dependency(reactor) +find_dependnecy(eigen3) +#find_dependency(reflect) +#find_dependency(webutil) +#find_dependency(printjson) +#find_dependency(callback) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/EigenUtil.hpp b/include/xo/kalmanfilter/EigenUtil.hpp similarity index 100% rename from EigenUtil.hpp rename to include/xo/kalmanfilter/EigenUtil.hpp diff --git a/include/xo/kalmanfilter/KalmanFilter.hpp b/include/xo/kalmanfilter/KalmanFilter.hpp new file mode 100644 index 00000000..0df87a61 --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilter.hpp @@ -0,0 +1,127 @@ +/* @file KalmanFilter.hpp */ + +#pragma once + +#include "KalmanFilterSpec.hpp" + +namespace xo { + namespace kalman { + /* Specification for an ordinary discrete linear kalman filter. + * + * The filter generates estimates for a process observed at a discrete + * set of times tk in {t0, t1, .., tn} + * + * At each time tk we have the following: + * + * 0. x(0) initial estimate at t(0) + * P(0) initial priors: error covariance matrix for x(0) + * + * 1. x_(k), [n x 1] vector: + * system state, denoted by vector. + * (state is not directly observable, + * filter will attempt to estimate it) + * + * 2. w_(k), [n x 1] vector + * Q(k), [n x n] matrix + * + * w_(k) denotes system noise, + * gaussian with covariance Q(k). + * noise w_(k) is not directly observable. + * + * 3. z(k), [m x 1] vector: + * + * observation vector for time tk + * + * 4. v_(k), [m x 1] vector + * R(k), [m x m] matrix + * + * v_(k) denotes observation errors, + * gaussian with covariance R(k). + * noise v_(k) is not directly observable. + * + * 5. F(k), [n x n] matrix + * state transition matrix + * model system evolves according to: + * + * x_(k+1) = F(x).x_(k) + w_(k) + * + * 6. observations z(k) depend on system state: + * + * z(k) = H(k).x_(k) + v_(k) + * + * 7. Kalman filter outputs: + * x(k), [n x 1] vector + * Q(k), [n x n] matrix + * + * x(k) is optimal estimate for system state x_(k) + * P(k) is covariance matrix specifying confidence intervals + * for pairs (x(k)[i], x(k)[j]) + * + * filter specification consists of: + * n, m, x(0), P(0), F(k), Q(k), H(k), R(k) + * The cardinality of observations z(k) can vary over time, + * so to be precise, m can vary with tk; write as m(k) + * + * More details: + * - avoid having to specify t(k) in advance; + * instead defer until observation available + * so t(k) can be taken from polling timestamp + */ + + /* encapsulate a (linear) kalman filter + * together with event publishing + */ + class KalmanFilter { + public: + using MatrixXd = Eigen::MatrixXd; + using VectorXd = Eigen::VectorXd; + using utc_nanos = xo::time::utc_nanos; + + public: + /* create filter with specification given by spec, and initial state s0 */ + explicit KalmanFilter(KalmanFilterSpec spec); + + uint32_t step_no() const { return state_ext_->step_no(); } + utc_nanos tm() const { return state_ext_->tm(); } + KalmanFilterSpec const & filter_spec() const { return filter_spec_; } + KalmanFilterStep const & step() const { return step_; } + ref::rp const & state_ext() const { return state_ext_; } + + /* notify kalman filter with input for time t(k+1) = input_kp1.tkp1() + * Require: input.tkp1() >= .current_tm() + * Promise: + * - .tm() = input_kp1.tkp1() + * - .step_no() = old .step_no() + 1 + * - .filter_spec_k, .step_k, .state_k updated + * for observations in input_kp1 + */ + void notify_input(ref::rp const & input_kp1); + + void display(std::ostream & os) const; + std::string display_string() const; + + private: + /* specification for kalman filter; + * produces process/observation matrices on demand + */ + KalmanFilterSpec filter_spec_; + + /* filter step for most recent observation */ + KalmanFilterStep step_; + + /* filter state as of most recent observation; + * result of applying KalmanFilterEngine::step() to contents of .step + */ + ref::rp state_ext_; + }; /*KalmanFilter*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilter const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilter.hpp */ diff --git a/include/xo/kalmanfilter/KalmanFilterEngine.hpp b/include/xo/kalmanfilter/KalmanFilterEngine.hpp new file mode 100644 index 00000000..ce21bf3c --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilterEngine.hpp @@ -0,0 +1,185 @@ +/* @file KalmanFilterEngine.hpp */ + +#pragma once + +#include "KalmanFilterStep.hpp" +#include "KalmanFilterState.hpp" +#include "KalmanFilterTransition.hpp" +#include "KalmanFilterObservable.hpp" +#include "KalmanFilterInput.hpp" + +namespace xo { + namespace kalman { + class KalmanFilterEngine { + public: + using MatrixXd = Eigen::MatrixXd; + using VectorXd = Eigen::VectorXd; + using utc_nanos = xo::time::utc_nanos; + + public: + /* evolution of system state + account for system noise, + * before incorporating effect of observations z(k+1) + * x(k) --> x(k+1|k) + * P(k) --> P(k+1|k) + * + * tkp1. time t(k+1) assoc'd with step k+1 + * sk. filter state at time tk: + * sk.k = k step # (starts from 0) + * sk.tk = t(k) time t(k) assoc'd with step #k + * sk.x = x(k) estimated state at time tk + * sk.P = P(k) quality of state estimate x(k) + * (error covariance matrix) + * Fk. state transition: + * Fk.F = F(k) state transition matrix + * Fk.Q = Q(k) covariance matrix for system noise + * + * returns propagated state estimate for t(k+1): + * retval.k = k+1 + * retval.tk = t(k+1) = tkp1 + * retval.x = x(k+1|k) + * retval.P = P(k+1|k) + */ + static ref::rp extrapolate(utc_nanos tkp1, + ref::rp const & sk, + KalmanFilterTransition const & Fk); + + /* compute kalman gain matrix for filter step t(k) -> t(k+1) + * Expensive implementation using matrix inversion + * + * T + * M(k+1) = H(k).P(k+1|k).H(k) + R(k) + * + * T -1 + * K(k+1) = P(k+1|k).H(k) .M(k+1) + * + * Require: + * - skp1_ext.n_state() = Hkp1.n_state() + * + * skp1_ext. extrapolated filter state at t(k+1) + * Hkp1. relates model state to observable variables, + * for step t(k) -> t(k+1) + */ + static MatrixXd kalman_gain(ref::rp const & skp1_ext, + KalmanFilterObservable const & Hkp1); + + /* compute kalman gain for a single observation z(k)[j]. + * This is useful iff the observation error matrix R is diagonal. + * For diagonal R we can present a set of observations z(k) serially + * instead of all at once, with lower time complexity + * + * Kalman Filter specifies some space with m observables. + * j identifies one of those observables, indexing from 0. + * This corresponds to row #j of H(k), and element R[j,j] of R. + * + * Effectively, we are projecting the kalman filter assoc'd with + * {skp1_ext, Hkp1} to a filter with a single observable variable z(k)[j], + * then computing the (scalar) kalman gain for this 1-variable filter + * + * The gain vector tells us for each member of filter state, + * how much to adjust our optimal estimate for that member for a unit + * amount of innovation in observable #j, i.e. for difference between + * expected and actual value for that observable. + */ + static VectorXd kalman_gain1(ref::rp const & skp1_ext, + KalmanFilterObservable const & Hkp1, + uint32_t j); + + /* correct extrapolated state+cov estimate; + * also computes kalman gain + * + * Require: + * - skp1_ext.n_state() = Hkp1.n_state() + * - zkp1.n_obs() == Hkp1.n_observable() + * + * skp1_ext. extrapolated kalman state + covaraince at t(k+1) + * Hkp1. relates model state to observable variables + * for step t(k) -> t(k+1) + * zkp1. observations for time t(k+1) + * + * return new filter state+cov + */ + static ref::rp correct(ref::rp const & skp1_ext, + KalmanFilterObservable const & Hkp1, + ref::rp const & zkp1); + + /* correct extrapolated filter state for observation + * of j'th filter observable z(k+1)[j] + * + * Can use this when observation errors are uncorrelated + * (i.e. observation error matrix R is diagonal) + */ + static ref::rp correct1(ref::rp const & skp1_ext, + KalmanFilterObservable const & Hkp1, + ref::rp const & zkp1, + uint32_t j); + + /* step filter from t(k) -> t(k+1) + * + * sk. filter state from previous step: + * x (state vector), P (state covar matrix) + * Fk. transition-related params: + * F (transition matrix), Q (system noise covar matrix) + * Hkp1. observation-related params: + * H (coupling matrix), R (error covar matrix) + * zkp1. observations z(k+1) for time t(k+1) + */ + static ref::rp step(utc_nanos tkp1, + ref::rp const & sk, + KalmanFilterTransition const & Fk, + KalmanFilterObservable const & Hkp1, + ref::rp const & zkp1); + + /* step filter from t(k) -> tk(k+1) + * same as + * .step(tkp1, sk, step_spec.model(), step_spec.obs(), zkp1); + * + * step_spec. encapsulates Fk (transition-related params) + * and Q (system noise covar matrix) + */ + static ref::rp step(KalmanFilterStep const & step_spec); + + /* step filter from t(k) -> t(k+1) + * + * sk. filter state from previous step: + * x (state vector), P (state covar matrix) + * Fk. transition-related params: + * F (transition matrix), Q (system noise covar matrix) + * Hkp1. observation-related params: + * H (coupling matrix), R (error covar matrix) + * zkp1. observations z(k+1) for time t(k+1) + * j. identifies a single filter observable -- + * step will only consume observation z(k+1)[j] + */ + static ref::rp step1(utc_nanos tkp1, + ref::rp const & sk, + KalmanFilterTransition const & Fk, + KalmanFilterObservable const & Hkp1, + ref::rp const & zkp1, + uint32_t j); + + /* step filter from t(k) -> t(k+1) + * + * same as + * .step1(step_spec.tkp1(), + * step_spec.state(), + * step_spec.model(), + * step_spec.obs(), + * step_spec.input(), + * j); + * + * step_spec. encapsulates + * x (state vector), + * P (state covar matrix), + * Fk (transition-related params), + * Q (system noise covar matrix) + * z (z(k+1), observations at time t(k+1)) + * j. identifies a single filter observable -- + * step will only consume observation z(k+1)[j] + */ + static ref::rp step1(KalmanFilterStep const & step_spec, + uint32_t j); + }; /*KalmanFilterEngine*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterEngine.hpp */ diff --git a/include/xo/kalmanfilter/KalmanFilterInput.hpp b/include/xo/kalmanfilter/KalmanFilterInput.hpp new file mode 100644 index 00000000..70fee5af --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilterInput.hpp @@ -0,0 +1,121 @@ +/* @file KalmanFilterInput.hpp */ + +#pragma once + +#include "xo/reflect/SelfTagging.hpp" +//#include "time/Time.hpp" +//#include "xo/refcnt/Refcounted.hpp" +#include +#include + +namespace xo { + /* FIXME. hack here to get member access working for reflection */ + namespace vf { class StrikesetKfinput; } + + namespace kalman { + /* represents a single kalman filter input event + * comprising: + * - time tkp1 + * - observation vector z[] + * - presence bits presence[] for z + * - observation errors Rd[] + */ + class KalmanFilterInput : public reflect::SelfTagging { + public: + using TaggedRcptr = xo::reflect::TaggedRcptr; + using utc_nanos = xo::time::utc_nanos; + using VectorXb = Eigen::Array; + using VectorXi = Eigen::VectorXi; + using VectorXd = Eigen::VectorXd; + using uint32_t = std::uint32_t; + + public: + KalmanFilterInput() = default; + + static ref::rp make(utc_nanos tkp1, + VectorXb const & presence, + VectorXd const & z, + VectorXd const & Rd); + + /* create input, with all presence bits set + not using Rd */ + static ref::rp make_present(utc_nanos tkp1, + VectorXd const & z); + + /* reflect KalmanFilterInput object representation */ + static void reflect_self(); + + /* alt name -- concession to reactor::DirectSource + * when event type is KalmanFilterInput + */ + utc_nanos tm() const { return tkp1_; } + + utc_nanos tkp1() const { return tkp1_; } + uint32_t n_obs() const { return z_.size(); } + VectorXb const & presence() const { return presence_; } + VectorXd const & z() const { return z_; } + VectorXd const & Rd() const { return Rd_; } + + /* computes reindexer keep[]: + * .presence[keep[j*]] + * is the j*'th true value in .presence + */ + VectorXi make_kept_index() const; + + virtual void display(std::ostream & os) const; + std::string display_string() const; + + // ----- inherited from SelfTagging ----- + + virtual TaggedRcptr self_tp() override; + + protected: + KalmanFilterInput(utc_nanos tkp1, VectorXb presence, VectorXd z, VectorXd Rd) + : tkp1_(tkp1), + presence_{std::move(presence)}, + z_{std::move(z)}, + Rd_{std::move(Rd)} {} + + friend class xo::vf::StrikesetKfinput; + + private: + /* t(k+1) - asof time for observations .z */ + utc_nanos tkp1_ = xo::time::timeutil::epoch(); + /* [m x 1] presence vector. + * an observation z[j] is present iff .presence[j] is true. + * rows/columns for an absent observation are removed from filter matrices + */ + VectorXb presence_; + /* [m x 1] observation vector z(k) */ + VectorXd z_; + + /* [m x 1] observation error vector Rd(k). + * This represents a side channel for passing the diagonal of + * observation matrix R(k), when both: + * (a) error of different observations are assumed to be uncorrelated (likely) + * (b) error variance is derived from input data, e.g. because of + * some input preprocessing. + * + * It's up to KalmanFilterSpec::MkStepFn to opt-in to using this information, + * which requires agreement with any input preparation step. + * + * This variable could just as well provide observation error matrix R + * + * NOTE: perhaps-cleaner alternative would be to inherit KalmanFilterInput to + * introduce additional state, then MkStepFn can dynamic_cast + */ + VectorXd Rd_; + }; /*KalmanFilterInput*/ + + using KalmanFilterInputPtr = ref::rp; + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterInput const & x) + { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterInput.hpp */ diff --git a/include/xo/kalmanfilter/KalmanFilterInputCallback.hpp b/include/xo/kalmanfilter/KalmanFilterInputCallback.hpp new file mode 100644 index 00000000..df86a16b --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilterInputCallback.hpp @@ -0,0 +1,14 @@ +/* @file KalmanFilterInputCallback.hpp */ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" +#include "KalmanFilter.hpp" + +namespace xo { + namespace kalman { + using KalmanFilterInputCallback = reactor::Sink1>; + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterInputCallback.hpp */ diff --git a/include/xo/kalmanfilter/KalmanFilterInputSource.hpp b/include/xo/kalmanfilter/KalmanFilterInputSource.hpp new file mode 100644 index 00000000..7e801936 --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilterInputSource.hpp @@ -0,0 +1,27 @@ +/* @file KalmanFilterInputSource.hpp */ + +#pragma once + +#include "xo/reactor/EventSource.hpp" +#include "KalmanFilterInputCallback.hpp" + +namespace xo { + namespace kalman { + /* Use: + * rp src = ...; + * rp in_cb = ...; + * + * src->add_callback(in_cb); + * ... + * // src invokes in_cb->notify_input( + * src->remove_callback(in_cb); + */ + using KalmanFilterInputSource = reactor::EventSource< + /*KalmanFilterInput,*/ + KalmanFilterInputCallback + /*&KalmanFilterInputCallback::notify_filter*/ + >; + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterInputSource.hpp */ diff --git a/include/xo/kalmanfilter/KalmanFilterInputToConsole.hpp b/include/xo/kalmanfilter/KalmanFilterInputToConsole.hpp new file mode 100644 index 00000000..7d81226a --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilterInputToConsole.hpp @@ -0,0 +1,30 @@ +/* @file KalmanFilterInputToConsole.hpp */ + +#pragma once + +#include "xo/reactor/Sink.hpp" +#include "KalmanFilterInput.hpp" + +namespace xo { + namespace kalman { + class KalmanFilterInputToConsole + : public xo::reactor::SinkToConsole> + { + public: + KalmanFilterInputToConsole() = default; + + static ref::rp make(); + + virtual void display(std::ostream & os) const; + //virtual std::string display_string() const; + }; /*KalmanFilterInputToConsole*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterInputToConsole const & x) { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace option*/ +} /*namespace xo*/ + +/* end KalmanFilterInputToConsole.hpp */ diff --git a/include/xo/kalmanfilter/KalmanFilterObservable.hpp b/include/xo/kalmanfilter/KalmanFilterObservable.hpp new file mode 100644 index 00000000..c3981095 --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilterObservable.hpp @@ -0,0 +1,109 @@ +/* @file KalmanFilterObservable.hpp */ + +#pragma once + +//#include "time/Time.hpp" +#include +#include + +namespace xo { + namespace kalman { + class KalmanFilterObservable { + public: + using MatrixXd = Eigen::MatrixXd; + using VectorXi = Eigen::VectorXi; + + public: + KalmanFilterObservable() = default; + + /* keep maps back to canonical observations z(j). + * H, R have been re-indexed + * + * If all m observations are included, then keep will be: + * [0, .., m-1] + */ + KalmanFilterObservable(VectorXi keep, MatrixXd H, MatrixXd R) + : keep_{std::move(keep)}, H_{std::move(H)}, R_{std::move(R)} { + assert(this->check_ok()); + } /*ctor*/ + + /* build KF observable object where keeping all observations */ + static KalmanFilterObservable keep_all(MatrixXd H, MatrixXd R); + + /* build KF observable object. replace H, R with reindexed versions H', R' + * according to indexing vector keep[]. keep[] indexes members of + * observation vector z(k)[j]. Reindexed z', H', R' as follows: + * + * z'[j*] = z[keep[j*]] + * H'[j*, i] = H[keep[j*], i] + * R'[j1*, j2*] = R[keep[j1*], keep[j2*]] + */ + static KalmanFilterObservable reindex(VectorXi keep, MatrixXd H, MatrixXd R); + + uint32_t n_state() const { return H_.cols(); } + uint32_t n_observable() const { return H_.rows(); } + VectorXi const & keep() const { return keep_; } + MatrixXd const & observable() const { return H_; } + MatrixXd const & observable_cov() const { return R_; } + + bool check_ok() const { + uint32_t m = H_.rows(); + bool keep_is_mx1 = (keep_.rows() == m); + bool r_is_mxm = ((R_.cols() == m) && (R_.rows() == m)); + + bool keep_is_well_ordered = true; + + /* members of .keep are non-negative integers, + * in strictly increasing order + */ + int64_t keep_jm1 = -1; + + for (uint32_t j = 0; j < keep_.rows(); ++j) { + if (keep_[j] < 0) + keep_is_well_ordered = false; + if (keep_[j] <= keep_jm1) + keep_is_well_ordered = false; + } + + /* also would like to require: R is +ve definite */ + + return keep_is_mx1 && keep_is_well_ordered && r_is_mxm; + } /*check_ok*/ + + void display(std::ostream & os) const; + std::string display_string() const; + + private: + + private: + /* m: #of observations that survived sanity/error checks + * + * H, R here will have been re-indexed to exclude rejected observations. + * observations z will also have been re-indexed. + * + * If an observation z[j] is excluded, then also exclude: + * - j'th row of H + * - j'th row and j'th column of R + * - j'th element of z + * + * Given re-indexed H, R, the j*'th row of H goes with z[keep[j*]] + */ + + /* [m x 1] maps back to indices in original observation vector */ + VectorXi keep_; + /* [m x n] observation matrix */ + MatrixXd H_; + /* [m x m] covariance matrix for observation noise */ + MatrixXd R_; + }; /*KalmanFilterObservable*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterObservable const & x) + { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterObservable.hpp */ diff --git a/include/xo/kalmanfilter/KalmanFilterOutputCallback.hpp b/include/xo/kalmanfilter/KalmanFilterOutputCallback.hpp new file mode 100644 index 00000000..cd858606 --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilterOutputCallback.hpp @@ -0,0 +1,15 @@ +/* @file KalmanFilterOutputCallback.hpp */ + +#pragma once + +#include "xo/reactor/Sink.hpp" +#include "KalmanFilter.hpp" + +namespace xo { + namespace kalman { + /* callback for consuming kalman filter output */ + using KalmanFilterOutputCallback = reactor::Sink1>; + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterOutputCallback.hpp */ diff --git a/include/xo/kalmanfilter/KalmanFilterSpec.hpp b/include/xo/kalmanfilter/KalmanFilterSpec.hpp new file mode 100644 index 00000000..306f298a --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilterSpec.hpp @@ -0,0 +1,86 @@ +/* @file KalmanFilterSpec.hpp */ + +#pragma once + +#include "KalmanFilterStep.hpp" + +namespace xo { + namespace kalman { + /* full specification for a kalman filter. + * + * For a textbook linear filter, expect a KalmanFilterStep + * instance to be independent of KalmanFilterState/KalmanFilterInput. + * + * Relaxing this requirement for two reasons: + * 1. (proper) want to allow filter with variable timing between observations, + * expecially if observations are event-driven. + * In that case it's likely that state transition matrices are a function + * of elapsed time between observations. Providing filter state sk + * allows MkStepFn to use sk.tm() + * 2. (sketchy) when observations represent market data, + * desirable to treat an observation as giving one-sided information + * about true value. For example treat a bid price as evidence + * true value is higher than that bid, but don't want to constrain + * "how much higher". Certainly no reason to think that + * bid price is normally distributed around fair value. + * Allow for hack in which we + * and modulate error distribution "as if it were normal" to assess + * a non-gaussian error distribution + */ + class KalmanFilterSpec { + public: + /* typical implementation will look something like: + * mk_step(KalmanFilterState const & sk, + * KalmanFilterInputPtr const & zkp1) + * { + * KalmanFilterTransition model = ...; + * KalmanFilterObservable obs = ...; + * + * return KalmanFilterStep(sk, model, obs, zkp1); + * } + */ + using MkStepFn = std::function const & sk, + KalmanFilterInputPtr const & zkp1)>; + + public: + explicit KalmanFilterSpec(ref::rp s0, MkStepFn mkstepfn) + : start_ext_{std::move(s0)}, + mk_step_fn_{std::move(mkstepfn)} {} + + ref::rp const & start_ext() const { return start_ext_; } + /* get step parameters (i.e. matrices F, Q, H, R) + * for step t(k) -> t(k+1). + * + * We supply t(k) state s and t(k+1) observation z(k+1): + * - to allow time stepping to be observation-driven + * - to allow for selective outlier removal + */ + KalmanFilterStep make_step(ref::rp const & sk, + ref::rp const & zkp1) { + return this->mk_step_fn_(sk, zkp1); + } /*make_step*/ + + void display(std::ostream & os) const; + std::string display_string() const; + + private: + /* starting state */ + ref::rp start_ext_; + + /* creates kalman filter step object on demand; + * step object specifies inputs to 1 step in discrete + * linear kalman filter + */ + MkStepFn mk_step_fn_; + }; /*KalmanFilterSpec*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterSpec const & x) { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterSpec.hpp */ diff --git a/include/xo/kalmanfilter/KalmanFilterState.hpp b/include/xo/kalmanfilter/KalmanFilterState.hpp new file mode 100644 index 00000000..504def9c --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilterState.hpp @@ -0,0 +1,163 @@ +/* @file KalmanFilterState.hpp */ + +#pragma once + +#include "xo/reflect/SelfTagging.hpp" +#include "KalmanFilterInput.hpp" +#include "KalmanFilterTransition.hpp" +//#include "time/Time.hpp" +#include +#include +#include + +namespace xo { + namespace kalman { + /* encapsulate state (i.e. initial state, and output after each step) + * for a kalman filter + */ + class KalmanFilterState : public reflect::SelfTagging { + public: + using TaggedRcptr = reflect::TaggedRcptr; + using utc_nanos = xo::time::utc_nanos; + using VectorXd = Eigen::VectorXd; + using MatrixXd = Eigen::MatrixXd; + using uint32_t = std::uint32_t; + + public: + static ref::rp make(); + static ref::rp make(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition); + virtual ~KalmanFilterState() = default; + + /* reflect KalmanFilterState object representation */ + static void reflect_self(); + + uint32_t step_no() const { return k_; } + utc_nanos tm() const { return tk_; } + /* with n = .n_state(): + * x_ is [n x 1] vector + * P_ is [n x n] matrix, + */ + uint32_t n_state() const { return x_.size(); } + VectorXd const & state_v() const { return x_; } + MatrixXd const & state_cov() const { return P_; } + + KalmanFilterTransition const & transition() const { return transition_; } + + virtual void display(std::ostream & os) const; + std::string display_string() const; + + // ----- inherited from SelfTagging ----- + + virtual TaggedRcptr self_tp() override; + + private: + KalmanFilterState(); + KalmanFilterState(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition); + + friend class KalmanFilterStateExt; + + private: + /* step# k, advances by +1 on each filter step */ + uint32_t k_ = 0; + /* time t(k) */ + utc_nanos tk_; + /* [n x 1] (estimated) system state xk = x(k) */ + VectorXd x_; + /* [n x n] covariance matrix for error assoc'd with with x(k) + * P(i,j) is the covariance of (ek[i], ek[j]), + * where ex(k) is the difference (x(k) - x_(k)) + * between estimated state x(k) + * (= this->x_) and model state x_(k) + */ + MatrixXd P_; + + /* F, Q matrices driving .x, .P */ + KalmanFilterTransition transition_; + }; /*KalmanFilterState*/ + + inline std::ostream & operator<<(std::ostream & os, + KalmanFilterState const & s) + { + s.display(os); + return os; + } /*operator<<*/ + + /* KalmanFilterStateExt: + * adds additional details from filter step to KalmanFilterState + */ + class KalmanFilterStateExt : public KalmanFilterState { + public: + using TaggedRcptr = reflect::TaggedRcptr; + using MatrixXd = Eigen::MatrixXd; + using int32_t = std::int32_t; + + public: + static ref::rp make(); + static ref::rp make(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition, + MatrixXd K, + int32_t j, + ref::rp zk); + + /* create state object for initial filter state */ + static ref::rp initial(utc_nanos t0, + VectorXd x0, + MatrixXd P0); + + /* reflect KalmanFilterStateExt object representation */ + static void reflect_self(); + + int32_t observable() const { return j_; } + MatrixXd const & gain() const { return K_; } + ref::rp const & zk() const { return zk_; } + + virtual void display(std::ostream & os) const override; + + // ----- inherited from SelfTagging ----- + + virtual TaggedRcptr self_tp() override; + + private: + KalmanFilterStateExt() = default; + KalmanFilterStateExt(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition, + MatrixXd K, + int32_t j, + ref::rp zk); + + private: + /* if -1: not used; + * if >= 0: identifies j'th of m observables; + * gain .K applies just to information obtainable from + * observing that scalar variable + */ + int32_t j_ = -1; + /* if .j is -1: + * [n x n] kalman gain + * if .j >= 0: + * [n x 1] kalman gain for observable #j + */ + MatrixXd K_; + /* input leading to state k. + * empty for initial state (i.e. when .k is 0) + */ + ref::rp zk_; + }; /*KalamnFilterStateExt*/ + } /*namespace filter*/ +} /*namespace xo*/ + +/* end KalmanFilterState.hpp */ diff --git a/include/xo/kalmanfilter/KalmanFilterStateToConsole.hpp b/include/xo/kalmanfilter/KalmanFilterStateToConsole.hpp new file mode 100644 index 00000000..9d072035 --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilterStateToConsole.hpp @@ -0,0 +1,30 @@ +/* @file KalmanFilterStateToConsole.hpp */ + +#pragma once + +#include "xo/reactor/Sink.hpp" +#include "KalmanFilterState.hpp" + +namespace xo { + namespace kalman { + class KalmanFilterStateToConsole + : public xo::reactor::SinkToConsole + { + public: + KalmanFilterStateToConsole() = default; + + static ref::rp make(); + + virtual void display(std::ostream & os) const; + //virtual std::string display_string() const; + }; /*KalmanFilterStateToConsole*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterStateToConsole const & x) { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace option*/ +} /*namespace xo*/ + +/* end KalmanFilterStateToConsole.hpp */ diff --git a/KalmanFilterStep.hpp b/include/xo/kalmanfilter/KalmanFilterStep.hpp similarity index 100% rename from KalmanFilterStep.hpp rename to include/xo/kalmanfilter/KalmanFilterStep.hpp diff --git a/include/xo/kalmanfilter/KalmanFilterSvc.hpp b/include/xo/kalmanfilter/KalmanFilterSvc.hpp new file mode 100644 index 00000000..94702a52 --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilterSvc.hpp @@ -0,0 +1,64 @@ +/* @file KalmanFilterSvc.hpp */ + +#include "xo/reactor/Sink.hpp" +#include "xo/reactor/DirectSourcePtr.hpp" +#include "KalmanFilter.hpp" +#include "KalmanFilterInputSource.hpp" +#include "KalmanFilterOutputCallback.hpp" +#include "xo/callback/CallbackSet.hpp" + +namespace xo { + namespace kalman { + /* encapsulate a passive KalmanFilter + * instance as an active event consumer+producer + * + * sinks that want to consume KalmanFilterSvc events will use + * .attach_sink() (or .add_callback()) + */ + class KalmanFilterSvc : public xo::reactor::Sink1>, + public xo::reactor::DirectSourcePtr> { + public: + using AbstractSource = xo::reactor::AbstractSource; + + public: + /* named ctor idiom */ + static ref::rp make(KalmanFilterSpec spec); + + KalmanFilter const & filter() const { return filter_; } + + /* notify incoming observations; will trigger kalman filter step */ + void notify_ev(ref::rp const & input_kp1) override; + + // ----- inherited from reactor::AbstractSink ----- + + /* filter captures KF input pointer */ + virtual bool allow_volatile_source() const override { return false; } + virtual uint32_t n_in_ev() const override { return n_in_ev_; } + virtual void display(std::ostream & os) const override; + + // ----- inherited from reactor::AbstractSource ----- + + /* note: correct since KalmanFilterEngine.extrapolate() + * always creates new state object + */ + virtual bool is_volatile() const override { return false; } + + // ----- Inherited from AbstractEventProcessor ----- + + private: + KalmanFilterSvc(KalmanFilterSpec spec); + + private: + /* passive kalman filter */ + KalmanFilter filter_; + /* receive filter input from this source; see .attach_input() */ + ref::rp input_src_; + /* counts lifetime #of input events (see .notify_ev()) */ + uint32_t n_in_ev_ = 0; + /* publish filter state updates to these callbacks */ + fn::RpCallbackSet pub_; + }; /*KalmanFilterSvc*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* KalmanFilterSvc.hpp */ diff --git a/include/xo/kalmanfilter/KalmanFilterTransition.hpp b/include/xo/kalmanfilter/KalmanFilterTransition.hpp new file mode 100644 index 00000000..22d3de5b --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilterTransition.hpp @@ -0,0 +1,62 @@ +/* @file KalmanFilterTransition.hpp */ + +#pragma once + +//#include "time/Time.hpp" +#include +#include + +namespace xo { + namespace kalman { + + /* encapsulate transition behavior for a kalman filter + * before taking observations into account + */ + class KalmanFilterTransition { + public: + using MatrixXd = Eigen::MatrixXd; + using uint32_t = std::uint32_t; + + public: + KalmanFilterTransition() = default; + KalmanFilterTransition(MatrixXd F, + MatrixXd Q) + : F_{std::move(F)}, Q_{std::move(Q)} { assert(this->check_ok()); } + + static void reflect_self(); + + /* n: cardinality of state vector */ + uint32_t n_state() const; + + MatrixXd const & transition_mat() const { return F_; } + MatrixXd const & transition_cov() const { return Q_; } + + bool check_ok() const { + uint32_t n = F_.rows(); + bool f_is_nxn = ((F_.rows() == n) && (F_.cols() == n)); + bool q_is_nxn = ((Q_.rows() == n) && (Q_.cols() == n)); + + /* also would like to require: Q is +ve definite */ + + return f_is_nxn && q_is_nxn; + } /*check_ok*/ + + void display(std::ostream & os) const; + std::string display_string() const; + + private: + /* [n x n] state transition matrix */ + MatrixXd F_; + /* [n x n] covariance matrix for system noise */ + MatrixXd Q_; + }; /*KalmanFilterTransition*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterTransition const & x) { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterTransition.hpp */ diff --git a/include/xo/kalmanfilter/init_filter.hpp b/include/xo/kalmanfilter/init_filter.hpp new file mode 100644 index 00000000..292a63ff --- /dev/null +++ b/include/xo/kalmanfilter/init_filter.hpp @@ -0,0 +1,20 @@ +/* file init_kalmanfilter.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "xo/subsys/Subsystem.hpp" + +namespace xo { + enum S_kalmanfilter_tag {}; + + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; +} /*namespace xo*/ + +/* end init_kalmanfilter.hpp */ diff --git a/include/xo/kalmanfilter/print_eigen.hpp b/include/xo/kalmanfilter/print_eigen.hpp new file mode 100644 index 00000000..ea6f7375 --- /dev/null +++ b/include/xo/kalmanfilter/print_eigen.hpp @@ -0,0 +1,41 @@ +/* @file print_eigen.hpp */ + +#include +#include + +namespace logutil { + template + class matrix { + public: + matrix(T x) : x_{std::move(x)} {} + + /* print this value */ + T x_; + }; /*matrix*/ + + template + using vector = matrix; + + template + inline std::ostream & + operator<<(std::ostream & s, matrix const & mat) + { + s << "["; + for(std::uint32_t i = 0, m = mat.x_.rows(); i 0) + s << "; "; + + for(std::uint32_t j = 0, n = mat.x_.cols(); j 0) + s << ' '; + + s << mat.x_(i, j); + } + } + s << "]"; + + return s; + } /*operator<<*/ +} /*namespace logutil*/ + +/* end print_eigen.hpp */ diff --git a/init_filter.cpp b/init_filter.cpp deleted file mode 100644 index 80407cc4..00000000 --- a/init_filter.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* file init_filter.cpp - * - * author: Roland Conybeare, Aug 2022 - */ - -#include "init_filter.hpp" -#include "reactor/init_reactor.hpp" -#include "KalmanFilterState.hpp" -#include "EigenUtil.hpp" -#include "printjson/PrintJson.hpp" - -namespace xo { - using xo::kalman::KalmanFilterInput; - using xo::kalman::KalmanFilterTransition; - using xo::kalman::KalmanFilterState; - using xo::kalman::KalmanFilterStateExt; - using xo::eigen::EigenUtil; - using xo::json::PrintJsonSingleton; - using xo::json::PrintJson; - - void - InitSubsys::init() - { - PrintJson * pjson = PrintJsonSingleton::instance().get(); - - EigenUtil::reflect_eigen(); - EigenUtil::provide_json_printers(pjson); - - KalmanFilterInput::reflect_self(); - KalmanFilterTransition::reflect_self(); - KalmanFilterState::reflect_self(); - KalmanFilterStateExt::reflect_self(); - - } /*init*/ - - InitEvidence - InitSubsys::require() - { - InitEvidence retval; - - /* subsystem dependencies for filter/ */ - retval ^= InitSubsys::require(); - - /* filter/'s own initialization code */ - retval ^= Subsystem::provide("filter", &init); - - return retval; - } /*require*/ -} /*namespace xo*/ - -/* end init_filter.cpp */ diff --git a/init_filter.hpp b/init_filter.hpp deleted file mode 100644 index 79cfc6d0..00000000 --- a/init_filter.hpp +++ /dev/null @@ -1,20 +0,0 @@ -/* file init_filter.hpp - * - * author: Roland Conybeare, Aug 2022 - */ - -#pragma once - -#include "subsys/Subsystem.hpp" - -namespace xo { - enum S_filter_tag {}; - - template<> - struct InitSubsys { - static void init(); - static InitEvidence require(); - }; -} /*namespace xo*/ - -/* end init_filter.hpp */ diff --git a/print_eigen.hpp b/print_eigen.hpp deleted file mode 100644 index 953c5904..00000000 --- a/print_eigen.hpp +++ /dev/null @@ -1,41 +0,0 @@ -/* @file print_eigen.hpp */ - -#include -#include - -namespace logutil { - template - class matrix { - public: - matrix(T x) : x_{std::move(x)} {} - - /* print this value */ - T x_; - }; /*matrix*/ - - template - using vector = matrix; - - template - inline std::ostream & - operator<<(std::ostream & s, matrix const & mat) - { - s << "["; - for(std::uint32_t i = 0, m = mat.x_.rows(); i 0) - s << "; "; - - for(std::uint32_t j = 0, n = mat.x_.cols(); j 0) - s << ' '; - - s << mat.x_(i, j); - } - } - s << "]"; - - return s; - } /*operator<<*/ -} /*namespace logutil*/ - -/* end print_eigen.hpp */ diff --git a/src/kalmanfilter/CMakeLists.txt b/src/kalmanfilter/CMakeLists.txt new file mode 100644 index 00000000..30ca8c02 --- /dev/null +++ b/src/kalmanfilter/CMakeLists.txt @@ -0,0 +1,31 @@ +# xo-kalmanfilter/src/kalmanfilter/CMakeLists.txt + +set(SELF_LIB xo_kalmanfilter) + +set(SELF_SRCS + EigenUtil.cpp + KalmanFilterInput.cpp + KalmanFilterInputToConsole.cpp + KalmanFilterState.cpp + KalmanFilterStateToConsole.cpp + KalmanFilterTransition.cpp + KalmanFilterObservable.cpp + KalmanFilterEngine.cpp + KalmanFilter.cpp + KalmanFilterStep.cpp + KalmanFilterSpec.cpp + KalmanFilterSvc.cpp + init_filter.cpp +) + +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) + +# ---------------------------------------------------------------- +# internal dependencies + +xo_dependency(${SELF_LIB} reactor) + +# ---------------------------------------------------------------- +# external dependencies + +xo_external_target_dependency(${SELF_LIB} eigen3 Eigen3::Eigen) diff --git a/src/kalmanfilter/EigenUtil.cpp b/src/kalmanfilter/EigenUtil.cpp new file mode 100644 index 00000000..b5770440 --- /dev/null +++ b/src/kalmanfilter/EigenUtil.cpp @@ -0,0 +1,157 @@ +/* file EigenUtil.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "EigenUtil.hpp" +#include "xo/printjson/PrintJson.hpp" +#include "xo/reflect/Reflect.hpp" +#include +#include +#include +#include + +namespace xo { + using xo::json::PrintJson; + using xo::json::JsonPrinter; + using xo::reflect::Reflect; + using xo::reflect::TypeDescr; + using VectorXb = Eigen::Array; + using Eigen::VectorXd; + using Eigen::MatrixXd; + +#ifdef NOT_YET + namespace reflect { + template + using EigenVectorX_Tdx = xo::reflect::StlVectorTdx>; + + /* probably need this to appear before decl for class xo::reflect::Reflect */ + template + class EstablishTdx> { + public: + static std::unique_ptr make() { + return EigenVectorX_Tdx::make(); + } /*make*/ + }; /*EstablishTdx*/ + } /*reflect*/ +#endif + + namespace eigen { + + namespace { + /* prints a VectorXd as json, in the obvious format, e.g. + * [1,2,3] + */ + template + class EigenVectorJsonPrinter : public JsonPrinter { + public: + EigenVectorJsonPrinter(PrintJson const * pjson) : JsonPrinter(pjson) {} + + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override + { + EigenVectorType * pv = this->check_recover_native(tp, p_os); + + if (pv) { + /* EigenVectorType (VectorXb, VectorXd, ..) + * is reflected as atomic for now, out of expedience. + * + * as soon as we reflect as mt_vector, will not need this helper. + */ + *p_os << "["; + + for (std::uint32_t i = 0, n = pv->size(); i < n; ++i) { + if (i > 0) + *p_os << ","; + + /* note: need to dispatch via json printer for vector elements, + * to get special treatment for non-finite values + */ + this->pjson()->print((*pv)[i], p_os); + //*p_os << jsonp((*pv)[i], this->pjson()); + } + + *p_os << "]"; + } + } /*print_json*/ + }; /*EigenVectorJsonPrinter*/ + + /* prints a MatrixXd as json, in row-major format, e.g. + * [[1,2,3], [4,5,6], [7,8,9]] + */ + class MatrixXdJsonPrinter : public JsonPrinter { + public: + MatrixXdJsonPrinter(PrintJson const * pjson) : JsonPrinter(pjson) {} + + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override + { + MatrixXd * pm = this->check_recover_native(tp, p_os); + + if (pm) { + /* MatrixXd is reflected as atomic for now, out of expedience */ + *p_os << "["; + + for(std::uint32_t i=0, m=pm->rows(); i 0) + *p_os << ", "; + *p_os << "["; + for(std::uint32_t j=0, n=pm->cols(); j 0) + *p_os << ","; + + /* note: need to dispatch via json printer for matrix elements, + * to get special treatment for non-finite values + */ + this->pjson()->print((*pm)(i, j), p_os); + //*p_os << jsonp((*pm)(i, j), this->pjson()); + } + *p_os << "]"; + } + + *p_os << "]"; + } + } /*print_json*/ + }; /*MatrixXdJsonPrinter*/ + + template + void + provide_eigen_vector_printer(PrintJson * p_pjson) + { + TypeDescr td = Reflect::require(); + std::unique_ptr pr(new EigenVectorJsonPrinter(p_pjson)); + + p_pjson->provide_printer(td, std::move(pr)); + } /*provide_eigen_vector_printer*/ + } /*namespace*/ + + void + EigenUtil::reflect_eigen() + { +#ifdef NOT_YET + Reflect::require(); + Reflect::require(); +#endif + } /*reflect_eigen*/ + + void + EigenUtil::provide_json_printers(PrintJson * p_pjson) + { + assert(p_pjson); + + provide_eigen_vector_printer(p_pjson); + provide_eigen_vector_printer(p_pjson); + + { + TypeDescr td = Reflect::require(); + std::unique_ptr pr(new MatrixXdJsonPrinter(p_pjson)); + + p_pjson->provide_printer(td, std::move(pr)); + } + } /*provide_json_printers*/ + } /*namespace eigen*/ +} /*namespace xo*/ + +/* end EigenUtil.cpp */ diff --git a/src/kalmanfilter/KalmanFilter.cpp b/src/kalmanfilter/KalmanFilter.cpp new file mode 100644 index 00000000..56507650 --- /dev/null +++ b/src/kalmanfilter/KalmanFilter.cpp @@ -0,0 +1,78 @@ +/* @file KalmanFilter.cpp */ + +#include "KalmanFilter.hpp" +#include "KalmanFilterEngine.hpp" +#include "print_eigen.hpp" +#include "xo/indentlog/scope.hpp" +#include "Eigen/src/Core/Matrix.h" + +namespace xo { + using xo::time::utc_nanos; + //using logutil::matrix; + using xo::scope; + using xo::tostr; + using xo::xtag; + using Eigen::MatrixXd; + using Eigen::VectorXd; + + namespace kalman { + // ----- KalmanFilter ----- + + KalmanFilter::KalmanFilter(KalmanFilterSpec spec) + : filter_spec_{std::move(spec)}, + state_ext_{filter_spec_.start_ext()} + {} /*ctor*/ + + void + KalmanFilter::notify_input(ref::rp const & input_kp1) + { + scope log(XO_ENTER0(info)); + + /* on entry: + * .state_ext refers to t(k) + * on exit: + * .step refers to t(k+1) + * .state_ext refers to t(k+1) + */ + + log && log(xtag("step_dt", + input_kp1->tkp1() - this->state_ext_->tm())); + + /* establish step inputs for this filter step: + * F(k+1) (system transition matrix) + * Q(k+1) (system noise covariance matrix) + * H(k+1) (observation coupling matrix) + * R(k+1) (observation noise covariance matrix) + * z(k+1) (observation vector) + */ + this->step_ = this->filter_spec_.make_step(this->state_ext_, input_kp1); + + //if (lscope.enabled()) { lscope.log(xtag("step", this->step_)); } + + /* extrapolate filter state to t(k+1), + * and correct based on z(k+1) + */ + this->state_ext_ = KalmanFilterEngine::step(this->step_); + + //if (lscope.enabled()) { lscope.log(xtag("state_ext", this->state_ext_)); } + } /*notify_input*/ + + void + KalmanFilter::display(std::ostream & os) const + { + os << ""; + } /*display*/ + + std::string + KalmanFilter::display_string() const + { + return tostr(*this); + } /*display_string*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilter.cpp */ diff --git a/src/kalmanfilter/KalmanFilterEngine.cpp b/src/kalmanfilter/KalmanFilterEngine.cpp new file mode 100644 index 00000000..e9533549 --- /dev/null +++ b/src/kalmanfilter/KalmanFilterEngine.cpp @@ -0,0 +1,360 @@ +/* @file KalmanFilterEngine.cpp + * + */ + +#include "KalmanFilterEngine.hpp" +#include "print_eigen.hpp" +#include "xo/indentlog/scope.hpp" +#include "Eigen/src/Core/Matrix.h" + +namespace xo { + using xo::time::utc_nanos; + using logutil::matrix; + using xo::scope; + using xo::xtag; + using Eigen::LDLT; + using Eigen::MatrixXd; + using Eigen::VectorXd; + + namespace kalman { + // ----- KalmanFilterEngine ----- + + ref::rp + KalmanFilterEngine::extrapolate(utc_nanos tkp1, + ref::rp const & s, + KalmanFilterTransition const & f) + { + //constexpr char const * c_self_name + // = "KalmanFilterEngine::extrapolate"; + + /* prior estimates at t(k) */ + VectorXd const & x = s->state_v(); + MatrixXd const & P = s->state_cov(); + + /* model change from t(k) -> t(k+1) */ + MatrixXd const & F = f.transition_mat(); + MatrixXd const & Q = f.transition_cov(); + + if(F.cols() != x.rows()) { + scope log(XO_DEBUG(true /*debug_flag*/)); + + log("error: F*x: expected F.cols=x.rows", + xtag("F.cols", F.cols()), xtag("x.rows", x.rows())); + } + + /* x(k+1|k) */ + VectorXd x_ext = F * x; + + /* P(k+1|k) */ + MatrixXd P_ext = (F * P * F.transpose()) + Q; + + /* creating new state object here + * allows KalmanFilterSvc.is_volatile()=false + */ + + return KalmanFilterState::make(s->step_no() + 1, + tkp1, + std::move(x_ext), + std::move(P_ext), + f); + } /*extrapolate*/ + + VectorXd + KalmanFilterEngine::kalman_gain1(ref::rp const & skp1_ext, + KalmanFilterObservable const & h, + uint32_t j) + { + constexpr bool c_debug_enabled = false; + + scope log(XO_DEBUG(c_debug_enabled)); + + /* P(k+1|k) :: [n x n] */ + MatrixXd const & P_ext = skp1_ext->state_cov(); + + /* H(k) :: [m x n] */ + MatrixXd const & H = h.observable(); + /* R(k) :: [m x m] */ + MatrixXd const & R = h.observable_cov(); + + /* i'th col of H couples element #i of filter state to each member of input z(k); + * j'th row of H couples filter state to j'th observable + * + * Hj :: [1 x n] Hj is a row-vector + */ + auto Hj = H.row(j); + + /* Rjj is the j'th diagonal element of R */ + double Rjj = R(j, j); + + /* T + * M(k) = Hj * P(k+1|k) * Hj + Rjj + * + * M(k) is a [1 x 1] matrix + */ + double m = Hj * (P_ext * Hj.transpose()) + Rjj; + + /* -1 + * M(k) trivial, since M is [1 x 1] + */ + double m_inv = 1.0 / m; + + /* K :: [n x 1] */ + VectorXd K = P_ext * Hj.transpose() * m_inv; + + log && log("result", + xtag("P(k+1|k)", matrix(P_ext)), + xtag("R", matrix(R)), + xtag("m", m)); + + return K; + } /*kalman_gain1*/ + + MatrixXd + KalmanFilterEngine::kalman_gain(ref::rp const & skp1_ext, + KalmanFilterObservable const & h) + { + scope log(XO_DEBUG(false /*debug_enabled*/)); + + /* P(k+1|k) */ + MatrixXd const & P_ext = skp1_ext->state_cov(); + + MatrixXd const & H = h.observable(); + MatrixXd const & R = h.observable_cov(); + + uint32_t m = H.rows(); + uint32_t n = H.cols(); + + if ((P_ext.rows() != n) || (P_ext.cols() != n)) { + std::string err_msg + = tostr("kalman_gain: with dim(H) = [m x n] expect dim(P) = [n x n]", + xtag("m", m), xtag("n", n), + xtag("P.rows", P_ext.rows()), + xtag("P.cols", P_ext.cols())); + + throw std::runtime_error(err_msg); + } + + if ((R.rows() != m) || (R.cols() != m)) { + std::string err_msg + = tostr("kalman_gain: with dim(H) = [m x n] expect dim(R) = [m x m]", + xtag("m", m), xtag("n", n), + xtag("R.rows", R.rows()), xtag("R.cols", R.cols())); + + throw std::runtime_error(err_msg); + } + + /* kalman gain: + * T -1 + * K(k+1) = P(k+1|k).H(k) .M + * + * T / T \ -1 + * = P(k+1|k).H(k) .| H(k).P(k+1|k).H(k) + R(k) | + * \ / + * + * Notes: + * 1. the matrix M being inverted is symmetric, since represents covariances. + * 2. if diagonal of R(k) has no zeroes (i.e. all measurements are subject to error), + * then it must be non-negative definite + * 3. unless observation errors are perfectly correlated, M(k) + * is positive definite. + * 4. even though 3. holds, there may be a nearby non-positive-definite matrix M+dM, + * Factoring M with finite-precision arithmetic solves for M+dM instead of M; + * which may run into difficulty if M is only 'slighlty' +ve definite. + * If necessary add small diagonal correction D to M, + * sufficient to make M+D positive definite. + * This is equivalent to introducing additional + * uncorrelated observation error, so benign from a robustness perspective + * 5. In generally we usually want to avoid fully realizing a matrix inverse. + * In this case need to explicitly compute K as ingredient used to + * correct state covariance later. + * 6. However, if R is diagonal (which is in practice quite likely), + * then it's easy to decompose a suite of vector observations z(k+1) = [z1, ..zm]T + * into separate zi, with dt=0 separating them. + * Can use this to avoid computing the inverse. + * See .kalman_gain1(), .correct1() + * 7. .kalman_gain() works unaltered when H, R have been reindexed + * to exclude outliers/errors; this is true because .kalman_gain() does not + * use the observation vector z[], i.e. operates entirely in the reduced + * reindexed space. + */ + + MatrixXd M = H * P_ext * H.transpose() + R; + + /* will use to write M as: + * + * T T + * M = P .L.D.L .P + * + * where: + * P is a permutation matrix + * L is lower triangular, with unit diagonal + * D is diagonal + */ + LDLT ldlt = M.ldlt(); + + /* solve for the identity matrix to realize the inverse this way */ + MatrixXd I = MatrixXd::Identity(M.rows(), M.cols()); + + /* -1 + * M + */ + MatrixXd M_inv = ldlt.solve(I); + + /* K(k+1) */ + MatrixXd K = P_ext * H.transpose() * M_inv; + + log && log("result", + xtag("k", skp1_ext->step_no()), + xtag("P(k+1|k)", matrix(P_ext)), + xtag("H", matrix(H)), + xtag("R", matrix(R)), + xtag("M", matrix(M)), + xtag("K", matrix(K))); + + return K; + } /*kalman_gain*/ + + ref::rp + KalmanFilterEngine::correct1(ref::rp const & skp1_ext, + KalmanFilterObservable const & h, + ref::rp const & zkp1, + uint32_t j) + { + uint32_t n = skp1_ext->n_state(); + /* Kj :: [n x 1] */ + VectorXd Kj = kalman_gain1(skp1_ext, h, j); + /* H :: [m x n] */ + MatrixXd const & H = h.observable(); + VectorXd const & z = zkp1->z(); + + /* Hj :: [1 x n] the j'th row of H */ + auto const & Hj = H.row(j); + + + /* x(k+1|x) :: [n x 1] */ + VectorXd const & x_ext = skp1_ext->state_v(); + + /* P(k+1|k) :: [n x n] */ + MatrixXd const & P_ext = skp1_ext->state_cov(); + + /* innovj : difference between jth 'actual observation' + * and jth 'predicted observation' + */ + double innovj = z[j] - (Hj * x_ext); + + /* x(k+1) */ + VectorXd xkp1 = x_ext + (Kj * innovj); + + MatrixXd I = MatrixXd::Identity(n, n); + /* note: Kj [n x 1], Hj [1 x n], + * so Kj * Hj [n x n], with rank 1 + */ + MatrixXd Pkp1 = (I - (Kj * Hj)) * P_ext; + + return KalmanFilterStateExt::make(skp1_ext->step_no(), + skp1_ext->tm(), + xkp1, + Pkp1, + skp1_ext->transition(), + Kj, + j, + zkp1); + } /*correct1*/ + + ref::rp + KalmanFilterEngine::correct(ref::rp const & skp1_ext, + KalmanFilterObservable const & h, + ref::rp const & zkp1) + { + uint32_t n = skp1_ext->n_state(); + /* K :: [n x m] */ + MatrixXd K = kalman_gain(skp1_ext, h); + MatrixXd const & H = h.observable(); + /* z_orig[] is original observation vector before reindexing */ + VectorXd const & z_orig = zkp1->z(); + /* reindex z_orig, keeping only elements that appear in + */ + VectorXd z = z_orig(h.keep()); + + /* 'ext' short for 'extrapolated' */ + VectorXd const & x_ext = skp1_ext->state_v(); + MatrixXd const & P_ext = skp1_ext->state_cov(); + + /* innov: difference between 'actual observations' + * and 'predicted observations' + */ + VectorXd innov = z - (H * x_ext); + + /* x(k+1) :: [n x 1] */ + VectorXd xkp1 = x_ext + K * innov; + MatrixXd I = MatrixXd::Identity(n, n); + MatrixXd Pkp1 = (I - K * H) * P_ext; + + return KalmanFilterStateExt::make(skp1_ext->step_no(), + skp1_ext->tm(), + xkp1, + Pkp1, + skp1_ext->transition(), + K, + -1 /*j: not used*/, + zkp1); + } /*correct*/ + + ref::rp + KalmanFilterEngine::step(utc_nanos tkp1, + ref::rp const & sk, + KalmanFilterTransition const & Fk, + KalmanFilterObservable const & Hkp1, + ref::rp const & zkp1) + { + ref::rp skp1_ext + = KalmanFilterEngine::extrapolate(tkp1, sk, Fk); + + ref::rp skp1 + = KalmanFilterEngine::correct(skp1_ext, Hkp1, zkp1); + + return skp1; + } /*step*/ + + ref::rp + KalmanFilterEngine::step(KalmanFilterStep const & step_spec) + { + return step(step_spec.tkp1(), + step_spec.state(), + step_spec.model(), + step_spec.obs(), + step_spec.input()); + } /*step*/ + + ref::rp + KalmanFilterEngine::step1(utc_nanos tkp1, + ref::rp const & sk, + KalmanFilterTransition const & Fk, + KalmanFilterObservable const & Hkp1, + ref::rp const & zkp1, + uint32_t j) + { + ref::rp skp1_ext + = KalmanFilterEngine::extrapolate(tkp1, sk, Fk); + + ref::rp skp1 + = KalmanFilterEngine::correct1(skp1_ext, Hkp1, zkp1, j); + + return skp1; + } /*step1*/ + + ref::rp + KalmanFilterEngine::step1(KalmanFilterStep const & step_spec, + uint32_t j) + { + return step1(step_spec.tkp1(), + step_spec.state(), + step_spec.model(), + step_spec.obs(), + step_spec.input(), + j); + } /*step1*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterEngine.cpp */ diff --git a/src/kalmanfilter/KalmanFilterInput.cpp b/src/kalmanfilter/KalmanFilterInput.cpp new file mode 100644 index 00000000..96f0d6ca --- /dev/null +++ b/src/kalmanfilter/KalmanFilterInput.cpp @@ -0,0 +1,118 @@ +/* @file KalmanFilterInput.cpp */ + +#include "KalmanFilterInput.hpp" +#include "xo/reflect/StructReflector.hpp" +#include "Eigen/src/Core/Matrix.h" +#include "print_eigen.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/reflect/TaggedRcptr.hpp" + +namespace xo { + using xo::reflect::Reflect; + using xo::reflect::TaggedRcptr; + using xo::reflect::StructReflector; + using xo::scope; + using logutil::matrix; + using xo::xtag; + using Eigen::MatrixXd; + using Eigen::VectorXi; + using std::uint32_t; + + namespace kalman { + ref::rp + KalmanFilterInput::make(utc_nanos tkp1, + VectorXb const & presence, + VectorXd const & z, + VectorXd const & Rd) + { + return new KalmanFilterInput(tkp1, presence, z, Rd); + } /*make*/ + + ref::rp + KalmanFilterInput::make_present(utc_nanos tkp1, + VectorXd const & z) + { + VectorXb presence = VectorXb::Ones(z.cols()); + + return new KalmanFilterInput(tkp1, + presence, + z, + VectorXd(0) /*Rd - not using*/); + } /*make*/ + + VectorXi + KalmanFilterInput::make_kept_index() const + { + scope log(XO_DEBUG(false /*!debug_flag*/)); + + log && log(xtag("presence", matrix(presence_))); + + /* count truth values */ + uint32_t mstar = 0; + + for (uint32_t j = 0, m = this->presence_.rows(); jpresence_[j]) + ++mstar; + } + + log && log(xtag("m*", mstar)); + + VectorXi keep(mstar); + + /* 2nd pass, populate keep[] */ + + uint32_t jstar = 0; + + for (uint32_t j = 0, m = this->presence_.rows(); jpresence_[j]) { + keep[jstar] = j; + ++jstar; + } + } + + log && log("keep", matrix(keep)); + + return keep; + } /*make_kept_index*/ + + void + KalmanFilterInput::reflect_self() + { + StructReflector sr; + + if (sr.is_incomplete()) { + REFLECT_MEMBER(sr, tkp1); + REFLECT_MEMBER(sr, presence); + REFLECT_MEMBER(sr, z); + REFLECT_MEMBER(sr, Rd); + } + } /*reflect_self*/ + + reflect::TaggedRcptr + KalmanFilterInput::self_tp() + { + return Reflect::make_rctp(this); + } /*self_tp*/ + + void + KalmanFilterInput::display(std::ostream & os) const + { + os << ""; + } /*display*/ + + std::string + KalmanFilterInput::display_string() const + { + std::stringstream ss; + this->display(ss); + return ss.str(); + } /*display_string*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterInput.cpp */ diff --git a/KalmanFilterInputToConsole.cpp b/src/kalmanfilter/KalmanFilterInputToConsole.cpp similarity index 93% rename from KalmanFilterInputToConsole.cpp rename to src/kalmanfilter/KalmanFilterInputToConsole.cpp index a0a848ca..8fadf031 100644 --- a/KalmanFilterInputToConsole.cpp +++ b/src/kalmanfilter/KalmanFilterInputToConsole.cpp @@ -1,7 +1,7 @@ /* @file KalmanFilterInputToConsole.cpp */ #include "KalmanFilterInputToConsole.hpp" -#include "indentlog/print/tag.hpp" +#include "xo/indentlog/print/tag.hpp" namespace xo { using xo::xtag; diff --git a/KalmanFilterObservable.cpp b/src/kalmanfilter/KalmanFilterObservable.cpp similarity index 98% rename from KalmanFilterObservable.cpp rename to src/kalmanfilter/KalmanFilterObservable.cpp index 90f31460..ad90c1f1 100644 --- a/KalmanFilterObservable.cpp +++ b/src/kalmanfilter/KalmanFilterObservable.cpp @@ -2,7 +2,7 @@ #include "KalmanFilterObservable.hpp" #include "print_eigen.hpp" -#include "indentlog/scope.hpp" +#include "xo/indentlog/scope.hpp" namespace xo { using xo::scope; diff --git a/src/kalmanfilter/KalmanFilterSpec.cpp b/src/kalmanfilter/KalmanFilterSpec.cpp new file mode 100644 index 00000000..45185551 --- /dev/null +++ b/src/kalmanfilter/KalmanFilterSpec.cpp @@ -0,0 +1,27 @@ +/* @file KalmanFilterSpec.cpp */ + +#include "KalmanFilterSpec.hpp" +#include "xo/indentlog/scope.hpp" + +namespace xo { + using xo::tostr; + using xo::xtag; + + namespace kalman { + void + KalmanFilterSpec::display(std::ostream & os) const + { + os << ""; + } /*display*/ + + std::string + KalmanFilterSpec::display_string() const + { + return tostr(*this); + } /*display_string*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterSpec.cpp */ diff --git a/src/kalmanfilter/KalmanFilterState.cpp b/src/kalmanfilter/KalmanFilterState.cpp new file mode 100644 index 00000000..4b5679e3 --- /dev/null +++ b/src/kalmanfilter/KalmanFilterState.cpp @@ -0,0 +1,231 @@ +/* @file KalmanFilterState.cpp */ + +#include "KalmanFilterState.hpp" +#include "print_eigen.hpp" +#include "xo/reflect/StructReflector.hpp" +#include "xo/reflect/TaggedPtr.hpp" +#include "xo/indentlog/scope.hpp" +#include "Eigen/src/Core/Matrix.h" +#include +#include + +namespace xo { + using xo::reflect::Reflect; + using xo::reflect::TaggedRcptr; + using xo::reflect::StructReflector; + using xo::time::utc_nanos; + using xo::ref::rp; + using logutil::matrix; + using logutil::vector; + //using xo::scope; + using xo::xtag; + using xo::tostr; + //using Eigen::LDLT; + using Eigen::MatrixXd; + using Eigen::VectorXd; + + namespace kalman { + // ----- KalmanFilterState ----- + + rp + KalmanFilterState::make() + { + return new KalmanFilterState(); + } /*make*/ + + rp + KalmanFilterState::make(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition) + { + return new KalmanFilterState(k, tk, + std::move(x), + std::move(P), + std::move(transition)); + } /*make*/ + + void + KalmanFilterState::reflect_self() + { + StructReflector sr; + + if (sr.is_incomplete()) { + REFLECT_MEMBER(sr, k); + REFLECT_MEMBER(sr, tk); + REFLECT_MEMBER(sr, x); + REFLECT_MEMBER(sr, P); + } + } /*reflect_self*/ + + KalmanFilterState::KalmanFilterState() = default; + + KalmanFilterState::KalmanFilterState(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition) + : k_{k}, tk_{tk}, + x_{std::move(x)}, P_{std::move(P)}, + transition_{std::move(transition)} + {} + + TaggedRcptr + KalmanFilterState::self_tp() + { + return Reflect::make_rctp(this); + } /*self_tp*/ + + // ----- KalmanFilterExt ----- + + rp + KalmanFilterStateExt::make() + { + return new KalmanFilterStateExt(); + } /*make*/ + + rp + KalmanFilterStateExt::make(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition, + MatrixXd K, + int32_t j, + rp zk) + { + return new KalmanFilterStateExt(k, + tk, + std::move(x), + std::move(P), + std::move(transition), + std::move(K), + j, + std::move(zk)); + } /*make*/ + + void + KalmanFilterStateExt::reflect_self() + { + StructReflector sr; + + if (sr.is_incomplete()) { + /* TODO: use sr.adopt_ancestors() */ + + REFLECT_EXPLICIT_MEMBER(sr, "k", &KalmanFilterState::k_); + REFLECT_EXPLICIT_MEMBER(sr, "tk", &KalmanFilterState::tk_); + REFLECT_EXPLICIT_MEMBER(sr, "x", &KalmanFilterState::x_); + REFLECT_EXPLICIT_MEMBER(sr, "P", &KalmanFilterState::P_); + REFLECT_EXPLICIT_MEMBER(sr, "transition", &KalmanFilterState::transition_); + REFLECT_MEMBER(sr, j); + REFLECT_MEMBER(sr, K); + REFLECT_MEMBER(sr, zk); + } + } /*reflect_self*/ + + KalmanFilterStateExt::KalmanFilterStateExt(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition, + MatrixXd K, + int32_t j, + rp zk) + : KalmanFilterState(k, tk, + std::move(x), + std::move(P), + std::move(transition)), + j_{j}, + K_{std::move(K)}, + zk_{std::move(zk)} + { + uint32_t n = x.size(); + + if (n != P.rows() || n != P.cols()) { + std::string err_msg + = tostr("with n=x.size expect [n x n] covar matrix P", + xtag("n", x.size()), + xtag("P.rows", P.rows()), + xtag("P.cols", P.cols())); + + throw std::runtime_error(err_msg); + } + + if ((K.rows() > 0) && (K.rows() > 0)) { + if (n != K.rows()) { + std::string err_msg + = tostr("with n=x.size expect [m x n] gain matrix K", + xtag("n", x.size()), + xtag("K.rows", K.rows()), + xtag("K.cols", K.cols())); + + throw std::runtime_error(err_msg); + } + } else { + /* bypass test with [0 x 0] matrix K; + * normal for initial filter state + */ + } + } /*ctor*/ + + void + KalmanFilterState::display(std::ostream & os) const + { + os << ""; + } /*display*/ + + std::string + KalmanFilterState::display_string() const + { + std::stringstream ss; + ss << *this; + return ss.str(); + } /*display_string*/ + + // ----- KalmanFilterStateExt ----- + + ref::rp + KalmanFilterStateExt::initial(utc_nanos t0, + VectorXd x0, + MatrixXd P0) + { + return KalmanFilterStateExt::make + (0 /*k*/, + t0, + std::move(x0), + std::move(P0), + KalmanFilterTransition(MatrixXd() /*F - not used for initial step*/, + MatrixXd() /*Q - not used for initial step*/), + MatrixXd() /*K - not used for initial step*/, + -1 /*j - not used for initial step*/, + nullptr /*zk - not defined for initial step*/); + } /*initial*/ + + void + KalmanFilterStateExt::display(std::ostream & os) const + { + os << "step_no()) + << xtag("tk", this->tm()) + << xtag("x", matrix(this->state_v())) + << xtag("P", matrix(this->state_cov())) + << xtag("K", matrix(K_)) + << xtag("j", j_) + << ">"; + } /*display*/ + + TaggedRcptr + KalmanFilterStateExt::self_tp() + { + return Reflect::make_rctp(this); + } /*self_tp*/ + } /*namespace filter*/ +} /*namespace xo*/ + +/* end KalmanFilterState.cpp */ diff --git a/KalmanFilterStateToConsole.cpp b/src/kalmanfilter/KalmanFilterStateToConsole.cpp similarity index 93% rename from KalmanFilterStateToConsole.cpp rename to src/kalmanfilter/KalmanFilterStateToConsole.cpp index 8e76ce84..9559d339 100644 --- a/KalmanFilterStateToConsole.cpp +++ b/src/kalmanfilter/KalmanFilterStateToConsole.cpp @@ -1,7 +1,7 @@ /* @file KalmanFilterStateToConsole.cpp */ #include "KalmanFilterStateToConsole.hpp" -#include "indentlog/print/tag.hpp" +#include "xo/indentlog/print/tag.hpp" namespace xo { using xo::xtag; diff --git a/src/kalmanfilter/KalmanFilterStep.cpp b/src/kalmanfilter/KalmanFilterStep.cpp new file mode 100644 index 00000000..8ca90211 --- /dev/null +++ b/src/kalmanfilter/KalmanFilterStep.cpp @@ -0,0 +1,84 @@ +/* @file KalmanFilterStep.cpp */ + +#include "KalmanFilterStep.hpp" +#include "KalmanFilterEngine.hpp" +#include "KalmanFilterState.hpp" +#include "xo/indentlog/scope.hpp" + +namespace xo { + using xo::scope; + using xo::tostr; + using xo::xtag; + using Eigen::MatrixXd; + using Eigen::VectorXd; + + namespace kalman { + ref::rp + KalmanFilterStep::extrapolate() const + { + return KalmanFilterEngine::extrapolate(this->tkp1(), + this->state(), + this->model() /*transition*/); + } /*extrapolate*/ + + MatrixXd + KalmanFilterStep::gain(ref::rp const & skp1_ext) const + { + return KalmanFilterEngine::kalman_gain(skp1_ext, + this->obs()); + } /*gain*/ + + VectorXd + KalmanFilterStep::gain1(ref::rp const & skp1_ext, + uint32_t j) const + { + return KalmanFilterEngine::kalman_gain1(skp1_ext, + this->obs(), + j); + + } /*gain1*/ + + ref::rp + KalmanFilterStep::correct(ref::rp const & skp1_ext) + { + return KalmanFilterEngine::correct(skp1_ext, + this->obs(), + this->input()); + } /*correct*/ + + ref::rp + KalmanFilterStep::correct1(ref::rp const & skp1_ext, + uint32_t j) + { + return KalmanFilterEngine::correct1(skp1_ext, + this->obs(), + this->input(), + j); + } /*correct1*/ + + void + KalmanFilterStep::display(std::ostream & os) const + { + //scope lscope("KalmanFilterStep::display"); + + os << "model()); + //lscope.log("obs:"); + os << xtag("obs", this->obs()); + //lscope.log("input:"); + os << xtag("input", this->input()); + os << ">"; + } /*display*/ + + std::string + KalmanFilterStep::display_string() const + { + return tostr(*this); + } /*display_string*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterStep.cpp */ diff --git a/KalmanFilterSvc.cpp b/src/kalmanfilter/KalmanFilterSvc.cpp similarity index 100% rename from KalmanFilterSvc.cpp rename to src/kalmanfilter/KalmanFilterSvc.cpp diff --git a/src/kalmanfilter/KalmanFilterTransition.cpp b/src/kalmanfilter/KalmanFilterTransition.cpp new file mode 100644 index 00000000..a6ec63ae --- /dev/null +++ b/src/kalmanfilter/KalmanFilterTransition.cpp @@ -0,0 +1,55 @@ +/* @file KalmanFilterTransition.cpp */ + +#include "KalmanFilterTransition.hpp" +#include "print_eigen.hpp" +#include "xo/reflect/StructReflector.hpp" +#include "xo/indentlog/scope.hpp" + +namespace xo { + using xo::reflect::StructReflector; + using logutil::matrix; + using xo::xtag; + + namespace kalman { + void + KalmanFilterTransition::reflect_self() + { + StructReflector sr; + + if (sr.is_incomplete()) { + REFLECT_MEMBER(sr, F); + REFLECT_MEMBER(sr, Q); + } + } /*reflect_self*/ + + uint32_t + KalmanFilterTransition::n_state() const + { + /* we know F.rows() == F.cols() = Q.cols() == Q.rows(), + * see .check_ok() + */ + + return F_.rows(); + } /*n_state*/ + + void + KalmanFilterTransition::display(std::ostream & os) const + { + os << ""; + } /*display*/ + + std::string + KalmanFilterTransition::display_string() const + { + std::stringstream ss; + this->display(ss); + return ss.str(); + } /*display_string*/ + + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterTransition.cpp */ diff --git a/src/kalmanfilter/init_filter.cpp b/src/kalmanfilter/init_filter.cpp new file mode 100644 index 00000000..3417214b --- /dev/null +++ b/src/kalmanfilter/init_filter.cpp @@ -0,0 +1,52 @@ +/* file init_filter.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "init_filter.hpp" +#include "xo/reactor/init_reactor.hpp" + +#include "KalmanFilterState.hpp" +#include "EigenUtil.hpp" +#include "xo/printjson/PrintJson.hpp" + +namespace xo { + using xo::kalman::KalmanFilterInput; + using xo::kalman::KalmanFilterTransition; + using xo::kalman::KalmanFilterState; + using xo::kalman::KalmanFilterStateExt; + using xo::eigen::EigenUtil; + using xo::json::PrintJsonSingleton; + using xo::json::PrintJson; + + void + InitSubsys::init() + { + PrintJson * pjson = PrintJsonSingleton::instance().get(); + + EigenUtil::reflect_eigen(); + EigenUtil::provide_json_printers(pjson); + + KalmanFilterInput::reflect_self(); + KalmanFilterTransition::reflect_self(); + KalmanFilterState::reflect_self(); + KalmanFilterStateExt::reflect_self(); + + } /*init*/ + + InitEvidence + InitSubsys::require() + { + InitEvidence retval; + + /* subsystem dependencies for filter/ */ + retval ^= InitSubsys::require(); + + /* filter/'s own initialization code */ + retval ^= Subsystem::provide("kalmanfilter", &init); + + return retval; + } /*require*/ +} /*namespace xo*/ + +/* end init_filter.cpp */ diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..9f21bfa4 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,54 @@ +# xo-kalmanfilter/utest/CMakeLists.txt + +set(SELF_EXE utest.filter) + +set(SELF_SRCS + KalmanFilter.test.cpp + filter_utest_main.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) +target_code_coverage(${SELF_EXE} AUTO ALL) + +# ---------------------------------------------------------------- +# create convenience symlink to canned data + +#create_symlink("${CMAKE_SOURCE_DIR}/utestdata/" "src/filter/utest/utestdata") + +# ---------------------------------------------------------------- +# generic project dependency + +# PROJECT_SOURCE_DIR: +# so we can for example write +# #include "logutil/scope.hpp" +# from anywhere in the project +# PROJECT_BINARY_DIR: +# since version file will be in build directory, need that directory +# to also be included in compiler's include path +# +xo_self_dependency(${SELF_EXE} xo_kalmanfilter) + +# ---------------------------------------------------------------- +# internal dependencies: logutil, ... + +xo_dependency(${SELF_EXE} xo_statistics) + +# ---------------------------------------------------------------- +# external dependencies: catch2.. + +xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) + +# ---------------------------------------------------------------- +# make standard directories for std:: includes explicit +# so that +# (1) they appear in compile_commands.json. +# (2) clangd (run from emacs lsp-mode) can find them +# +if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES + ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +endif() + +# end CMakeLists.txt diff --git a/utest/KalmanFilter.test.cpp b/utest/KalmanFilter.test.cpp new file mode 100644 index 00000000..679f461f --- /dev/null +++ b/utest/KalmanFilter.test.cpp @@ -0,0 +1,690 @@ +/* @file KalmanFilter.test.cpp */ + +#include "xo/kalmanfilter/KalmanFilter.hpp" +#include "xo/kalmanfilter/KalmanFilterEngine.hpp" +#include "xo/kalmanfilter/print_eigen.hpp" +#include "statistics/SampleStatistics.hpp" +#include "xo/randomgen/normalgen.hpp" +#include "xo/randomgen/xoshiro256.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/log_level.hpp" +#include +#include + +namespace xo { + using xo::kalman::KalmanFilterSpec; + using xo::kalman::KalmanFilterStep; + using xo::kalman::KalmanFilterEngine; + using xo::kalman::KalmanFilterStateExt; + using xo::kalman::KalmanFilterState; + using xo::kalman::KalmanFilterTransition; + using xo::kalman::KalmanFilterObservable; + using xo::kalman::KalmanFilterInput; + using xo::statistics::SampleStatistics; + using xo::rng::normalgen; + using xo::rng::xoshiro256ss; + using xo::time::timeutil; + using xo::time::utc_nanos; + using xo::time::seconds; + using xo::ref::rp; + using xo::log_level; + using logutil::matrix; + //using logutil::scope; + //using logutil::tostr; + //using logutil::xtag; + using xo::print::ccs; + using Eigen::MatrixXd; + using Eigen::VectorXd; + + namespace ut { + namespace { + /* step for kalman filter with: + * - single state variable x[0] + * - identity process model - x(k+1) = F(k).x(k), with F(k) = | 1 | + * - no process noise + * - single observation z[0] + * - identity coupling matrix - z(k) = H(k).x(k) + w(k), with H(k) = | 1 | + */ + KalmanFilterSpec::MkStepFn + kalman_identity1_mkstep_fn() + { + /* kalman state transition matrix: use identity <--> state is constant */ + MatrixXd F = MatrixXd::Identity(1, 1); + + /* state transition noise: set this to zero; + * measuring something that's known to be constant + */ + MatrixXd Q = MatrixXd::Zero(1, 1); + + /* single direct observation */ + MatrixXd H = MatrixXd::Identity(1, 1); + + /* observation errors understood to have + * mean 0, sdev 1 + * + * This is consistent with normal_rng below, + * so R is correctly specified + */ + MatrixXd R = MatrixXd::Identity(1, 1); + + return [F, Q, H, R](rp const & sk, + rp const & zkp1) { + KalmanFilterTransition Fk(F, Q); + KalmanFilterObservable Hk = KalmanFilterObservable::keep_all(H, R); + + return KalmanFilterStep(sk, Fk, Hk, zkp1); + }; + } /*kalman_identity1_mkstep_fn*/ + } /*namespace*/ + + /* example 1. + * repeated direct observation of a scalar + * use rng to generate observations + */ + TEST_CASE("kalman-identity", "[kalmanfilter]") { + /* setting up trivial filter for repeated indept + * measurements of a constant. + * + * True value of unknown set to 10, + * utest observes filter converging toward that value + */ + + /* seed for rng */ + uint64_t seed = 14950319842636922572UL; + + /* N(0,1) random numbers */ + auto normal_rng + = (normalgen::make + (seed, + std::normal_distribution(0.0 /*mean*/, + 1.0 /*sdev*/))); + + /* accumulate statistics on 'measurements', + * use as reference implementation for filter + */ + SampleStatistics z_stats; + + utc_nanos t0 = timeutil::ymd_midnight(20220707); + + /* estimate x(0) = [0] */ + VectorXd x0(1); + x0 << 10.0 + normal_rng(); + + INFO(tostr("x0=", x0)); + + z_stats.include_sample(x0[0]); + + /* kalman prior : Variance = 1, sdev = 1 */ + MatrixXd P0 = 1.0 * MatrixXd::Identity(1, 1); + + /* F, Q, K, j, zk not used for initial state */ + rp s0 + = KalmanFilterStateExt::make(0 /*step#*/, + t0, + x0, + P0, + KalmanFilterTransition(MatrixXd::Zero(1, 1) /*F*/, + MatrixXd::Zero(1, 1) /*Q*/), + MatrixXd::Zero(1, 1) /*K*/, + -1 /*j*/, + nullptr /*zk*/); + + auto mk_step_fn + = kalman_identity1_mkstep_fn(); + + KalmanFilterSpec spec(s0, mk_step_fn); + + rp sk = spec.start_ext(); + + for(uint32_t i_step = 1; i_step < 100; ++i_step) { + /* note: for this filter, measurement time doesn't matter */ + utc_nanos tkp1 = sk->tm() + seconds(1); + + VectorXd z(1); + z << 10.0 + normal_rng(); + + INFO(tostr("z=", z)); + + z_stats.include_sample(z[0]); + + ref::rp inputk + = KalmanFilterInput::make_present(tkp1, z); + + KalmanFilterStep step_spec = spec.make_step(sk, inputk); + + rp skp1 + = KalmanFilterEngine::step(step_spec); + + REQUIRE(skp1->step_no() == i_step); + REQUIRE(skp1->tm() == tkp1); + REQUIRE(skp1->n_state() == 1); + REQUIRE(skp1->state_v().size() == 1); + REQUIRE(skp1->state_v()[0] == Approx(z_stats.mean()).epsilon(1e-6)); + REQUIRE(skp1->state_cov().rows() == 1); + REQUIRE(skp1->state_cov().cols() == 1); + REQUIRE(skp1->gain().rows() == 1); + REQUIRE(skp1->gain().cols() == 1); + REQUIRE(skp1->observable() == -1); + + /* z_stats reflects k = z_stats.n_sample() N(0,1) 'random' vars; + * variance of sum (i.e. z_stats.mean() * k) is proportional to k; + * variance of mean like 1/k + * + * kalman filter also should compute covariance estimate like 1/k + */ + + REQUIRE(skp1->state_cov()(0, 0) == Approx(1.0 / z_stats.n_sample()).epsilon(1e-6)); + + REQUIRE(skp1->gain()(0, 0) == Approx(1.0 / z_stats.n_sample()).epsilon(1e-6)); + + /* estimate at each step should be (approximately) + * average of measurements taken so far. + * approximate because also affected by prior + */ + + sk = skp1; + } + + REQUIRE(sk->state_v()[0] == Approx(10.0).epsilon(1e-2)); + REQUIRE(sk->state_cov()(0, 0) == Approx(0.01).epsilon(1e-6)); + REQUIRE(sk->gain()(0, 0) == Approx(0.01).epsilon(1e-6)); + } /*TEST_CASE(kalman-identity)*/ + + /* example 2. + * like example 1, but using "separate observation" variants: + * KalmanGain::correct1() // per observation + * instead of + * KalmanGain::correct() // per observation set + */ + TEST_CASE("kalman-identity1", "[kalmanfilter]") { + /* setting up trivial filter for repeated indept + * measurements of a constant. + * + * True value of unknown set to 10, + * utest observes filter converging toward that value + * + */ + + /* seed for rng */ + uint64_t seed = 14950319842636922572UL; + + /* N(0,1) random numbers */ + auto normal_rng + = (normalgen::make + (seed, + std::normal_distribution(0.0 /*mean*/, + 1.0 /*sdev*/))); + + /* accumulate statistics on 'measurements', + * use as reference implementation for filter + */ + SampleStatistics z_stats; + + utc_nanos t0 = timeutil::ymd_midnight(20220707); + + /* estimate x(0) = [0] */ + VectorXd x0(1); + x0 << 10.0 + normal_rng(); + + INFO(tostr("x0=", x0)); + + z_stats.include_sample(x0[0]); + + /* kalman prior : Variance = 1, sdev = 1 */ + MatrixXd P0 = 1.0 * MatrixXd::Identity(1, 1); + + rp s0 + = KalmanFilterStateExt::make(0 /*step#*/, + t0, + x0, + P0, + KalmanFilterTransition(MatrixXd::Zero(1, 1) /*F*/, + MatrixXd::Zero(1, 1) /*Q*/), + MatrixXd::Zero(1, 1) /*K*/, + -1, + nullptr /*zk*/); + + auto mk_step_fn + = kalman_identity1_mkstep_fn(); + + KalmanFilterSpec spec(s0, mk_step_fn); + + rp sk = spec.start_ext(); + + for(uint32_t i_step = 1; i_step < 100; ++i_step) { + /* note: for this filter, measurement time doesn't matter */ + utc_nanos tkp1 = sk->tm() + seconds(1); + + VectorXd z(1); + z << 10.0 + normal_rng(); + + INFO(tostr("z=", z)); + + z_stats.include_sample(z[0]); + + ref::rp inputk + = KalmanFilterInput::make_present(tkp1, z); + + KalmanFilterStep step_spec + = spec.make_step(sk, inputk); + + rp skp1 + = KalmanFilterEngine::step1(step_spec, 0 /*j*/); + + REQUIRE(skp1->step_no() == i_step); + REQUIRE(skp1->tm() == tkp1); + REQUIRE(skp1->n_state() == 1); + REQUIRE(skp1->state_v().size() == 1); + REQUIRE(skp1->state_v()[0] == Approx(z_stats.mean()).epsilon(1e-6)); + REQUIRE(skp1->state_cov().rows() == 1); + REQUIRE(skp1->state_cov().cols() == 1); + REQUIRE(skp1->gain().rows() == 1); + REQUIRE(skp1->gain().cols() == 1); + REQUIRE(skp1->observable() == 0); + + /* z_stats reflects k = z_stats.n_sample() N(0,1) 'random' vars; + * variance of sum (i.e. z_stats.mean() * k) is proportional to k; + * variance of mean like 1/k + * + * kalman filter also should compute covariance estimate like 1/k + */ + + REQUIRE(skp1->state_cov()(0, 0) == Approx(1.0 / z_stats.n_sample()).epsilon(1e-6)); + + REQUIRE(skp1->gain()(0, 0) == Approx(1.0 / z_stats.n_sample()).epsilon(1e-6)); + + /* estimate at each step should be (approximately) + * average of measurements taken so far. + * approximate because also affected by prior + */ + + sk = skp1; + } + + REQUIRE(sk->state_v()[0] == Approx(10.0).epsilon(1e-2)); + REQUIRE(sk->state_cov()(0, 0) == Approx(0.01).epsilon(1e-6)); + REQUIRE(sk->gain()(0, 0) == Approx(0.01).epsilon(1e-6)); + } /*TEST_CASE(kalman-identity1)*/ + + namespace { + /* step for kalman filter with: + * - single state variable x[0] + * - identity process model: x(k+1) = F(k).x(k), with F(k) = | 1 | + * - no process noise + * - two observations z[0], z[1] + * - identity coupling matrix: z(k) = H(k).x(k) + w(k), with + * H(k) = | 1 | + * | 1 | + * + * w(k) = | w1 | with w1 ~ N(0,1) + * | w2 | + */ + KalmanFilterSpec::MkStepFn + kalman_identity2_mkstep_fn() + { + /* kalman state transition matrix: use identity <-> state is constant */ + MatrixXd F = MatrixXd::Identity(1, 1); + + /* state transition noise: set to 0 */ + MatrixXd Q = MatrixXd::Zero(1, 1); + + /* two direct observations */ + MatrixXd H = MatrixXd::Constant(2 /*#rows*/, 1 /*#cols*/, 1.0 /*M(i,j)*/); + + /* observation errors: N(0,1) */ + MatrixXd R = MatrixXd::Identity(2, 2); + + return [F, Q, H, R](rp const & sk, + rp const & zkp1) { + KalmanFilterTransition Fk(F, Q); + KalmanFilterObservable Hk = KalmanFilterObservable::keep_all(H, R); + + return KalmanFilterStep(sk, Fk, Hk, zkp1); + }; + } /*kalman_identity2_mkstep_fn*/ + } /*namespace*/ + + TEST_CASE("kalman-identity2", "[kalmanfilter]") { + /* variation on filter in kalman-identity1 utest above; + * this time make 2 observations per step + */ + + /* seed for rng */ + uint64_t seed = 14950319842636922572UL; + + /* N(0,1) random numbers */ + auto normal_rng + = (normalgen::make + (seed, + std::normal_distribution(0.0 /*mean*/, + 1.0 /*sdev*/))); + + /* accumulate statistics on 'measurements', + * use as reference implementation for filter + */ + SampleStatistics z_stats; + + utc_nanos t0 = timeutil::ymd_midnight(20220707); + + /* estimate x(0) = [0] */ + VectorXd x0(1); + x0 << 10.0 + normal_rng(); + + INFO(tostr("x0=", x0)); + + z_stats.include_sample(x0[0]); + + /* kalman prior : Variance = 1, sdev = 1 */ + MatrixXd P0 = 1.0 * MatrixXd::Identity(1, 1); + + rp s0 + = KalmanFilterStateExt::make(0 /*step#*/, + t0, + x0, + P0, + KalmanFilterTransition(MatrixXd::Zero(1, 1) /*F*/, + MatrixXd::Zero(1, 1) /*Q*/), + MatrixXd::Zero(1, 1) /*K*/, + -1 /*j*/, + nullptr /*zk*/); + + auto mk_step_fn + = kalman_identity2_mkstep_fn(); + + KalmanFilterSpec spec(s0, mk_step_fn); + rp sk = spec.start_ext(); + + /* need 1/2 as many filter steps to reach same confidence + * as in test "kalman-identity" + */ + for(uint32_t i_step = 1; i_step < 51; ++i_step) { + INFO(tostr(xtag("i_step", i_step))); + + /* note: for this filter, measurement time doesn't affect behavior */ + utc_nanos tkp1 = sk->tm() + seconds(1); + + VectorXd z(2); + z << 10.0 + normal_rng(), 10.0 + normal_rng(); + + z_stats.include_sample(z[0]); + z_stats.include_sample(z[1]); + + INFO(tostr(xtag("i_step", i_step), xtag("z", z))); + + ref::rp inputk + = KalmanFilterInput::make_present(tkp1, z); + + KalmanFilterStep step_spec = spec.make_step(sk, inputk); + + rp skp1 = KalmanFilterEngine::step(step_spec); + + REQUIRE(skp1->step_no() == i_step); + REQUIRE(skp1->tm() == tkp1); + REQUIRE(skp1->n_state() == 1); + REQUIRE(skp1->state_v().size() == 1); + REQUIRE(skp1->state_v()[0] == Approx(z_stats.mean()).epsilon(1e-6)); + REQUIRE(skp1->state_cov().rows() == 1); + REQUIRE(skp1->state_cov().cols() == 1); + REQUIRE(skp1->gain().rows() == 1); + REQUIRE(skp1->gain().cols() == 2); + REQUIRE(skp1->observable() == -1); + /* z_stats reflects 2*k = z_stats.n_sample() N(0,1) 'random' vars + * (since 2 vars per step) + * variance of sum (i.e. z_stats.mean() * k) is proportional to k; + * variance of mean like 1/k + * + * kalman filter also should compute covariance estimate like 1/k + * + */ + + REQUIRE(skp1->state_cov()(0, 0) == Approx(1.0 / z_stats.n_sample()).epsilon(1e-6)); + REQUIRE(skp1->gain()(0, 0) == Approx(1.0 / z_stats.n_sample()).epsilon(1e-6)); + REQUIRE(skp1->gain()(0, 1) == Approx(1.0 / z_stats.n_sample()).epsilon(1e-6)); + + /* estimate at each step should be (approximately) + * average of measurements taken so far. + * approximate because also affected by prior + */ + + sk = skp1; + } + + REQUIRE(sk->state_v()[0] == Approx(10.0).epsilon(1e-2)); + REQUIRE(sk->state_cov()(0, 0) == Approx(1.0 / z_stats.n_sample()).epsilon(1e-3)); + /* result is close but not identical, + * because initial confidence P0 counts as one sample, + * so have odd #of samples + */ + REQUIRE(sk->gain()(0, 0) == Approx(1.0 / z_stats.n_sample()).epsilon(1e-3)); + REQUIRE(sk->gain()(0, 1) == Approx(1.0 / z_stats.n_sample()).epsilon(1e-3)); + } /*TEST_CASE(kalman-identity2)*/ + + namespace { + /* step for kalman filter with: + * - two state variables x[0], x[1] + * x[0] subject to random disturbances, reverts towards mean 1 + * x[1] is 1 + * - process model: x(k+1) = F(k).x(k) + v(k) with + * F(k) = | 0.95 0.05 | v(k) = | v1 |, v ~ N(0, 0.25) + * | 0 1 | | 0 | + * - one observation z[0] + * - coupling matrix: z(k) = H(k).x(k) + w(k), with + * H(k) = | 1 0 | + * + * w(k) ~ N(0,1) + */ + KalmanFilterSpec::MkStepFn + kalman_revert1_mkstep_fn() + { + /* kalman state transition matrix */ + MatrixXd F(2,2); + F << 0.95, 0.05, 0, 1; + + /* state transition noise */ + MatrixXd Q(2,2); + Q << 0.0001, 0.0, 0.0, 0.0; + + /* coupling matrix */ + MatrixXd H(1,2); + H << 1.0, 0.0; + + /* observation errors */ + MatrixXd R(1,1); + R << 0.25; + + return [F, Q, H, R](rp const & sk, + rp const & zkp1) { + KalmanFilterTransition Fk(F, Q); + KalmanFilterObservable Hk = KalmanFilterObservable::keep_all(H, R); + + return KalmanFilterStep(sk, Fk, Hk, zkp1); + }; + } /*kalman_revert1_mkstep_fn*/ + } /*namespace*/ + + TEST_CASE("kalman-revert1", "[kalmanfilter]") { + /* like test "kalman-identity", + * but introduce some system noise. + */ + + constexpr bool c_debug_enabled = false; + scope lscope(XO_DEBUG2(c_debug_enabled, "TEST(kalman_revert)")); + + /* seed for rng */ + uint64_t seed = 14950139742636922572UL; + + /* N(0,1) random numbers */ + auto normal_rng + = (normalgen::make + (seed, + std::normal_distribution(0.0 /*mean*/, + 1.0 /*sdev*/))); + + /* accumulate statistics on observations, + * use as reference when assessing filter behavior + */ + SampleStatistics z_stats; + + /* write output to file - use as baseline for regression testing */ + std::string self_test_name = Catch::getResultCapture().getCurrentTestName(); + + /* write space-delimited output, suitable for gnuplot + * omit always-constant values, rely on unit test verifying these + */ + std::ofstream out(self_test_name); + out << "step z0 x0 P00 K0" << std::endl; + + utc_nanos t0 = timeutil::ymd_midnight(20220707); + + /* estimate x(0). + * x(0)[1] is constant 1, used to achieve mean reversion + */ + VectorXd x0(2); + x0 << 1.0 + normal_rng(), 1.0; + + z_stats.include_sample(x0[0]); + + /* kalman prior : Variance = 1, sdev = 1 */ + MatrixXd P0(2,2); + P0 << 1.0, 0.0, 0.0, 0.0; + + rp s0 + = KalmanFilterStateExt::make(0 /*step#*/, + t0, + x0, + P0, + KalmanFilterTransition(MatrixXd::Zero(1, 1) /*F*/, + MatrixXd::Zero(1, 1) /*Q*/), + MatrixXd::Zero(1, 1) /*K*/, + -1 /*j*/, + nullptr /*zk*/); + + auto mk_step_fn + = kalman_revert1_mkstep_fn(); + + KalmanFilterSpec spec(s0, mk_step_fn); + rp sk = spec.start_ext(); + + for(uint32_t i_step = 1; i_step < 100; ++i_step) { + /* note: for this filter, measurement time doesn't affect behavior */ + utc_nanos tkp1 = sk->tm() + seconds(1); + + VectorXd z(1); + z << 1.0 + normal_rng(); + + z_stats.include_sample(z[0]); + + ref::rp inputk + = KalmanFilterInput::make_present(tkp1, z); + KalmanFilterStep step_spec = spec.make_step(sk, inputk); + rp skp1 = KalmanFilterEngine::step(step_spec); + + if (c_debug_enabled) { + lscope.log("filter", + xtag("step", i_step), + xtag("z", matrix(z)), + xtag("x", matrix(skp1->state_v())), + xtag("P", matrix(skp1->state_cov())), + xtag("K", matrix(skp1->gain()))); + } + + /* headings: step z0 x0 P00 K0 */ + out << i_step + << " " << z(0) + << " " << skp1->state_v()(0) + << " " << skp1->state_cov()(0, 0) + << " " << skp1->gain()(0, 0) + << "\n"; + + REQUIRE(skp1->step_no() == i_step); + REQUIRE(skp1->tm() == tkp1); + REQUIRE(skp1->n_state() == 2); + // + REQUIRE(skp1->state_v().size() == 2); + REQUIRE(skp1->state_v()(1) == 1.0); + // + REQUIRE(skp1->state_cov().rows() == 2); + REQUIRE(skp1->state_cov().cols() == 2); + // test skp1->state_cov()(0,0) vs baseline + REQUIRE(skp1->state_cov()(0, 0) >= 0.0); + REQUIRE(skp1->state_cov()(1, 0) == 0.0); + REQUIRE(skp1->state_cov()(0, 1) == 0.0); + REQUIRE(skp1->state_cov()(1, 1) == 0.0); + // + REQUIRE(skp1->gain().rows() == 2); + REQUIRE(skp1->gain().cols() == 1); + REQUIRE(skp1->gain()(0, 0) > 0.0); + REQUIRE(skp1->gain()(1, 0) == 0.0); + // + REQUIRE(skp1->observable() == -1); + // + + sk = skp1; + } + + out << std::flush; + out.close(); + + /* compare output with regression baseline. + * command like: + * diff kalman-revert1 utestdata/filter/kalman-revert1 + */ + char cmd_buf[256]; + snprintf(cmd_buf, sizeof(cmd_buf), + "diff %s utestdata/filter/%s\n", + self_test_name.c_str(), + self_test_name.c_str()); + + INFO(tostr(self_test_name, xtag("cmd", ccs(cmd_buf)))); + + std::int32_t err = ::system(cmd_buf); + + REQUIRE(err == 0); + } /*TEST_CASE(kalman-drift)*/ + +#ifdef NOT_IN_USE + namespace { + /* step for kalman filter with: + * - two state variables x[0], x[1] + * - identity process model: x(k+1) = F(k).x(k), with + * F(k) = | 1 0 | + * | 0 1 | + * - no process noise + * - two observations z[0], z[1] + * - simple coupling matrix: z(k) = H(k).x(k) + w(k), with + * H(k) = | 1 0 | + * | 0 -1 | + * (so sign of z[1] is reversed w.r.t x[1]) + * + * w(k) = | w1 | with w1 ~ N(0,1) + * | w2 | + */ + KalmanFilterSpec::MkStepFn + kalman_identity2x2_mkstep_fn() + { + /* kalman state transition matrix: use identity <-> state is constant */ + MatrixXd F = MatrixXd::Identity(2, 2); + + /* state transition noise: set to 0 */ + MatrixXd Q = MatrixXd::Zero(2, 2); + + /* two direct observations */ + MatrixXd H = MatrixXd::Constant(2 /*#rows*/, 1 /*#cols*/, 1.0 /*M(i,j)*/); + + /* observation errors: N(0,1) */ + MatrixXd R = MatrixXd::Identity(2, 2); + + return [F, Q, H, R](KalmanFilterState const & sk, + KalmanFilterInput const & zkp1) { + KalmanFilterTransition Fk(F, Q); + KalmanFilterObservable Hk(H, R); + + return KalmanFilterStep(sk, Fk, Hk, zkp1); + }; + } /*kalman_identity2_mkstep_fn*/ + } /*namespace*/ +#endif + } /*namespace ut*/ +} /*namespace xo*/ + +/* end KalmanFilter.test.cpp */ diff --git a/utest/filter_utest_main.cpp b/utest/filter_utest_main.cpp new file mode 100644 index 00000000..9ad0266a --- /dev/null +++ b/utest/filter_utest_main.cpp @@ -0,0 +1,6 @@ +/* @file filter_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end filter_utest_main.cpp */ From 1bd545590afccc62070e4c7603fe1a37b6288db0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 12:01:29 -0400 Subject: [PATCH 0518/2693] bugfix: include paths --- include/xo/reactor/DirectSourcePtr.hpp | 28 +-- include/xo/reactor/LastReducer.hpp | 248 ++++++++++++------------- include/xo/reactor/SecondarySource.hpp | 3 +- 3 files changed, 139 insertions(+), 140 deletions(-) diff --git a/include/xo/reactor/DirectSourcePtr.hpp b/include/xo/reactor/DirectSourcePtr.hpp index db9c7734..8947a74a 100644 --- a/include/xo/reactor/DirectSourcePtr.hpp +++ b/include/xo/reactor/DirectSourcePtr.hpp @@ -2,24 +2,24 @@ #pragma once -#include "reactor/SecondarySource.hpp" -#include "reactor/LastReducer.hpp" -#include "reactor/EventTimeFn.hpp" +#include "SecondarySource.hpp" +#include "LastReducer.hpp" +#include "EventTimeFn.hpp" namespace xo { - namespace reactor { - template - using DirectSource = SecondarySource>>; + namespace reactor { + template + using DirectSource = SecondarySource>>; - /* use when Event is ref::rp for some T */ - template - using DirectSourcePtr = SecondarySource>>; + /* use when Event is ref::rp for some T */ + template + using DirectSourcePtr = SecondarySource>>; - } /*namespace reactor*/ + } /*namespace reactor*/ } /*namespace xo*/ /* end DirectSourcePtr.hpp */ diff --git a/include/xo/reactor/LastReducer.hpp b/include/xo/reactor/LastReducer.hpp index 86a5913c..b18f1a09 100644 --- a/include/xo/reactor/LastReducer.hpp +++ b/include/xo/reactor/LastReducer.hpp @@ -2,153 +2,153 @@ #pragma once -#include "reactor/Reducer.hpp" +#include "Reducer.hpp" #include namespace xo { - namespace reactor { - /* implementation record used in LastReducer. - * LastReducer (see below) remembers a single event, - * + will be updated on successive calls to - * LastReducer.include_event() - * - * need to remember the _first_ (& therefore earliest) - * event timestamp in such a wave, since that establishes when simulator - * should deliver the event -- even if event is subsequently - * overwritten. - * - * once event is delivered, timestamp can reset - * - * otherwise if upstream producer sends events with - * future timestamps, can get indefinite postponement - * with simulation clock failing to catch up to event time. - * - */ - - template - class EventRecd { - public: - using utc_nanos = xo::time::utc_nanos; + namespace reactor { + /* implementation record used in LastReducer. + * LastReducer (see below) remembers a single event, + * + will be updated on successive calls to + * LastReducer.include_event() + * + * need to remember the _first_ (& therefore earliest) + * event timestamp in such a wave, since that establishes when simulator + * should deliver the event -- even if event is subsequently + * overwritten. + * + * once event is delivered, timestamp can reset + * + * otherwise if upstream producer sends events with + * future timestamps, can get indefinite postponement + * with simulation clock failing to catch up to event time. + * + */ - public: - EventRecd() = default; - EventRecd(utc_nanos tm, Event ev) : trigger_tm_{tm}, ev_{ev} {} - EventRecd(utc_nanos tm, Event && ev) : trigger_tm_{tm}, ev_{std::move(ev)} {} + template + class EventRecd { + public: + using utc_nanos = xo::time::utc_nanos; - public: - /* if sim, deliver event when simulation clock reaches - * .trigger_tm; .trigger_tm can be earlier than .ev time - */ - utc_nanos trigger_tm_; - /* event to deliver */ - Event ev_; - }; + public: + EventRecd() = default; + EventRecd(utc_nanos tm, Event ev) : trigger_tm_{tm}, ev_{ev} {} + EventRecd(utc_nanos tm, Event && ev) : trigger_tm_{tm}, ev_{std::move(ev)} {} - /* reducer that just remembers the last event - * - * Require: - * - Event is null-contructible - * - Event is copyable - * - * LastReducer provides reentrancy support. This support doesn't operate - * if Event copy is not deep, e.g. for Event = rpn - * - * .include_event() - * /-------\ -----------------> /------\ - * | empty | | full | - * \-------/ <----------------- \------/ - * . .annex_one() . - * . . - * .is_empty()=true .is_empty()=false - */ - template> - class LastReducer : public ReducerBase { - public: - using utc_nanos = xo::time::utc_nanos; + public: + /* if sim, deliver event when simulation clock reaches + * .trigger_tm; .trigger_tm can be earlier than .ev time + */ + utc_nanos trigger_tm_; + /* event to deliver */ + Event ev_; + }; - public: - LastReducer() = default; - LastReducer(EventTimeFn const & evtfn) : ReducerBase(evtfn) {} + /* reducer that just remembers the last event + * + * Require: + * - Event is null-contructible + * - Event is copyable + * + * LastReducer provides reentrancy support. This support doesn't operate + * if Event copy is not deep, e.g. for Event = rpn + * + * .include_event() + * /-------\ -----------------> /------\ + * | empty | | full | + * \-------/ <----------------- \------/ + * . .annex_one() . + * . . + * .is_empty()=true .is_empty()=false + */ + template> + class LastReducer : public ReducerBase { + public: + using utc_nanos = xo::time::utc_nanos; - bool is_empty() const { return empty_flag_; } - /* require: .is_empty() = false */ - utc_nanos next_tm() const { - return this->last_ev_[this->last_ix_].trigger_tm_; - //return this->event_tm(this->last_ev_[this->last_ix_]); - } - /* #of events stored in this reducer (0 or 1) */ - uint32_t n_event() const { return this->empty_flag_ ? 0 : 1; } + public: + LastReducer() = default; + LastReducer(EventTimeFn const & evtfn) : ReducerBase(evtfn) {} - Event const & last_annexed_ev() const { - return this->last_ev_[1 - this->last_ix_].ev_; - } + bool is_empty() const { return empty_flag_; } + /* require: .is_empty() = false */ + utc_nanos next_tm() const { + return this->last_ev_[this->last_ix_].trigger_tm_; + //return this->event_tm(this->last_ev_[this->last_ix_]); + } + /* #of events stored in this reducer (0 or 1) */ + uint32_t n_event() const { return this->empty_flag_ ? 0 : 1; } - EventRecd & include_event_aux(Event const & ev) { - EventRecd & evr - = this->last_ev_[this->last_ix_]; + Event const & last_annexed_ev() const { + return this->last_ev_[1 - this->last_ix_].ev_; + } - if (this->empty_flag_) { - /* evr.trigger_tm will be preserved across - * successive calls to .include_event(); - * until .annex_one() - */ - evr.trigger_tm_ = this->event_tm(ev); + EventRecd & include_event_aux(Event const & ev) { + EventRecd & evr + = this->last_ev_[this->last_ix_]; - this->empty_flag_ = false; - } + if (this->empty_flag_) { + /* evr.trigger_tm will be preserved across + * successive calls to .include_event(); + * until .annex_one() + */ + evr.trigger_tm_ = this->event_tm(ev); - return evr; - } /*include_event_aux*/ + this->empty_flag_ = false; + } - void include_event(Event const & ev) { - EventRecd & evr - = this->include_event_aux(ev); + return evr; + } /*include_event_aux*/ - evr.ev_ = ev; - } /*include_event*/ + void include_event(Event const & ev) { + EventRecd & evr + = this->include_event_aux(ev); - void include_event(Event && ev) { - EventRecd & evr - = this->include_event_aux(ev); - - evr.ev_ = std::move(ev); - } /*include_event*/ + evr.ev_ = ev; + } /*include_event*/ - Event & annex_one() { - std::uint32_t annexed_ix = this->last_ix_; + void include_event(Event && ev) { + EventRecd & evr + = this->include_event_aux(ev); - /* since .empty_flag is true, - * next call to .include_event_aux() will - * capture new timestamp - */ - this->empty_flag_ = true; - this->last_ix_ = (1 - this->last_ix_); + evr.ev_ = std::move(ev); + } /*include_event*/ - return this->last_ev_[annexed_ix].ev_; - } /*annex_one*/ + Event & annex_one() { + std::uint32_t annexed_ix = this->last_ix_; - // ----- Inherited from ReducerBase ----- + /* since .empty_flag is true, + * next call to .include_event_aux() will + * capture new timestamp + */ + this->empty_flag_ = true; + this->last_ix_ = (1 - this->last_ix_); - //utc_nanos event_tm(Event const & ev) const { return this->event_tm_fn_(ev); } + return this->last_ev_[annexed_ix].ev_; + } /*annex_one*/ - private: - /* true when reducer contains 0 queued events, - * not counting any annexed event - */ - bool empty_flag_ = true; + // ----- Inherited from ReducerBase ----- - /* .last_ev[.last_ix] updated by .include_event() - */ - std::uint32_t last_ix_ = 0; - /* remember two events - * (a) a single queued event (updated by .include_event()) - * (b) a single removed event (reported by .annex_one()) - * - * roles of .last_ev[0], .last_ev[1] reverse each time .annex_one() runs - */ - std::array, 2> last_ev_; - }; /*LastReducer*/ - } /*namespace reactor*/ + //utc_nanos event_tm(Event const & ev) const { return this->event_tm_fn_(ev); } + + private: + /* true when reducer contains 0 queued events, + * not counting any annexed event + */ + bool empty_flag_ = true; + + /* .last_ev[.last_ix] updated by .include_event() + */ + std::uint32_t last_ix_ = 0; + /* remember two events + * (a) a single queued event (updated by .include_event()) + * (b) a single removed event (reported by .annex_one()) + * + * roles of .last_ev[0], .last_ev[1] reverse each time .annex_one() runs + */ + std::array, 2> last_ev_; + }; /*LastReducer*/ + } /*namespace reactor*/ } /*namespace xo*/ /* end LastReducer.hpp */ diff --git a/include/xo/reactor/SecondarySource.hpp b/include/xo/reactor/SecondarySource.hpp index c58c2e92..3b10de63 100644 --- a/include/xo/reactor/SecondarySource.hpp +++ b/include/xo/reactor/SecondarySource.hpp @@ -2,9 +2,8 @@ #pragma once -//#include "time/Time.hpp" +#include "EventSource.hpp" #include "Sink.hpp" -//#include "xo/reactor/DirectSource.hpp" #include "Reactor.hpp" #include "HeapReducer.hpp" #include "xo/callback/CallbackSet.hpp" From 10d49e511cc4f92e131c518721b6bdade27edbdf Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 12:02:52 -0400 Subject: [PATCH 0519/2693] bugfix: spelling - must use Eigen3 w/ find_package() on linux --- src/kalmanfilter/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kalmanfilter/CMakeLists.txt b/src/kalmanfilter/CMakeLists.txt index 30ca8c02..b7036526 100644 --- a/src/kalmanfilter/CMakeLists.txt +++ b/src/kalmanfilter/CMakeLists.txt @@ -28,4 +28,4 @@ xo_dependency(${SELF_LIB} reactor) # ---------------------------------------------------------------- # external dependencies -xo_external_target_dependency(${SELF_LIB} eigen3 Eigen3::Eigen) +xo_external_target_dependency(${SELF_LIB} Eigen3 Eigen3::Eigen) From 9bb3f8c668f09d5a075a8d62584187203719388a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 12:30:37 -0400 Subject: [PATCH 0520/2693] kalmanfilter: genx symlinks -> canned baseline data --- utest/CMakeLists.txt | 5 +- utest/KalmanFilter.test.cpp | 3 - utest/utestdata/filter/kalman-revert1 | 100 ++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 utest/utestdata/filter/kalman-revert1 diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 9f21bfa4..a118b8a7 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -13,9 +13,12 @@ add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) target_code_coverage(${SELF_EXE} AUTO ALL) # ---------------------------------------------------------------- -# create convenience symlink to canned data +# create convenience symlink from build dir back to canned data. +# This is ok since we don't implement install for unit tests #create_symlink("${CMAKE_SOURCE_DIR}/utestdata/" "src/filter/utest/utestdata") +file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/utest/utestdata) +file(CREATE_LINK ${PROJECT_SOURCE_DIR}/utest/utestdata/filter ${PROJECT_BINARY_DIR}/utest/utestdata/filter SYMBOLIC) # ---------------------------------------------------------------- # generic project dependency diff --git a/utest/KalmanFilter.test.cpp b/utest/KalmanFilter.test.cpp index 679f461f..c0b35601 100644 --- a/utest/KalmanFilter.test.cpp +++ b/utest/KalmanFilter.test.cpp @@ -29,9 +29,6 @@ namespace xo { using xo::ref::rp; using xo::log_level; using logutil::matrix; - //using logutil::scope; - //using logutil::tostr; - //using logutil::xtag; using xo::print::ccs; using Eigen::MatrixXd; using Eigen::VectorXd; diff --git a/utest/utestdata/filter/kalman-revert1 b/utest/utestdata/filter/kalman-revert1 new file mode 100644 index 00000000..343af232 --- /dev/null +++ b/utest/utestdata/filter/kalman-revert1 @@ -0,0 +1,100 @@ +step z0 x0 P00 K0 +1 1.33055 1.28103 0.195775 0.783099 +2 1.26559 1.2664 0.103557 0.414227 +3 0.790814 1.1272 0.0680813 0.272325 +4 0.173362 0.933668 0.0493859 0.197543 +5 2.12197 1.11662 0.0378989 0.151595 +6 1.00197 1.09766 0.0301647 0.120659 +7 -0.312247 0.954347 0.0246315 0.0985261 +8 1.76439 1.02286 0.020499 0.081996 +9 0.518422 0.986867 0.0173123 0.0692491 +10 0.756683 0.973864 0.0147938 0.0591754 +11 -0.609708 0.894249 0.0127646 0.0510585 +12 0.330435 0.874259 0.011104 0.0444159 +13 1.03713 0.886639 0.00972751 0.03891 +14 -0.994029 0.827609 0.00857454 0.0342982 +15 1.58353 0.858947 0.00760022 0.0304009 +16 0.494725 0.855945 0.00677073 0.0270829 +17 1.0205 0.866962 0.00606004 0.0242401 +18 1.98651 0.897865 0.00544782 0.0217913 +19 1.70559 0.918761 0.00491797 0.0196719 +20 1.44881 0.932201 0.00445755 0.0178302 +21 2.16318 0.955508 0.00405605 0.0162242 +22 0.353787 0.948782 0.00370485 0.0148194 +23 1.72676 0.961879 0.00339684 0.0135874 +24 -1.55312 0.932313 0.00312606 0.0125043 +25 -0.523229 0.918847 0.00288753 0.0115501 +26 1.81676 0.932476 0.00267702 0.0107081 +27 1.65387 0.943006 0.00249094 0.00996377 +28 -0.766534 0.929922 0.00232623 0.00930491 +29 1.1143 0.935004 0.00218024 0.00872095 +30 0.908743 0.938011 0.0020507 0.00820282 +31 1.80832 0.947825 0.00193566 0.00774263 +32 1.61306 0.955293 0.00183339 0.00733354 +33 1.10591 0.958563 0.0017424 0.00696961 +34 1.54404 0.964512 0.0016614 0.00664561 +35 1.47595 0.969526 0.00158925 0.00635699 +36 -1.16643 0.958012 0.00152494 0.00609975 +37 2.772 0.970748 0.00146759 0.00587036 +38 0.0147533 0.966786 0.00141643 0.00566572 +39 1.5648 0.971716 0.00137077 0.00548308 +40 0.758209 0.971987 0.00133001 0.00532003 +41 0.602221 0.971467 0.0012936 0.00517441 +42 1.02795 0.973171 0.00126108 0.00504433 +43 1.61126 0.977651 0.00123203 0.0049281 +44 3.23237 0.98964 0.00120606 0.00482423 +45 -1.20629 0.979766 0.00118284 0.00473137 +46 2.34054 0.987098 0.00116209 0.00464835 +47 1.886 0.991852 0.00114353 0.00457412 +48 1.62241 0.9951 0.00112693 0.00450773 +49 0.10733 0.991395 0.00111209 0.00444835 +50 0.111089 0.987954 0.00109881 0.00439523 +51 -0.045289 0.984062 0.00108693 0.00434771 +52 0.339478 0.98208 0.0010763 0.0043052 +53 -0.109018 0.978316 0.00106679 0.00426715 +54 -1.15684 0.970358 0.00105828 0.00423311 +55 -0.289587 0.966538 0.00105066 0.00420265 +56 1.04772 0.968543 0.00104385 0.00417538 +57 -0.0931435 0.965703 0.00103774 0.00415098 +58 2.56415 0.974011 0.00103228 0.00412914 +59 0.352559 0.972751 0.0010274 0.00410959 +60 0.173725 0.970838 0.00102302 0.00409209 +61 0.80765 0.971625 0.00101911 0.00407643 +62 2.41789 0.978913 0.0010156 0.0040624 +63 2.63322 0.986663 0.00101246 0.00404985 +64 2.59753 0.993833 0.00100965 0.00403861 +65 0.530642 0.992274 0.00100714 0.00402855 +66 1.24782 0.993686 0.00100489 0.00401955 +67 0.695031 0.992802 0.00100287 0.00401149 +68 2.80672 1.00042 0.00100107 0.00400427 +69 0.122651 0.996894 0.000999451 0.0039978 +70 1.22154 0.997945 0.000998005 0.00399202 +71 0.269797 0.995145 0.00099671 0.00398684 +72 1.78662 0.998538 0.00099555 0.0039822 +73 0.4817 0.996555 0.000994512 0.00397805 +74 1.05221 0.996948 0.000993582 0.00397433 +75 1.75822 1.00012 0.00099275 0.003971 +76 1.31536 1.00137 0.000992005 0.00396802 +77 1.29006 1.00244 0.000991338 0.00396535 +78 0.857458 1.00175 0.000990741 0.00396296 +79 0.269498 0.998761 0.000990206 0.00396082 +80 0.131184 0.995388 0.000989727 0.00395891 +81 0.736283 0.994592 0.000989298 0.00395719 +82 -0.32478 0.989642 0.000988914 0.00395566 +83 1.3352 0.991525 0.00098857 0.00395428 +84 0.279734 0.989133 0.000988263 0.00395305 +85 1.74807 0.992673 0.000987987 0.00395195 +86 1.82522 0.996328 0.00098774 0.00395096 +87 -0.281213 0.991464 0.000987519 0.00395008 +88 1.61161 0.994338 0.000987322 0.00394929 +89 2.63571 1.0011 0.000987144 0.00394858 +90 -0.139888 0.996542 0.000986986 0.00394794 +91 3.48906 1.00655 0.000986844 0.00394738 +92 2.01113 1.01019 0.000986717 0.00394687 +93 1.84914 1.01299 0.000986603 0.00394641 +94 0.480262 1.01025 0.000986501 0.003946 +95 1.45288 1.01148 0.00098641 0.00394564 +96 -0.0838873 1.00659 0.000986328 0.00394531 +97 -0.570744 1.00004 0.000986255 0.00394502 +98 -0.214659 0.995244 0.000986189 0.00394476 +99 1.7904 0.998618 0.000986131 0.00394452 From 5ae6f41e63bae9abd468bcc787e314ca0542f3f3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 13:34:53 -0400 Subject: [PATCH 0521/2693] + xo_cxx_toplevel_options() to consolidate boilerplate --- cmake/xo_macros/xo-project-macros.cmake | 2 ++ cmake/xo_macros/xo_cxx.cmake | 30 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 cmake/xo_macros/xo-project-macros.cmake diff --git a/cmake/xo_macros/xo-project-macros.cmake b/cmake/xo_macros/xo-project-macros.cmake new file mode 100644 index 00000000..a18c9b8d --- /dev/null +++ b/cmake/xo_macros/xo-project-macros.cmake @@ -0,0 +1,2 @@ +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index 2f9560db..1e308aa0 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -1,4 +1,16 @@ +macro(xo_cxx_toplevel_options) + enable_language(CXX) + xo_toplevel_compile_options() + xo_toplevel_testing_options() +endmacro() + +macro(xo_toplevel_testing_options) + enable_testing() + add_code_coverage() + add_code_coverage_all_targets(EXCLUDE /nix/store* utest/*) +endmacro() + macro(xo_toplevel_compile_options) define_property( TARGET @@ -214,6 +226,24 @@ endmacro() # e.g. # - target=xo_pyutil cmake target name for this library # +macro(xo_add_headeronly_library4 target projectTargets) + add_library(${target} INTERFACE) + + set_property( + TARGET ${target} + PROPERTY xo_deps "${target}") + set_property( + TARGET ${target} + PROPERTY xo_srcdir ${PROJECT_SOURCE_DIR}) + set_property( + TARGET ${target} + PROPERTY xo_bindir ${PROJECT_BINARY_DIR}) + + xo_include_headeronly_options(${target}) + + xo_install_library4(${target} ${projectTargets}) +endmacro() + macro(xo_add_headeronly_library target) add_library(${target} INTERFACE) From 64842065a39588695f4aba6eed712b3a4c55c3b4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 13:36:51 -0400 Subject: [PATCH 0522/2693] bugfix: + install for xo-project-macros.cmake --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 197416e8..e7e30b56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ set(XO_PROJECT_NAME xo_macros) install( FILES + "cmake/xo_macros/xo-project-macros.cmake" "cmake/xo_macros/xo_cxx.cmake" "cmake/xo_macros/code-coverage.cmake" PERMISSIONS OWNER_READ GROUP_READ WORLD_READ From e281bc9213ec0b83711a0c40b564d968e2f9d0d6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 13:46:10 -0400 Subject: [PATCH 0523/2693] initial implementation --- CMakeLists.txt | 20 + cmake/xo_distributionConfig.cmake.in | 4 + include/xo/distribution/Distribution.hpp | 20 + include/xo/distribution/Empirical.hpp | 41 + include/xo/distribution/ExplicitDist.hpp | 748 ++++++++++++++++++ include/xo/distribution/Exponential.hpp | 94 +++ include/xo/distribution/KolmogorovSmirnov.hpp | 257 ++++++ include/xo/distribution/Normal.hpp | 51 ++ include/xo/distribution/StdEmpirical.hpp | 226 ++++++ include/xo/distribution/Uniform.hpp | 47 ++ 10 files changed, 1508 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/xo_distributionConfig.cmake.in create mode 100644 include/xo/distribution/Distribution.hpp create mode 100644 include/xo/distribution/Empirical.hpp create mode 100644 include/xo/distribution/ExplicitDist.hpp create mode 100644 include/xo/distribution/Exponential.hpp create mode 100644 include/xo/distribution/KolmogorovSmirnov.hpp create mode 100644 include/xo/distribution/Normal.hpp create mode 100644 include/xo/distribution/StdEmpirical.hpp create mode 100644 include/xo/distribution/Uniform.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..467f52cc --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,20 @@ +# xo-distribution/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_distribution VERSION 1.0) + +# common XO cmake macros (see github.com:Rconybea/xo-cmake) +include(xo_macros/xo-project-macros) + +xo_cxx_toplevel_options() + +#add_subdirectory(example) +#add_subdirectory(uteest) + +xo_add_headeronly_library4(xo_distribution ${PROJECT_NAME}Targets) +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- + +#install(Targets ex1 DESTINATION bin/distribution/example) diff --git a/cmake/xo_distributionConfig.cmake.in b/cmake/xo_distributionConfig.cmake.in new file mode 100644 index 00000000..3c6c4bd4 --- /dev/null +++ b/cmake/xo_distributionConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/xo_distributionTargets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/distribution/Distribution.hpp b/include/xo/distribution/Distribution.hpp new file mode 100644 index 00000000..5768e9d9 --- /dev/null +++ b/include/xo/distribution/Distribution.hpp @@ -0,0 +1,20 @@ +/* @file Distribution.hpp */ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" + +namespace xo { + namespace distribution { + /* abstract api for a cumulative probability distribution. + * over supplied Domain + */ + template + class Distribution : public ref::Refcount { + public: + virtual double cdf(Domain const & x) const = 0; + }; /*Distribution*/ + } /*namespace distribution*/ +} /*namespace xo*/ + +/* end Distribution.hpp */ diff --git a/include/xo/distribution/Empirical.hpp b/include/xo/distribution/Empirical.hpp new file mode 100644 index 00000000..2b7b9d67 --- /dev/null +++ b/include/xo/distribution/Empirical.hpp @@ -0,0 +1,41 @@ +/* @file Empirical.hpp */ + +#pragma once + +#include "xo/distribution/Distribution.hpp" +#include "xo/ordinaltree/RedBlackTree.hpp" +#include "xo/indentlog/scope.hpp" +#include +#include + +namespace xo { + namespace distribution { + + /* representation for counter, + * recording #of samples with the same value + */ + using CounterRep = uint32_t; + + /* counter; for use with StdEmpirical distribution below + */ + class Counter { + public: + Counter() = default; + Counter(CounterRep n) : count_(n) {} + + CounterRep count() const { return count_; } + + void incr() { ++count_; } + + operator CounterRep () const { return count_; } + + Counter & operator+=(CounterRep n) { count_ += n; return *this; } + + private: + CounterRep count_ = 0; + }; /*Counter*/ + + } /*namespace disitribution*/ +} /*namespace xo*/ + +/* end Empirical.hpp */ diff --git a/include/xo/distribution/ExplicitDist.hpp b/include/xo/distribution/ExplicitDist.hpp new file mode 100644 index 00000000..516413e1 --- /dev/null +++ b/include/xo/distribution/ExplicitDist.hpp @@ -0,0 +1,748 @@ +/* file ExplicitDist.hpp + * + * author: Roland Conybeare, Oct 2022 + */ + +#pragma once + +#include "Distribution.hpp" +#include "Normal.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/vector.hpp" +#include "xo/indentlog/print/tostr.hpp" +#include +#include +#include + +namespace xo { + using xo::xtag; + + namespace distribution { + class ProbabilityBucket { + public: + ProbabilityBucket() = default; + + double weight() const { return wt_; } + double cdf() const { return cdf_; } + + /* note: when calling this, must invalidate ExplicitDist.cdf_valid_flag */ + void scale_weight(double k) { this->wt_ *= k; } + + void assign_weight(double w) { this->wt_ = w; } + /* implementation method: only ExplicitDist.renormalize() should call this */ + void assign_cdf(double x) { this->cdf_ = x; } + + void display(std::ostream & os) const { + using xo::xtag; + + os << "wt_) + << xtag("cdf", this->cdf_) + << ">"; + } /*display*/ + + private: + /* probability assigned to this bucket. + * updated in-place by ExplicitDist.include_sample() + */ + double wt_ = 0.0; + /* cumulative probability assigned to this bucket b + * and all buckets b[i] for domain values < b + * + * invalidated by ExplicitDist.scale_bucket() + * see ExplicitDist. + */ + double cdf_ = 0.0; + }; /*ProbabilityBucket*/ + + inline std::ostream & operator<<(std::ostream & os, ProbabilityBucket const & x) { + x.display(os); + return os; + } /*operator<<*/ + + namespace detail { + /* three kinds of index here: + * 1. lo: non-negative index into ExplicitDist.lo_v_[] + * 2. hi: non-negative index into ExplicitDist.hi_v_[] + * 3. signed: if >=0: index into ExplicitDist.hi_v_[] + * if <0: index into ExplicitDist.lo_v_[] + * + * with lz=.lo_v.size(), hz=hi_v.size(): + * + * .hi_v[0] .hi_v[hz-1] + * | v v + * | + * +--+--+ +--+--|--+--+ +--+ + * | | | ... | | | | | ... | | + * +--+--+ +--+--|--+--+ +--+ + * ^ ^ + * .lo_v[lz-1] .lo_v[0] + * + * ^ ^ ^ ^ + * signed -lz -1| 0 ... hz-1 + */ + class ExplicitDistIndexUtil { + public: + static size_t signed2hi(int32_t signed_ix) { return signed_ix; } + static size_t signed2lo(int32_t signed_ix) { return -signed_ix - 1; } + static int32_t lo2signed(size_t lo_ix) { return -static_cast(lo_ix)-1; } + static int32_t hi2signed(size_t hi_ix) { return hi_ix; } + + static ProbabilityBucket const * signed2bucket_c(int32_t signed_ix, + std::vector const * lo_v, + std::vector const * hi_v) { + if (signed_ix >= 0) { + size_t hi_ix = signed2hi(signed_ix); + + if (hi_v && (hi_ix < hi_v->size())) + return &(*hi_v)[hi_ix]; + } else { + size_t lo_ix = signed2lo(signed_ix); + + if (lo_v && (lo_ix < lo_v->size())) + return &(*lo_v)[lo_ix]; + } + + return nullptr; + } /*signed2bucket_c*/ + + static ProbabilityBucket * signed2bucket(int32_t signed_ix, + std::vector * lo_v, + std::vector * hi_v) { + ProbabilityBucket const * b = signed2bucket_c(signed_ix, lo_v, hi_v); + return const_cast(b); + } /*signed2bucket*/ + + }; /*ExplicitDistIndexUtil*/ + + class ExplicitDistIterator : public ExplicitDistIndexUtil { + public: + ExplicitDistIterator() = default; + ExplicitDistIterator(int32_t signed_ix, + std::vector * lo_v, + std::vector * hi_v) + : signed_ix_{signed_ix}, p_lo_v_{lo_v}, p_hi_v_{hi_v} {} + + ProbabilityBucket & operator*() { + ProbabilityBucket * pb = signed2bucket(this->signed_ix_, + this->p_lo_v_, + this->p_hi_v_); + if (!pb) { + throw std::runtime_error("ExplicitDistIterator: attempt to deref invalid iterator"); + } + + return *pb; + } /*operator**/ + + ExplicitDistIterator & operator++() { ++(this->signed_ix_); return *this; } + ExplicitDistIterator & operator--() { --(this->signed_ix_); return *this; } + + private: + /* signed iterator */ + int32_t signed_ix_ = 0; + /* will be ExplicitDist.lo_v[] */ + std::vector * p_lo_v_ = nullptr; + /* will be ExplicitDist.hi_v[] */ + std::vector * p_hi_v_ = nullptr; + }; /*ExplicitDistIterator*/ + } /*namespace detail*/ + + /* explicit distribution with buckets. + * (if you want to avoid bucketing at the expense of memory, + * see StdEmpirical) + * + * sample-independent buckets can be faster to the extent + * {#of buckets} << {#of samples} + * + * in particular + * | StdEmpirical | PackedEmpirical + * ------------------------------+--------------+----------------- + * update existing sample/bucket | O(log(n)) | O(1) + * create new sample/bucket | O(log(n)) | O(log(n)) + * cdf | O(log(n)) | O(n) + * + * PackedEmpirical offers scaling + renormalization + * + * since .cdf() is slow, .ks_stat_1sided() is supported only in StdEmpirical. + * (could generalize in future to some other implementation with fast .cdf()) + * + * Require: + * Domain is something with metric, e.g. int|double. + * categorical domain not supported here. + * + * see also: statistics/Histogram. Histogram keeps more per-bucket statistics + */ + template + class ExplicitDist : public Distribution, public detail::ExplicitDistIndexUtil { + public: + using WeightVector = std::vector; + using iterator = detail::ExplicitDistIterator; + + public: + static ref::rp make(Domain bucket_dx, Domain ref_value) { + return new ExplicitDist(bucket_dx, ref_value); + } + /* create distribution with n buckets of width bucket_dx, + * covering range [ref_value, ref_value + n * bucket_dx] + */ + static ref::rp make_n(size_t n, Domain bucket_dx, Domain ref_value) { + return new ExplicitDist(n, bucket_dx, ref_value); + } + + size_t n_bucket() const { return this->lo_v_.size() + this->hi_v_.size(); } + + /* lub domain value with .cdf(lo) = 0 */ + Domain lo() const { + std::size_t lz = this->lo_v_.size(); + + return this->ref_value_ - lz * this->bucket_dx_; + } /*lo*/ + + /* glb domain value with .cdf(hi) = 1 */ + Domain hi() const { + std::size_t hz = this->hi_v_.size(); + + return this->ref_value_ + hz * this->bucket_dx_; + } /*hi*/ + + Domain bucket_lo(int32_t signed_ix) const { + return this->ref_value_ + signed_ix * this->bucket_dx_; + } /*bucket_lo*/ + + Domain bucket_mid(int32_t signed_ix) const { + return this->ref_value_ + (0.5 + signed_ix) * this->bucket_dx_; + } /*bucket_mid*/ + + Domain bucket_hi(int32_t signed_ix) const { + return this->bucket_lo(signed_ix + 1); + } /*bucket_hi*/ + + iterator begin() { return iterator(lo2signed(this->lo_v_.size() - 1), + &(this->lo_v_), + &(this->hi_v_)); } + iterator end() { return iterator(hi2signed(this->hi_v_.size()), + &(this->lo_v_), + &(this->hi_v_)); } + + /* probability density at domain value x */ + double density(Domain const & x) const { + /* careful! may need to renormalize */ + { + ExplicitDist * self = const_cast *>(this); + + self->check_renormalize(); + } + + auto v = this->lookup_bucket(x); + ProbabilityBucket const * b = v.first; + + if (b) { + /* probability density is constant within each bucket */ + return b->weight() / this->bucket_dx_; + } else { + return 0.0; + } + } /*density*/ + + /* pair {bucket glb, density} for all buckets, in increasing domain order */ + std::vector> density_v() const { + /* careful! may need to renormalize */ + { + ExplicitDist * self = const_cast *>(this); + + self->check_renormalize(); + } + + size_t n = this->n_bucket(); + std::vector> retval(n); + + double w2d = 1.0 / this->bucket_dx_; + + for(size_t i=0; ibucket_lo(i); + ProbabilityBucket const * b = this->lookup_signed_bucket(i); + + assert(b); + + double density = b->weight() * w2d; + + retval[i] = std::make_pair(lo, density); + } + + return retval; + } /*density_v*/ + + /* return: + * .first signed bucket index ix; + * refers to .lo_v[1-ix] if ix<0; + * refers to .hi_v[+ix] if ix>=0 + * .second fractional weight within bucket associated with x. + * not scaled by ProbabilityWeight.weight + */ + std::pair signed_bucket_index(Domain const & x) const { + double ix_f = ::floor((x - this->ref_value_) / this->bucket_dx_); + int32_t ix = ix_f; + + /* + * ^ + * |............ + * | : | + * | wt : | unit area + * | : | + * +-----------+ + * bucket_lo ^ bucket_lo + .bucket_dx + * x + */ + + Domain bucket_lo = this->ref_value_ + (ix * this->bucket_dx_); + double wt = (x - bucket_lo) / this->bucket_dx_; + + return std::make_pair(ix, wt); + } /*signed_bucket_index*/ + + ProbabilityBucket const * lookup_signed_bucket(int32_t signed_ix) const { + return signed2bucket_c(signed_ix, &(this->lo_v_), &(this->hi_v_)); + } /*lookup_signed_bucket*/ + + /* non-const version with write access */ + ProbabilityBucket * lookup_signed_bucket(int32_t signed_ix) { + return signed2bucket(signed_ix, &(this->lo_v_), &(this->hi_v_)); + } /*lookup_signed_bucket*/ + + /* return null pointer if bucket that would contain x not represented + * .second is fractional weight (relative to unity) within selected bucket + */ + std::pair lookup_bucket(Domain const & x) const { + /* signed index. + * ix>=0 => .lo_v[] + * ix< 0 => .hi_v[] + */ + std::pair v = this->signed_bucket_index(x); + + int32_t ix = v.first; + double fraction_wt = v.second; + ProbabilityBucket const * pw = this->lookup_signed_bucket(ix); + + return std::make_pair(pw, fraction_wt); + } /*lookup_bucket*/ + + /* non-const version */ + std::pair lookup_bucket(Domain const & x) { + ExplicitDist const * c_self = this; + + std::pair v = c_self->lookup_bucket(x); + ProbabilityBucket * b = const_cast(v.first); + double fraction_wt = v.second; + + return std::make_pair(b, fraction_wt); + } /*lookup_bucket*/ + + /* like .lookup_bucket(), but create bucket if not already present + * .second reports fractional weight (relative to unity) within bucket + * that contains x. + */ + std::pair establish_bucket(Domain const & x) { + /* signed index. + * ix>=0 => .lo_v[] + * ix< 0 => .hi_v[] + */ + std::pair v = this->signed_bucket_index(x); + + int32_t ix = v.first; + double fraction_wt = v.second; + ProbabilityBucket * pw = nullptr; + + if (ix >= 0) { + size_t hi_ix = signed2hi(ix); + + if (hi_ix >= this->hi_v_.size()) { + /* need to expand .hi_v[] */ + this->hi_v_.resize(hi_ix+1); + } + + pw = &(this->hi_v_[hi_ix]); + } else { + size_t lo_ix = signed2lo(ix); + + if (lo_ix >= this->lo_v_.size()) { + /* need to expand .lo_v[] */ + this->lo_v_.resize(lo_ix+1); + } + + pw = &(this->lo_v_[lo_ix]); + } + + return std::make_pair(pw, fraction_wt); + } /*establish_bucket*/ + + void scale_bucket_by_signed_index(int32_t signed_ix, double k) { + ProbabilityBucket * b = this->lookup_signed_bucket(signed_ix); + + if (b) { + b->scale_weight(k); + + this->cdf_valid_flag_ = false; + } else { + /* if bucket isn't represented, then corresponding weight is zero, + * both before and after scaling. In this special case + * representation didn't change -> don't invalidate cdf + */ + } + } /*scale_bucket_by_signed_index*/ + + /* scale probability assigned to bucket for domain value x, by factor k */ + void scale_bucket(Domain const & x, double k) { + assert(k >= 0.0); + + std::pair v = this->signed_bucket_index(x); + + this->scale_bucket_by_signed_index(v.first, k); + } /*scale_bucket*/ + + /* scale probability weight for buckets in [lo, hi] by fn(p) + * for a point p in each bucket, however: + * under no circumstances try to evaluate fn() outside [lo, hi] + * (relevant if either lo/hi fall inside a bucket) + */ + template + void scale_interval(Domain const & lo, + Domain const & hi, + Function && fn) + { + XO_SCOPE_DISABLED(lscope); + + /* note: using inclusive upper index bounds here; + * variying from idiomatic c++ style for symmetry + */ + + int32_t min_bucket_ix = lo2signed(this->lo_v_.size() - 1); + int32_t max_bucket_ix = hi2signed(this->hi_v_.size() - 1); + + int32_t lo_ix = this->signed_bucket_index(lo).first; + int32_t hi_ix = this->signed_bucket_index(hi).first; + + /* start_ix: lowest explicit bucket associating with [lo, hi) */ + int32_t start_ix = std::max(min_bucket_ix, lo_ix + 1); + /* end_ix: highest explicit bucket associating with [lo, hi) */ + int32_t end_ix = std::min(max_bucket_ix, hi_ix); + + if (lscope.enabled()) { + lscope.log(xtag("min_bucket_ix", min_bucket_ix), + xtag("max_bucket_ix", max_bucket_ix), + xtag("lo_ix", lo_ix), + xtag("hi_ix", hi_ix), + xtag("start_ix", start_ix), + xtag("end_ix", end_ix)); + } + + /* for endpoints: avoid evaluating fn() outside [lo, hi] */ + if (min_bucket_ix <= lo_ix) { + double lo_k = fn(lo); + + if (lscope.enabled()) + lscope.log("A", xtag("lo_ix", lo_ix), xtag("lo_k", lo_k)); + + this->scale_bucket_by_signed_index(lo_ix, lo_k); + } + + for(int32_t ix = start_ix; ix <= end_ix; ++ix) { + double k = fn(this->bucket_mid(ix)); + + if (lscope.enabled()) + lscope.log("B", xtag("ix", ix), xtag("k", k)); + + this->scale_bucket_by_signed_index(ix, k); + } + + /* for endpoints: avoid evaluating fn() outside [lo, hi] */ + if (hi_ix <= max_bucket_ix) { + double hi_k = fn(hi); + + if (lscope.enabled()) + lscope.log("C", xtag("hi_ix", hi_ix), xtag("hi_k", hi_k)); + + this->scale_bucket_by_signed_index(hi_ix, hi_k); + } + } /*scale_interval*/ + + template + void scale_all(Function && fn) + { + this->scale_interval(this->lo(), this->hi(), fn); + } /*scale_all*/ + + /* convenience: scale by scaled normal cdf. + * + * support use case where: + * 1. explicit dist represents distribution of asset value; + * 2. assume one party A to trade/order has noisy signal, + * with normally-distributed error around (unknown) true value s, + * with variance o^2; + * 3. observe a trade/order at some price p, + * giving us one-sided information about A's knowledge in two scenarios: + * i. A trades + * ii. A does not trade + * + * We will be applying Bayes' rule to update prior. + * If buy(p) = {A buys}, + * N(x) is cumulative normal distribution: + * N(x) -> 0 as x -> -oo; + * N(x) -> 1 as x -> +oo; + * + * -1/2 2 + * (d/dx)N(x) = (2.pi) . exp(-x /2) + * then: + * P{s=s'} + * P{s=s'|buy(p)} = ---------.P{buy(p)|s=s'} + * P{buy(p)} + * + * P{s=s'} + * = ---------.N[(1/o)(s'-p)], + * P{buy(p)} + * + * In this scenario, P{s=s'} is our prior, represented by distribution *this. + * The unconditional probability P{buy(p)} is not a function of s', so: + * + * / + * | + * P{buy(p)} = | P{s=s'} . N[(1/o)(s'-p)] . ds' + * | + * / + * + * in other words can scale explicit prior *this by N[(1/o)(s'-p)] then renormalize + * so total probability weight is 1. + * + * Conversely, if event is {A sells}, similar argument leads to scaling + * by N[(1/o)(p-s')] + * + * sign. scale by N[(1/sigma).sign.(x-mean)] + */ + void scale_by_normal_cdf(int sign, Domain const & mean, Domain const & sigma) { + auto fn([sign, mean, sigma](Domain const & s) { + return Normal::cdf_impl(sign * (s - mean) / sigma); + }); + + this->scale_all(fn); + } /*scale_by_normal_cdf*/ + + // ----- inherited from Distribution ----- + + /* note: marked const; actually "logically const" */ + virtual double cdf(Domain const & x) const override { + /* .cdf() is slow here, because partial sums aren't stored + * --> have to sum over O(n) buckets + */ + { + ExplicitDist * self = const_cast *>(this); + + self->check_renormalize(); + } + + std::pair v = this->lookup_bucket(x); + + ProbabilityBucket const * b = v.first; + double fraction_wt = v.second; + + if (b) { + /* for bucket that x belongs to, treat as uniformly distributed + * fraction_wt = 1.0 -> 100% of b -> b.cdf() + * fraction_wt = 0.0 -> 0% of b -> b.cdf() - b.weight() + */ + return b->cdf() + (fraction_wt - 1.0) * b->weight(); + } else { + if (x >= this->ref_value_) { + /* x falls above farthest .hi_v[] */ + return 1.0; + } else { + /* x falls below farthest .lo_v[] */ + return 0.0; + } + } + } /*cdf*/ + + /* O(n). restores .cdf_valid_flag */ + void renormalize() { + /* [1] compute sum of probability weights + * [2] rescale all buckets so sum is 1. + * [3] restore .cdf_valid_flag + */ + + /* [1] */ + double lo_sum_prob = 0.0; + for (ProbabilityBucket & b : this->lo_v_) { + assert(lo_sum_prob >= 0.0); + lo_sum_prob += b.weight(); + } + + double hi_sum_prob = 0.0; + for (ProbabilityBucket & b : this->hi_v_) { + assert(hi_sum_prob >= 0.0); + hi_sum_prob += b.weight(); + } + + assert(lo_sum_prob + hi_sum_prob > 0.0); + + /* [2] */ + double renorm_factor = 1.0 / (lo_sum_prob + hi_sum_prob); + + { + double lo_tail = lo_sum_prob; + + /* iterate over buckets < .ref_value, in /descending/ domain order */ + for (ProbabilityBucket & b : this->lo_v_) { + b.scale_weight(renorm_factor); + b.assign_cdf(lo_tail); + + /* reduce tail after assign cdf to b, since + * lo_tail includes b's probability weight + */ + lo_tail -= b.weight(); + + /* numerical roundoff can cause this */ + if (lo_tail < 0.0) + lo_tail = 0.0; + } + } + + { + double cum_prob = lo_sum_prob; + + /* iterate over buckets >= .refvalue, in /ascending/ domain order */ + for (ProbabilityBucket & b : this->hi_v_) { + b.scale_weight(renorm_factor); + + /* increase cum_prob before assign cdf to b, + * since b.cdf should include b's probability weight + */ + cum_prob += b.weight(); + + /* numerical roundoff can cause this */ + if (cum_prob >= 1.0) + cum_prob = 1.0; + + b.assign_cdf(cum_prob); + } + } + + /* [3] */ + this->cdf_valid_flag_ = true; + } /*renormalize*/ + + void check_renormalize() { + if (!this->cdf_valid_flag_) { + this->renormalize(); + } + } /*check_renormalize*/ + + void display(std::ostream & os) const { + os << "cdf_valid_flag_); + os << xtag("bucket_dx", this->bucket_dx_); + os << xtag("ref_value", this->ref_value_); + os << xtag("lz", this->lo_v_.size()); + os << xtag("hz", this->hi_v_.size()); + os << xtag("lo_v", this->lo_v_); + os << xtag("hi_v", this->hi_v_); + os << ">"; + } /*display*/ + + std::string display_string() const { return xo::tostr(*this); } + + private: + ExplicitDist(Domain bucket_dx, Domain ref_value) + : cdf_valid_flag_{true}, + bucket_dx_{bucket_dx}, + ref_value_{ref_value}, + lo_v_{}, + hi_v_{1} + { + assert(bucket_dx_ > 0.0); + + /* must have at least one bucket, since need total probability weight = 1 */ + this->hi_v_[0].assign_weight(1.0); + this->hi_v_[0].assign_cdf(1.0); + } + + ExplicitDist(size_t n, Domain bucket_dx, Domain ref_value) + : cdf_valid_flag_{true}, + bucket_dx_{bucket_dx}, + ref_value_{ref_value}, + lo_v_{}, + hi_v_{n} + { + assert(bucket_dx > 0.0); + /* must have at least one bucket, since need total probability weight = 1 */ + assert(n > 0); + + double w = 1.0 / n; + + for(size_t i = 0; ihi_v_[i].assign_weight(w); + this->hi_v_[i].assign_cdf((i+1)*w); + } + } /*ctor*/ + + private: + /* with lz=.lo_v.size(), hz=hi_v.size(): + * + * .ref_value - .bucket_dx * lz + * | .ref_value + * | | .hi_v[0] .hi_v[hz-1] + * v v v v + * | + * +--+--+ +--+--|--+--+ +--+ + * | | | ... | | | | | ... | | + * +--+--+ +--+--|--+--+ +--+ + * | + * ^ ^ ^ + * .lo_v[lz-1] .lo_v[0] .ref_value + .bucket_dx * hz + * + */ + + /* .cdf_valid_flag: + * -> false whenever .scale_bucket() runs. + * -> true whenever .renormalize() runs. + * + * if false, then *this is 'not a probability distribution': + * Sum b[i].weight != 1.0 (summing over probability buckets .lo_v[], .hi_v[]) + * i + */ + bool cdf_valid_flag_ = true; + + /* width of each bucket */ + Domain bucket_dx_; + + /* natural value here would be 0. + * use .pos_v[] for values >= .ref_value + * use .neg_v[] for values < .ref_value + */ + Domain ref_value_; + + /* buckets for domain values < .ref_value + * + * with dx = .bucket_dx + * .lo_v[i] represents weights in range + * [.ref_value - (i+1) * dx, .ref_value - i * dx) + */ + WeightVector lo_v_; + + /* buckets for domain values > .ref_value + * + * with dx = .bucket_dx + * .hi_v[i] represents weights in range + * [.ref_value + i * dx, .ref_value + (i+1) * dx)) + */ + WeightVector hi_v_; + + }; /*ExplicitDist*/ + + template + inline std::ostream & + operator<<(std::ostream & os, ExplicitDist const & x) { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace distribution*/ +} /*namespace xo*/ + +/* end ExplicitDist.hpp */ diff --git a/include/xo/distribution/Exponential.hpp b/include/xo/distribution/Exponential.hpp new file mode 100644 index 00000000..f8dd2594 --- /dev/null +++ b/include/xo/distribution/Exponential.hpp @@ -0,0 +1,94 @@ +/* @file Exponential.hpp */ + +#pragma once + +#include "xo/distribution/Distribution.hpp" +#include +#include + +namespace xo { + namespace distribution { + /* Exponential probability distribution */ + class Exponential : public Distribution { + public: + explicit Exponential(double lm) : lambda_(lm) {} + + /* exponential probability density: + * + * -lm.x + * p(x) = { lm . e , x > 0 + * { 0 , x <= 0 + * + */ + static double density_impl(double lambda, double x) { + if(x <= 0.0) + return 0.0; + + return lambda * ::exp(-lambda * x); + } /*density_impl*/ + + /* exponential distribution: + * + * -lm.x + * F(x) = 1 - e , x > 0 + * F(x) = 0 , x <= 0 + */ + static double distr_impl(double lambda, double x) { + if(x <= 0.0) + return 0.0; + + return 1.0 - ::exp(-lambda * x); + } /*distr_impl*/ + + /* compute x: F(x)=y, where F(x) + * is the cumulative exponential probability distributio. + * + * -lm.x + * F(x) = 1 - e , x>0 + * + * -lm.x + * e = 1 - F(x) + * + * -lm.x = ln(1 - F(x)) + * + * x = -ln(1 - F(x)) / lm, 0 < F(x) < 1 + */ + static double distr_inverse_impl(double lambda, double Fx) { + if(Fx < 0.0) + return -1.0 * std::numeric_limits::infinity(); + if(Fx >= 1.0) + return +1.0 * std::numeric_limits::infinity(); + + return (-1.0 / lambda) * ::log(1.0 - Fx); + } /*distr_inverse_impl*/ + + double lambda() const { return lambda_; } + + double density(double x) const { + return density_impl(this->lambda_, x); + } /*density*/ + + double distribution(double x) const { + return distr_impl(this->lambda_, x); + } /*distribution*/ + + double distribution_inverse(double y) const { + return distr_inverse_impl(this->lambda_, y); + } /*distribution_inverse*/ + + // ----- inherited from Distribution ----- + + virtual double cdf(double const & x) const override { + return distribution(x); + } /*cdf*/ + + private: + /* intensity parameter. + * require: lambda > 0 + */ + double lambda_ = 1.0; + }; /*Exponential*/ + } /*namespace distribution*/ +} /*namespace xo*/ + +/* end Exponential.cpp */ diff --git a/include/xo/distribution/KolmogorovSmirnov.hpp b/include/xo/distribution/KolmogorovSmirnov.hpp new file mode 100644 index 00000000..ee0ec160 --- /dev/null +++ b/include/xo/distribution/KolmogorovSmirnov.hpp @@ -0,0 +1,257 @@ +/* @file KolmogorovSmirnov.hpp */ + +#pragma once + +#include "Distribution.hpp" +#include "xo/indentlog/scope.hpp" +#include +#include + +namespace xo { + /* Kolmogorov-Smirnov probability distribution */ + namespace distribution { + + class KolmogorovSmirnov : public Distribution { + public: + KolmogorovSmirnov() = default; + + /* kolmogorov-smirnov (KS) cumulative distribution + * + * cdf is defined by the series: + * + * +oo / j 2 2 \ + * P1(x) = 1 + 2 Sum | (-1) .exp(-2.j .x ) | + * j=1 \ / + * + * this converges rapidly for x > 1. expanding the first few terms: + * + * / 2 2 2 2 2 2 2 \ + * P1(x) ~= 1 + 2.| -exp(-2.x ) + exp(-2.2 .x ) - exp(-2.3 .x ) + exp(-2.4 .x ) | + * \ / + * + * / 2 2 4 2 9 2 16 \ + * = 1 + 2.| -exp(-2.x ) + exp(-2.x ) - exp(2.x ) + exp(-2.x ) | + * \ / + * + * / 4 9 16 \ + * = 1 + 2.| T(x) + T(x) + T(x) + T(x) | + * \ / + * + * with T(x) = .term_aux(1, x) + * + * with x=1: + * term1_aux(1, 1): -0.135335 + * term1_aux(2, 1): 3.35463e-4 + * term1_aux(3, 1): -1.52300e-8 + * term1_aux(4, 1): 1.26642e-14 + * term1_aux(5, 1): -1.92875e-22 + * + * with x=1.18: + * term1_aux(1, 1): -0.0617414 + * term1_aux(2, 1): 1.45314e-5 + * term1_aux(3, 1): -1.30374e-11 + * term1_aux(4, 1): 4.45890e-20 + * term1_aux(5, 1): -5.83124e-31 + * + * ---------------------------------------------------------------- + * + * There's an alternative series for the KS distribution, + * that converges rapidly for small x (x < ~1.18) + * + * / 2 2 \ + * sqrt(2.pi) +oo / | (2j - 1) .pi | \ + * P2(x) = ---------- . Sum | exp | - ------------ | | + * x j=1 \ | 2 | / + * \ 8.x / + * + * / 2 \ + * | pi | + * witu U(x) = exp | - ---- | + * | 2 | + * \ 8.x / + * we have + * / 2 \ + * sqrt(2.pi) +oo | (2j - 1) | + * P2(x) = ---------- . Sum | U(x) | + * x j=1 | | + * \ / + * + * / \ + * sqrt(2.pi) | 9 25 49 | + * = ---------- . | U(x) + U(x) + U(x) + U(x) + .. | + * x | | + * \ / + * + * with x=1.0: + * term2_aux(1, 1): 0.291213 + * term2_aux(2, 1): 1.50625e-5 + * term2_aux(3, 1): 4.02964e-14 + * term2_aux(4, 1): 5.57599e-27 + * + * with x=1.18: + * term2_aux(1, 1.18): 0.412292 + * term2_aux(2, 1.18): 3.44223e-4 + * term2_aux(3, 1.18): 2.39945e-10 + * term2_aux(4, 1.18): 1.39652e-19 + */ + static double term1_aux(uint32_t j, double x) { + double f = ::exp(-2.0 * x * x); + /* sgn: + * +1 for j in {2, 4, 6, ..} + * -1 for j in {1, 3, 5, ..} + */ + int32_t sgn = (((j & 0x1) == 0) ? +1 : -1); + + if(j == 1) { + return sgn * f; + } else { + return sgn * ::pow(f, j*j); + } + } /*term1_aux*/ + + /* implements P1(x) above; truncating at 4 terms. + * use .distr1() for x > ~1.18 + */ + static double distr1_impl(double x) { + double f = term1_aux(1.0, x); + double f2 = f*f; /*f^2*/ + double f4 = f2*f2; /*f^4*/ + double f8 = f4*f4; /*f^8*/ + double f9 = f8*f; /*f^9*/ + double f16 = f8*f8; /*f^16*/ + + double r = ((f16 + f9) + f4) + f; + + return 1.0 + 2.0*r; + } /*distr1_impl*/ + + /* pi: 3.141592... */ + static constexpr double c_pi = M_PI; + /* pi^2 / 8 */ + static constexpr double c_pi2_8 = 0.125 * c_pi * c_pi; + + /* computes + * + * (2j-1)^2 + * U(x) + * + * require: + * - j >= 1 + */ + static double term2_aux(uint32_t j, double x) { + double u = ::exp(-c_pi2_8 / (x * x)); + + if(j == 1) { + return u; + } else { + uint32_t j2m1 = 2*j - 1; + + return ::pow(u, j2m1 * j2m1); + } + } /*term2_aux*/ + + /* implements P2(x) above; truncating at 4 terms */ + static double distr2_impl(double x) { + static double c_sqrt_2pi + = ::sqrt(2.0 * c_pi); + + double scale = c_sqrt_2pi / x; + + double u = term2_aux(1, x); + double u2 = u*u; /*u^2*/ + double u4 = u2*u2; /*u^4*/ + double u8 = u4*u4; /*u^8*/ + double u16 = u8*u8; /*u^16*/ + double u32 = u16*u16; /*u^32*/ + + double u9 = u8*u; /*u^9*/ + double u25 = u16*u8*u; /*u^25*/ + double u49 = u32*u16*u; /*u^49*/ + + double r = ((u49 + u25) + u9) + u; + + return scale * r; + + } /*distr2_impl*/ + + static double distr_impl(double x) { + using xo::tostr; + + constexpr char const * c_self = "KolmogorovSmirnov::distr_impl"; + + if(x < 0.0) + throw std::runtime_error(tostr(c_self, "KS(x) cdf defined for x>=0")); + + if(x == 0.0) + return 0; + + if(x < 1.18) { + /* P2(x) converges fastest */ + return distr2_impl(x); + } else { + /* P1(x) converges fastest */ + return distr1_impl(x); + } + } /*distr_impl*/ + + /* p-value for significance of a particular value of the KS-statistic + * obtain this statistic for a sample distribution using + * Empirical.ks_stat_1sided(dexp) + * for some target distribution dexp + * + * ne. + * for 1-sided test: #of points in sample, i.e. Empirical.n_sample() + * for 2-sided test: (n1 . n2) / (n1 + n2) + * D. + * max difference between observed and expected cumulative distributions. + * see Empirical.ks_stat_1sided() + */ + static double ks_pvalue(uint32_t ne, double D) + { + using xo::scope; + using xo::xtag; + + constexpr bool logging_enabled_flag = false; + + scope log(XO_DEBUG(logging_enabled_flag)); + + double ne_sqrt = ::sqrt(ne); + + /* argument to KS-distribution. + * x in [0, +oo) + * + * A large value for x -> small value for 1 - KSdist(x), + * i.e. represents probability that a sample of size ne with a + * KS-statistic of magnitude D or larger, could be drawn from + * distribution dexp. + */ + double x = ne_sqrt + 0.12 + (0.11 / ne_sqrt); + + double xD = x * D; + + double pvalue = 1.0 - distr_impl(xD); + + log && log(xtag("ne", ne), + xtag("D", D), + xtag("ne_sqrt", ne_sqrt), + xtag("x", x), + xtag("xD", xD), + xtag("pvalue", pvalue)); + + return pvalue; + } /*ks_pvalue*/ + + /* cumulative distribution function */ + double distribution(double x) const { + return distr_impl(x); + } /*distribution*/ + + // ----- inherited from Distribution ----- + virtual double cdf(double const & x) const { + return this->distribution(x); + } /*cdf*/ + }; /*KolmogorovSmirnov*/ + } /*namespace distribution*/ +} /*namespace xo*/ + +/* end KolmogorovSmirnov.hpp */ diff --git a/include/xo/distribution/Normal.hpp b/include/xo/distribution/Normal.hpp new file mode 100644 index 00000000..5cef9ed1 --- /dev/null +++ b/include/xo/distribution/Normal.hpp @@ -0,0 +1,51 @@ +/* @file Normal.hpp */ + +#pragma once + +#include "distribution/Distribution.hpp" +#include + +namespace xo { + namespace distribution { + /* the guassian distribution, with mean 0 and variance 1 + */ + class Normal : public Distribution { + public: + Normal() = default; + + /* normal probability density: + * + * x^2 + * -(1/2) 1/2 + * p(x) = e / (2.pi) + */ + static double density(double x) { + static double c_sqrt_2pi = ::sqrt(2 * M_PI); + + return ::exp(-0.5 * x * x) / c_sqrt_2pi; + } /*density*/ + + /* cumulative distribution function for N(0,1): + * + * / x + * | + * | p(x).dx + * | + * / -oo + * + * where p(x) is the normal density function p(x) = e^[-x^2/2] + */ + static double cdf_impl(double x) { + return 0.5 * std::erfc(-M_SQRT1_2 * x); + } /*cdf_impl*/ + + // ----- inherited from Distribution ----- + + virtual double cdf(double const & x) const override { + return cdf_impl(x); + } /*cdf*/ + }; /*Normal*/ + } /*namespace distribution*/ +} /*namespace xo*/ + +/* end Normal.hpp */ diff --git a/include/xo/distribution/StdEmpirical.hpp b/include/xo/distribution/StdEmpirical.hpp new file mode 100644 index 00000000..d4475510 --- /dev/null +++ b/include/xo/distribution/StdEmpirical.hpp @@ -0,0 +1,226 @@ +/* @file StdEmpirical.hpp */ + +#pragma once + +#include "Empirical.hpp" +#include "xo/ordinaltree/RedBlackTree.hpp" +#include "xo/indentlog/scope.hpp" +#include +#include + +namespace xo { + namespace distribution { + /* an empirical distribution over a given domain + * (e.g. double as proxy for IR), + * obtained by sorting equally-weighted samples + */ + template + class StdEmpirical : public Distribution { + public: + using SampleMap = xo::tree::RedBlackTree>; + using const_iterator = typename SampleMap::const_iterator; + + public: + StdEmpirical() = default; + + uint32_t n_sample() const { return n_sample_; } + const_iterator begin() const { return sample_map_.begin(); } + const_iterator end() const { return sample_map_.end(); } + + /* compute kolmogorov-smirnov statistic with a non-sampled distribution. + * if d2 is sampled, should use .ks_stat_2sided() instead + */ + std::pair ks_stat_1sided(Distribution const & d2) const { + using xo::scope; + using xo::xtag; + + constexpr char const * c_self = "Empirical::ks_stat_1sided"; + constexpr bool c_logging_enabled = false; + + scope lscope(c_self, c_logging_enabled); + + double ks_stat = 0.0; + + /* for i'th loop iteration below: + * xj_sum = sum of all x[j] with j<=i + */ + uint32_t xj_sum = 0; + + /* #of sample in this distribution, as double */ + double nr = 1.0 / this->n_sample(); + + /* loop over elements x[i] of this (sampled) distribution, + * compare cdf(x[i]) with d2.cdf(x[i]) + * + * KS stat is the maximum observed difference. + */ + for(auto const & point : this->sample_map_) { + Domain const & xi = point.first; + uint32_t xi_count = point.second; + + xj_sum += xi_count; + + /* p1 = xi_sum / n1, where n1 = .n_sample() */ + double p1 = xj_sum * nr; + double p2 = d2.cdf(xi); + + double dp = std::abs(p1 - p2); + + if(c_logging_enabled) + lscope.log(c_self, + xtag("xi", xi), + xtag("xi_count", xi_count), + xtag("xj_sum", xj_sum), + xtag("p1", p1), + xtag("p2", p2), + xtag("dp", dp)); + + ks_stat = std::max(ks_stat, dp); + } + + return std::pair(this->n_sample(), ks_stat); + } /*ks_stat_1sided*/ + + /* compute kolmogorov-smirnov statistic with a sampled distribution; + * assess likelihood that both samples come from the same population. + */ + std::pair ks_stat_2sided(StdEmpirical const & d2) { + /* loop once over both sample distributions; + * algorithm is O(n1 + n2) for two empirical + * distributions with n1,n2 points respectively + */ + + /* return value observed here */ + double ks_stat = 0.0; + + auto ix1 = this->sample_map_.begin(); + auto end_ix1 = this->sample_map_.end(); + + auto ix2 = d2.sample_map_.begin(); + auto end_ix2 = this->sample_map_.end(); + + uint32_t xj1_sum = 0; + uint32_t xj2_sum = 0; + + uint32_t n1 = this->n_sample(); + uint32_t n2 = d2.n_sample(); + + double nr1 = 1.0 / n1; + double nr2 = 1.0 / n2; + + /* ^ + * 1| **. + * | ...*.. + * | . * + * | ********** + * | * . + * | ......... + * | . * + * | ******** + * | ..*..... + * +-----------------------> + * ^ ^ + * ix1 ix2 + */ + + while ((ix1 != end_ix1) || (ix2 != end_ix2)) { + /* on each iteration, compare sample distributions + * at smallest of (ix1->first, ix2->first) + */ + + bool advance_ix1_flag = false; + bool advance_ix2_flag = false; + + if (ix1 == end_ix1) { + /* only ix2 dereferenceable */ + advance_ix2_flag = true; + } else if (ix2 == end_ix2) { + /* only ix1 dereferenceable */ + advance_ix1_flag = true; + } else { + /* ix1,ix2 both dereferenceable */ + + advance_ix1_flag = (ix1->first <= ix2->first); + advance_ix2_flag = (ix2->first <= ix1->first); + } + + if (advance_ix1_flag) { + xj1_sum += ix1->first; + ++ix1; + } + if (advance_ix2_flag) { + xj2_sum += ix2->first; + ++ix2; + } + + double p1 = xj1_sum * nr1; + double p2 = xj2_sum * nr2; + + double dp = std::abs(p1 - p2); + + ks_stat = std::max(ks_stat, dp); + } + + /* ne = effective #of points to use when comparing 2 sample dist/s */ + double ne = (n1 * n2) / static_cast(n1 + n2); + + return std::pair(ne, ks_stat); + +#ifdef OBSOLETE + /* NOTE: this will cost O(n.log(n)) + * (for empirical distributions with n points) + * if we loop over both sets of points in parallel, + * can get O(n) solution + * + */ + + /* loop over points in *this */ + double ks1 = this->ks_stat_1sided(d2); + /* loop over points in d2 */ + double ks2 = d2.ks_stat_1sided(*this); + + return std::max(ks1, ks2); +#endif + } /*ks_stat_2sided*/ + + // ----- inherited from Empirical ----- + + /* introduce one new sample into this distribution */ + virtual void include_sample(Domain const & x) { + ++(this->n_sample_); + + /* note: xo::tree::RedBlackTree doesn't provide the usual reference result + * from operator[]; it needs to intervene after assignment to update + * order statistics + */ + auto lhs = this->sample_map_[x]; + + lhs += 1; + } /*include_sample*/ + + // ----- inherited from Distribution ----- + + virtual double cdf(Domain const & x) const override { + /* computes #of samples with values <= x */ + uint32_t nx = this->sample_map_.reduce_lub(x, true /*is_closed*/); + size_t n = this->n_sample(); + + return static_cast(nx) / n; + } /*cdf*/ + + private: + /* count #of calls to .include_sample() */ + CounterRep n_sample_ = 0; + + /* .sample_map_[x] counts the #of times + * .include_sample(x) has been called. + */ + SampleMap sample_map_; + }; /*StdEmpirical*/ + + } /*namespace distribution*/ +} /*namespace xo*/ + +/* end StdEmpirical.hpp */ diff --git a/include/xo/distribution/Uniform.hpp b/include/xo/distribution/Uniform.hpp new file mode 100644 index 00000000..d1bc8668 --- /dev/null +++ b/include/xo/distribution/Uniform.hpp @@ -0,0 +1,47 @@ +/* @file Uniform.hpp */ + +#pragma once + +#include "Distribution.hpp" + +namespace xo { + namespace distribution { + /* uniform distribution on an interval */ + class Uniform : public Distribution { + public: + Uniform(double lo, double hi) : lo_(lo), hi_(hi) {} + + static Uniform unit() { return Uniform(0.0, 1.0); } + + static double density_impl(double lo, double hi) { + return 1.0 / (hi - lo); + } /*density_impl*/ + + static double distr_impl(double lo, double hi, double x) { + return (x - lo) / (hi - lo); + } /*distr_impl*/ + + double lo() const { return lo_; } + double hi() const { return hi_; } + + double density(double /*x*/) const { + return density_impl(this->lo_, this->hi_); + } /*density*/ + + double distribution(double x) const { + return distr_impl(this->lo_, this->hi_, x); + } /*distribution*/ + + // ----- inherited from Distribution ----- + + virtual double cdf(double const & x) const override { + return this->distribution(x); + } /*cdf*/ + + private: + /* Invariant: .lo < .hi */ + double lo_; + double hi_; + }; /*Uniform*/ + } /*namespace distribution*/ +} /*namespace xo*/ From 264d74caed15bfd6068a0503d34ab7a4c1d5ecad Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 14:33:19 -0400 Subject: [PATCH 0524/2693] cmake: minor consolidation --- CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c6799082..a4b00e15 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(indentlog VERSION 0.1) enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(xo_macros/xo-project-macros) # ---------------------------------------------------------------- # unit test setup From 797db3e021a702cb575ac06a788d121d44d6817d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 15:54:57 -0400 Subject: [PATCH 0525/2693] compile fixes + unit tests --- CMakeLists.txt | 4 ++-- cmake/xo_distributionConfig.cmake.in | 2 ++ include/xo/distribution/Normal.hpp | 5 ++++- include/xo/distribution/Uniform.hpp | 22 ++++++++++++++++------ src/distribution/CMakeLists.txt | 5 +++++ src/distribution/Normal.cpp | 9 +++++++++ utest/CMakeLists.txt | 19 +++++++++++++++++++ utest/Normal.test.cpp | 24 ++++++++++++++++++++++++ utest/Uniform.test.cpp | 27 +++++++++++++++++++++++++++ utest/distribution_utest_main.cpp | 6 ++++++ 10 files changed, 114 insertions(+), 9 deletions(-) create mode 100644 src/distribution/CMakeLists.txt create mode 100644 src/distribution/Normal.cpp create mode 100644 utest/CMakeLists.txt create mode 100644 utest/Normal.test.cpp create mode 100644 utest/Uniform.test.cpp create mode 100644 utest/distribution_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 467f52cc..153acdac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,9 +10,9 @@ include(xo_macros/xo-project-macros) xo_cxx_toplevel_options() #add_subdirectory(example) -#add_subdirectory(uteest) +add_subdirectory(src/distribution) # note refcnt dep -> not header-only +add_subdirectory(utest) -xo_add_headeronly_library4(xo_distribution ${PROJECT_NAME}Targets) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- diff --git a/cmake/xo_distributionConfig.cmake.in b/cmake/xo_distributionConfig.cmake.in index 3c6c4bd4..d906d196 100644 --- a/cmake/xo_distributionConfig.cmake.in +++ b/cmake/xo_distributionConfig.cmake.in @@ -1,4 +1,6 @@ @PACKAGE_INIT@ +include(CMakeFindDependencyMacro) +find_dependency(refcnt) include("${CMAKE_CURRENT_LIST_DIR}/xo_distributionTargets.cmake") check_required_components("@PROJECT_NAME@") diff --git a/include/xo/distribution/Normal.hpp b/include/xo/distribution/Normal.hpp index 5cef9ed1..6d4d88e1 100644 --- a/include/xo/distribution/Normal.hpp +++ b/include/xo/distribution/Normal.hpp @@ -2,7 +2,7 @@ #pragma once -#include "distribution/Distribution.hpp" +#include "Distribution.hpp" #include namespace xo { @@ -13,6 +13,9 @@ namespace xo { public: Normal() = default; + /* N(0,1): mean 0, sdev 1 */ + static ref::rp unit() { return new Normal(); } + /* normal probability density: * * x^2 diff --git a/include/xo/distribution/Uniform.hpp b/include/xo/distribution/Uniform.hpp index d1bc8668..e40c62a6 100644 --- a/include/xo/distribution/Uniform.hpp +++ b/include/xo/distribution/Uniform.hpp @@ -11,21 +11,31 @@ namespace xo { public: Uniform(double lo, double hi) : lo_(lo), hi_(hi) {} - static Uniform unit() { return Uniform(0.0, 1.0); } + static ref::rp unit() { return new Uniform(0.0, 1.0); } + + static double density_impl(double lo, double hi, double x) { + if (x <= lo) + return 0.0; + if (x >= hi) + return 0.0; - static double density_impl(double lo, double hi) { return 1.0 / (hi - lo); } /*density_impl*/ static double distr_impl(double lo, double hi, double x) { + if (x <= lo) + return 0.0; + if (x >= hi) + return 1.0; + return (x - lo) / (hi - lo); } /*distr_impl*/ double lo() const { return lo_; } double hi() const { return hi_; } - double density(double /*x*/) const { - return density_impl(this->lo_, this->hi_); + double density(double x) const { + return density_impl(this->lo_, this->hi_, x); } /*density*/ double distribution(double x) const { @@ -40,8 +50,8 @@ namespace xo { private: /* Invariant: .lo < .hi */ - double lo_; - double hi_; + double lo_ = 0.0; + double hi_ = 1.0; }; /*Uniform*/ } /*namespace distribution*/ } /*namespace xo*/ diff --git a/src/distribution/CMakeLists.txt b/src/distribution/CMakeLists.txt new file mode 100644 index 00000000..56ce881e --- /dev/null +++ b/src/distribution/CMakeLists.txt @@ -0,0 +1,5 @@ +set(SELF_LIB distribution) +set(SELF_SRCS Normal.cpp) + +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_dependency(${SELF_LIB} refcnt) diff --git a/src/distribution/Normal.cpp b/src/distribution/Normal.cpp new file mode 100644 index 00000000..f1a0f9ca --- /dev/null +++ b/src/distribution/Normal.cpp @@ -0,0 +1,9 @@ +/* @file Normal.cpp */ + +#include "Normal.hpp" + +namespace xo { + +} /*namespace xo*/ + +/* end Normal.cpp */ diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..27b1a78b --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,19 @@ +# build unittest distribution/utest + +set(SELF_EXE utest.distribution) +set(SELF_SRCS + distribution_utest_main.cpp + Normal.test.cpp + Uniform.test.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) +target_code_coverage(${SELF_EXE} AUTO ALL) + +xo_self_dependency(${SELF_EXE} distribution) + +xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) + +# end CMakeLists.txt diff --git a/utest/Normal.test.cpp b/utest/Normal.test.cpp new file mode 100644 index 00000000..70f0a7ad --- /dev/null +++ b/utest/Normal.test.cpp @@ -0,0 +1,24 @@ +/* @file Normal.test.cpp */ + +#include "xo/distribution/Normal.hpp" +#include + +namespace xo { + using xo::distribution::Normal; + + namespace ut { + TEST_CASE("normal", "[distribution]") { + auto n01 = Normal::unit(); + + CHECK(n01->cdf(-3.0) == Approx(0.001349898).margin(1e-9)); + CHECK(n01->cdf(-2.0) == Approx(0.0227501319).margin(1e-9)); + CHECK(n01->cdf(-1.0) == Approx(0.1586552539).margin(1e-9)); + CHECK(n01->cdf(0.0) == 0.5); + CHECK(n01->cdf(1.0) == 1.0 - n01->cdf(-1.0)); + CHECK(n01->cdf(2.0) == 1.0 - n01->cdf(-2.0)); + CHECK(n01->cdf(3.0) == 1.0 - n01->cdf(-3.0)); + } /*TEST_CASE(normal)*/ + } /*namespace ut*/ +} /*namespace xo*/ + +/* end Normal.test.cpp */ diff --git a/utest/Uniform.test.cpp b/utest/Uniform.test.cpp new file mode 100644 index 00000000..10153d66 --- /dev/null +++ b/utest/Uniform.test.cpp @@ -0,0 +1,27 @@ +/* @file Uniform.test.cpp */ + +#include "xo/distribution/Uniform.hpp" +#include + +namespace xo { + using xo::distribution::Uniform; + + namespace ut { + TEST_CASE("uniform", "[distribution]") { + auto u = Uniform::unit(); + + CHECK(u->cdf(-3.0) == 0.0); + CHECK(u->cdf(-2.0) == 0.0); + CHECK(u->cdf(-1.0) == 0.0); + CHECK(u->cdf(0.0) == 0.0); + CHECK(u->cdf(0.05) == 0.05); + CHECK(u->cdf(0.5) == 0.5); + CHECK(u->cdf(0.95) == 0.95); + CHECK(u->cdf(1.0) == 1.0); + CHECK(u->cdf(2.0) == 1.0); + CHECK(u->cdf(3.0) == 1.0); + } /*TEST_CASE(uniform)*/ + } /*namespace ut*/ +} /*namespace xo*/ + +/* end Uniform.test.cpp */ diff --git a/utest/distribution_utest_main.cpp b/utest/distribution_utest_main.cpp new file mode 100644 index 00000000..b5e05a73 --- /dev/null +++ b/utest/distribution_utest_main.cpp @@ -0,0 +1,6 @@ +/* file distribution_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end distribution_utest_main.cpp */ From 7d599854fbc9f6052f5b353af1be5d56b7c8dc4c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 15:55:55 -0400 Subject: [PATCH 0526/2693] bugfix: bad include install in vanilla build --- cmake/xo_macros/xo_cxx.cmake | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index 1e308aa0..06a28990 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -349,6 +349,8 @@ endmacro() macro(xo_establish_symlink_install) if(NOT DEFINED XO_SYMLINK_INSTALL) set(XO_SYMLINK_INSTALL False) + + message(XO_SYMLINK_INSTALL=${XO_SYMLINK_INSTALL}) endif() endmacro() @@ -387,14 +389,15 @@ endmacro() macro(xo_install_include_tree3 subdir_path) xo_establish_symlink_install() + # ugh. cmake doesn't allow input path argument to cmake_path() + # to be a macro variable. + set(_xo_install_include_tree3_subdir_path ${subdir_path}) + set(_xo_install_include_tree3_dirname "") + set(_xo_install_include_tree3_basename "") + cmake_path(GET _xo_install_include_tree3_subdir_path PARENT_PATH _xo_install_include_tree3_dirname) + cmake_path(GET _xo_install_include_tree3_subdir_path FILENAME _xo_install_include_tree3_basename) + if(XO_SYMLINK_INSTALL) - # ugh. cmake doesn't allow input path argument to cmake_path() - # to be a macro variable. - set(_xo_install_include_tree3_subdir_path ${subdir_path}) - set(_xo_install_include_tree3_dirname "") - set(_xo_install_include_tree3_basename "") - cmake_path(GET _xo_install_include_tree3_subdir_path PARENT_PATH _xo_install_include_tree3_dirname) - cmake_path(GET _xo_install_include_tree3_subdir_path FILENAME _xo_install_include_tree3_basename) xo_install_make_symlink( ${PROJECT_SOURCE_DIR}/${_xo_install_include_tree3_dirname} @@ -404,7 +407,7 @@ macro(xo_install_include_tree3 subdir_path) install( DIRECTORY ${PROJECT_SOURCE_DIR}/${subdir_path} FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ - DESTINATION ${CMAKE_INSTALL_PREFIX}/${subdir_path}) + DESTINATION ${CMAKE_INSTALL_PREFIX}/${_xo_install_include_tree3_dirname}) endif() endmacro() @@ -554,6 +557,7 @@ macro(xo_dependency_helper target visibility dep) xo_dependency_helper1(${target} ${visibility} repo/${_nxo_dep}/include) endif() else() + message("xo_dependency_helper: find_package() on ${dep} for ${target}") find_package(${dep} CONFIG REQUIRED) endif() From c7c29daf5e171fa8a95d9d38241aecf118a2689c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 20:16:01 -0400 Subject: [PATCH 0527/2693] initial implementation --- CMakeLists.txt | 13 ++++ README.md | 77 ++++++++++++++++++++++++ cmake/xo_pydistributionConfig.cmake.in | 4 ++ include/README.md | 1 + src/pydistribution/CMakeLists.txt | 7 +++ src/pydistribution/pydistribution.cpp | 74 +++++++++++++++++++++++ src/pydistribution/pydistribution.hpp.in | 25 ++++++++ 7 files changed, 201 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/xo_pydistributionConfig.cmake.in create mode 100644 include/README.md create mode 100644 src/pydistribution/CMakeLists.txt create mode 100644 src/pydistribution/pydistribution.cpp create mode 100644 src/pydistribution/pydistribution.hpp.in diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..d2823501 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,13 @@ +# xo-pydistribution/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_pydistribution VERSION 1.0) + +include(xo_macros/xo-project-macros) + +xo_cxx_toplevel_options() + +add_subdirectory(src/pydistribution) + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/README.md b/README.md new file mode 100644 index 00000000..ef597486 --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +# python bindings for c++ reflection library (xo-distribution) + +## Getting Started + +### build + install dependencies + +- [github/Rconybea/xo-pyutil](https://github.com/Rconybea/xo-pyutil) +- [github/Rconybea/xo-reflect](https://github.com/Rconybea/xo-distribution) + +### build + install +``` +$ cd xo-pydistribution +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer, e.g. ~/local +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +### build for unit test coverage +``` +$ cd xo-pydistribution +$ mkdir build-ccov +$ cd build-ccov +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCODE_COVERAGE=ON \ + -DCMAKE_BUILD_TYPE=Debug .. +``` + +### LSP (language server) support + +LSP looks for compile commands in the root of the source tree; +while Cmake creates them in the root of its build directory. + +``` +$ cd xo-pydistribution +$ ln -s build/compile_commands.json # supply compile commands to LSP +``` + +## Examples + +Assumes `xo-pydistribution` installed to `~/local2/lib` + +``` +PYTHONPATH=~/local2/lib python +>>> import pydistribution +>>> dir(pydistribution) +['Distribution', 'ExplicitDist', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'normalcdf'] +>>> from pydistribution import * +``` + +normal distribution +``` +>>> normalcdf(0.0) +0.5 +>>> normalcdf(3.0) +0.9986501019683699 +``` + +explicit distribution (online implementation). +intended to model empirically a Bayesian prior. +``` +>>> d=ExplicitDist.make(bucket_dx=0.01, ref_value=1e-6) +>>> d +]"> +>>> d.cdf(0.0) +0.0 +>>> d.cdf(0.01) +1.0 +``` diff --git a/cmake/xo_pydistributionConfig.cmake.in b/cmake/xo_pydistributionConfig.cmake.in new file mode 100644 index 00000000..9c15f36a --- /dev/null +++ b/cmake/xo_pydistributionConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/README.md b/include/README.md new file mode 100644 index 00000000..ec349995 --- /dev/null +++ b/include/README.md @@ -0,0 +1 @@ +placeholder for future pydistribution #include files diff --git a/src/pydistribution/CMakeLists.txt b/src/pydistribution/CMakeLists.txt new file mode 100644 index 00000000..a050be15 --- /dev/null +++ b/src/pydistribution/CMakeLists.txt @@ -0,0 +1,7 @@ +# xo_pydistribution/src/pydistribution/CMakeLists.txt + +set(SELF_LIB pydistribution) +set(SELF_SRCS pydistribution.cpp) + +xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) +xo_pybind11_dependency(${SELF_LIB} xo_distribution) diff --git a/src/pydistribution/pydistribution.cpp b/src/pydistribution/pydistribution.cpp new file mode 100644 index 00000000..5d107233 --- /dev/null +++ b/src/pydistribution/pydistribution.cpp @@ -0,0 +1,74 @@ +/* @file pydistribution.cpp */ + +#include "pydistribution.hpp" +#include "xo/distribution/Normal.hpp" +#include "xo/distribution/ExplicitDist.hpp" +#include "xo/reflect/SelfTagging.hpp" +#include "xo/pyutil/pyutil.hpp" +#include +#include + +namespace xo { + using xo::distribution::Normal; + using xo::distribution::Distribution; + using xo::distribution::ExplicitDist; + using xo::ref::rp; + + namespace sim { + namespace py = pybind11; + + PYBIND11_MODULE(PYDISTRIBUTION_MODULE_NAME(), m) { + m.doc() = "pybind11 distribution plugin"; // optional module docstring + + m.def("normalcdf", + &Normal::cdf_impl, + "cumulative normal distribution", + py::arg("x")); + + py::class_, + rp>>(m, "Distribution") + .def("cdf", &Distribution::cdf, + "return cumulative distribution function at x", + py::arg("x")); + + py::class_, + Distribution, + rp>>(m, "ExplicitDist") + .def_static("make", &ExplicitDist::make, + "create instance", + py::arg("bucket_dx"), py::arg("ref_value")) + .def_static("make_n", &ExplicitDist::make_n, + "create instance with n buckets", + py::arg("n"), py::arg("bucket_dx"), py::arg("ref_value")) + .def("n_bucket", &ExplicitDist::n_bucket, + "return number of explicitly-represented buckets in distribution") + .def("lo", &ExplicitDist::lo, + "return least upper bound x: cdf(x)=0") + .def("hi", &ExplicitDist::hi, + "return greatest lower bound x: cdf(x)=1") + .def("density", &ExplicitDist::density, + "return probability density at x", + py::arg("x")) + .def("density_v", &ExplicitDist::density_v, + "return probability density vector for all explicit buckets." + " each member is pair {lh bucket edge, density}") + .def("signed_bucket_index", &ExplicitDist::signed_bucket_index, + "signed index to probability bucket. ref_value -> 0", + py::arg("x")) + .def("scale_bucket", &ExplicitDist::scale_bucket, + "scale probability weight in bucket containing x by k", + py::arg("x"), py::arg("k")) + .def("scale_by_normal_cdf", &ExplicitDist::scale_by_normal_cdf, + "scale by normal cumulative distribution N(sign.(x-mean)/sigma)." + " expect sign in {+1, -1}", + py::arg("sign"), py::arg("mean"), py::arg("sigma")) + .def("renormalize", &ExplicitDist::renormalize, + "renormalize to ensure sum of weights=1") + .def("check_renormalize", &ExplicitDist::check_renormalize, + "renormalize if needed, otherwise do nothing") + .def("__repr__", &ExplicitDist::display_string); + } + } /*namespace sim*/ +} /*namespace xo*/ + +/* end pydistribution.cpp */ diff --git a/src/pydistribution/pydistribution.hpp.in b/src/pydistribution/pydistribution.hpp.in new file mode 100644 index 00000000..bf94e785 --- /dev/null +++ b/src/pydistribution/pydistribution.hpp.in @@ -0,0 +1,25 @@ +/* @file pydistribution.hpp + * + * automatically generated from src/pydistribution/pydistribution.hpp.in + * see src/pydistribution/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(PYDISTRIBUTION_MODULE_NAME(), m) { ... } + */ +#define PYDISTRIBUTION_MODULE_NAME() @SELF_LIB@ + +/* example: + * py::module_::import(PYDISTRIBUTION_MODULE_NAME_STR) + */ +#define PYDISTRIBUTION_MODULE_NAME_STR "@SELF_LIB@" + +/* example: + * PYDISTRIBUTION_IMPORT_MODULE() + * replaces + * py::module_::import("pydistribution") + */ +#define PYDISTRIBUTION_IMPORT_MODULE() py::module_::import("@SELF_LIB@") + +/* end pydistribution.hpp */ From e3743cc01feb9daa8b9c0481f1d6939a0a5829df Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 20:18:45 -0400 Subject: [PATCH 0528/2693] + bernoulli/exponential/uniform/guussianpair --- include/xo/randomgen/bernoulligen.hpp | 29 ++++++ include/xo/randomgen/exponentialgen.hpp | 21 +++++ include/xo/randomgen/gaussianpairgen.hpp | 108 +++++++++++++++++++++++ include/xo/randomgen/uniformgen.hpp | 25 ++++++ 4 files changed, 183 insertions(+) create mode 100644 include/xo/randomgen/bernoulligen.hpp create mode 100644 include/xo/randomgen/exponentialgen.hpp create mode 100644 include/xo/randomgen/gaussianpairgen.hpp create mode 100644 include/xo/randomgen/uniformgen.hpp diff --git a/include/xo/randomgen/bernoulligen.hpp b/include/xo/randomgen/bernoulligen.hpp new file mode 100644 index 00000000..9de59300 --- /dev/null +++ b/include/xo/randomgen/bernoulligen.hpp @@ -0,0 +1,29 @@ +/* @file bernoulligen.hpp */ + +#pragma once + +#include "generator.hpp" +#include + +namespace xo { + namespace rng { + /* Engine: e.g. xo::rng::xoshiro256ss or std::mt19937 */ + template + class bernoulligen : public generator> { + public: + using generator_type = generator>; + + template + static generator_type make(Engine engine, double prob) { + return generator_type::make(std::move(engine), + std::bernoulli_distribution(prob)); + } + + template + static generator_type conflip(Engine engine) { + return generator_type::make(std::move(engine), + std::bernoulli_distribution(0.5)); + } + }; + } /*namespace rng*/ + } /*namespace xo*/ diff --git a/include/xo/randomgen/exponentialgen.hpp b/include/xo/randomgen/exponentialgen.hpp new file mode 100644 index 00000000..35945f5b --- /dev/null +++ b/include/xo/randomgen/exponentialgen.hpp @@ -0,0 +1,21 @@ +/* @file exponentialgen.hpp */ + +#pragma once + +#include "generator.hpp" +#include + +namespace xo { + namespace rng { + template + class exponentialgen : public generator> { + public: + using generator_type = generator>; + + template + static generator_type make(Engine eng, double lambda) { + return make_generator(std::move(eng), std::exponential_distribution(lambda)); + } + }; + } /*namespace rng*/ +} /*namespace xo*/ diff --git a/include/xo/randomgen/gaussianpairgen.hpp b/include/xo/randomgen/gaussianpairgen.hpp new file mode 100644 index 00000000..d5cc4699 --- /dev/null +++ b/include/xo/randomgen/gaussianpairgen.hpp @@ -0,0 +1,108 @@ +/* @file gaussianpairgen.hpp */ + +#pragma once + +#include "generator.hpp" +#include +#include + +namespace xo { + namespace random { + /* editor bait: 2d normal, normal xy + * + * if + * N1 ~ N(0,1) + * N2 ~ N(0,1) + * are two indepenent, normally-distributed r.v's with + * mean 0 and variance 1, then + * let + * A = | 1 0 | X = | N1 | + * | r q | | N2 | + * + * with r^2 + q^2 = 1 + * + * and consider + * A.X = | N1 | := | Y1 | + * | r.N1 + q.N2 | | Y2 | + * + * Y1, Y2 both have mean 0, + * since both are linear combination of 0-mean N(0,1) variables + * + * Var(Y1) = 1 + * Var(Y2) = r^2.Var(N1) + q^2.Var(N2) + * = r^2 + q^2 + * = 1 + * + * (since N1,N2 indept, and Var(N1)=Var(N2)=1) + * + * Cov(Y1,Y2) = r.Cov(N1,N1) + q.Cov(N1,N2) + * = r.Var(N1) + * = r + * + * (since Cov(N1,N2)=0) + * + * we have correlation coefficient for Y1,Y2: + * + * Cov(Y1,Y2) + * p(Y1,Y2) = -------------------- + * sqrt(Var(Y1).Var(Y2)) + * + * = r + */ + template + class gaussianpair_dist { + public: + using result_type = std::array; + + public: + /* generate pairs of gaussian N(0,1) random numbers, + * with correlation coefficient rho + * + * Require: + * - rho in the interval [-1, +1] + */ + explicit gaussianpair_dist(FloatType rho) + : r_(rho), q_(std::sqrt(1.0 - rho*rho)) {} + + template + result_type operator()(Engine & engine) { + FloatType n1 = this->ndist_(engine); + FloatType n2 = this->ndist_(engine); + + FloatType y1 = n1; + FloatType y2 = this->r_ * n1 + this->q_ * n2; + + return {y1, y2}; + } /*operator()*/ + + private: + /* correlation coefficient r + * 2nd random variable Y2 in each pair will be constructed by + * r.N1 + sqrt(1-r^2).N2 + */ + FloatType r_; + /* q := sqrt(1-r^2) */ + FloatType q_; + + /* state for generating indept normally-distributed r.v's */ + std::normal_distribution ndist_; + }; /*gaussianpair_dist*/ + + /* generate pairs of correlated gaussian random variables */ + template + class gaussianpairgen { + public: + using engine_type = Engine; + using generator_type = generator>; + + template + static generator_type make(Engine eng, + double rho) { + return generator_type::make(std::move(eng), + gaussianpair_dist(rho)); + } + }; /*GaussianPairGen*/ + } /*namespace random*/ +} /*namespace xo*/ + +/* end gaussianpairgen.hpp */ diff --git a/include/xo/randomgen/uniformgen.hpp b/include/xo/randomgen/uniformgen.hpp new file mode 100644 index 00000000..d1c4d62b --- /dev/null +++ b/include/xo/randomgen/uniformgen.hpp @@ -0,0 +1,25 @@ +/* @file uniformgen.hpp */ + +#pragma once + +#include "generator.hpp" +#include + +namespace xo { + namespace rng { + template + class uniformgen : public generator> { + public: + using generator_type = generator>; + + template + static generator_type unit(Engine eng) { + return make_generator(std::move(eng), + std::uniform_real_distribution(0.0, 1.0)); + } + }; + } /*namespace rng*/ +} /*namespace xo*/ + + +/* end uniformgen.hpp */ From 3d2cdc159bf5e1116993497875abebd280bb89df Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 20:21:22 -0400 Subject: [PATCH 0529/2693] library name distribution -> xo_distribution --- src/distribution/CMakeLists.txt | 2 +- utest/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/distribution/CMakeLists.txt b/src/distribution/CMakeLists.txt index 56ce881e..41023695 100644 --- a/src/distribution/CMakeLists.txt +++ b/src/distribution/CMakeLists.txt @@ -1,4 +1,4 @@ -set(SELF_LIB distribution) +set(SELF_LIB xo_distribution) set(SELF_SRCS Normal.cpp) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 27b1a78b..652bf644 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -12,7 +12,7 @@ xo_include_options2(${SELF_EXE}) add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) target_code_coverage(${SELF_EXE} AUTO ALL) -xo_self_dependency(${SELF_EXE} distribution) +xo_self_dependency(${SELF_EXE} xo_distribution) xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) From 86b97d238f46e1390537a22f4a3a3cbfecef2848 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 20:27:14 -0400 Subject: [PATCH 0530/2693] bugfix: include structure missing xo/ subdir --- include/{ => xo}/statistics/Accumulator.hpp | 0 include/{ => xo}/statistics/Histogram.hpp | 0 include/{ => xo}/statistics/SampleStatistics.hpp | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename include/{ => xo}/statistics/Accumulator.hpp (100%) rename include/{ => xo}/statistics/Histogram.hpp (100%) rename include/{ => xo}/statistics/SampleStatistics.hpp (100%) diff --git a/include/statistics/Accumulator.hpp b/include/xo/statistics/Accumulator.hpp similarity index 100% rename from include/statistics/Accumulator.hpp rename to include/xo/statistics/Accumulator.hpp diff --git a/include/statistics/Histogram.hpp b/include/xo/statistics/Histogram.hpp similarity index 100% rename from include/statistics/Histogram.hpp rename to include/xo/statistics/Histogram.hpp diff --git a/include/statistics/SampleStatistics.hpp b/include/xo/statistics/SampleStatistics.hpp similarity index 100% rename from include/statistics/SampleStatistics.hpp rename to include/xo/statistics/SampleStatistics.hpp From 026560c4272b7794de73451b1bf350dd04f1ac4b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 20:28:52 -0400 Subject: [PATCH 0531/2693] bugfix: compile fixes --- src/pydistribution/CMakeLists.txt | 1 + src/pydistribution/pydistribution.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pydistribution/CMakeLists.txt b/src/pydistribution/CMakeLists.txt index a050be15..8adaad7e 100644 --- a/src/pydistribution/CMakeLists.txt +++ b/src/pydistribution/CMakeLists.txt @@ -5,3 +5,4 @@ set(SELF_SRCS pydistribution.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} xo_distribution) +xo_pybind11_dependency(${SELF_LIB} xo_pyutil) diff --git a/src/pydistribution/pydistribution.cpp b/src/pydistribution/pydistribution.cpp index 5d107233..8104ccaf 100644 --- a/src/pydistribution/pydistribution.cpp +++ b/src/pydistribution/pydistribution.cpp @@ -3,7 +3,7 @@ #include "pydistribution.hpp" #include "xo/distribution/Normal.hpp" #include "xo/distribution/ExplicitDist.hpp" -#include "xo/reflect/SelfTagging.hpp" +//#include "xo/reflect/SelfTagging.hpp" #include "xo/pyutil/pyutil.hpp" #include #include From d7a884f651d0d57d0e099149f68339f8711a6caa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 20:29:14 -0400 Subject: [PATCH 0532/2693] bugfix: compile fix (include path ) --- utest/KalmanFilter.test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utest/KalmanFilter.test.cpp b/utest/KalmanFilter.test.cpp index c0b35601..fea572ec 100644 --- a/utest/KalmanFilter.test.cpp +++ b/utest/KalmanFilter.test.cpp @@ -3,7 +3,7 @@ #include "xo/kalmanfilter/KalmanFilter.hpp" #include "xo/kalmanfilter/KalmanFilterEngine.hpp" #include "xo/kalmanfilter/print_eigen.hpp" -#include "statistics/SampleStatistics.hpp" +#include "xo/statistics/SampleStatistics.hpp" #include "xo/randomgen/normalgen.hpp" #include "xo/randomgen/xoshiro256.hpp" #include "xo/indentlog/scope.hpp" From 471a0d4d43864e0f474e49b9fc9e93e943689c6e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 11:13:48 -0400 Subject: [PATCH 0533/2693] initial implementation --- CMakeLists.txt | 13 ++ cmake/xo_pykalmanfilterConfig.cmake.in | 4 + include/README.md | 1 + src/pykalmanfilter/CMakeLists.txt | 33 +++ src/pykalmanfilter/pykalmanfilter.cpp | 286 +++++++++++++++++++++++ src/pykalmanfilter/pykalmanfilter.hpp.in | 25 ++ 6 files changed, 362 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/xo_pykalmanfilterConfig.cmake.in create mode 100644 include/README.md create mode 100644 src/pykalmanfilter/CMakeLists.txt create mode 100644 src/pykalmanfilter/pykalmanfilter.cpp create mode 100644 src/pykalmanfilter/pykalmanfilter.hpp.in diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..2a1602f6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,13 @@ +# xo-pykalmanfilter/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_pykalmanfilter VERSION 1.0) + +include(xo_macros/xo-project-macros) + +xo_cxx_toplevel_options() + +add_subdirectory(src/pykalmanfilter) + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/cmake/xo_pykalmanfilterConfig.cmake.in b/cmake/xo_pykalmanfilterConfig.cmake.in new file mode 100644 index 00000000..9c15f36a --- /dev/null +++ b/cmake/xo_pykalmanfilterConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/README.md b/include/README.md new file mode 100644 index 00000000..4454f162 --- /dev/null +++ b/include/README.md @@ -0,0 +1 @@ +placeholder for future pyreflect #include files diff --git a/src/pykalmanfilter/CMakeLists.txt b/src/pykalmanfilter/CMakeLists.txt new file mode 100644 index 00000000..9fd9178c --- /dev/null +++ b/src/pykalmanfilter/CMakeLists.txt @@ -0,0 +1,33 @@ +# xo_pykalmanfilter/src/pykalmanfilter/CMakeLists.txt + +set(SELF_LIB pykalmanfilter) +set(SELF_SRCS pykalmanfilter.cpp) + +xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) +xo_pybind11_dependency(${SELF_LIB} xo_kalmanfilter) +xo_pybind11_dependency(${SELF_LIB} xo_pyutil) + +# cmake config generated by xo-kalman filter +# (xo-kalmanfilter/cmake/xo_kalmanfilterConfig.cmake) +# doesn't work here. +# kalmanfilterConfig.cmake include path that would make +# #include +# work; we want to be able to use +# #include +# +# BTW this tells us that there's some discrepancy between what +# cmake does in response to +# +# (A) xo_kalmanfilterTarget.cmake: +# set_target_properties(xo_kalmanfilter PROPERTIES +# .. +# INTERFACE_LINK_LIBRARIES "reactor;Eigen3::Eigen") +# +# (B) target_link_libraries() below; via the original +# xo_external_target_dependency +# (xo_kalmanfilter Eigen3 Eigen3::Eigen) +# in xo-kalmanfilter/src/kalmanfilter/CMakeLists.txt +# +target_link_libraries(${SELF_LIB} PUBLIC Eigen3::Eigen) + +# CMakeLists.txt diff --git a/src/pykalmanfilter/pykalmanfilter.cpp b/src/pykalmanfilter/pykalmanfilter.cpp new file mode 100644 index 00000000..1f7320ce --- /dev/null +++ b/src/pykalmanfilter/pykalmanfilter.cpp @@ -0,0 +1,286 @@ +/* @file pykalmanfilter.cpp */ + +#include "pykalmanfilter.hpp" +#include "xo/pyreactor/pyreactor.hpp" +#include "xo/pyutil/pyutil.hpp" + +#include "xo/kalmanfilter/init_filter.hpp" +#include "xo/refcnt/Refcounted.hpp" +#include "xo/kalmanfilter/KalmanFilterSvc.hpp" +#include "xo/kalmanfilter/KalmanFilter.hpp" +#include "xo/kalmanfilter/KalmanFilterEngine.hpp" +#include "xo/kalmanfilter/KalmanFilterSpec.hpp" +#include "xo/kalmanfilter/KalmanFilterStep.hpp" +#include "xo/kalmanfilter/KalmanFilterStateToConsole.hpp" +#include "xo/kalmanfilter/KalmanFilterState.hpp" +#include "xo/kalmanfilter/KalmanFilterTransition.hpp" +#include "xo/kalmanfilter/KalmanFilterObservable.hpp" +#include "xo/kalmanfilter/KalmanFilterInputToConsole.hpp" +#include "xo/kalmanfilter/KalmanFilterInput.hpp" +#include "xo/reactor/EventStore.hpp" +#include "xo/subsys/Subsystem.hpp" + +#include +#include +#include +#include +#include +//#include + +namespace xo { + using xo::kalman::KalmanFilterSvc; + using xo::kalman::KalmanFilter; + using xo::kalman::KalmanFilterState; + using xo::kalman::KalmanFilterEngine; + using xo::kalman::KalmanFilterSpec; + using xo::kalman::KalmanFilterStepBase; + using xo::kalman::KalmanFilterStep; + using xo::kalman::KalmanFilterStateToConsole; + using xo::kalman::KalmanFilterState; + using xo::kalman::KalmanFilterStateExt; + using xo::kalman::KalmanFilterTransition; + using xo::kalman::KalmanFilterObservable; + using xo::kalman::KalmanFilterInputToConsole; + using xo::kalman::KalmanFilterInput; + using xo::reactor::AbstractSource; + using xo::reactor::AbstractSink; + using xo::reactor::AbstractEventStore; + using xo::reactor::ReactorSource; + using xo::reactor::PtrEventStore; + using xo::reflect::SelfTagging; + using xo::ref::rp; + using xo::time::utc_nanos; + using Eigen::VectorXd; + using Eigen::VectorXi; + using Eigen::MatrixXd; + namespace py = pybind11; + + namespace filter { + PYBIND11_MODULE(PYKALMANFILTER_MODULE_NAME(), m) { + /* ensure filter/ will be initialized */ + InitSubsys::require(); + /* ..and immediately perform init steps + * (this 2nd step is sketchy -- want to pass an application context + * to initialize_all()) + */ + Subsystem::initialize_all(); + + /* e.g. need python wrapper for for xo::reactor::AbstractSource + */ + PYREACTOR_IMPORT_MODULE(); + + m.doc() = "pybind11 plugin for xo.filter"; + + m.def("print_matrix", + [](MatrixXd const & m) { + std::cout << m << std::endl; + }); + + m.def("print_vector", + [](VectorXd const & v) { + std::cout << v << std::endl; + }); + + // ----- xo::kalman::KalmanFilterState ----- + + py::class_>(m, "KalmanFilterState") + .def_static("make", + py::overload_cast(&KalmanFilterState::make), + py::arg("k"), py::arg("tk"), + py::arg("x"), py::arg("P"), + py::arg("transition")) + .def("step_no", &KalmanFilterState::step_no) + .def("tm", &KalmanFilterState::tm) + .def("n_state", &KalmanFilterState::n_state) + .def("state_v", &KalmanFilterState::state_v) + .def("state_cov", &KalmanFilterState::state_cov) + .def_property_readonly("k", &KalmanFilterState::step_no) + .def_property_readonly("tk", &KalmanFilterState::tm) + .def_property_readonly("x", &KalmanFilterState::state_v) + .def_property_readonly("P", &KalmanFilterState::state_cov) + .def("__repr__", &KalmanFilterState::display_string); + + // ----- xo::kalman::KalmanFilterStateExt ----- + + py::class_>(m, "KalmanFilterStateExt") + .def_static("make", + py::overload_cast>(&KalmanFilterStateExt::make), + py::arg("k"), + py::arg("tk"), + py::arg("x"), + py::arg("P"), + py::arg("transition"), + py::arg("K"), + py::arg("j"), + py::arg("zk")) + .def_property_readonly("j", &KalmanFilterStateExt::observable) + .def_property_readonly("K", &KalmanFilterStateExt::gain) + .def_property_readonly("zk", &KalmanFilterStateExt::zk); + + // ----- xo::kalman::KalmanFilterTransition ----- + + py::class_(m, "KalmanFilterTransition") + .def(py::init()) + .def("n_state", &KalmanFilterTransition::n_state) + .def("transition_mat", &KalmanFilterTransition::transition_mat) + .def("transition_cov", &KalmanFilterTransition::transition_cov) + .def("check_ok", &KalmanFilterTransition::check_ok) + .def_property_readonly("F", &KalmanFilterTransition::transition_mat) + .def_property_readonly("Q", &KalmanFilterTransition::transition_cov) + .def("__repr__", &KalmanFilterTransition::display_string); + + // ----- xo::kalman::KalmanFilterObservable ----- + + py::class_(m, "KalmanFilterObservable") + .def(py::init(), + py::arg("keep"), py::arg("H"), py::arg("R")) + .def("n_state", &KalmanFilterObservable::n_state) + .def("n_observable", &KalmanFilterObservable::n_observable) + .def("observable_mat", &KalmanFilterObservable::observable) + .def("observable_cov", &KalmanFilterObservable::observable_cov) + .def_property_readonly("H", &KalmanFilterObservable::observable) + .def_property_readonly("R", &KalmanFilterObservable::observable_cov) + .def("__repr__", &KalmanFilterObservable::display_string); + + // ----- xo::kalman::KalmanFilterInput ----- + + py::class_>(m, "KalmanFilterInput") + //.def(py::init(), + // py::arg("tkp1"), py::arg("z")) + .def("n_observable", &KalmanFilterInput::n_obs) + .def_property_readonly("tkp1", &KalmanFilterInput::tkp1) + .def_property_readonly("z", &KalmanFilterInput::z) + .def("__repr__", &KalmanFilterInput::display_string); + + m.def("make_kalman_filter_input", &KalmanFilterInput::make, + py::arg("tkp1"), py::arg("presence"), py::arg("z"), py::arg("zerr")); + + // ----- xo::kalman::KalmanFilterStep ----- + + py::class_(m, "KalmanFilterStep") + .def(py::init, KalmanFilterTransition, KalmanFilterObservable, ref::rp>(), + py::arg("state"), py::arg("model"), py::arg("obs"), py::arg("input")) + .def_property_readonly("state", &KalmanFilterStep::state) + .def_property_readonly("model", &KalmanFilterStepBase::model) + .def_property_readonly("obs", &KalmanFilterStepBase::obs) + .def_property_readonly("input", &KalmanFilterStep::input) + .def("extrapolate", &KalmanFilterStep::extrapolate) + .def("gain", &KalmanFilterStep::gain) + .def("gain1", &KalmanFilterStep::gain1) + .def("correct", &KalmanFilterStep::correct) + .def("correct1", &KalmanFilterStep::correct1) + .def("__repr__", &KalmanFilterStep::display_string); + + // ----- xo::kalman::KalmanFilterSpec ----- + + py::class_(m, "KalmanFilterSpec") + .def(py::init, KalmanFilterSpec::MkStepFn>(), + py::arg("s0"), py::arg("mkstepfn")) + .def("start_ext", &KalmanFilterSpec::start_ext) + .def("make_step", &KalmanFilterSpec::make_step, + py::arg("sk"), py::arg("zkp1")) + .def("__repr__", &KalmanFilterSpec::display_string); + + // ----- xo::kalman::KalmanFilterEngine ----- + + m.def("kf_engine_extrapolate", + &KalmanFilterEngine::extrapolate); + m.def("kf_engine_gain", + &KalmanFilterEngine::kalman_gain); + m.def("kf_engine_gain1", + &KalmanFilterEngine::kalman_gain1); + m.def("kf_engine_correct", + &KalmanFilterEngine::correct); + m.def("kf_engine_correct1", + &KalmanFilterEngine::correct1); + + // ----- xo::kalman::KalmanFilter ----- + + py::class_(m, "KalmanFilter") + .def(py::init(), + py::arg("spec")) + .def_property_readonly("step_no", &KalmanFilter::step_no) + .def_property_readonly("tm", &KalmanFilter::tm) + .def_property_readonly("filter_spec", &KalmanFilter::filter_spec) + .def_property_readonly("step", &KalmanFilter::step) + .def_property_readonly("state_ext", &KalmanFilter::state_ext) + .def("notify_input", &KalmanFilter::notify_input) + .def("__repr__", &KalmanFilter::display_string); + + // ----- xo::kalman::KalmanFilterSvc ----- + + py::class_>(m, "KalmanFilterSvc") + .def_property_readonly("filter", &KalmanFilterSvc::filter) + .def_property_readonly("last_annexed_ev", &KalmanFilterSvc::last_annexed_ev); + + m.def("make_kalman_filter", + &KalmanFilterSvc::make, + py::arg("spec")); + + // ----- xo::kalman::KalmanFilterInputToConsole ----- + + py::class_> + (m, "KalmanFilterInputToConsole"); + + /* prints KalmanFilterInput events to console */ + m.def("make_kalman_filter_input_printer", &KalmanFilterInputToConsole::make); + + // ----- xo::kalman::KalmanFilterStateToConsole ----- + + py::class_> + (m, "KalmanFilterStateToConsole"); + + /* prints KalmanFilterStateExt events to console */ + m.def("make_kalman_filter_state_printer", &KalmanFilterStateToConsole::make); + + // ----- xo::kalman::KalmanFilterStateStore ----- + + using KalmanFilterStateEventStore + = PtrEventStore>; + + /* see also: UpxEventStore in [pyprocess/pyprocess.cpp] + * BboTickStore in [pyoption/pyoption.cpp] + */ + py::class_> + (m, "KalmanFilterStateEventStore") + .def_static("make", &KalmanFilterStateEventStore::make) + .def("last_n", &KalmanFilterStateEventStore::last_n, py::arg("n")) + .def("last_dt", &KalmanFilterStateEventStore::last_dt, py::arg("dt")); + //.def("__repr__", &KalmanFilterStateEventStore::display_string); + + +#ifdef OBSOLETE + // ----- xo::option::Pxtick ----- + + py::enum_(m, "Pxtick") + .value("nickel_dime", Pxtick::nickel_dime); + //.export_values(); // only need this for pre-c++11-style enum inside a class + + // ----- xo::option::OptionStrikeSet ----- + + py::class_>(m, "OptionStrikeSet") + .def("get_options", + [](OptionStrikeSet const & x) { + std::vector> v; + x.append_options(&v); + return v; + }) +#endif + } /*filter_py*/ + } /*namespace filter*/ +} /*namespace xo*/ + +/* end pykalmanfilter.cpp */ diff --git a/src/pykalmanfilter/pykalmanfilter.hpp.in b/src/pykalmanfilter/pykalmanfilter.hpp.in new file mode 100644 index 00000000..f2b1207d --- /dev/null +++ b/src/pykalmanfilter/pykalmanfilter.hpp.in @@ -0,0 +1,25 @@ +/* @file pykalmanfilter.hpp + * + * automatically generated from src/pykalmanfilter/pykalmanfilter.hpp.in + * see src/pykalmanfilter/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(PYKALMANFILTER_MODULE_NAME(), m) { ... } + */ +#define PYKALMANFILTER_MODULE_NAME() @SELF_LIB@ + +/* example: + * py::module_::import(PYKALMANFILTER_MODULE_NAME_STR) + */ +#define PYKALMANFILTER_MODULE_NAME_STR "@SELF_LIB@" + +/* example: + * PYKALMANFILTER_IMPORT_MODULE() + * replaces + * py::module_::import("pykalmanfilter") + */ +#define PYKALMANFILTER_IMPORT_MODULE() py::module_::import("@SELF_LIB@") + +/* end pykalmanfilter.hpp */ From 098a208ee0299197197e756b23de5ddf4c76d9fe Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 11:42:19 -0400 Subject: [PATCH 0534/2693] bugfix: + pyreactor dep + eigen3 dep for submodule build --- src/pykalmanfilter/CMakeLists.txt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/pykalmanfilter/CMakeLists.txt b/src/pykalmanfilter/CMakeLists.txt index 9fd9178c..f551c368 100644 --- a/src/pykalmanfilter/CMakeLists.txt +++ b/src/pykalmanfilter/CMakeLists.txt @@ -5,6 +5,7 @@ set(SELF_SRCS pykalmanfilter.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} xo_kalmanfilter) +xo_pybind11_header_dependency(${SELF_LIB} pyreactor) xo_pybind11_dependency(${SELF_LIB} xo_pyutil) # cmake config generated by xo-kalman filter @@ -23,11 +24,14 @@ xo_pybind11_dependency(${SELF_LIB} xo_pyutil) # .. # INTERFACE_LINK_LIBRARIES "reactor;Eigen3::Eigen") # -# (B) target_link_libraries() below; via the original -# xo_external_target_dependency +# (B) xo_external_target_dependency # (xo_kalmanfilter Eigen3 Eigen3::Eigen) -# in xo-kalmanfilter/src/kalmanfilter/CMakeLists.txt +# below # -target_link_libraries(${SELF_LIB} PUBLIC Eigen3::Eigen) +# reminder XO_SUBMODULE_BUILD relies on +# xo_external_target_dependency() calling find_package() +# +xo_external_target_dependency(${SELF_LIB} Eigen3 Eigen3::Eigen) +#target_link_libraries(${SELF_LIB} PUBLIC Eigen3::Eigen) # CMakeLists.txt From 361a957e3c263dcec19011c36fb12d18f4b245f4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 12:24:03 -0400 Subject: [PATCH 0535/2693] + .hpp deps pyreactor,pywebutil --- src/pyprocess/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pyprocess/CMakeLists.txt b/src/pyprocess/CMakeLists.txt index 1b6c5347..03237164 100644 --- a/src/pyprocess/CMakeLists.txt +++ b/src/pyprocess/CMakeLists.txt @@ -6,3 +6,5 @@ set(SELF_SRCS pyprocess.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} process) +xo_pybind11_header_dependency(${SELF_LIB} pyreactor) +xo_pybind11_header_dependency(${SELF_LIB} pywebutil) From 38c95a554238825e314a46ffdbedf985592f5aab Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 12:26:21 -0400 Subject: [PATCH 0536/2693] build: drop XO_SYMLINK_INSTALL console message --- cmake/xo_macros/xo_cxx.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index 06a28990..08d0b942 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -350,7 +350,7 @@ macro(xo_establish_symlink_install) if(NOT DEFINED XO_SYMLINK_INSTALL) set(XO_SYMLINK_INSTALL False) - message(XO_SYMLINK_INSTALL=${XO_SYMLINK_INSTALL}) + #message(XO_SYMLINK_INSTALL=${XO_SYMLINK_INSTALL}) endif() endmacro() From fa93e06bd5af2cbb28140eeda222945ddb36132a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 13:11:04 -0400 Subject: [PATCH 0537/2693] get build working --- CMakeLists.txt | 13 +++++++ cmake/xo_pywebsockConfig.cmake.in | 4 ++ include/README.md | 1 + src/pywebsock/CMakeLists.txt | 9 +++++ src/pywebsock/pywebsock.cpp | 63 +++++++++++++++++++++++++++++++ src/pywebsock/pywebsock.hpp.in | 25 ++++++++++++ 6 files changed, 115 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/xo_pywebsockConfig.cmake.in create mode 100644 include/README.md create mode 100644 src/pywebsock/CMakeLists.txt create mode 100644 src/pywebsock/pywebsock.cpp create mode 100644 src/pywebsock/pywebsock.hpp.in diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..20774e1c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,13 @@ +# xo-pywebsock/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_pywebsock VERSION 1.0) + +include(xo_macros/xo-project-macros) + +xo_cxx_toplevel_options() + +add_subdirectory(src/pywebsock) + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/cmake/xo_pywebsockConfig.cmake.in b/cmake/xo_pywebsockConfig.cmake.in new file mode 100644 index 00000000..9c15f36a --- /dev/null +++ b/cmake/xo_pywebsockConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/README.md b/include/README.md new file mode 100644 index 00000000..1c13f62a --- /dev/null +++ b/include/README.md @@ -0,0 +1 @@ +placeholder. needed at install for generated .hpp file diff --git a/src/pywebsock/CMakeLists.txt b/src/pywebsock/CMakeLists.txt new file mode 100644 index 00000000..0ad6f596 --- /dev/null +++ b/src/pywebsock/CMakeLists.txt @@ -0,0 +1,9 @@ +# xo_pywebsock/src/pywebsock/CMakeLists.txt + +set(SELF_LIB pywebsock) +set(SELF_SRCS pywebsock.cpp) + +xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) +xo_pybind11_dependency(${SELF_LIB} websock) +#xo_pybind11_header_dependency(${SELF_LIB pyfoo) +xo_pybind11_dependency(${SELF_LIB} xo_pyutil) diff --git a/src/pywebsock/pywebsock.cpp b/src/pywebsock/pywebsock.cpp new file mode 100644 index 00000000..e92c43ee --- /dev/null +++ b/src/pywebsock/pywebsock.cpp @@ -0,0 +1,63 @@ +/* @file pywebsock.cpp */ + +#include "pywebsock.hpp" +#include "xo/pywebutil/pywebutil.hpp" +#include "xo/websock/Webserver.hpp" +#include "xo/printjson/PrintJson.hpp" +//#include "web_util/EndpointDescr.hpp" +#include "xo/pyutil/pyutil.hpp" +#include + +namespace xo { + using xo::web::WebserverConfig; + using xo::web::Webserver; + using xo::web::Runstate; + using xo::json::PrintJsonSingleton; + using xo::ref::rp; + namespace py = pybind11; + + namespace web { + PYBIND11_MODULE(PYWEBSOCK_MODULE_NAME(), m) { + PYWEBUTIL_IMPORT_MODULE(); // = py::module_::import("pywebutil") + + /* module docstring */ + m.doc() = "pybind11 plugin for xo.websock"; + + py::enum_(m, "Runstate") + .value("stopped", Runstate::stopped) + .value("stop_requested", Runstate::stop_requested) + .value("running", Runstate::running); + + py::class_(m, "WebserverConfig") + .def(py::init(), + py::arg("port"), + py::arg("tls_flag"), + py::arg("host_check_flag"), + py::arg("use_retry_flag")) + .def_property_readonly("port", &WebserverConfig::port) + .def_property_readonly("tls_flag", &WebserverConfig::tls_flag) + .def_property_readonly("host_check_flag", &WebserverConfig::host_check_flag) + .def_property_readonly("use_retry_flag", &WebserverConfig::use_retry_flag); + + py::class_>(m, "Webserver") + .def_static("make", + [](WebserverConfig const & ws_config) + { + return Webserver::make(ws_config, + PrintJsonSingleton::instance()); + }) + .def_property_readonly("state", &Webserver::state) + .def("register_http_endpoint", &Webserver::register_http_endpoint) + .def("register_stream_endpoint", &Webserver::register_stream_endpoint) + .def("start_webserver", &Webserver::start_webserver) + .def("stop_webserver", &Webserver::stop_webserver) + .def("join_webserver", &Webserver::join_webserver) + .def("__repr__", &Webserver::display_string); + + m.def("make_webserver", + &Webserver::make); + } /*pywebsock*/ + } /*web*/ +} /*namespace xo*/ + +/* end pywebsock.cpp */ diff --git a/src/pywebsock/pywebsock.hpp.in b/src/pywebsock/pywebsock.hpp.in new file mode 100644 index 00000000..4d3ec777 --- /dev/null +++ b/src/pywebsock/pywebsock.hpp.in @@ -0,0 +1,25 @@ +/* @file pywebsock.hpp + * + * automatically generated from src/pywebsock/pywebsock.hpp.in + * see src/pywebsock/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(PYWEBSOCK_MODULE_NAME(), m) { ... } + */ +#define PYWEBSOCK_MODULE_NAME() @SELF_LIB@ + +/* example: + * py::module_::import(PYWEBSOCK_MODULE_NAME_STR) + */ +#define PYWEBSOCK_MODULE_NAME_STR "@SELF_LIB@" + +/* example: + * PYWEBSOCK_IMPORT_MODULE() + * replaces + * py::module_::import("pywebsock") + */ +#define PYWEBSOCK_IMPORT_MODULE() py::module_::import("@SELF_LIB@") + +/* end pywebsock.hpp */ From 2868cefc53e10bc1231fb37c769169558d5f5db2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 13:19:16 -0400 Subject: [PATCH 0538/2693] build: fix missing xo deps --- cmake/websockConfig.cmake.in | 6 +++--- src/websock/CMakeLists.txt | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/cmake/websockConfig.cmake.in b/cmake/websockConfig.cmake.in index ac43847f..a3dad29f 100644 --- a/cmake/websockConfig.cmake.in +++ b/cmake/websockConfig.cmake.in @@ -4,10 +4,10 @@ include(CMakeFindDependencyMacro) # note: changes to find_dependency() calls here # must coordinate with xo_dependency() calls -# in xo-reactor/src/reactor/CMakeLists.txt +# in xo-websock/src/websock/CMakeLists.txt # -#find_dependency(reflect) -#find_dependency(callback) +find_dependency(reactor) +find_dependency(webutil) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") diff --git a/src/websock/CMakeLists.txt b/src/websock/CMakeLists.txt index 42f37a7d..1071bf7f 100644 --- a/src/websock/CMakeLists.txt +++ b/src/websock/CMakeLists.txt @@ -8,6 +8,9 @@ xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 $ # ---------------------------------------------------------------- # external dependencies +# note: changes to xo_dependency() calls here +# must coordinate with find_dependency() calls in +# xo-websock/cmake/websockConfig.cmake.in xo_dependency(${SELF_LIB} reactor) xo_dependency(${SELF_LIB} webutil) @@ -15,7 +18,3 @@ xo_dependency(${SELF_LIB} webutil) xo_external_target_dependency(${SELF_LIB} Libwebsockets websockets_shared) # see jsoncpp-namespaced-targets.cmake (maybe?) for available targets xo_external_target_dependency(${SELF_LIB} jsoncpp jsoncpp_lib) - -# note: changes to xo_dependency() calls here -# must coordinate with find_dependency() calls in -# xo-websock/cmake/websockConfig.cmake.in From e9b9da29696fe14534add8f25a08bb34688bf132 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 13:47:56 -0400 Subject: [PATCH 0539/2693] build: need pywebutil dep --- src/pywebsock/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pywebsock/CMakeLists.txt b/src/pywebsock/CMakeLists.txt index 0ad6f596..2823da75 100644 --- a/src/pywebsock/CMakeLists.txt +++ b/src/pywebsock/CMakeLists.txt @@ -5,5 +5,5 @@ set(SELF_SRCS pywebsock.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} websock) -#xo_pybind11_header_dependency(${SELF_LIB pyfoo) +xo_pybind11_header_dependency(${SELF_LIB} pywebutil) xo_pybind11_dependency(${SELF_LIB} xo_pyutil) From 500855f426b5aaa808b580b81a5ff8c9e8f60b4f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 16:16:38 -0400 Subject: [PATCH 0540/2693] build: streamline .cmake instructions --- CMakeLists.txt | 35 ++----------------------------- EXAMPLES | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 33 deletions(-) create mode 100644 EXAMPLES diff --git a/CMakeLists.txt b/CMakeLists.txt index af0bae5b..36786fb8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,42 +3,11 @@ cmake_minimum_required(VERSION 3.10) project(xo_pyprocess VERSION 0.1) -enable_language(CXX) -# common XO cmake macros (see github.com:Rconybea/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(xo_macros/xo-project-macros) -# ---------------------------------------------------------------- -# unit test setup - -enable_testing() -# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) -add_code_coverage() -# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. -# we're not interested in code coverage for these sources. -# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; -# rather, want coverage on the code that the unit tests exercise. -# -# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target -# -add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) - -# ---------------------------------------------------------------- -# c++ settings (usually temporary) - -set(PROJECT_CXX_FLAGS "") -add_definitions(${PROJECT_CXX_FLAGS}) - -xo_toplevel_compile_options() - -# ---------------------------------------------------------------- -# sources +xo_cxx_toplevel_options() add_subdirectory(src/pyprocess) -#add_subdirectory(utest) - -# ---------------------------------------------------------------- -# provide find_package() support xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/EXAMPLES b/EXAMPLES new file mode 100644 index 00000000..a8ce9feb --- /dev/null +++ b/EXAMPLES @@ -0,0 +1,57 @@ +process module, using pybind11 to wrap c++ implementation + +To demo + +1. build the kalman project + see path/to/kalman/README + + python-compatible .so will be at: + path/to/kalman/build/process_py/process_py.cpython-39-darwin.so + +2. run python: + $ cd path/to/kalman/build/pyprocess + $ python3 + Python 3.9.12 (main, May 13 2022, 08:13:55) + [Clang 11.1.0 ] on darwin + Type "help", "copyright", "credits" or "license" for more information. + >>> + +3. import pybind11 module and run: + >>> import pyprocess + >>> import datetime as dt + >>> from datetime import datetime as clock + >>> t0=clock.now() + # brownian motion, 50% annual volatility + >>> bm=pyprocess.make_brownian_motion(t0, 0.5) + >>> bm + + >>> bm.exterior_sample(t0, [t0, 2.0]) + 2.0 + >>> bm.exterior_sample(t0, [t0, 1.0]) + 1.0 + >>> bm.exterior_sample(t0 + dt.timedelta(days=30, [t0, 1.0])) + 1.959941114989831 + + >>> ebm=pyprocess.make_exponential_brownian_motion(t0, 0.5) + >>> ebm + + >>> ebm.exterior_sample(t + dt.timedelta(days=180, [t0, 50.0])) + 42.3212369005776 + >>> ebm.exterior_sample(t + dt.timedelta(days=180, [t0, 50.0])) + 56.16317742801309 + + >>> r=pyprocess.make_realization_source(ebm, dt.timedelta(seconds=1)) + +4. attach printer + + >>> import reactor_py + >>> p=reactor_py.make_realization_printer() + >>> r.attach_sink(p) + >>> bm.deliver_one() + >>> bm.deliver_one() + >>> r.deliver_one() + [20220718:224818.909576, 0] + 1 + >>> r.deliver_one() + [20220718:224819.909576, -0.000111338] + 1 From 5668e1b18831ccd25bd61877fb04b5eef7e90363 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 16:25:33 -0400 Subject: [PATCH 0541/2693] build: fix find_package() deps: + reactor+printjson --- cmake/processConfig.cmake.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/processConfig.cmake.in b/cmake/processConfig.cmake.in index fa80b6be..dc6207c5 100644 --- a/cmake/processConfig.cmake.in +++ b/cmake/processConfig.cmake.in @@ -6,8 +6,8 @@ include(CMakeFindDependencyMacro) # must coordinate with xo_dependency() calls # in xo-process/src/process/CMakeLists.txt # -find_dependency(reflect) -#find_dependency(callback) +find_dependency(reactor) +find_dependency(printjson) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") From 3645ffaf734b4548fabd226a047db2f507e1a0d3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 16:34:44 -0400 Subject: [PATCH 0542/2693] build: supply xo_pyutil dep with find_package() support --- cmake/xo_pywebutilConfig.cmake.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmake/xo_pywebutilConfig.cmake.in b/cmake/xo_pywebutilConfig.cmake.in index 9c15f36a..18eaf04d 100644 --- a/cmake/xo_pywebutilConfig.cmake.in +++ b/cmake/xo_pywebutilConfig.cmake.in @@ -1,4 +1,6 @@ @PACKAGE_INIT@ +include(CMakeFindDependencyMacro) +find_dependency(xo_pyutil) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") From bb26c99660be1b7e2b76b110bb3834dc713b1b44 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 16:36:51 -0400 Subject: [PATCH 0543/2693] build: tidy using streamlined xo-pywebutil --- cmake/cmake | 4 ---- src/pyprocess/CMakeLists.txt | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) delete mode 100644 cmake/cmake diff --git a/cmake/cmake b/cmake/cmake deleted file mode 100644 index b49b4828..00000000 --- a/cmake/cmake +++ /dev/null @@ -1,4 +0,0 @@ - /home/roland/proj/xo-pyprocess/cmake: - drwxr-xr-x 2 roland roland 4096 Oct 12 21:49 . - drwxr-xr-x 6 roland roland 4096 Oct 12 21:49 .. - -rw-r--r-- 1 roland roland 125 Oct 12 21:49 xo_pyprocessConfig.cmake.in diff --git a/src/pyprocess/CMakeLists.txt b/src/pyprocess/CMakeLists.txt index 03237164..cf35ab2e 100644 --- a/src/pyprocess/CMakeLists.txt +++ b/src/pyprocess/CMakeLists.txt @@ -6,5 +6,5 @@ set(SELF_SRCS pyprocess.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} process) -xo_pybind11_header_dependency(${SELF_LIB} pyreactor) -xo_pybind11_header_dependency(${SELF_LIB} pywebutil) +xo_pybind11_header_dependency(${SELF_LIB} xo_pyreactor) +xo_pybind11_header_dependency(${SELF_LIB} xo_pywebutil) From 52df00721c19179e9f53a5391cf1c3a4e0cfc350 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 16:39:27 -0400 Subject: [PATCH 0544/2693] bugfix: SELF_LIBRARY_NAME->SELF_LIB in .hpp.in --- src/pyreactor/pyreactor.cpp | 4 ++-- src/pyreactor/pyreactor.hpp.in | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pyreactor/pyreactor.cpp b/src/pyreactor/pyreactor.cpp index 5f8cf77b..a5120f96 100644 --- a/src/pyreactor/pyreactor.cpp +++ b/src/pyreactor/pyreactor.cpp @@ -1,4 +1,4 @@ -/* @file ReactorPy.cpp */ +/* @file pyreactor.cpp */ #include "pyreactor.hpp" #include "xo/pyprintjson/pyprintjson.hpp" @@ -138,4 +138,4 @@ namespace xo { } /*namespace reactor*/ } /*namespace xo*/ -/* end ReactorPy.cpp */ +/* end pyreactor.cpp */ diff --git a/src/pyreactor/pyreactor.hpp.in b/src/pyreactor/pyreactor.hpp.in index edbb2e78..140ded1b 100644 --- a/src/pyreactor/pyreactor.hpp.in +++ b/src/pyreactor/pyreactor.hpp.in @@ -8,18 +8,18 @@ * example: * PYBIND11_MODULE(PYREACTOR_MODULE_NAME(), m) { ... } */ -#define PYREACTOR_MODULE_NAME() @SELF_LIBRARY_NAME@ +#define PYREACTOR_MODULE_NAME() @SELF_LIB@ /* example: * py::module_::import(PYREACTOR_MODULE_NAME_STR) */ -#define PYREACTOR_MODULE_NAME_STR "@SELF_LIBRARY_NAME@" +#define PYREACTOR_MODULE_NAME_STR "@SELF_LIB@" /* example: * PYREACTOR_IMPORT_MODULE() * replaces * py::module_::import("pyreactor") */ -#define PYREACTOR_IMPORT_MODULE() py::module_::import("@SELF_LIBRARY_NAME@") +#define PYREACTOR_IMPORT_MODULE() py::module_::import("@SELF_LIB@") /* end pyreactor.hpp */ From 5327277ee4a83bdcd6f61a0cc9e7f8791c88b62a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 17:07:54 -0400 Subject: [PATCH 0545/2693] build: pyreactor -> xo_pyreactor --- src/pykalmanfilter/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pykalmanfilter/CMakeLists.txt b/src/pykalmanfilter/CMakeLists.txt index f551c368..af687d48 100644 --- a/src/pykalmanfilter/CMakeLists.txt +++ b/src/pykalmanfilter/CMakeLists.txt @@ -1,12 +1,12 @@ # xo_pykalmanfilter/src/pykalmanfilter/CMakeLists.txt -set(SELF_LIB pykalmanfilter) +set(SELF_LIB xo_pykalmanfilter) set(SELF_SRCS pykalmanfilter.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} xo_kalmanfilter) -xo_pybind11_header_dependency(${SELF_LIB} pyreactor) -xo_pybind11_dependency(${SELF_LIB} xo_pyutil) +xo_pybind11_header_dependency(${SELF_LIB} xo_pyreactor) +#xo_pybind11_dependency(${SELF_LIB} xo_pyutil) # cmake config generated by xo-kalman filter # (xo-kalmanfilter/cmake/xo_kalmanfilterConfig.cmake) From 5529e36b8136e98af4cba7c58767c88f1dc3c7e2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 17:08:20 -0400 Subject: [PATCH 0546/2693] build: pyreactor -> xo_pyreactor --- src/pyreactor/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyreactor/CMakeLists.txt b/src/pyreactor/CMakeLists.txt index b7d5695f..501a2572 100644 --- a/src/pyreactor/CMakeLists.txt +++ b/src/pyreactor/CMakeLists.txt @@ -1,6 +1,6 @@ # xo_pyreactor/src/pyreactor/CMakeLists.txt -set(SELF_LIB pyreactor) +set(SELF_LIB xo_pyreactor) set(SELF_SRCS pyreactor.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) From 8c1c0c181102ab69867201a6da7c10e5850e6db0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 17:08:47 -0400 Subject: [PATCH 0547/2693] build: pywebutil -> xo_pywebutil --- src/pywebsock/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pywebsock/CMakeLists.txt b/src/pywebsock/CMakeLists.txt index 2823da75..57c5d75a 100644 --- a/src/pywebsock/CMakeLists.txt +++ b/src/pywebsock/CMakeLists.txt @@ -5,5 +5,5 @@ set(SELF_SRCS pywebsock.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} websock) -xo_pybind11_header_dependency(${SELF_LIB} pywebutil) +xo_pybind11_header_dependency(${SELF_LIB} xo_pywebutil) xo_pybind11_dependency(${SELF_LIB} xo_pyutil) From abd1fb2040d69f8d958c4e1789c827dfa801c706 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 17:09:10 -0400 Subject: [PATCH 0548/2693] build: pywebutil -> xo_pywebutil --- src/pywebutil/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pywebutil/CMakeLists.txt b/src/pywebutil/CMakeLists.txt index 95f8df26..cc68d6e1 100644 --- a/src/pywebutil/CMakeLists.txt +++ b/src/pywebutil/CMakeLists.txt @@ -1,6 +1,6 @@ # xo-pywebutil/src/pywebutil/CMakeLists.txt -set(SELF_LIB pywebutil) +set(SELF_LIB xo_pywebutil) set(SELF_SRCS pywebutil.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) From 037b34d9e903e11ba53b23dcf384527ba7a6b214 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 17:25:44 -0400 Subject: [PATCH 0549/2693] build: pyprintjson -> xo-pyprintjson --- src/pyreactor/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyreactor/CMakeLists.txt b/src/pyreactor/CMakeLists.txt index 501a2572..686e0b69 100644 --- a/src/pyreactor/CMakeLists.txt +++ b/src/pyreactor/CMakeLists.txt @@ -6,4 +6,4 @@ set(SELF_SRCS pyreactor.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} reactor) -xo_pybind11_header_dependency(${SELF_LIB} pyprintjson) +xo_pybind11_header_dependency(${SELF_LIB} xo_pyprintjson) From 389a6375756da93aa8c7cc3f5aa64ab59fc8266f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 17:39:14 -0400 Subject: [PATCH 0550/2693] github: + xo-ordinaltree dep --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 30ac4a91..7efc00fe 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -196,6 +196,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/ordinaltree + + - name: Configure ordinaltree + # configure cmake for ordinaltree in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/ordinaltree + + - name: Build ordinaltree + run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}} + + - name: Install ordinaltree + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_ordinaltree + + # ---------------------------------------------------------------- + - name: Configure self (reactor) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 484b2e9f0d3118dbfae826a2beaa9417b13df0c3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 20:36:44 -0400 Subject: [PATCH 0551/2693] cosmetic: drop unneeded comments --- src/pyreflect/CMakeLists.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/pyreflect/CMakeLists.txt b/src/pyreflect/CMakeLists.txt index 94a71814..e53926d0 100644 --- a/src/pyreflect/CMakeLists.txt +++ b/src/pyreflect/CMakeLists.txt @@ -3,10 +3,5 @@ set(SELF_LIB pyreflect) set(SELF_SRCS pyreflect.cpp) -# ---------------------------------------------------------------- -# pybind11 dep - xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) - xo_pybind11_dependency(${SELF_LIB} reflect) - From ba0b989b6dab3cf8bb099d0f73ac2e63cb8bbe10 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 22:08:35 -0400 Subject: [PATCH 0552/2693] build: bugfix for .cmake eigen dep --- cmake/xo_kalmanfilterConfig.cmake.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/xo_kalmanfilterConfig.cmake.in b/cmake/xo_kalmanfilterConfig.cmake.in index 366e45ba..950e70c0 100644 --- a/cmake/xo_kalmanfilterConfig.cmake.in +++ b/cmake/xo_kalmanfilterConfig.cmake.in @@ -7,7 +7,7 @@ include(CMakeFindDependencyMacro) # in xo-reactor/src/reactor/CMakeLists.txt # find_dependency(reactor) -find_dependnecy(eigen3) +find_dependency(Eigen3) #find_dependency(reflect) #find_dependency(webutil) #find_dependency(printjson) From f95cd5edc5ce5bac3f8e55530fdc26c38f626739 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 22:09:49 -0400 Subject: [PATCH 0553/2693] + .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..378eac25 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build From e72c0d27af5ab35a346dc471b001d13c05d7c320 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 22:10:11 -0400 Subject: [PATCH 0554/2693] build: bugfix: pywebutil -> xo_pywebutil --- src/pywebutil/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pywebutil/CMakeLists.txt b/src/pywebutil/CMakeLists.txt index 95f8df26..cc68d6e1 100644 --- a/src/pywebutil/CMakeLists.txt +++ b/src/pywebutil/CMakeLists.txt @@ -1,6 +1,6 @@ # xo-pywebutil/src/pywebutil/CMakeLists.txt -set(SELF_LIB pywebutil) +set(SELF_LIB xo_pywebutil) set(SELF_SRCS pywebutil.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) From 8472e4196d7627331a5a7893b226b87b3f3697e7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 22:11:18 -0400 Subject: [PATCH 0555/2693] + .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..d6536bad --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +compile_commands.json From 9dc0c6e25695d4d67e87e7ee4a504e5a86661f98 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 22:13:04 -0400 Subject: [PATCH 0556/2693] pyprintjson -> xo_pyprintjson --- src/pyprintjson/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyprintjson/CMakeLists.txt b/src/pyprintjson/CMakeLists.txt index 5e77c995..2370e6a2 100644 --- a/src/pyprintjson/CMakeLists.txt +++ b/src/pyprintjson/CMakeLists.txt @@ -1,6 +1,6 @@ # xo_pyprintjson/src/pyprintjson/CMakeLists.txt -set(SELF_LIB pyprintjson) +set(SELF_LIB xo_pyprintjson) set(SELF_SRCS pyprintjson.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) From 9d412f10e19099c39e13bb51354e7684acbb099f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 22:15:00 -0400 Subject: [PATCH 0557/2693] + .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..378eac25 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build From aa539e1075a9d612ad7028f2b39f462e8ffcdfd0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 22:15:43 -0400 Subject: [PATCH 0558/2693] + .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..d6536bad --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +compile_commands.json From e4169def1ca0b6ab541b2b126e7758bfc3392674 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 22:18:06 -0400 Subject: [PATCH 0559/2693] tweak message on INTERFACE_LINK_LIBRARIES --- cmake/xo_macros/xo_cxx.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index 06a28990..eb389ad7 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -884,7 +884,7 @@ macro(xo_pybind11_dependency target dep) # ok to keep dep libraries on link line in submodule build #message("xo_pybind11_dependency: ${target}: don't clobber ${dep}.INTERFACE_LINK_LIBRARIES") else() - message("xo_pybind11_dependency: ${target}: clobbering ${dep}.INTERFACE_LINK_LIBRARIES") + message("xo_pybind11_dependency: ${target}: remove ${dep}.INTERFACE_LINK_LIBRARIES to avoid problems with transitive deps") set_property(TARGET ${dep} PROPERTY INTERFACE_LINK_LIBRARIES "") # also have to clobber libraries for From 09cb38f26565d769d89bee5eefadd5fe3bfad3db Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 22:19:09 -0400 Subject: [PATCH 0560/2693] + .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..d6536bad --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +compile_commands.json From 45a81a2a7a77b87fe0703834390b5eb34e8a30b0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 22:19:57 -0400 Subject: [PATCH 0561/2693] + .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..378eac25 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build From 31105757e6e41b5f5438dd1a2fab3656acb01fab Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 22:20:14 -0400 Subject: [PATCH 0562/2693] tidy -- remove dead model file --- CMakeLists.txt.old | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 CMakeLists.txt.old diff --git a/CMakeLists.txt.old b/CMakeLists.txt.old deleted file mode 100644 index e85d245f..00000000 --- a/CMakeLists.txt.old +++ /dev/null @@ -1,37 +0,0 @@ -# filter/CMakeLists.txt - -set(SELF_LIBRARY_NAME filter) - -set(SELF_SOURCE_FILES KalmanFilterSvc.cpp KalmanFilter.cpp KalmanFilterState.cpp KalmanFilterTransition.cpp KalmanFilterObservable.cpp KalmanFilterInput.cpp KalmanFilterStep.cpp KalmanFilterSpec.cpp KalmanFilterEngine.cpp KalmanFilterInputToConsole.cpp KalmanFilterStateToConsole.cpp EigenUtil.cpp init_filter.cpp) - -# build shared liburary 'filter' -add_library(${SELF_LIBRARY_NAME} SHARED ${SELF_SOURCE_FILES}) - -set_target_properties(${SELF_LIBRARY_NAME} - PROPERTIES - VERSION ${PROJECT_VERSION} - SOVERSION 1 - PUBLIC_HEADER init_filter.hpp) - -# ---------------------------------------------------------------- -# all the warnings! -# -xo_compile_options(${SELF_LIBRARY_NAME}) -xo_include_options(${SELF_LIBRARY_NAME}) - -# ---------------------------------------------------------------- -# internal dependencies: reactor, ... - -#target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC process) -target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC reactor) -target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC printjson) -target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC refcnt) - -# ---------------------------------------------------------------- -# 3rd party dependency: eigen: - -xo_eigen_dependency(${SELF_LIBRARY_NAME}) - -xo_install_library(${SELF_LIBRARY_NAME}) - -# end CMakeLists.txt From b0b3e7c0c8b297fad95daefa4ad197f1828e9592 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 13 Mar 2024 15:30:00 -0400 Subject: [PATCH 0563/2693] cosmetic: layout, copypasta comments --- utest/StructTdx.test.cpp | 76 +++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 39 deletions(-) diff --git a/utest/StructTdx.test.cpp b/utest/StructTdx.test.cpp index cb845e18..6b1fb2cd 100644 --- a/utest/StructTdx.test.cpp +++ b/utest/StructTdx.test.cpp @@ -7,53 +7,51 @@ #include namespace xo { - using xo::reflect::Reflect; - using xo::reflect::TaggedPtr; - using xo::reflect::TypeDescr; - using xo::reflect::Metatype; + using xo::reflect::Reflect; + using xo::reflect::TaggedPtr; + using xo::reflect::TypeDescr; + using xo::reflect::Metatype; - namespace ut { - TEST_CASE("std-pair-reflect", "[reflect]") { - std::pair p; + namespace ut { + TEST_CASE("std-pair-reflect", "[reflect]") { + std::pair p; - TaggedPtr tp = Reflect::make_tp(&p); - //TypeDescr td = Reflect::require>(); + TaggedPtr tp = Reflect::make_tp(&p); - REQUIRE(Reflect::is_reflected>() == true); + REQUIRE(Reflect::is_reflected>() == true); - REQUIRE(tp.td()->complete_flag()); - REQUIRE(tp.address() == &p); - REQUIRE(tp.is_struct()); - REQUIRE(tp.is_vector() == false); - REQUIRE(tp.td()->metatype() == Metatype::mt_struct); - REQUIRE(tp.recover_native>() == &p); - REQUIRE(tp.n_child() == 2); /* struct with 2 members */ - REQUIRE(tp.struct_member_name(0) == "first"); - REQUIRE(tp.struct_member_name(1) == "second"); + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &p); + REQUIRE(tp.is_struct()); + REQUIRE(tp.is_vector() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_struct); + REQUIRE(tp.recover_native>() == &p); + REQUIRE(tp.n_child() == 2); /* struct with 2 members */ + REQUIRE(tp.struct_member_name(0) == "first"); + REQUIRE(tp.struct_member_name(1) == "second"); - TaggedPtr tp0 = tp.get_child(0); + TaggedPtr tp0 = tp.get_child(0); - REQUIRE(tp0.td()->complete_flag()); - REQUIRE(tp0.address() == &(p.first)); - REQUIRE(!tp0.is_vector()); - REQUIRE(!tp0.is_struct()); - REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); - REQUIRE(tp0.recover_native() == &(p.first)); - REQUIRE(tp0.n_child() == 0); + REQUIRE(tp0.td()->complete_flag()); + REQUIRE(tp0.address() == &(p.first)); + REQUIRE(!tp0.is_vector()); + REQUIRE(!tp0.is_struct()); + REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp0.recover_native() == &(p.first)); + REQUIRE(tp0.n_child() == 0); - TaggedPtr tp1 = tp.get_child(1); + TaggedPtr tp1 = tp.get_child(1); - REQUIRE(tp1.td()->complete_flag()); - REQUIRE(tp1.address() == &(p.second)); - REQUIRE(!tp1.is_vector()); - REQUIRE(!tp1.is_struct()); - REQUIRE(tp1.td()->metatype() == Metatype::mt_atomic); - REQUIRE(tp1.recover_native() == &(p.second)); - REQUIRE(tp1.n_child() == 0); + REQUIRE(tp1.td()->complete_flag()); + REQUIRE(tp1.address() == &(p.second)); + REQUIRE(!tp1.is_vector()); + REQUIRE(!tp1.is_struct()); + REQUIRE(tp1.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp1.recover_native() == &(p.second)); + REQUIRE(tp1.n_child() == 0); - } /*TEST_CASE(std-pair-reflect)*/ - - } /*namespace ut*/ + } /*TEST_CASE(std-pair-reflect)*/ + } /*namespace ut*/ } /*namespace xo*/ -/* end VectorTdx.test.cpp */ +/* end StructTdx.test.cpp */ From aa8f431cc292353c70c7df71db771e052d21cdc8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 13 Mar 2024 15:30:31 -0400 Subject: [PATCH 0564/2693] indentlog: cosmetic: + comment --- include/xo/indentlog/log_streambuf.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/indentlog/log_streambuf.hpp b/include/xo/indentlog/log_streambuf.hpp index 3a467b82..0c4b3aae 100644 --- a/include/xo/indentlog/log_streambuf.hpp +++ b/include/xo/indentlog/log_streambuf.hpp @@ -80,7 +80,7 @@ namespace xo { char * p_hi = p_base + this->buf_v_.capacity(); this->setp(p_base, p_hi); - this->pbump(old_n + 1); + this->pbump(old_n + 1); /*see 'this->buf_v_[old_n] - new_ch' above*/ return new_ch; } /*overflow*/ From 93910f901300825e6348bed1806e6b534b7d15b2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 13 Mar 2024 16:34:07 -0400 Subject: [PATCH 0565/2693] pybind11 python-fetching deprecated, drop it --- cmake/xo_macros/xo_cxx.cmake | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index 1f392ad7..f0b62c86 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -803,13 +803,7 @@ macro(xo_pybind11_library target projectTargets source_files) DESTINATION ${CMAKE_INSTALL_PREFIX}/include/xo/${_nxo_target}) endif() - # find_package(Python..) finds python in - # /Library/Frameworks/Python.framework/... - # but we want to use python from nix - # - #find_package(Python COMPONENTS Interpreter Development REQUIRED) - # - + find_package(Python COMPONENTS Interpreter Development REQUIRED) find_package(pybind11) # this only works if one source file, right? From ccdf8d467f088e99dd659f1f4ba60d34d1121616 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 13 Mar 2024 16:36:15 -0400 Subject: [PATCH 0566/2693] websock: export libwebsockets dep --- cmake/websockConfig.cmake.in | 1 + src/websock/CMakeLists.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/cmake/websockConfig.cmake.in b/cmake/websockConfig.cmake.in index a3dad29f..68730bdb 100644 --- a/cmake/websockConfig.cmake.in +++ b/cmake/websockConfig.cmake.in @@ -8,6 +8,7 @@ include(CMakeFindDependencyMacro) # find_dependency(reactor) find_dependency(webutil) +find_dependency(Libwebsockets websockets_shared) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") diff --git a/src/websock/CMakeLists.txt b/src/websock/CMakeLists.txt index 1071bf7f..0debcb3c 100644 --- a/src/websock/CMakeLists.txt +++ b/src/websock/CMakeLists.txt @@ -15,6 +15,7 @@ xo_dependency(${SELF_LIB} reactor) xo_dependency(${SELF_LIB} webutil) # see LibwebsocketsTargets-release.cmake for available targets +# this dependency doesn't show up via cmake-export xo_external_target_dependency(${SELF_LIB} Libwebsockets websockets_shared) # see jsoncpp-namespaced-targets.cmake (maybe?) for available targets xo_external_target_dependency(${SELF_LIB} jsoncpp jsoncpp_lib) From d34315d33f485fd5affb810979fcf5880c155b3b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 13 Mar 2024 16:50:58 -0400 Subject: [PATCH 0567/2693] bugfix: find_dependency() extra arg --- cmake/websockConfig.cmake.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/websockConfig.cmake.in b/cmake/websockConfig.cmake.in index 68730bdb..3e03d576 100644 --- a/cmake/websockConfig.cmake.in +++ b/cmake/websockConfig.cmake.in @@ -8,7 +8,7 @@ include(CMakeFindDependencyMacro) # find_dependency(reactor) find_dependency(webutil) -find_dependency(Libwebsockets websockets_shared) +find_dependency(Libwebsockets) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") From ca721658e9d4f2b892c86b47cf48cafd8bc3bd92 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:20:18 -0400 Subject: [PATCH 0568/2693] stremaline xo-cmake info messages --- .gitignore | 2 +- cmake/xo_macros/xo_cxx.cmake | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 378eac25..24e5b0a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -build +.build diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index f0b62c86..e63762f8 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -557,7 +557,7 @@ macro(xo_dependency_helper target visibility dep) xo_dependency_helper1(${target} ${visibility} repo/${_nxo_dep}/include) endif() else() - message("xo_dependency_helper: find_package() on ${dep} for ${target}") + message("-- [${target}] find_package(${dep}) (xo_dependency_helper)") find_package(${dep} CONFIG REQUIRED) endif() @@ -713,6 +713,7 @@ endmacro() # target_link_libraries(foo PUBLIC Catch2::Catch2) # macro(xo_external_target_dependency target pkg pkgtarget) + message("-- [${target}] find_package(${pkg}) (xo_external_target_dependency)") find_package(${pkg} CONFIG REQUIRED) target_link_libraries(${target} PUBLIC ${pkgtarget}) #target_link_libraries(${target} ${pkgtarget}) @@ -803,7 +804,9 @@ macro(xo_pybind11_library target projectTargets source_files) DESTINATION ${CMAKE_INSTALL_PREFIX}/include/xo/${_nxo_target}) endif() + message("-- [${target}] find_package(Python) (xo_pybind11_library)") find_package(Python COMPONENTS Interpreter Development REQUIRED) + message("-- [${target}] find_package(pybind11) (xo_pybind11_library)") find_package(pybind11) # this only works if one source file, right? @@ -878,7 +881,7 @@ macro(xo_pybind11_dependency target dep) # ok to keep dep libraries on link line in submodule build #message("xo_pybind11_dependency: ${target}: don't clobber ${dep}.INTERFACE_LINK_LIBRARIES") else() - message("xo_pybind11_dependency: ${target}: remove ${dep}.INTERFACE_LINK_LIBRARIES to avoid problems with transitive deps") + message("-- [${target}] remove ${dep}.INTERFACE_LINK_LIBRARIES to avoid problems with transitive deps (xo_pybind11_dependency)") set_property(TARGET ${dep} PROPERTY INTERFACE_LINK_LIBRARIES "") # also have to clobber libraries for From bcf9713e07f2fa7a3c66ecab33c1df63bc17a5a7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:23:15 -0400 Subject: [PATCH 0569/2693] build: streamline CMAKE_MODULE_PATH interaction --- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From 94be37cef77355d81cf861bbb9564930cc594571 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:23:51 -0400 Subject: [PATCH 0570/2693] build: streamline CMAKE_MODULE_PATH interaction (2) --- .gitignore | 3 +-- CMakeLists.txt | 2 +- indentlogConfig.cmake.in | 4 ---- 3 files changed, 2 insertions(+), 7 deletions(-) delete mode 100644 indentlogConfig.cmake.in diff --git a/.gitignore b/.gitignore index 81c5ae6d..7ebf47c3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,4 @@ compile_commands.json # lsp keeps state here .cache # typical build dirs -build -ccov +.build* diff --git a/CMakeLists.txt b/CMakeLists.txt index f8d04834..43ded08e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ project(indentlog VERSION 0.1) enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) -include(xo_macros/xo-project-macros) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/indentlogConfig.cmake.in b/indentlogConfig.cmake.in deleted file mode 100644 index cc57615e..00000000 --- a/indentlogConfig.cmake.in +++ /dev/null @@ -1,4 +0,0 @@ -@PACKAGE_INIT@ - -include("${CMAKE_CURRENT_LIST_DIR}/indentlogTargets.cmake") -check_required_components("@PROJECT_NAME@") From c40f5f3ccf2a5b05be83f56272675876b1b0079a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:24:55 -0400 Subject: [PATCH 0571/2693] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 3 +- CMakeLists.txt | 3 +- cmake/xo-bootstrap-macros.cmake | 12 + utest/intrusive_ptr.test.cpp | 420 ++++++++++++++++---------------- 4 files changed, 224 insertions(+), 214 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index 86d062ab..9648517d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,4 @@ compile_commands.json # LSP keeps state here .cache # typical build directories -build -ccov +.build* diff --git a/CMakeLists.txt b/CMakeLists.txt index bcb080d4..e9176eee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(refcnt VERSION 0.1) enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) diff --git a/utest/intrusive_ptr.test.cpp b/utest/intrusive_ptr.test.cpp index bc45d38c..890832b7 100644 --- a/utest/intrusive_ptr.test.cpp +++ b/utest/intrusive_ptr.test.cpp @@ -7,295 +7,295 @@ #include namespace xo { - using xo::ref::Refcount; - using xo::ref::Borrow; - using xo::ref::rp; - using xo::ref::brw; - using xo::ref::intrusive_ptr_refcount; - using xo::ref::intrusive_ptr_add_ref; - using xo::ref::intrusive_ptr_release; + using xo::ref::Refcount; + using xo::ref::Borrow; + using xo::ref::rp; + using xo::ref::brw; + using xo::ref::intrusive_ptr_refcount; + using xo::ref::intrusive_ptr_add_ref; + using xo::ref::intrusive_ptr_release; - namespace ut { - namespace { - static uint32_t ctor_count = 0; - static uint32_t dtor_count = 0; + namespace ut { + namespace { + static uint32_t ctor_count = 0; + static uint32_t dtor_count = 0; - /* empty object, except for refcount */ - class JustRefcount : public ref::Refcount { - public: - JustRefcount() { ++ctor_count; } - ~JustRefcount() { ++dtor_count; } - }; /*JustRefcount*/ + /* empty object, except for refcount */ + class JustRefcount : public ref::Refcount { + public: + JustRefcount() { ++ctor_count; } + ~JustRefcount() { ++dtor_count; } + }; /*JustRefcount*/ - inline std::ostream & operator<<(std::ostream & os, JustRefcount & x) { - os << "JustRefcount"; - return os; - } /*operator<<*/ - } /*namespace*/ + inline std::ostream & operator<<(std::ostream & os, JustRefcount & x) { + os << "JustRefcount"; + return os; + } /*operator<<*/ + } /*namespace*/ - TEST_CASE("refcount", "[refcnt][trivial]") { - REQUIRE(std::is_default_constructible() == true); - REQUIRE(std::has_virtual_destructor() == true); + TEST_CASE("refcount", "[refcnt][trivial]") { + REQUIRE(std::is_default_constructible() == true); + REQUIRE(std::has_virtual_destructor() == true); - /* refcount object self-initializes to 0 */ - Refcount x; - REQUIRE(x.reference_counter() == 0); - } /*TEST_CASE(refcount)*/ + /* refcount object self-initializes to 0 */ + Refcount x; + REQUIRE(x.reference_counter() == 0); + } /*TEST_CASE(refcount)*/ - TEST_CASE("null-intrusive-ptr", "[refcnt][trivial]") { - //constexpr std::string_view c_self = "TEST_CASE:null-intrusive-ptr"; + TEST_CASE("null-intrusive-ptr", "[refcnt][trivial]") { + //constexpr std::string_view c_self = "TEST_CASE:null-intrusive-ptr"; - REQUIRE(std::has_virtual_destructor() == true); + REQUIRE(std::has_virtual_destructor() == true); - rp p1; - rp p2; + rp p1; + rp p2; - REQUIRE(sizeof(p1) == sizeof(JustRefcount*)); + REQUIRE(sizeof(p1) == sizeof(JustRefcount*)); - REQUIRE(p1.get() == nullptr); - REQUIRE(p1.operator->() == nullptr); + REQUIRE(p1.get() == nullptr); + REQUIRE(p1.operator->() == nullptr); - REQUIRE(p2.get() == nullptr); - REQUIRE(p2.operator->() == nullptr); + REQUIRE(p2.get() == nullptr); + REQUIRE(p2.operator->() == nullptr); - /* can assign a nullptr */ - rp p3; + /* can assign a nullptr */ + rp p3; - REQUIRE(p3.get() == nullptr); - p3 = p1; - REQUIRE(p3.get() == nullptr); + REQUIRE(p3.get() == nullptr); + p3 = p1; + REQUIRE(p3.get() == nullptr); - /* can use aux functions on null pointers */ - REQUIRE(intrusive_ptr_refcount(p1.get()) == 0); + /* can use aux functions on null pointers */ + REQUIRE(intrusive_ptr_refcount(p1.get()) == 0); - intrusive_ptr_add_ref(nullptr); - intrusive_ptr_release(nullptr); + intrusive_ptr_add_ref(nullptr); + intrusive_ptr_release(nullptr); - /* can borrow a null intrusive_ptr */ - brw p1_brw = p1.borrow(); - brw p2_brw = p2.borrow(); + /* can borrow a null intrusive_ptr */ + brw p1_brw = p1.borrow(); + brw p2_brw = p2.borrow(); - REQUIRE(p1_brw.get() == nullptr); - REQUIRE(p1_brw.operator->() == nullptr); - /* null borrow is false-y */ - REQUIRE(p1_brw == false); + REQUIRE(p1_brw.get() == nullptr); + REQUIRE(p1_brw.operator->() == nullptr); + /* null borrow is false-y */ + REQUIRE(p1_brw == false); - /* can promote a borrowed pointer */ - rp pp = p1_brw.promote(); + /* can promote a borrowed pointer */ + rp pp = p1_brw.promote(); - REQUIRE(p1.get() == pp.get()); + REQUIRE(p1.get() == pp.get()); - /* comparisons */ - REQUIRE(Borrow::compare(p1_brw, p2_brw) == 0); - REQUIRE(p1_brw == p2_brw); - REQUIRE((p1_brw != p2_brw) == false); - REQUIRE(p1 == p1_brw); - REQUIRE((p1 != p1_brw) == false); - REQUIRE(p1_brw == p1); - REQUIRE((p1_brw != p1) == false); - } /*TEST_CASE(null-intrusive_ptr)*/ + /* comparisons */ + REQUIRE(Borrow::compare(p1_brw, p2_brw) == 0); + REQUIRE(p1_brw == p2_brw); + REQUIRE((p1_brw != p2_brw) == false); + REQUIRE(p1 == p1_brw); + REQUIRE((p1 != p1_brw) == false); + REQUIRE(p1_brw == p1); + REQUIRE((p1_brw != p1) == false); + } /*TEST_CASE(null-intrusive_ptr)*/ - TEST_CASE("intrusive-ptr-identity", "[refcnt][identity]") - { - uint32_t cc = ctor_count; - uint32_t dc = dtor_count; + TEST_CASE("intrusive-ptr-identity", "[refcnt][identity]") + { + uint32_t cc = ctor_count; + uint32_t dc = dtor_count; - rp p1(new JustRefcount()); + rp p1(new JustRefcount()); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); - REQUIRE(p1.get() != nullptr); - REQUIRE(p1.get() == p1.operator->()); - REQUIRE(intrusive_ptr_refcount(p1.get()) == 1); - REQUIRE(p1->reference_counter() == 1); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get() != nullptr); + REQUIRE(p1.get() == p1.operator->()); + REQUIRE(intrusive_ptr_refcount(p1.get()) == 1); + REQUIRE(p1->reference_counter() == 1); - intrusive_ptr_add_ref(p1.get()); + intrusive_ptr_add_ref(p1.get()); - REQUIRE(intrusive_ptr_refcount(p1.get()) == 2); + REQUIRE(intrusive_ptr_refcount(p1.get()) == 2); - intrusive_ptr_release(p1.get()); + intrusive_ptr_release(p1.get()); - REQUIRE(intrusive_ptr_refcount(p1.get()) == 1); + REQUIRE(intrusive_ptr_refcount(p1.get()) == 1); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); - rp p2(new JustRefcount()); + rp p2(new JustRefcount()); - REQUIRE(ctor_count == cc + 2); - REQUIRE(dtor_count == dc); + REQUIRE(ctor_count == cc + 2); + REQUIRE(dtor_count == dc); - REQUIRE(p2.get() != nullptr); - REQUIRE(p2.get() != p1.get()); - REQUIRE(p2.get() == p2.operator->()); - REQUIRE(p2->reference_counter() == 1); + REQUIRE(p2.get() != nullptr); + REQUIRE(p2.get() != p1.get()); + REQUIRE(p2.get() == p2.operator->()); + REQUIRE(p2->reference_counter() == 1); - /* can borrow a non-null intrusive-ptr */ - brw p1_brw = p1.borrow(); + /* can borrow a non-null intrusive-ptr */ + brw p1_brw = p1.borrow(); - REQUIRE(p1_brw.get() == p1.get()); + REQUIRE(p1_brw.get() == p1.get()); - /* borrowing does not change refcount, borrow not tracked */ - REQUIRE(ctor_count == cc + 2); - REQUIRE(dtor_count == dc); - REQUIRE(p1.get()->reference_counter() == 1); + /* borrowing does not change refcount, borrow not tracked */ + REQUIRE(ctor_count == cc + 2); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get()->reference_counter() == 1); - /* copying borrowed pointer does not touch refcount */ - brw p1_brw2 = p1_brw; + /* copying borrowed pointer does not touch refcount */ + brw p1_brw2 = p1_brw; - REQUIRE(ctor_count == cc + 2); - REQUIRE(dtor_count == dc); - REQUIRE(p1_brw2.get() == p1.get()); + REQUIRE(ctor_count == cc + 2); + REQUIRE(dtor_count == dc); + REQUIRE(p1_brw2.get() == p1.get()); - REQUIRE(p1.get()->reference_counter() == 1); - } /*TEST_CASE(identity-intrusive-ptr)*/ + REQUIRE(p1.get()->reference_counter() == 1); + } /*TEST_CASE(identity-intrusive-ptr)*/ - TEST_CASE("intrusive-ptr-release", "[refcnt][release]") - { - uint32_t cc = ctor_count; - uint32_t dc = dtor_count; + TEST_CASE("intrusive-ptr-release", "[refcnt][release]") + { + uint32_t cc = ctor_count; + uint32_t dc = dtor_count; - rp p1(new JustRefcount()); + rp p1(new JustRefcount()); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); - REQUIRE(p1.get() != nullptr); - REQUIRE(p1->reference_counter() == 1); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get() != nullptr); + REQUIRE(p1->reference_counter() == 1); - /* reference count going to 0 -> delete object */ - p1 = nullptr; + /* reference count going to 0 -> delete object */ + p1 = nullptr; - REQUIRE(p1.get() == nullptr); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc + 1); - } /*TEST_CASE(intrusive-ptr-release)*/ + REQUIRE(p1.get() == nullptr); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc + 1); + } /*TEST_CASE(intrusive-ptr-release)*/ - TEST_CASE("intrusive-ptr-copy", "[refcnt][copy]") - { - uint32_t cc = ctor_count; - uint32_t dc = dtor_count; + TEST_CASE("intrusive-ptr-copy", "[refcnt][copy]") + { + uint32_t cc = ctor_count; + uint32_t dc = dtor_count; - rp p1(new JustRefcount()); - JustRefcount * p1_native = p1.get(); + rp p1(new JustRefcount()); + JustRefcount * p1_native = p1.get(); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); - REQUIRE(p1.get() != nullptr); - REQUIRE(p1->reference_counter() == 1); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get() != nullptr); + REQUIRE(p1->reference_counter() == 1); - /* copy ctor ran to make copy of p1, did not allocate */ - rp p2(p1); + /* copy ctor ran to make copy of p1, did not allocate */ + rp p2(p1); - REQUIRE(p1->reference_counter() == 2); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); + REQUIRE(p1->reference_counter() == 2); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); - } /*TEST_CASE(intrusive-ptr-copy)*/ + } /*TEST_CASE(intrusive-ptr-copy)*/ - TEST_CASE("intrusive-ptr-move", "[refcnt][move]") - { - uint32_t cc = ctor_count; - uint32_t dc = dtor_count; + TEST_CASE("intrusive-ptr-move", "[refcnt][move]") + { + uint32_t cc = ctor_count; + uint32_t dc = dtor_count; - rp p1(new JustRefcount()); - JustRefcount * p1_native = p1.get(); + rp p1(new JustRefcount()); + JustRefcount * p1_native = p1.get(); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); - REQUIRE(p1.get() != nullptr); - REQUIRE(p1->reference_counter() == 1); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get() != nullptr); + REQUIRE(p1->reference_counter() == 1); - rp p2{std::move(p1)}; + rp p2{std::move(p1)}; - REQUIRE(p2->reference_counter() == 1); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); + REQUIRE(p2->reference_counter() == 1); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); - p2 = nullptr; + p2 = nullptr; - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc + 1); - } /*TEST_CASE(intrusive-ptr-move)*/ + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc + 1); + } /*TEST_CASE(intrusive-ptr-move)*/ - TEST_CASE("instrusive-ptr-assign", "[refcnt][assign]") - { - uint32_t cc = ctor_count; - uint32_t dc = dtor_count; + TEST_CASE("instrusive-ptr-assign", "[refcnt][assign]") + { + uint32_t cc = ctor_count; + uint32_t dc = dtor_count; - rp p1(new JustRefcount()); - JustRefcount * p1_native = p1.get(); + rp p1(new JustRefcount()); + JustRefcount * p1_native = p1.get(); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); - REQUIRE(p1.get() != nullptr); - REQUIRE(p1->reference_counter() == 1); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get() != nullptr); + REQUIRE(p1->reference_counter() == 1); - rp p2; + rp p2; - REQUIRE(p2.get() == nullptr); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); + REQUIRE(p2.get() == nullptr); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); - p2 = p1; + p2 = p1; - REQUIRE(p2.get() == p1.get()); - REQUIRE(p2->reference_counter() == 2); + REQUIRE(p2.get() == p1.get()); + REQUIRE(p2->reference_counter() == 2); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); - p1 = nullptr; + p1 = nullptr; - REQUIRE(p2->reference_counter() == 1); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); + REQUIRE(p2->reference_counter() == 1); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); - p2 = nullptr; + p2 = nullptr; - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc + 1); - } /*TEST_CASE(intrusive-ptr-assign)*/ + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc + 1); + } /*TEST_CASE(intrusive-ptr-assign)*/ - TEST_CASE("intrusive-ptr-move-assign", "[refcnt][move-assign]") - { - uint32_t cc = ctor_count; - uint32_t dc = dtor_count; + TEST_CASE("intrusive-ptr-move-assign", "[refcnt][move-assign]") + { + uint32_t cc = ctor_count; + uint32_t dc = dtor_count; - rp p1(new JustRefcount()); - JustRefcount * p1_native = p1.get(); + rp p1(new JustRefcount()); + JustRefcount * p1_native = p1.get(); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); - REQUIRE(p1.get() != nullptr); - REQUIRE(p1->reference_counter() == 1); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get() != nullptr); + REQUIRE(p1->reference_counter() == 1); - rp p2; + rp p2; - REQUIRE(p2.get() == nullptr); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); + REQUIRE(p2.get() == nullptr); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); - p2 = std::move(p1); + p2 = std::move(p1); - REQUIRE(p1.get() == nullptr); - REQUIRE(p2.get() == p1_native); - REQUIRE(p2->reference_counter() == 1); + REQUIRE(p1.get() == nullptr); + REQUIRE(p2.get() == p1_native); + REQUIRE(p2->reference_counter() == 1); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); - p1 = nullptr; /*no-op*/ + p1 = nullptr; /*no-op*/ - REQUIRE(p2->reference_counter() == 1); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); + REQUIRE(p2->reference_counter() == 1); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); - p2 = nullptr; + p2 = nullptr; - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc + 1); - } /*TEST_CASE(intrusive-ptr-move-assign)*/ - } /*namespace ut*/ + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc + 1); + } /*TEST_CASE(intrusive-ptr-move-assign)*/ + } /*namespace ut*/ } /*namespace xo*/ /* end intrusive_ptr.test.cpp */ From 8f11a8e413e22e2c651d8fda388875e2a5915ef3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:25:48 -0400 Subject: [PATCH 0572/2693] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 8 ++++++-- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index d6bd1a25..9648517d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ -# typical build location -build +# symlink to ${mybuilddirectory}/compile_commands.json for LSP +compile_commands.json +# LSP keeps state here +.cache +# typical build directories +.build* diff --git a/CMakeLists.txt b/CMakeLists.txt index 766058b1..9d6fef68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,8 +3,7 @@ cmake_minimum_required(VERSION 3.10) project(subsys VERSION 0.1) enable_language(CXX) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) enable_testing() # activate code coverage for all executables + libraries (when -DCODE_COVERAGE=ON) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From fe986a320a2b1993a8399602cb3a3b6dc8c86253 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:26:43 -0400 Subject: [PATCH 0573/2693] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 2 +- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index eff45bd2..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # clangd working space (see emacs+lsp) .cache # typical cmake build directory (source-tree-nephew) -build*/* +.build* # symlink to builddir/compile_commands.json; should be set manually in dev sandbox compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index e7c1b6eb..de455b56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,7 @@ enable_language(CXX) #message(STATUS "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}") # common XO cmake macros (see proj/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From c0ff16e3acca3b0e5c22360b3bd5d5edc5cbba57 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:27:50 -0400 Subject: [PATCH 0574/2693] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 2 +- CMakeLists.txt | 4 +--- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index e90fd723..b0029e6b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # clangd (run via lsp) keeps state here .cache # typical build directory -build +.build # compile_commands.json: symlink to build directory, should be created manually compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 8506e5b6..52606f5a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,9 +5,7 @@ cmake_minimum_required(VERSION 3.10) project(randomgen VERSION 0.1) enable_language(CXX) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) -#include(cmake/cxx.cmake) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From faf58781e9e7a45f7f2edf23eb52ae5d28384c03 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:28:45 -0400 Subject: [PATCH 0575/2693] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 9 +++++---- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index 9e716afc..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ -# lsp keeps state here +# clangd working space (see emacs+lsp) .cache -# typical build directories -build -ccov +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index f336712c..ff3d8726 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(xo_ordinaltree VERSION 0.1) enable_language(CXX) # common XO macros (see github:Rconybea/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From f81b6abda80a87693494378c4936a8bc977c7b68 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:29:24 -0400 Subject: [PATCH 0576/2693] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 9 ++++++--- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index 49f711e2..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ -# typical build directories -build -ccov +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b8e6720..e412e4a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(xo_pyutil VERSION 0.1) enable_language(CXX) # common XO cmake macros (see github:Rconybea/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) # very little to unit test here +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From fd279f0e38601c288451354cf5d052c7ce3d9c0f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:30:07 -0400 Subject: [PATCH 0577/2693] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 2 +- CMakeLists.txt | 4 +--- README.md | 36 ++++++++++++++++++++++----------- cmake/xo-bootstrap-macros.cmake | 12 +++++++++++ 4 files changed, 38 insertions(+), 16 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index f52f1311..53a9c92f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # lsp keeps state here .cache # typical build directory -build +.build* # lsp: symlink to file in build directory (established manually) compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index b2e5ca94..5d84942e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,9 +5,7 @@ cmake_minimum_required(VERSION 3.10) project(xo_pyreflect VERSION 0.1) enable_language(CXX) -# common XO cmake macros (see github.com:Rconybea/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/README.md b/README.md index 7be07dd9..078e1a1b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# python bindings for c++ reflection library (xo-reflect) +# python bindings for c++ reflection library (xo-pyreflect) ## Getting Started @@ -22,6 +22,22 @@ $ make install ``` (also see .github/workflows/main.yml) +## Examples + +Assumes `xo-pyreflect` installed to `~/local2/lib` + +``` +PYTHONPATH=~/local2/lib python +>>> import xo_pyreflect +>>> dir(xo_pyreflect) +['SelfTagging', 'TaggedRcptr', 'TypeDescr', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__'] +>>> xo_pyreflect.TypeDescr.print_reflected_types() + +``` +(Not _immediately_ interesting: no reflected types in `pyreflect` itself) + +## Development + ### build for unit test coverage ``` $ cd xo-pyreflect @@ -37,24 +53,20 @@ $ cmake \ ### LSP (language server) support LSP looks for compile commands in the root of the source tree; -while Cmake creates them in the root of its build directory. +while `cmake` creates them in the root of its build directory. ``` $ cd xo-pyreflect $ ln -s build/compile_commands.json # supply compile commands to LSP ``` -## Examples +### display cmake variables -Assumes `xo-pyreflect` installed to `~/local2/lib` +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text ``` -PYTHONPATH=~/local2/lib python ->>> import pyreflect ->>> dir(pyreflect) -['SelfTagging', 'TaggedRcptr', 'TypeDescr', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__'] ->>> pyreflect.TypeDescr.print_reflected_types() - +$ cd xo-pyprintjson/build +$ cmake -LAH ``` - -Not _immediately_ interesting: no reflected types in `pyreflect` itself diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From 80f8b7a96eff7f5509cd3956a9385b2b2755c9f6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:30:44 -0400 Subject: [PATCH 0578/2693] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 6 +++++- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index d6536bad..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ -build +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 153acdac..c6c5fcb1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,8 +4,7 @@ cmake_minimum_required(VERSION 3.10) project(xo_distribution VERSION 1.0) -# common XO cmake macros (see github.com:Rconybea/xo-cmake) -include(xo_macros/xo-project-macros) +include(cmake/xo-bootstrap-macros.cmake) xo_cxx_toplevel_options() diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From b07ce785c5d51c6aa9a0d452dab674f5ee1c944f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:31:30 -0400 Subject: [PATCH 0579/2693] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 6 +++++- CMakeLists.txt | 2 +- README.md | 6 +++--- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ src/pydistribution/CMakeLists.txt | 2 +- 5 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index d6536bad..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ -build +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index d2823501..7b4de072 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.10) project(xo_pydistribution VERSION 1.0) -include(xo_macros/xo-project-macros) +include(cmake/xo-bootstrap-macros.cmake) xo_cxx_toplevel_options() diff --git a/README.md b/README.md index ef597486..98199fef 100644 --- a/README.md +++ b/README.md @@ -50,10 +50,10 @@ Assumes `xo-pydistribution` installed to `~/local2/lib` ``` PYTHONPATH=~/local2/lib python ->>> import pydistribution ->>> dir(pydistribution) +>>> import xo_pydistribution +>>> dir(xo_pydistribution) ['Distribution', 'ExplicitDist', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'normalcdf'] ->>> from pydistribution import * +>>> from xo_pydistribution import * ``` normal distribution diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) diff --git a/src/pydistribution/CMakeLists.txt b/src/pydistribution/CMakeLists.txt index 8adaad7e..356ba92e 100644 --- a/src/pydistribution/CMakeLists.txt +++ b/src/pydistribution/CMakeLists.txt @@ -1,6 +1,6 @@ # xo_pydistribution/src/pydistribution/CMakeLists.txt -set(SELF_LIB pydistribution) +set(SELF_LIB xo_pydistribution) set(SELF_SRCS pydistribution.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) From 90c20ddae2c93aa514e67ad0d473957f7c2b0832 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:32:04 -0400 Subject: [PATCH 0580/2693] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 7 ++++++- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index 378eac25..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ -build +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 68eb1445..1732dfa4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,8 +5,7 @@ cmake_minimum_required(VERSION 3.10) project(xo_statistics VERSION 1.0) enable_language(CXX) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From 884eb3acc41e80a580313c0b6b4a04f0c0e712f3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:32:51 -0400 Subject: [PATCH 0581/2693] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 3 +-- CMakeLists.txt | 5 ++--- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index 2323db6f..7d076252 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ # lsp keeps state here .cache # typical build directories -build -ccov +.build* # compile_commands.json symlink -> build/compile_commands.json should be created manually compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 99aa239f..b12b9f56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,9 +5,8 @@ cmake_minimum_required(VERSION 3.10) project(printjson VERSION 0.1) enable_language(CXX) -# common XO cmake macros (see proj/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +# common XO cmake macros (see https://github.com/rconybea/xo-cmake) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From a96142ffea81ef2ce8da64e18d03b8de4f929dfa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:33:37 -0400 Subject: [PATCH 0582/2693] build: streamline CMAKE_MODULE_PATH interaction + README.md xpand --- .gitignore | 9 ++++----- CMakeLists.txt | 3 +-- README.md | 12 ++++++++++++ cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ include/README.md | 1 + include/xo/pyprintjson/pyprintjson.hpp | 25 ------------------------- 6 files changed, 30 insertions(+), 32 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake create mode 100644 include/README.md delete mode 100644 include/xo/pyprintjson/pyprintjson.hpp diff --git a/.gitignore b/.gitignore index 8ea1f615..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ -# lsp keep state here +# clangd working space (see emacs+lsp) .cache -# typical build directories -build -ccov -# for lsp: manual symlink to chosen build directory +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f326e13..603f7253 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(xo_pyprintjson VERSION 1.0) enable_language(CXX) # common XO cmake macros (see github.com:Rconybea/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/README.md b/README.md index 34b77909..e85bc227 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,18 @@ $ make install ``` (also see .github/workflows/main.yml) +## Examples + +``` +PYTHONPATH=~/local2/lib:$PYTHONPATH python +>>> import xo_pyprintjson +>>> dir(xo_pyprintjson) +['PrintJson', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__'] +>>> dir(xo_pyprintjson.PrintJson) +['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'instance', 'print'] +>>> +``` + ## Development ### build for unit test coverage diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) diff --git a/include/README.md b/include/README.md new file mode 100644 index 00000000..5d2b4c67 --- /dev/null +++ b/include/README.md @@ -0,0 +1 @@ +placeholder for future pyprintjson #include files diff --git a/include/xo/pyprintjson/pyprintjson.hpp b/include/xo/pyprintjson/pyprintjson.hpp deleted file mode 100644 index c5fa007d..00000000 --- a/include/xo/pyprintjson/pyprintjson.hpp +++ /dev/null @@ -1,25 +0,0 @@ -/* @file pyprintjson.hpp - * - * automatically generated from src/pyprintjson/pyprintjson.hpp.in - * see src/pyprintjson/CMakeLists.txt - */ - -/* python requires module name = library name - * example: - * PYBIND11_MODULE(PYPRINTJSON_MODULE_NAME(), m) { ... } - */ -#define PYPRINTJSON_MODULE_NAME() pyprintjson - -/* example: - * py::module_::import(PYPRINTJSON_MODULE_NAME_STR) - */ -#define PYPRINTJSON_MODULE_NAME_STR "pyprintjson" - -/* example: - * PYPRINTJSON_IMPORT_MODULE() - * replaces - * py::module_::import("pyprintjson") - */ -#define PYPRINTJSON_IMPORT_MODULE() py::module_::import("pyprintjson") - -/* end pyprintjson.hpp */ From 9e69bd6ad71606059cb537b3ef47f680866961e2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:34:30 -0400 Subject: [PATCH 0583/2693] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 3 +-- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index e9746d99..132925c0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,4 @@ compile_commands.json # lsp keeps state here .cache # typical build directories -build -ccov +.build* diff --git a/CMakeLists.txt b/CMakeLists.txt index 52296781..c6e1c8bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(callback VERSION 0.1) enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup (for consistency with other xo libraries. no unit tests as of 10oct2023) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From 260f3435e1f06a4b95a3af41e99a6ac543ea4909 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:35:02 -0400 Subject: [PATCH 0584/2693] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 2 +- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index 3e50f879..b1adb07b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ # symlink -> build/compile_commands.json should be created manually compile_commands.json # typical build directories -build +.build* diff --git a/CMakeLists.txt b/CMakeLists.txt index b63e290e..94fd52ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(webutil VERSION 1.0) enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup (no unit tests yet, but want 'make tests' to do something) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From 9893b7148388be2a2bfc5bd3ada6bbd31df7e12f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:35:34 -0400 Subject: [PATCH 0585/2693] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 2 +- CMakeLists.txt | 3 +-- README.md | 11 +++++++++++ cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 4 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index f52f1311..53a9c92f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # lsp keeps state here .cache # typical build directory -build +.build* # lsp: symlink to file in build directory (established manually) compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index ed35dc91..22a91f72 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(xo_pywebutil VERSION 0.1) enable_language(CXX) # common XO cmake macros (see github.com:Rconybea/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/README.md b/README.md index 293fa8a4..52d702da 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,17 @@ $ make install ``` (also see .github/workflows/main.yml) +## Examples + +Assumes `xo-pywebutil` installed to `~/local2/lib` +``` +$ PYTHONPATH=~/local2/lib:$PYTHONPATH python +>>> import xo_pywebutil +>>> dir(xo_pywebutil) +['EndpointDescr', 'StreamEndpointDescr', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__'] +>>> from xo_pywebutil import * +``` + ## Development ### build for unit test coverage diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From 421e786e1d7403341c5cd08100769e135a7569b1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:36:14 -0400 Subject: [PATCH 0586/2693] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 3 +-- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index 8ea1f615..f57a21b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ # lsp keep state here .cache # typical build directories -build -ccov +.build* # for lsp: manual symlink to chosen build directory compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index b24e139a..3c4d8f9f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(reactor VERSION 0.1) enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From 20475c59e194b2fd1a73779cdfd7193273d2d3e4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:37:32 -0400 Subject: [PATCH 0587/2693] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 2 +- CMakeLists.txt | 3 +-- EXAMPLES | 12 ++++++++++++ README.md | 11 +++++++++++ cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 5 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 EXAMPLES create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index f52f1311..53a9c92f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # lsp keeps state here .cache # typical build directory -build +.build* # lsp: symlink to file in build directory (established manually) compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index aec8d379..773425bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(xo_pyreactor VERSION 1.0) enable_language(CXX) # common XO cmake macros (see github.com:Rconybea/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/EXAMPLES b/EXAMPLES new file mode 100644 index 00000000..4b67a31a --- /dev/null +++ b/EXAMPLES @@ -0,0 +1,12 @@ +* see ../process_py/README + +>>> import reactor_py +>>> p=reactor_py.make_realization_printer() +>>> p + +>>> + +>>> import inspect +>>> inspect.getmro(reactor_py.SinkToConsole) + +>>> dir(reactor_py) diff --git a/README.md b/README.md index f618c641..bda29b5f 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,17 @@ $ make install ``` (also see .github/workflows/main.yml) +## Examples + +Assumes `xo-pyreactor` installed to `~/local2/lib` +``` +PYTHONPATH=~/local2/lib:$PYTHONPATH python +>>> import xo_pyreactor +>>> dir(xo_pyreactor) +['AbstractEventProcessor', 'AbstractEventStore', 'AbstractSink', 'AbstractSource', 'CallbackId', 'Reactor', 'ReactorSource', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'time2str'] +>>> +``` + ## Development ### build for unit test coverage diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From 69d30651cceadca6cef8fb5c0056f71446b88cb3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:38:10 -0400 Subject: [PATCH 0588/2693] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 3 +-- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ src/simulator/CMakeLists.txt | 2 -- 4 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index 8ea1f615..f57a21b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ # lsp keep state here .cache # typical build directories -build -ccov +.build* # for lsp: manual symlink to chosen build directory compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 82d94e6b..b9020fd0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(simulator VERSION 1.0) enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) diff --git a/src/simulator/CMakeLists.txt b/src/simulator/CMakeLists.txt index 3a7879f5..2e8aaf73 100644 --- a/src/simulator/CMakeLists.txt +++ b/src/simulator/CMakeLists.txt @@ -15,5 +15,3 @@ xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 $ # in xo-simulator/cmake/simulatorConfig.cmake.in # xo_dependency(${SELF_LIB} reactor) -#xo_dependency(${SELF_LIB} webutil) -#xo_dependency(${SELF_LIB} callback) From 99bfbeb7f32460acd2f2a2658d22431c39eb1f59 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:39:05 -0400 Subject: [PATCH 0589/2693] pysimulator: initial commit --- .gitignore | 6 +++ CMakeLists.txt | 44 ++++++++++++++++++ README.md | 70 +++++++++++++++++++++++++++++ cmake/xo_pysimulatorConfig.cmake.in | 4 ++ include/README.md | 2 + src/pysimulator/CMakeLists.txt | 8 ++++ src/pysimulator/pysimulator.cpp | 66 +++++++++++++++++++++++++++ src/pysimulator/pysimulator.hpp.in | 25 +++++++++++ 8 files changed, 225 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/xo_pysimulatorConfig.cmake.in create mode 100644 include/README.md create mode 100644 src/pysimulator/CMakeLists.txt create mode 100644 src/pysimulator/pysimulator.cpp create mode 100644 src/pysimulator/pysimulator.hpp.in diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..53a9c92f --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# lsp keeps state here +.cache +# typical build directory +.build* +# lsp: symlink to file in build directory (established manually) +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..25398891 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,44 @@ +# xo-pysimulator/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_pysimulator VERSION 0.1) +enable_language(CXX) + +# common XO cmake macros (see https://github.com:rconybea/xo-cmake.git) +include(xo_macros/xo-project-macros) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() +# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) +add_code_coverage() +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) + +# ---------------------------------------------------------------- +# c++ settings (usually temporary) + +set(PROJECT_CXX_FLAGS "") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- +# sources + +add_subdirectory(src/pysimulator) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + diff --git a/README.md b/README.md new file mode 100644 index 00000000..676fe720 --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +# python bindings for c++ reflection library (xo-pysimulator) + +## Getting Started + +### build + install dependencies + +- [github/Rconybea/xo-simulator](https://github.com/Rconybea/xo-simulator) +- [github/Rconybea/xo-pyutil](https://github.com/Rconybea/xo-pyutil) +- [github/Rconybea/xo-pyreflect](https://github.com/Rconybea/xo-pyreflect) +- [github/Rconybea/xo-pyprintjson](https://github.com/Rconybea/xo-pyprintjson) +- [github/Rconybea/xo-pyreactor](https://github.com/Rconybea/xo-pyreactor) + +### build + install +``` +$ cd xo-pysimulator +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer, e.g. ~/local +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +## Examples + +Assumes `xo-pysimulator` installed to `~/local2/lib` +``` +PYTHONPATH=~/local2/lib:$PYTHONPATH python +>>> import xo_pysimulator +>>> dir(xo_pysimulator) +['Simulator', 'SourceTimestamp', 'TimeSlip', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'make_simulator'] +>>> + +## Development + +### build for unit test coverage +``` +$ cd xo-pysimulator +$ mkdir build-ccov +$ cd build-ccov +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCODE_COVERAGE=ON \ + -DCMAKE_BUILD_TYPE=Debug .. +``` + +### LSP (language server) support + +LSP looks for compile commands in the root of the source tree; +while `cmake` creates them in the root of its build directory. + +``` +$ cd xo-pysimulator +$ ln -s build/compile_commands.json # supply compile commands to LSP +``` + +## Examples + +Assumes `xo-pysimulator` installed to `~/local2/lib` + +``` +PYTHONPATH=~/local2/lib python +>>> import pysimulator +>>> dir(pysimulator) +``` diff --git a/cmake/xo_pysimulatorConfig.cmake.in b/cmake/xo_pysimulatorConfig.cmake.in new file mode 100644 index 00000000..9c15f36a --- /dev/null +++ b/cmake/xo_pysimulatorConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/README.md b/include/README.md new file mode 100644 index 00000000..12c02f54 --- /dev/null +++ b/include/README.md @@ -0,0 +1,2 @@ +placeholder for future pysimulator #include files + diff --git a/src/pysimulator/CMakeLists.txt b/src/pysimulator/CMakeLists.txt new file mode 100644 index 00000000..c88c44d0 --- /dev/null +++ b/src/pysimulator/CMakeLists.txt @@ -0,0 +1,8 @@ +# xo_pysimulator/src/pysimulator/CMakeLists.txt + +set(SELF_LIB xo_pysimulator) +set(SELF_SRCS pysimulator.cpp) + +xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) +xo_pybind11_dependency(${SELF_LIB} simulator) +xo_pybind11_dependency(${SELF_LIB} xo_pyutil) diff --git a/src/pysimulator/pysimulator.cpp b/src/pysimulator/pysimulator.cpp new file mode 100644 index 00000000..c1077b0a --- /dev/null +++ b/src/pysimulator/pysimulator.cpp @@ -0,0 +1,66 @@ +/* @file pysimulator.cpp */ + +#include "pysimulator.hpp" +#include "xo/pyreactor/pyreactor.hpp" + +#include "xo/simulator/Simulator.hpp" +#include "xo/simulator/SourceTimestamp.hpp" +#include "xo/simulator/TimeSlip.hpp" +#include "xo/pyutil/pyutil.hpp" +//#include +//#include +//#include + +namespace xo { + using xo::reactor::Reactor; + //using xo::scope; + namespace py = pybind11; + + namespace sim { + PYBIND11_MODULE(PYSIMULATOR_MODULE_NAME(), m) { + /* e.g. for Reactor */ + PYREACTOR_IMPORT_MODULE(); //py::module_::import("pyreactor") + + /* module docstring */ + m.doc() = "pybind11 plugin for xo.simulator"; + + m.def("make_simulator", + []() { + return xo::sim::Simulator::make(xo::time::timeutil::epoch()); + }, + "create new Simulator instance"); + + py::class_(m, "TimeSlip") + .def_property_readonly("sim_tm", &TimeSlip::sim_tm) + .def_property_readonly("real_tm", &TimeSlip::real_tm) + // .def("__repr__", &TimeSlip::display_string) // TODO + ; + + py::class_>(m, "Simulator") + .def_static("make", []() { return xo::sim::Simulator::make(xo::time::timeutil::epoch()); }) + .def_property_readonly("start_tm", &Simulator::t0) + .def_property_readonly("last_tm", &Simulator::last_tm) + .def_property_readonly("n_event", &Simulator::n_event) + .def_property_readonly("is_exhausted", &Simulator::is_exhausted) + .def("next_tm", &Simulator::next_tm) + .def("next_src", &Simulator::next_src) + .def("timeslip", &Simulator::timeslip) + .def("throttled_event_dt", &Simulator::throttled_event_dt) + .def("heap_contents", &Simulator::heap_contents) + .def("log_heap_contents", + [](Simulator & self) { + scope log(XO_LITERAL(log_level::always, "pysimulator", ".log_heap_contents")); + self.log_heap_contents(&log); + }) + .def("__repr__", &Simulator::display_string); + + py::class_(m, "SourceTimestamp") + .def("__repr__", &SourceTimestamp::display_string); + + } /*pysimulator*/ + } /*namespace sim*/ +} /*namespace xo*/ + +/* end pysimulator.cpp */ diff --git a/src/pysimulator/pysimulator.hpp.in b/src/pysimulator/pysimulator.hpp.in new file mode 100644 index 00000000..db3656c5 --- /dev/null +++ b/src/pysimulator/pysimulator.hpp.in @@ -0,0 +1,25 @@ +/* @file pysimulator.hpp + * + * automatically generated from src/xo_pysimulator/pysimulator.hpp.in + * see src/xo_pysimulator/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(PYSIMULATOR_MODULE_NAME(), m) { ... } + */ +#define PYSIMULATOR_MODULE_NAME() @SELF_LIB@ + +/* example: + * py::module_::import(PYSIMULATOR_MODULE_NAME_STR) + */ +#define PYSIMULATOR_MODULE_NAME_STR "@SELF_LIB@" + +/* example: + * PYSIMULATOR_IMPORT_MODULE() + * replaces + * py::module_::import("pysimulator") + */ +#define PYSIMULATOR_IMPORT_MODULE() py::module_::import("@SELF_LIB@") + +/* end pysimulator.hpp */ From 1b00b98a8f719ea45fd4b44df2c6618caf1f145f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:39:50 -0400 Subject: [PATCH 0590/2693] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 2 +- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index eff45bd2..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # clangd working space (see emacs+lsp) .cache # typical cmake build directory (source-tree-nephew) -build*/* +.build* # symlink to builddir/compile_commands.json; should be set manually in dev sandbox compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d562914..0a10d628 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(process VERSION 1.0) enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From 2f908b7c3638bd66a926626a4d94c26b2f848752 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:40:26 -0400 Subject: [PATCH 0591/2693] build: bugfix SELF_LIB consistency + streamline CMAKE_MODULE_PATH --- .gitignore | 2 +- CMakeLists.txt | 2 +- README.md | 26 +++++++++++++++++++++++++- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ src/pyprocess/CMakeLists.txt | 2 +- src/pyprocess/pyprocess.hpp.in | 6 +++--- 6 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index f52f1311..53a9c92f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # lsp keeps state here .cache # typical build directory -build +.build* # lsp: symlink to file in build directory (established manually) compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 36786fb8..d53e2ca6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.10) project(xo_pyprocess VERSION 0.1) -include(xo_macros/xo-project-macros) +include(cmake/xo-bootstrap-macros.cmake) xo_cxx_toplevel_options() diff --git a/README.md b/README.md index 2ad68ffb..07725a19 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,17 @@ # python bindings for c++ stochastic process library (xo-process) -# build + install +## Getting Started + +### build + install dependencies + +- [github/Rconybea/xo-process](https://github.com/Rconybea/xo-process) +- [github/Rconybea/xo-reactor](https://github.com/Rconybea/xo-reactor) +- [github/Rconybea/xo-pyutil](https://github.com/Rconybea/xo-pyutil) +- [github/Rconybea/xo-pyreflect](https://github.com/Rconybea/xo-pyreflect) +- [github/Rconybea/xo-pyprintjson](https://github.com/Rconybea/xo-pyprintjson) +- [github/Rconybea/xo-pyreactor](https://github.com/Rconybea/xo-pyreactor) + +### build + install ``` $ cd xo-pyprocess $ mkdir build @@ -15,6 +26,19 @@ $ make install ``` (also see .github/workflows/main.yml) +## Examples + +Assumes `xo-pyprocess` installed to `~/local2/lib` +``` +PYTHONPATH=~/local2/lib:$PYTHONPATH python +>>> import xo_pyprocess +>>> dir(xo_pyprocess) +['BrownianMotion', 'ExpProcess', 'RealizationSource', 'RealizationTracer', 'StochasticProcess', 'UpxAdapterSink', 'UpxEvent', 'UpxEventStore', 'UpxToConsole', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'make_brownian_motion', 'make_exponential_brownian_motion', 'make_realization_printer', 'make_realization_source', 'make_tracer'] +>>> +``` + +## Development + # build for unit test coverage ``` $ cd xo-pyprocess diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) diff --git a/src/pyprocess/CMakeLists.txt b/src/pyprocess/CMakeLists.txt index cf35ab2e..87a23e90 100644 --- a/src/pyprocess/CMakeLists.txt +++ b/src/pyprocess/CMakeLists.txt @@ -1,6 +1,6 @@ # xo_pyprocess/src/pyprocess/CMakeLists.txt -set(SELF_LIB pyprocess) +set(SELF_LIB xo_pyprocess) set(SELF_SRCS pyprocess.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) diff --git a/src/pyprocess/pyprocess.hpp.in b/src/pyprocess/pyprocess.hpp.in index d2f5cec1..938bf0c9 100644 --- a/src/pyprocess/pyprocess.hpp.in +++ b/src/pyprocess/pyprocess.hpp.in @@ -8,18 +8,18 @@ * example: * PYBIND11_MODULE(PYPROCESS_MODULE_NAME(), m) { ... } */ -#define PYPROCESS_MODULE_NAME() @SELF_LIBRARY_NAME@ +#define PYPROCESS_MODULE_NAME() @SELF_LIB@ /* example: * py::module_::import(PYPROCESS_MODULE_NAME_STR) */ -#define PYPROCESS_MODULE_NAME_STR "@SELF_LIBRARY_NAME@" +#define PYPROCESS_MODULE_NAME_STR "@SELF_LIB@" /* example: * PYPROCESS_IMPORT_MODULE() * replaces * py::module_::import("pyprocess") */ -#define PYPROCESS_IMPORT_MODULE() py::module_::import("@SELF_LIBRARY_NAME@") +#define PYPROCESS_IMPORT_MODULE() py::module_::import("@SELF_LIB@") /* end pyprocess.hpp */ From 4b2a991dc661b1c466fafe9b5ffdd12c06a32d3d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:41:08 -0400 Subject: [PATCH 0592/2693] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 7 ++++++- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index 378eac25..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ -build +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index f4120636..3c83fec9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(xo_kalmanfilter VERSION 1.0) enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From b5f6ede37758a4c3865eef53fd6a2172da0a699f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:41:33 -0400 Subject: [PATCH 0593/2693] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 7 +++- CMakeLists.txt | 2 +- README.md | 70 +++++++++++++++++++++++++++++++++ cmake/xo-bootstrap-macros.cmake | 12 ++++++ 4 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 README.md create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index 378eac25..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ -build +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a1602f6..d044769a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.10) project(xo_pykalmanfilter VERSION 1.0) -include(xo_macros/xo-project-macros) +include(cmake/xo-bootstrap-macros.cmake) xo_cxx_toplevel_options() diff --git a/README.md b/README.md new file mode 100644 index 00000000..7d7d1b11 --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +# python bindings for c++ kalman filter library (xo-kalmanfilter) + +## Getting Started + +### build + install dependencies + +- [github/Rconybea/xo-kalmanfilter](https://github.com/Rconybea/xo-kalmanfilter) +- [github/Rconybea/xo-pyreactor](https://github.com/Rconybea/xo-pyreactor) + +### build + install + +``` +$ cd xo-pykalmanfilter +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer, e.g. ~/local +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +## Examples + +Assumes `xo-pykalmanfilter` installed to `~/local2/lib` +``` +PYTHONPATH=~/local2/lib:$PYTHONPATH python +>>> import xo_pykalmanfilter +>>> dir(xo_pykalmanfilter) +['KalmanFilter', 'KalmanFilterInput', 'KalmanFilterInputToConsole', 'KalmanFilterObservable', 'KalmanFilterSpec', 'KalmanFilterState', 'KalmanFilterStateEventStore', 'KalmanFilterStateExt', 'KalmanFilterStateToConsole', 'KalmanFilterStep', 'KalmanFilterSvc', 'KalmanFilterTransition', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'kf_engine_correct', 'kf_engine_correct1', 'kf_engine_extrapolate', 'kf_engine_gain', 'kf_engine_gain1', 'make_kalman_filter', 'make_kalman_filter_input', 'make_kalman_filter_input_printer', 'make_kalman_filter_state_printer', 'print_matrix', 'print_vector'] +>>> +``` + +## Development + +### build for unit test coverage +``` +$ cd xo-pykalmanfilter +$ mkdir build-ccov +$ cd build-ccov +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCODE_COVERAGE=ON \ + -DCMAKE_BUILD_TYPE=Debug .. +``` + +### LSP (language server) support + +LSP looks for compile commands in the root of the source tree; +while Cmake creates them in the root of its build directory. + +``` +$ cd xo-pykalmanfilter +$ ln -s build/compile_commands.json # supply compile commands to LSP +``` + +### display cmake variables + +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text + +``` +$ cd xo-pykalmanfilter/build +$ cmake -LAH +``` diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From 0f7f6d0e1ce61d602fae5afa8e3d27064d8d9d4a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:42:04 -0400 Subject: [PATCH 0594/2693] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 2 +- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index eff45bd2..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # clangd working space (see emacs+lsp) .cache # typical cmake build directory (source-tree-nephew) -build*/* +.build* # symlink to builddir/compile_commands.json; should be set manually in dev sandbox compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 2178a12d..e3d66093 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(websock VERSION 1.0) enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From f7abb8fedba3f1ae98ae9f2af7c9ca6908dba824 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:42:35 -0400 Subject: [PATCH 0595/2693] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 6 ++- CMakeLists.txt | 3 +- README.md | 71 +++++++++++++++++++++++++++++++++ cmake/xo-bootstrap-macros.cmake | 12 ++++++ src/pywebsock/CMakeLists.txt | 2 +- 5 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 README.md create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index d6536bad..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ -build +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 20774e1c..56e9f2eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,9 @@ # xo-pywebsock/CMakeLists.txt cmake_minimum_required(VERSION 3.10) - project(xo_pywebsock VERSION 1.0) -include(xo_macros/xo-project-macros) +include(cmake/xo-bootstrap-macros.cmake) xo_cxx_toplevel_options() diff --git a/README.md b/README.md new file mode 100644 index 00000000..468c605d --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ +# python bindings for c++ websocket library (xo-websock) + +## Getting Started + +### build + install dependencies + +- [github/Rconybea/xo-websock](https://github.com/Rconybea/xo-websock) +- [github/Rconybea/xo-pyutil](https://github.com/Rconybea/xo-pyutil) +- [github/Rconybea/xo-pyreactor](https://github.com/Rconybea/xo-pyreactor) + +### build + install + +``` +$ cd xo-pywebsock +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer, e.g. ~/local +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +## Examples + +Assumes `xo-pywebsock` installed to `~/local2/lib` +``` +PYTHONPATH=~/local2/lib:$PYTHONPATH python +>>> import xo_pywebsock +>>> dir(xo_pywebsock) +['Runstate', 'Webserver', 'WebserverConfig', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'make_webserver'] +>>> +``` + +## Development + +### build for unit test coverage +``` +$ cd xo-pywebsock +$ mkdir build-ccov +$ cd build-ccov +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCODE_COVERAGE=ON \ + -DCMAKE_BUILD_TYPE=Debug .. +``` + +### LSP (language server) support + +LSP looks for compile commands in the root of the source tree; +while Cmake creates them in the root of its build directory. + +``` +$ cd xo-pywebsock +$ ln -s build/compile_commands.json # supply compile commands to LSP +``` + +### display cmake variables + +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text + +``` +$ cd xo-pywebsock/build +$ cmake -LAH +``` diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) diff --git a/src/pywebsock/CMakeLists.txt b/src/pywebsock/CMakeLists.txt index 57c5d75a..1e40a064 100644 --- a/src/pywebsock/CMakeLists.txt +++ b/src/pywebsock/CMakeLists.txt @@ -1,6 +1,6 @@ # xo_pywebsock/src/pywebsock/CMakeLists.txt -set(SELF_LIB pywebsock) +set(SELF_LIB xo_pywebsock) set(SELF_SRCS pywebsock.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) From 45d718820aace6f9a1d373c64f43c0bec18164b0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:57:00 -0400 Subject: [PATCH 0596/2693] github: initial workflow (wip -- broken) --- .github/workflows/main.yml | 63 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..8223aab6 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,63 @@ +name: build xo-pyreflect + dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + - name: Install pybind11-dev + run: sudo apt-get install -y pybind11-dev + + # ---------------------------------------------------------------- + + - name: clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Configure self (pysimulator) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_pysimulator -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (pysimulator) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_pysimulator --config ${{env.BUILD_TYPE}} + + - name: Test self (pysimulator) + working-directory: ${{github.workspace}}/build_pysimulator + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From 9430b4ee59b13f56383ca075bdd0fc6a37ac2d5a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:59:55 -0400 Subject: [PATCH 0597/2693] workflow: streamlining (wip - broken) --- .github/workflows/main.yml | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8223aab6..e6ad516f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,12 +21,16 @@ jobs: - name: checkout source uses: actions/checkout@v3 - - name: Install catch2 - # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] - run: sudo apt-get install -y catch2 + - name: Install dependencies + run: | + echo "::group::install catch2" + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + sudo apt-get install -y catch2 + echo "::endgroup" - - name: Install pybind11-dev - run: sudo apt-get install -y pybind11-dev + echo "::group::install pybind11" + run: sudo apt-get install -y pybind11-dev + echo "::endgroup" # ---------------------------------------------------------------- @@ -35,9 +39,8 @@ jobs: with: repository: Rconybea/xo-cmake path: repo/xo-cmake - - - name: Configure xo-cmake - run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + run: | + cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake - name: Build xo-cmake (trivial) run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} From 9c36ab7e2eb593f449ddd0d18109949334da79be Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:02:25 -0400 Subject: [PATCH 0598/2693] workflow: streamline xo-cmake prep --- .github/workflows/main.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e6ad516f..d6a7adae 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,14 +39,20 @@ jobs: with: repository: Rconybea/xo-cmake path: repo/xo-cmake + + - name: build xo-cmake run: | + echo "::group::configure xo-cmake" cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + echo "::endgroup" - - name: Build xo-cmake (trivial) - run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + echo "::group::compile xo-cmake" + cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + echo "::endgroup" - - name: Install xo-cmake - run: cmake --install ${{github.workspace}}/build_xo-cmake + echo "::group::local install xo-cmake" + cmake --install ${{github.workspace}}/build_xo-cmake + echo "::endgroup" # ---------------------------------------------------------------- From 3817677fc8372480ea64e9dd49e6d9c1cc38631b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:03:18 -0400 Subject: [PATCH 0599/2693] workflow: bugfix: typo in apt-get for pybind11-dev --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d6a7adae..4aafb3b2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,7 +29,7 @@ jobs: echo "::endgroup" echo "::group::install pybind11" - run: sudo apt-get install -y pybind11-dev + sudo apt-get install -y pybind11-dev echo "::endgroup" # ---------------------------------------------------------------- From 4693064fd3b3f1ecce7e6adf8041a1527e8291e9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:11:39 -0400 Subject: [PATCH 0600/2693] workflow: + build xo-simulator (wip - broken) --- .github/workflows/main.yml | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4aafb3b2..95a81b40 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,7 +24,8 @@ jobs: - name: Install dependencies run: | echo "::group::install catch2" - # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + # install catch2. see + # [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] sudo apt-get install -y catch2 echo "::endgroup" @@ -54,8 +55,36 @@ jobs: cmake --install ${{github.workspace}}/build_xo-cmake echo "::endgroup" + echo "::group::local dir tree" + tree ${{github.workspace}}/local + echo "::endgroup" + # ---------------------------------------------------------------- + - name: clone xo-simulator + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-simulator + path: repo/xo-simulator + + - name: build xo-simulator + run: | + echo "::group::configure xo-simulator + cmake -B ${{github.workspace}}/build_xo-simulator -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-simulator + echo "::endgroup" + + echo "::group::compile xo-simulator" + cmake --build ${{github.workspace}}/build_xo-simulator --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::local install xo-simulator"; + cmake --install ${{github.workspace}}/build_xo-simulator + echo "::endgroup" + + echo "::group::local dir tree" + tree ${{github.workspace}//local + echo "::endgroup" + - name: Configure self (pysimulator) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 14cec30749dd882def5f8a071b9bf3b2fe9538f7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:12:41 -0400 Subject: [PATCH 0601/2693] workflow: bugfix: typo in .yml file --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 95a81b40..d64f7f5f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -69,7 +69,7 @@ jobs: - name: build xo-simulator run: | - echo "::group::configure xo-simulator + echo "::group::configure xo-simulator" cmake -B ${{github.workspace}}/build_xo-simulator -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-simulator echo "::endgroup" From 963ba96f6cd441c3833f2dd44eb49967eae20e57 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:13:20 -0400 Subject: [PATCH 0602/2693] workflow: bugfix: syntax! --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d64f7f5f..71a02ba7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -82,7 +82,7 @@ jobs: echo "::endgroup" echo "::group::local dir tree" - tree ${{github.workspace}//local + tree ${{github.workspace}}/local echo "::endgroup" - name: Configure self (pysimulator) From 1348ba81c89b68318538948f71ae1089158f5ca5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:16:02 -0400 Subject: [PATCH 0603/2693] workflow: build xo-reactor (wip - broken) --- .github/workflows/main.yml | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 71a02ba7..17bd0709 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -61,24 +61,29 @@ jobs: # ---------------------------------------------------------------- - - name: clone xo-simulator + - name: clone xo-reactor uses: actions/checkout@v3 with: - repository: Rconybea/xo-simulator - path: repo/xo-simulator + repository: Rconybea/xo-reactor + path: repo/xo-reactor - - name: build xo-simulator + - name: build xo-reactor run: | - echo "::group::configure xo-simulator" - cmake -B ${{github.workspace}}/build_xo-simulator -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-simulator + XONAME=xo-reactor + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::configure xo-reactor" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} echo "::endgroup" - echo "::group::compile xo-simulator" - cmake --build ${{github.workspace}}/build_xo-simulator --config ${{env.BUILD_TYPE}} + echo "::group::compile xo-reactor" + cmake --build ${{github.workspace}}/build_xo-reactor --config ${{env.BUILD_TYPE}} echo "::endgroup" - echo "::group::local install xo-simulator"; - cmake --install ${{github.workspace}}/build_xo-simulator + echo "::group::local install xo-reactor"; + cmake --install ${{github.workspace}}/build_xo-reactor echo "::endgroup" echo "::group::local dir tree" From b5590ce449713ed66dc47b798f0a75a261beedd0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:18:57 -0400 Subject: [PATCH 0604/2693] workflow: build xo-reflect (wip - broken) --- .github/workflows/main.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 17bd0709..672c052d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -61,33 +61,33 @@ jobs: # ---------------------------------------------------------------- - - name: clone xo-reactor + - name: clone xo-reflect uses: actions/checkout@v3 with: - repository: Rconybea/xo-reactor - path: repo/xo-reactor + repository: Rconybea/xo-reflect + path: repo/xo-reflect - - name: build xo-reactor + - name: build xo-reflect run: | - XONAME=xo-reactor + XONAME=xo-reflect XOSRC=repo/${XONAME} BUILDDIR=${{github.workspace}}/build_${XONAME} PREFIX=${{github.workspace}}/local - echo "::group::configure xo-reactor" + echo "::group::configure ${XONAME} cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} echo "::endgroup" - echo "::group::compile xo-reactor" - cmake --build ${{github.workspace}}/build_xo-reactor --config ${{env.BUILD_TYPE}} + echo "::group::compile ${XONAME} + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} echo "::endgroup" - echo "::group::local install xo-reactor"; - cmake --install ${{github.workspace}}/build_xo-reactor + echo "::group::local install ${XONAME}; + cmake --install ${BUILDDIR} echo "::endgroup" echo "::group::local dir tree" - tree ${{github.workspace}}/local + tree ${PREFIX} echo "::endgroup" - name: Configure self (pysimulator) From 8d83e37ac7e10df72535c9c3d45fc9ef426136b9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:20:25 -0400 Subject: [PATCH 0605/2693] workflow: bugfix: remote repo name --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 672c052d..47f656a6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -64,7 +64,7 @@ jobs: - name: clone xo-reflect uses: actions/checkout@v3 with: - repository: Rconybea/xo-reflect + repository: Rconybea/reflect # not xo-reflect ! path: repo/xo-reflect - name: build xo-reflect From 76c7733f7728bd398d96b6761ae1af16a641ed6c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:26:02 -0400 Subject: [PATCH 0606/2693] workflow: xo-simulator: debugging (wip - broken) --- .github/workflows/main.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 47f656a6..cb4c9442 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -64,7 +64,7 @@ jobs: - name: clone xo-reflect uses: actions/checkout@v3 with: - repository: Rconybea/reflect # not xo-reflect ! + repository: Rconybea/reflect path: repo/xo-reflect - name: build xo-reflect @@ -74,6 +74,10 @@ jobs: BUILDDIR=${{github.workspace}}/build_${XONAME} PREFIX=${{github.workspace}}/local + echo "::group::repo dir tree" + tree repo + echo "::endgroup" + echo "::group::configure ${XONAME} cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} echo "::endgroup" From 05357a639808806e30cd9d7cbf98944953fef46a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:28:08 -0400 Subject: [PATCH 0607/2693] workflow: bugfix: yaml typos! (wip - broken) --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cb4c9442..34526171 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -75,10 +75,10 @@ jobs: PREFIX=${{github.workspace}}/local echo "::group::repo dir tree" - tree repo + tree -L 2 repo echo "::endgroup" - echo "::group::configure ${XONAME} + echo "::group::configure ${XONAME}" cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} echo "::endgroup" @@ -86,7 +86,7 @@ jobs: cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} echo "::endgroup" - echo "::group::local install ${XONAME}; + echo "::group::local install ${XONAME}" cmake --install ${BUILDDIR} echo "::endgroup" From daa42fcaddc3a4f92a564233a0636e7b27afcdf9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:31:00 -0400 Subject: [PATCH 0608/2693] workflow: build xo-refcnt (wip - broken) --- .github/workflows/main.yml | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 34526171..5a9f6d1c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -43,33 +43,38 @@ jobs: - name: build xo-cmake run: | - echo "::group::configure xo-cmake" - cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + XONAME=xo-cmake + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::configure ${XONAME} + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} echo "::endgroup" - echo "::group::compile xo-cmake" + echo "::group::compile ${XONAME} cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} echo "::endgroup" - echo "::group::local install xo-cmake" + echo "::group::local install ${XONAME} cmake --install ${{github.workspace}}/build_xo-cmake echo "::endgroup" echo "::group::local dir tree" - tree ${{github.workspace}}/local + tree ${PREFIX} echo "::endgroup" # ---------------------------------------------------------------- - - name: clone xo-reflect + - name: clone xo-refcnt uses: actions/checkout@v3 with: - repository: Rconybea/reflect - path: repo/xo-reflect + repository: Rconybea/refcnt + path: repo/xo-refcnt - - name: build xo-reflect + - name: build xo-refcnt run: | - XONAME=xo-reflect + XONAME=xo-refcnt XOSRC=repo/${XONAME} BUILDDIR=${{github.workspace}}/build_${XONAME} PREFIX=${{github.workspace}}/local From c520ad82945a27617549bd7fa13fffcf0349677e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:31:37 -0400 Subject: [PATCH 0609/2693] workflow: bugfix: yaml typos! (wip - broken) --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5a9f6d1c..945c720c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -48,15 +48,15 @@ jobs: BUILDDIR=${{github.workspace}}/build_${XONAME} PREFIX=${{github.workspace}}/local - echo "::group::configure ${XONAME} + echo "::group::configure ${XONAME}" cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} echo "::endgroup" - echo "::group::compile ${XONAME} + echo "::group::compile ${XONAME}" cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} echo "::endgroup" - echo "::group::local install ${XONAME} + echo "::group::local install ${XONAME}" cmake --install ${{github.workspace}}/build_xo-cmake echo "::endgroup" From 99190e461551d06aa84e14af463f209472b297bc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:35:35 -0400 Subject: [PATCH 0610/2693] workflow: build indentlog (wip - broken) --- .github/workflows/main.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 945c720c..3ed07489 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -53,11 +53,11 @@ jobs: echo "::endgroup" echo "::group::compile ${XONAME}" - cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} echo "::endgroup" echo "::group::local install ${XONAME}" - cmake --install ${{github.workspace}}/build_xo-cmake + cmake --install ${BUILDDIR} echo "::endgroup" echo "::group::local dir tree" @@ -66,15 +66,15 @@ jobs: # ---------------------------------------------------------------- - - name: clone xo-refcnt + - name: clone xo-indentlog uses: actions/checkout@v3 with: - repository: Rconybea/refcnt - path: repo/xo-refcnt + repository: Rconybea/indentlog + path: repo/xo-indentlog - - name: build xo-refcnt + - name: build xo-indentlog run: | - XONAME=xo-refcnt + XONAME=xo-indentlog XOSRC=repo/${XONAME} BUILDDIR=${{github.workspace}}/build_${XONAME} PREFIX=${{github.workspace}}/local @@ -87,7 +87,7 @@ jobs: cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} echo "::endgroup" - echo "::group::compile ${XONAME} + echo "::group::compile ${XONAME}" cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} echo "::endgroup" From 3ed31ad824dc67afeab67559ed8ed52c1847852d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:38:01 -0400 Subject: [PATCH 0611/2693] workflow: back to xo-indentlog build (wip - broken) --- .github/workflows/main.yml | 39 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3ed07489..763f0782 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -88,7 +88,7 @@ jobs: echo "::endgroup" echo "::group::compile ${XONAME}" - cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j echo "::endgroup" echo "::group::local install ${XONAME}" @@ -96,7 +96,42 @@ jobs: echo "::endgroup" echo "::group::local dir tree" - tree ${PREFIX} + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/xo-refcnt + + - name: build xo-refcnt + run: | + XONAME=xo-refcnt + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} echo "::endgroup" - name: Configure self (pysimulator) From 6417e0e098fb87f1a34ff053399efcd96f7bfad4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:39:59 -0400 Subject: [PATCH 0612/2693] workflow: back to xo-reflect build (wip - broken) --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 763f0782..610dfbe3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -134,6 +134,41 @@ jobs: tree -L 3 ${PREFIX} echo "::endgroup" + # ---------------------------------------------------------------- + + - name: clone xo-reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/xo-reflect + + - name: build xo-reflect + run: | + XONAME=xo-reflect + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + - name: Configure self (pysimulator) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 4c912d6fe02b6d210fe54cb4f3d03fc27c526cf3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:43:24 -0400 Subject: [PATCH 0613/2693] workflow: + xo-subsys build (wip - broken) --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 610dfbe3..040ee495 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -136,6 +136,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/xo-subsys + + - name: build xo-subsys + run: | + XONAME=xo-subsys + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-reflect uses: actions/checkout@v3 with: From 1021889c01438a3279967f9042092927f17f6954 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:45:34 -0400 Subject: [PATCH 0614/2693] workflow: bugfix: return to xo-reactor build (wip - broken) --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 040ee495..3aaa0a36 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -204,6 +204,41 @@ jobs: tree -L 3 ${PREFIX} echo "::endgroup" + # ---------------------------------------------------------------- + + - name: clone xo-reactor + uses: actions/checkout@v3 + with: + repository: Rconybea/reactor + path: repo/xo-reactor + + - name: build xo-reactor + run: | + XONAME=xo-reactor + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + - name: Configure self (pysimulator) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From d0700cb3619b5c4ebef38a5a7caddaf9dd4e985a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:46:48 -0400 Subject: [PATCH 0615/2693] workflow: bugfix: fix xo-reactor repo name --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3aaa0a36..f420f343 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -209,7 +209,7 @@ jobs: - name: clone xo-reactor uses: actions/checkout@v3 with: - repository: Rconybea/reactor + repository: Rconybea/xo-reactor path: repo/xo-reactor - name: build xo-reactor From 3e7b9a000a66ecf1ac23cafa8daad0e101718e8f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:49:18 -0400 Subject: [PATCH 0616/2693] workflow: + xo-webutil (wip - broken) --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f420f343..f760ece0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -206,6 +206,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-webutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-webutil + path: repo/xo-webutil + + - name: build xo-webutil + run: | + XONAME=xo-webutil + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-reactor uses: actions/checkout@v3 with: From ee82037ba9ba9fd179ec70548742063fe2cc96d2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:51:07 -0400 Subject: [PATCH 0617/2693] workflow: + xo-callback (wip - broken) --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f760ece0..e36d85a1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -206,6 +206,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/xo-callback + + - name: build xo-callback + run: | + XONAME=xo-callback + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-webutil uses: actions/checkout@v3 with: From 591b7388fed02b9946a11ee8513d7c3a1a9d1cd0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:53:31 -0400 Subject: [PATCH 0618/2693] workflow: + xo-printjson (wip - broken) --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e36d85a1..e7eb52f0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -241,6 +241,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-printjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-printjson + path: repo/xo-printjson + + - name: build xo-printjson + run: | + XONAME=xo-printjson + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-webutil uses: actions/checkout@v3 with: From aa77c5f4b493efae91433d0d507cf0c3dd4cdea5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:57:09 -0400 Subject: [PATCH 0619/2693] workflow: + xo-ordinaltree (wip - broken) --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e7eb52f0..f1d95a42 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -276,6 +276,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/xo-ordinaltree + + - name: build xo-ordinaltree + run: | + XONAME=xo-ordinaltree + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-webutil uses: actions/checkout@v3 with: From b1e08d791a93087c66730f4794fb75b671125cd6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:59:09 -0400 Subject: [PATCH 0620/2693] workflow: + xo-randomgen (wip - broken) --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f1d95a42..d0a7c5e2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -276,6 +276,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-randomgen + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-randomgen + path: repo/xo-randomgen + + - name: build xo-randomgen + run: | + XONAME=xo-randomgen + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-ordinaltree uses: actions/checkout@v3 with: From 6276893847f2ab19743f512c32b2b4757c957955 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 21:02:50 -0400 Subject: [PATCH 0621/2693] workflow: bugfix: xo-randomgen repo name --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d0a7c5e2..6121a8d1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -279,7 +279,7 @@ jobs: - name: clone xo-randomgen uses: actions/checkout@v3 with: - repository: Rconybea/xo-randomgen + repository: Rconybea/randomgen path: repo/xo-randomgen - name: build xo-randomgen From 78bb709ddc2b3ac0c1737ce4734f1a9e3f020c67 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 21:06:04 -0400 Subject: [PATCH 0622/2693] workflow: + xo-simulator (wip - broken) --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6121a8d1..7f4e8374 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -414,6 +414,41 @@ jobs: tree -L 3 ${PREFIX} echo "::endgroup" + # ---------------------------------------------------------------- + + - name: clone xo-simulator + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-simulator + path: repo/xo-simulator + + - name: build xo-simulator + run: | + XONAME=xo-simulator + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + - name: Configure self (pysimulator) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 204e3bae4f902de142cc2d14658239bafab88b43 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 21:11:08 -0400 Subject: [PATCH 0623/2693] workflow: + xo-pyutil (wip - broken) --- .github/workflows/main.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7f4e8374..03e17a9c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -449,6 +449,43 @@ jobs: tree -L 3 ${PREFIX} echo "::endgroup" + # ---------------------------------------------------------------- + + - name: clone xo-pyutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyutil + path: repo/xo-pyutil + + - name: build xo-pyutil + run: | + XONAME=xo-pyutil + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: Configure self (pysimulator) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 630ab081c62a510ebc2d70709496c1f894811861 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 21:15:18 -0400 Subject: [PATCH 0624/2693] build: report xo_pyreactor dependency --- src/pysimulator/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pysimulator/CMakeLists.txt b/src/pysimulator/CMakeLists.txt index c88c44d0..541c3378 100644 --- a/src/pysimulator/CMakeLists.txt +++ b/src/pysimulator/CMakeLists.txt @@ -4,5 +4,6 @@ set(SELF_LIB xo_pysimulator) set(SELF_SRCS pysimulator.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) +xo_pybind11_dependency(${SELF_LIB} xo_pyreactor) xo_pybind11_dependency(${SELF_LIB} simulator) xo_pybind11_dependency(${SELF_LIB} xo_pyutil) From a8161f301ee2f8e1d63c59fe41ba8f6d54ec5223 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 21:16:08 -0400 Subject: [PATCH 0625/2693] workflow: + xo-pyreactor (wip - broken) --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 03e17a9c..17201d86 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -486,6 +486,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-pyreactor + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyreactor + path: repo/xo-pyreactor + + - name: build xo-pyreactor + run: | + XONAME=xo-pyreactor + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: Configure self (pysimulator) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 15f42c083a71d73ff6b7647cdabe1bf344928f23 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 21:19:33 -0400 Subject: [PATCH 0626/2693] workflow: + xo-pyprintjson (wip - broken) --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 17201d86..e125db88 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -486,6 +486,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-pyprintjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyprintjson + path: repo/xo-pyprintjson + + - name: build xo-pyprintjson + run: | + XONAME=xo-pyprintjson + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-pyreactor uses: actions/checkout@v3 with: From 209f2be30432aceb666b0d14862d4b9e480ac9c9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 21:22:35 -0400 Subject: [PATCH 0627/2693] workflow: + xo-pyreflect (wip - broken) --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e125db88..2c07e63a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -521,6 +521,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-pyreflect + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyreflect + path: repo/xo-pyreflect + + - name: build xo-pyreflect + run: | + XONAME=xo-pyreflect + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-pyreactor uses: actions/checkout@v3 with: From 692078e1a0878d34bb5bdbea9ed57f5e98b87315 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 21:25:56 -0400 Subject: [PATCH 0628/2693] workflow: need xo-pyreflect before xo-pyprintjson (wip - broken) --- .github/workflows/main.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2c07e63a..6cf1b768 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -486,15 +486,15 @@ jobs: # ---------------------------------------------------------------- - - name: clone xo-pyprintjson + - name: clone xo-pyreflect uses: actions/checkout@v3 with: - repository: Rconybea/xo-pyprintjson - path: repo/xo-pyprintjson + repository: Rconybea/xo-pyreflect + path: repo/xo-pyreflect - - name: build xo-pyprintjson + - name: build xo-pyreflect run: | - XONAME=xo-pyprintjson + XONAME=xo-pyreflect XOSRC=repo/${XONAME} BUILDDIR=${{github.workspace}}/build_${XONAME} PREFIX=${{github.workspace}}/local @@ -521,15 +521,15 @@ jobs: # ---------------------------------------------------------------- - - name: clone xo-pyreflect + - name: clone xo-pyprintjson uses: actions/checkout@v3 with: - repository: Rconybea/xo-pyreflect - path: repo/xo-pyreflect + repository: Rconybea/xo-pyprintjson + path: repo/xo-pyprintjson - - name: build xo-pyreflect + - name: build xo-pyprintjson run: | - XONAME=xo-pyreflect + XONAME=xo-pyprintjson XOSRC=repo/${XONAME} BUILDDIR=${{github.workspace}}/build_${XONAME} PREFIX=${{github.workspace}}/local From 66abd390bd81607e06298f9581e2898cc6f5e4f2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 21:34:20 -0400 Subject: [PATCH 0629/2693] workflow: streamline xo-pysimulator build step --- .github/workflows/main.yml | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6cf1b768..621046c5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -594,14 +594,27 @@ jobs: - name: Configure self (pysimulator) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build_pysimulator -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: | + XONAME=xo-pysimulator + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local - - name: Build self (pysimulator) - # Build your program with the given configuration - run: cmake --build ${{github.workspace}}/build_pysimulator --config ${{env.BUILD_TYPE}} + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" - - name: Test self (pysimulator) - working-directory: ${{github.workspace}}/build_pysimulator - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest -C ${{env.BUILD_TYPE}} + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + (cd ${BUILDDIR} && ctest -C ${{env.BUILD_TYPE}}) From c8f17169d95504e89634bb4e57668fdcc65e2d49 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 13:04:46 -0400 Subject: [PATCH 0630/2693] xo-kalmanfilter: + README.md --- README.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..fd4ef52d --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# kalman filter library + +linear kalman filter implementation. + +## Getting Started + +### build + install dependencies + +build+install these first + +- xo-reactor [github.com/Rconybea/xo-reactor](https://github.com/Rconybea/xo-reactor) + +See `.github/workflows/main.yml` in this repo for example build+install on ubuntu + +### build + install xo-kalmanfilter +``` +$ cd xo-kalmanfilter +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +### build for unit test coverage +``` +$ cd xo-kalmanfilter +$ mkdir ccov +$ cd ccov +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + +## Development + +### LSP support + +LSP looks for compile commands in the root of the source tree; +cmake creates them in the root of its build directory. + +``` +$ cd xo-kalmanfilter +$ ln -s build/compile_commands.json +``` + +### display cmake variables + +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text + +``` +$ cd xo-kalmanfilter/build +$ cmake -LAH +``` From 637e0902437318a026a7f5015a7497bb09bf8b2d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 13:06:44 -0400 Subject: [PATCH 0631/2693] github: worfklow for ubuntu build --- .github/workflows/main.yml | 309 +++++++++++++++++++++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..a34ea6aa --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,309 @@ +name: build xo-optionutil + dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install dependencies + run: | + echo "::group::install catch2" + # install catch2. see + # [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + sudo apt-get install -y catch2 + echo "::endgroup" + + #echo "::group::install pybind11" + #sudo apt-get install -y pybind11-dev + #echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: build xo-cmake + run: | + XONAME=xo-cmake + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/xo-indentlog + + - name: build xo-indentlog + run: | + XONAME=xo-indentlog + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/xo-refcnt + + - name: build xo-refcnt + run: | + XONAME=xo-refcnt + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/xo-subsys + + - name: build xo-subsys + run: | + XONAME=xo-subsys + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/xo-reflect + + - name: build xo-reflect + run: | + XONAME=xo-reflect + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + +# - name: clone xo-callback +# uses: actions/checkout@v3 +# with: +# repository: Rconybea/xo-callback +# path: repo/xo-callback +# +# - name: build xo-callback +# run: | +# XONAME=xo-callback +# XOSRC=repo/${XONAME} +# BUILDDIR=${{github.workspace}}/build_${XONAME} +# PREFIX=${{github.workspace}}/local +# +# echo "::group::repo dir tree" +# tree -L 2 repo +# echo "::endgroup" +# +# echo "::group::configure ${XONAME}" +# cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} +# echo "::endgroup" +# +# echo "::group::compile ${XONAME}" +# cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j +# echo "::endgroup" +# +# echo "::group::local install ${XONAME}" +# cmake --install ${BUILDDIR} +# echo "::endgroup" +# +# echo "::group::local dir tree" +# tree -L 3 ${PREFIX} +# echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-printjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-printjson + path: repo/xo-printjson + + - name: build xo-printjson + run: | + XONAME=xo-printjson + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: Configure self (kalmanfilter) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: | + XONAME=xo-kalmanfilter + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + (cd ${BUILDDIR} && ctest -C ${{env.BUILD_TYPE}}) From ce1ed005a5f419d97176cf8a3944cbc0e49a5725 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 13:11:56 -0400 Subject: [PATCH 0632/2693] worfklow: + xo-reactor dep --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a34ea6aa..d99c8cb7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -276,6 +276,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-reactor + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-reactor + path: repo/xo-reactor + + - name: build xo-reactor + run: | + XONAME=xo-reactor + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: Configure self (kalmanfilter) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 856e2f8de2a80f6b4408140601220632c00bcedd Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 13:15:13 -0400 Subject: [PATCH 0633/2693] worfklow: + xo-webutil dep --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d99c8cb7..38bd87d6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -276,6 +276,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-webutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-webutil + path: repo/xo-webutil + + - name: build xo-webutil + run: | + XONAME=xo-webutil + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-reactor uses: actions/checkout@v3 with: From 661f52373e9d3c1f447c27a60bcf01bcacdc030b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 13:17:33 -0400 Subject: [PATCH 0634/2693] worfklow: + xo-callback dep --- .github/workflows/main.yml | 64 +++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 38bd87d6..3060d6ab 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -206,38 +206,38 @@ jobs: # ---------------------------------------------------------------- -# - name: clone xo-callback -# uses: actions/checkout@v3 -# with: -# repository: Rconybea/xo-callback -# path: repo/xo-callback -# -# - name: build xo-callback -# run: | -# XONAME=xo-callback -# XOSRC=repo/${XONAME} -# BUILDDIR=${{github.workspace}}/build_${XONAME} -# PREFIX=${{github.workspace}}/local -# -# echo "::group::repo dir tree" -# tree -L 2 repo -# echo "::endgroup" -# -# echo "::group::configure ${XONAME}" -# cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} -# echo "::endgroup" -# -# echo "::group::compile ${XONAME}" -# cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j -# echo "::endgroup" -# -# echo "::group::local install ${XONAME}" -# cmake --install ${BUILDDIR} -# echo "::endgroup" -# -# echo "::group::local dir tree" -# tree -L 3 ${PREFIX} -# echo "::endgroup" + - name: clone xo-callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/xo-callback + + - name: build xo-callback + run: | + XONAME=xo-callback + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" # ---------------------------------------------------------------- From 12df1e773bc9f28a57822378d7f769e56d948819 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 13:20:19 -0400 Subject: [PATCH 0635/2693] worfklow: + xo-ordinaltree dep --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3060d6ab..4de8815b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -311,6 +311,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/xo-ordinaltree + + - name: build xo-ordinaltree + run: | + XONAME=xo-ordinaltree + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-reactor uses: actions/checkout@v3 with: From 9157edb3f90d3a453de0166b9ecb71d56fe1d3be Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 13:24:34 -0400 Subject: [PATCH 0636/2693] workflows: + xo-randomgen dep --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4de8815b..e0d1e7d4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -311,6 +311,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-randomgen + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-randomgen + path: repo/xo-randomgen + + - name: build xo-randomgen + run: | + XONAME=xo-randomgen + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-ordinaltree uses: actions/checkout@v3 with: From 9e19cb85823aafc474afe593c6a3ea3443c12d28 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 13:32:51 -0400 Subject: [PATCH 0637/2693] worfklow: bugfix: repo name for xo-randomgen --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e0d1e7d4..a5ec0bb5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -314,7 +314,7 @@ jobs: - name: clone xo-randomgen uses: actions/checkout@v3 with: - repository: Rconybea/xo-randomgen + repository: Rconybea/randomgen path: repo/xo-randomgen - name: build xo-randomgen From a70370d239bfb78e930d290cf905eac4c43a768b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 13:43:42 -0400 Subject: [PATCH 0638/2693] workflows: + eigen3 dep --- .github/workflows/main.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a5ec0bb5..f6a824ec 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,6 +29,10 @@ jobs: sudo apt-get install -y catch2 echo "::endgroup" + echo "::group::install eigen3" + sudo apt-get install -y eigen3-dev + echo "::endgroup" + #echo "::group::install pybind11" #sudo apt-get install -y pybind11-dev #echo "::endgroup" From 70f53491381c4dd5736175b104f5d150b25a77db Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 13:45:24 -0400 Subject: [PATCH 0639/2693] workflow: bugfix: eigen3-dev -> libeigen3-dev --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f6a824ec..ee59661d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,7 +30,7 @@ jobs: echo "::endgroup" echo "::group::install eigen3" - sudo apt-get install -y eigen3-dev + sudo apt-get install -y libeigen3-dev echo "::endgroup" #echo "::group::install pybind11" From da011d38cf4713c02a2e6bb9676ed3cd126756fa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 14:18:11 -0400 Subject: [PATCH 0640/2693] workflow: + xo-statistics dep --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ee59661d..3c4faa53 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -420,6 +420,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-statistics + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-statistics + path: repo/xo-statistics + + - name: build xo-statistics + run: | + XONAME=xo-statistics + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: Configure self (kalmanfilter) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 99e029c352db26f01edda3930068797dc50b4f56 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 14:33:31 -0400 Subject: [PATCH 0641/2693] worfklows: + ubuntu build on main branch --- .github/workflows/main.yml | 488 +++++++++++++++++++++++++++++++++++++ 1 file changed, 488 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..2f07df30 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,488 @@ +name: build xo-optionutil + dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install dependencies + run: | + echo "::group::install catch2" + # install catch2. see + # [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + sudo apt-get install -y catch2 + echo "::endgroup" + + echo "::group::install eigen3" + sudo apt-get install -y libeigen3-dev + echo "::endgroup" + + #echo "::group::install pybind11" + #sudo apt-get install -y pybind11-dev + #echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: build xo-cmake + run: | + XONAME=xo-cmake + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/xo-indentlog + + - name: build xo-indentlog + run: | + XONAME=xo-indentlog + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/xo-refcnt + + - name: build xo-refcnt + run: | + XONAME=xo-refcnt + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/xo-subsys + + - name: build xo-subsys + run: | + XONAME=xo-subsys + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/xo-reflect + + - name: build xo-reflect + run: | + XONAME=xo-reflect + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/xo-callback + + - name: build xo-callback + run: | + XONAME=xo-callback + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-printjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-printjson + path: repo/xo-printjson + + - name: build xo-printjson + run: | + XONAME=xo-printjson + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-webutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-webutil + path: repo/xo-webutil + + - name: build xo-webutil + run: | + XONAME=xo-webutil + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-randomgen + uses: actions/checkout@v3 + with: + repository: Rconybea/randomgen + path: repo/xo-randomgen + + - name: build xo-randomgen + run: | + XONAME=xo-randomgen + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/xo-ordinaltree + + - name: build xo-ordinaltree + run: | + XONAME=xo-ordinaltree + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-reactor + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-reactor + path: repo/xo-reactor + + - name: build xo-reactor + run: | + XONAME=xo-reactor + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-statistics + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-statistics + path: repo/xo-statistics + + - name: build xo-statistics + run: | + XONAME=xo-statistics + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: Configure self (pykalmanfilter) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: | + XONAME=xo-pykalmanfilter + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + (cd ${BUILDDIR} && ctest -C ${{env.BUILD_TYPE}}) From 037665298029a42981c211346aa67607f6996d4a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 14:35:44 -0400 Subject: [PATCH 0642/2693] worfklow: + missing pybind11 dep --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2f07df30..81b35479 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,8 +33,8 @@ jobs: sudo apt-get install -y libeigen3-dev echo "::endgroup" - #echo "::group::install pybind11" - #sudo apt-get install -y pybind11-dev + echo "::group::install pybind11" + sudo apt-get install -y pybind11-dev #echo "::endgroup" # ---------------------------------------------------------------- From 17fc09190cf2f91f295efb60363447d4ff6165ad Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 14:38:30 -0400 Subject: [PATCH 0643/2693] workflow: + xo-kalmanfilter dep --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 81b35479..2a1a335f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -455,6 +455,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-kalmanfilter + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-kalmanfilter + path: repo/xo-kalmanfilter + + - name: build xo-kalmanfilter + run: | + XONAME=xo-kalmanfilter + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: Configure self (pykalmanfilter) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From a85a7782a378d62a587270a2d7387b9db6d92d9d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 14:47:17 -0400 Subject: [PATCH 0644/2693] workflow: + xo-pyreactor dep --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2a1a335f..f82d7615 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -420,6 +420,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-pyreactor + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyreactor + path: repo/xo-pyreactor + + - name: build xo-pyreactor + run: | + XONAME=xo-pyreactor + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-statistics uses: actions/checkout@v3 with: From 86acc27ce6455673621578609ed26c1e07cb70d0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 14:55:35 -0400 Subject: [PATCH 0645/2693] workflow: + xo-pyprintjson dep --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f82d7615..d9aa8e13 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -280,6 +280,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-pyprintjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyprintjson + path: repo/xo-pyprintjson + + - name: build xo-pyprintjson + run: | + XONAME=xo-pyprintjson + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-webutil uses: actions/checkout@v3 with: From 1e4d83ae418a67783c552f0b0e8b70ade67f1427 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 14:58:35 -0400 Subject: [PATCH 0646/2693] workflow: + xo-pyreflect dep --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d9aa8e13..0b6ae004 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -210,6 +210,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-pyreflect + uses: actions/checkout@v3 + with: + repository: Rconybea/pyreflect + path: repo/xo-pyreflect + + - name: build xo-pyreflect + run: | + XONAME=xo-pyreflect + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-callback uses: actions/checkout@v3 with: From ced5e755b5e926b4efe2a27b8f02123af819deaa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 15:02:16 -0400 Subject: [PATCH 0647/2693] workflow: bugfix: rconybea/pyreactor -> rconybea/xo-pyreactor --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0b6ae004..2a06f02d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -213,7 +213,7 @@ jobs: - name: clone xo-pyreflect uses: actions/checkout@v3 with: - repository: Rconybea/pyreflect + repository: Rconybea/xo-pyreflect path: repo/xo-pyreflect - name: build xo-pyreflect From 37649bca068cdb00037506cec6cade6a7aa9470e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 15:05:46 -0400 Subject: [PATCH 0648/2693] workflow: bugfix: + xo-pyutil dep --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2a06f02d..24a0c9c7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -210,6 +210,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-pyutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyutil + path: repo/xo-pyutil + + - name: build xo-pyutil + run: | + XONAME=xo-pyutil + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-pyreflect uses: actions/checkout@v3 with: From ddc15c0416a863658d68e19c881052544f9caff2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0649/2693] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From fdefb286b0b7222687192d505ec50706ffb5582a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0650/2693] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 35ac47555864c3f7051d9870d55f9581b57108a9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0651/2693] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 591c2451d86fe0568e4022da5b3afc72af1e69ef Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0652/2693] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From dd2a897192fc51feb6429ed28fcacb1963d0c5df Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0653/2693] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 8c985854977d210cd6c51e675cabe866dd291904 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0654/2693] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From c8947337066160bb5be3544f995e9be6395cf496 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0655/2693] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 06bd4324305996db9b24a98c010a63c4593c1e21 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0656/2693] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 17d26aaf4efa1e772ea7961d09c297f3d4055234 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0657/2693] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From dd2bebc7e871f3112fc5ca0f64068e5095ab424a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0658/2693] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 0bce4cdc91bfd3d6023beb67ea6932252c792839 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0659/2693] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 791583399dc274ba958047ed2c4c4cf7f0b0f8ae Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0660/2693] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 8c196a35a7b2d0d5dcd804167da9f104ba77e448 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0661/2693] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 1051e0e60f13639bd9e5aafcadd05c6f966bef85 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0662/2693] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From a929e8112b1d96d250755ae0fb0b39c32c2f6a47 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0663/2693] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From cc5c53d470f943f3db4935f52a1c2a8e7e3af4d3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0664/2693] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 5db07574c9d4089b4821c1b233f901ed4ad80699 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0665/2693] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 267b8aa78389f4c8f638811f9ce6d46679b30d3e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0666/2693] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 5a45859f5167c625b225774efe81e376558a08de Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0667/2693] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From a647a2535bf57772d2421fbee848a19ca9d24163 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0668/2693] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 9e7aebd6dd8c74c7e637f0645e7067149ccdf2da Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0669/2693] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 4aaf33164f6760c65173405714882237ed5a6ca9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0670/2693] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 2abb2a47f4cfb9ebcb85eed91028a2a2c79765df Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0671/2693] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 707af0552005a8d98124659a90253cd3325c1707 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0672/2693] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From cffffdae7c15273dfad8e04acf64193e644bda81 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:43:12 -0400 Subject: [PATCH 0673/2693] comsetic: whitespace --- include/xo/cxxutil/demangle.hpp | 120 ++++++++++++++++---------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/include/xo/cxxutil/demangle.hpp b/include/xo/cxxutil/demangle.hpp index 8b8b8ba3..e2184f5d 100644 --- a/include/xo/cxxutil/demangle.hpp +++ b/include/xo/cxxutil/demangle.hpp @@ -8,85 +8,85 @@ #include // std::index_sequence namespace xo { - namespace reflect { + namespace reflect { - template - constexpr auto - substring_as_array(std::string_view str, - std::index_sequence indexes) - { - //return std::array{str[Idxs]..., '\n'}; - return std::array{str[Idxs]...}; - } /*substring_as_array*/ + template + constexpr auto + substring_as_array(std::string_view str, + std::index_sequence indexes) + { + //return std::array{str[Idxs]..., '\n'}; + return std::array{str[Idxs]...}; + } /*substring_as_array*/ - template constexpr auto type_name_array() { + template constexpr auto type_name_array() { #if defined(__clang__) - constexpr auto prefix = std::string_view{"[T = "}; - constexpr auto suffix = std::string_view{"]"}; - constexpr auto function = std::string_view{__PRETTY_FUNCTION__}; + constexpr auto prefix = std::string_view{"[T = "}; + constexpr auto suffix = std::string_view{"]"}; + constexpr auto function = std::string_view{__PRETTY_FUNCTION__}; #elif defined(__GNUC__) - constexpr auto prefix = std::string_view{"with T = "}; - constexpr auto suffix = std::string_view{"]"}; - constexpr auto function = std::string_view{__PRETTY_FUNCTION__}; + constexpr auto prefix = std::string_view{"with T = "}; + constexpr auto suffix = std::string_view{"]"}; + constexpr auto function = std::string_view{__PRETTY_FUNCTION__}; #elif defined(_MSC_VER) - constexpr auto prefix = std::string_view{"type_name_array<"}; - constexpr auto suffix = std::string_view{">(void)"}; - constexpr auto function = std::string_view{__FUNCSIG__}; + constexpr auto prefix = std::string_view{"type_name_array<"}; + constexpr auto suffix = std::string_view{">(void)"}; + constexpr auto function = std::string_view{__FUNCSIG__}; #else # error type_name_array: Unsupported compiler #endif - constexpr auto start = function.find(prefix) + prefix.size(); - constexpr auto end = function.rfind(suffix); + constexpr auto start = function.find(prefix) + prefix.size(); + constexpr auto end = function.rfind(suffix); - //static_assert(start < end); + //static_assert(start < end); - constexpr auto name = function.substr(start, (end - start)); + constexpr auto name = function.substr(start, (end - start)); - constexpr auto ixseq = std::make_index_sequence{}; + constexpr auto ixseq = std::make_index_sequence{}; - return substring_as_array(name, ixseq); - } /*type_name_array*/ + return substring_as_array(name, ixseq); + } /*type_name_array*/ - template - struct type_name_holder { - static inline constexpr auto value = type_name_array(); - }; - - template - constexpr auto type_name() -> std::string_view - { - constexpr auto& value = type_name_holder::value; - return std::string_view{value.data(), value.size()}; - } + template + struct type_name_holder { + static inline constexpr auto value = type_name_array(); + }; + + template + constexpr auto type_name() -> std::string_view + { + constexpr auto& value = type_name_holder::value; + return std::string_view{value.data(), value.size()}; + } #ifdef NOT_IN_USE - template - struct join - { - // Join all strings into a single std::array of chars - static constexpr auto impl() noexcept - { - constexpr std::size_t len = (Strs.size() + ... + 0); - std::array arr{}; - auto append = [i = 0, &arr](auto const& s) mutable { - for (auto c : s) arr[i++] = c; + template + struct join + { + // Join all strings into a single std::array of chars + static constexpr auto impl() noexcept + { + constexpr std::size_t len = (Strs.size() + ... + 0); + std::array arr{}; + auto append = [i = 0, &arr](auto const& s) mutable { + for (auto c : s) arr[i++] = c; + }; + (append(Strs), ...); + arr[len] = 0; + return arr; + } + // Give the joined string static storage + static constexpr auto arr = impl(); + // View as a std::string_view + static constexpr std::string_view value {arr.data(), arr.size() - 1}; }; - (append(Strs), ...); - arr[len] = 0; - return arr; - } - // Give the joined string static storage - static constexpr auto arr = impl(); - // View as a std::string_view - static constexpr std::string_view value {arr.data(), arr.size() - 1}; - }; - // Helper to get the value out - template - static constexpr auto join_v = join::value; + // Helper to get the value out + template + static constexpr auto join_v = join::value; #endif - } /*namespace reflect*/ + } /*namespace reflect*/ } /*namespace xo*/ /* end demangle.hpp */ From a449316999d2724f26f5711c5dfd6350c43d221f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:43:50 -0400 Subject: [PATCH 0674/2693] uniformgen: + interval() method --- include/xo/randomgen/uniformgen.hpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/include/xo/randomgen/uniformgen.hpp b/include/xo/randomgen/uniformgen.hpp index d1c4d62b..28e311ca 100644 --- a/include/xo/randomgen/uniformgen.hpp +++ b/include/xo/randomgen/uniformgen.hpp @@ -12,11 +12,19 @@ namespace xo { public: using generator_type = generator>; - template - static generator_type unit(Engine eng) { + /* named ctor idiom */ + template + static generator_type unit(Eng eng) { return make_generator(std::move(eng), std::uniform_real_distribution(0.0, 1.0)); } + + /* named ctor idiom */ + template + static generator_type interval(Eng eng, double lo, double hi) { + return make_generator(std::move(eng), + std::uniform_real_distribution(lo, hi)); + } }; } /*namespace rng*/ } /*namespace xo*/ From be5a3261f5783bf297c912610b0421c36f14d79b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:44:15 -0400 Subject: [PATCH 0675/2693] minor comment --- include/xo/randomgen/random_seed.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/xo/randomgen/random_seed.hpp b/include/xo/randomgen/random_seed.hpp index 92447b3c..d68fc10c 100644 --- a/include/xo/randomgen/random_seed.hpp +++ b/include/xo/randomgen/random_seed.hpp @@ -76,6 +76,7 @@ namespace xo { operator<<(std::ostream & os, Seed const & x) { + /* NOTE: if compile error here, may want caller to #include [indentlog/print/vector.hpp] */ os << x.seed_; return os; } /*operator<<*/ From 2602b751945aa5b77155fff70890c85014c3035b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:47:57 -0400 Subject: [PATCH 0676/2693] minor: unused decl nit --- utest/bplustree.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utest/bplustree.cpp b/utest/bplustree.cpp index a38fd545..825f33b4 100644 --- a/utest/bplustree.cpp +++ b/utest/bplustree.cpp @@ -20,7 +20,7 @@ namespace { using utest::TreeUtil; using xo::scope; - using xo::scope_setup; + //using xo::scope_setup; using xo::xtag; using BtreeKey = int; From a5f060f3580b47426008a4cc68b6fec3f8376b73 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:51:22 -0400 Subject: [PATCH 0677/2693] cosmetic: whitespace --- include/xo/kalmanfilter/KalmanFilterStep.hpp | 202 +++++++++---------- src/kalmanfilter/CMakeLists.txt | 9 +- 2 files changed, 105 insertions(+), 106 deletions(-) diff --git a/include/xo/kalmanfilter/KalmanFilterStep.hpp b/include/xo/kalmanfilter/KalmanFilterStep.hpp index 89813cc2..432ac570 100644 --- a/include/xo/kalmanfilter/KalmanFilterStep.hpp +++ b/include/xo/kalmanfilter/KalmanFilterStep.hpp @@ -8,121 +8,121 @@ #include "KalmanFilterObservable.hpp" namespace xo { - namespace kalman { - /* encapsulate {state + observation} models for a single time step t(k). - * Emitted by KalmanFilterSpec, q.v. - */ - class KalmanFilterStepBase { - public: - KalmanFilterStepBase() = default; - KalmanFilterStepBase(KalmanFilterTransition model, - KalmanFilterObservable obs) - : model_{std::move(model)}, - obs_{std::move(obs)} {} + namespace kalman { + /* encapsulate {state + observation} models for a single time step t(k). + * Emitted by KalmanFilterSpec, q.v. + */ + class KalmanFilterStepBase { + public: + KalmanFilterStepBase() = default; + KalmanFilterStepBase(KalmanFilterTransition model, + KalmanFilterObservable obs) + : model_{std::move(model)}, + obs_{std::move(obs)} {} - /* aka system_model() */ - KalmanFilterTransition const & model() const { return model_; } - KalmanFilterObservable const & obs() const { return obs_; } + /* aka system_model() */ + KalmanFilterTransition const & model() const { return model_; } + KalmanFilterObservable const & obs() const { return obs_; } - private: - /* model for process being observed (state transition + noise) */ - KalmanFilterTransition model_; - /* what can be observed (observables + noise) */ - KalmanFilterObservable obs_; - }; /*KalmanFilterStepBase*/ + private: + /* model for process being observed (state transition + noise) */ + KalmanFilterTransition model_; + /* what can be observed (observables + noise) */ + KalmanFilterObservable obs_; + }; /*KalmanFilterStepBase*/ - /* encapsulate {state + observation} models for a single time step t(k). - * Emitted by KalmanFilterSpec, q.v. - * - * holds: - * x(k) - * P(k) - * F(k) - * H(k+1) - * z(k+1) - * - * contains all the inputs needed to compute: - * x(k+1) - * P(k+1) - * - * does not provide that result - */ - class KalmanFilterStep : public KalmanFilterStepBase { - public: - using utc_nanos = xo::time::utc_nanos; - using MatrixXd = Eigen::MatrixXd; - using VectorXd = Eigen::VectorXd; + /* encapsulate {state + observation} models for a single time step t(k). + * Emitted by KalmanFilterSpec, q.v. + * + * holds: + * x(k) + * P(k) + * F(k) + * H(k+1) + * z(k+1) + * + * contains all the inputs needed to compute: + * x(k+1) + * P(k+1) + * + * does not provide that result + */ + class KalmanFilterStep : public KalmanFilterStepBase { + public: + using utc_nanos = xo::time::utc_nanos; + using MatrixXd = Eigen::MatrixXd; + using VectorXd = Eigen::VectorXd; - public: - KalmanFilterStep() = default; - KalmanFilterStep(ref::rp state, - KalmanFilterTransition model, - KalmanFilterObservable obs, - ref::rp zkp1) - : KalmanFilterStepBase(model, obs), - state_{std::move(state)}, - input_{std::move(zkp1)} {} - - ref::rp const & state() const { return state_; } - ref::rp const & input() const { return input_; } + public: + KalmanFilterStep() = default; + KalmanFilterStep(ref::rp state, + KalmanFilterTransition model, + KalmanFilterObservable obs, + ref::rp zkp1) + : KalmanFilterStepBase(model, obs), + state_{std::move(state)}, + input_{std::move(zkp1)} {} - utc_nanos tkp1() const { return input_->tkp1(); } + ref::rp const & state() const { return state_; } + ref::rp const & input() const { return input_; } - /* extrapolate kalman filter state forward to time - * .tkp1() (i.e. to t(k+1)); computes - * x(k+1|k) - * P(k+1|k) - * does not use the t(k+1) observations .input.z - */ - ref::rp extrapolate() const; + utc_nanos tkp1() const { return input_->tkp1(); } - /* compute kalman gain matrix K(k+1) - * given extrapolated t(k+1) state skp1_ext = {x(k+1|k), P(k+1|k)} - * - * note that .state() != skp1_ext; .state() reports {x(k), P(k)} - */ - MatrixXd gain(ref::rp const & skp1_ext) const; + /* extrapolate kalman filter state forward to time + * .tkp1() (i.e. to t(k+1)); computes + * x(k+1|k) + * P(k+1|k) + * does not use the t(k+1) observations .input.z + */ + ref::rp extrapolate() const; - /* compute kalman gain vector K(k+1) - * given extrapolated t(k+1) state skp1_ext = {x(k+1|k), P(k+1|k)}, - * on behalf of a single observation z[j]. - * actual observation z[j] is not given here, - * just computing the gain vector. i'th member of gain vector - * gives effect of innovation on i'th member of kalman filter state. - */ - VectorXd gain1(ref::rp const & skp1_ext, - uint32_t j) const; + /* compute kalman gain matrix K(k+1) + * given extrapolated t(k+1) state skp1_ext = {x(k+1|k), P(k+1|k)} + * + * note that .state() != skp1_ext; .state() reports {x(k), P(k)} + */ + MatrixXd gain(ref::rp const & skp1_ext) const; - /* compute correction to extrapolated filter state {x(k+1|k), P(k+1|k)}, - * for observation z(k+1) = .input.z() - */ - ref::rp correct(ref::rp const & skp1_ext); + /* compute kalman gain vector K(k+1) + * given extrapolated t(k+1) state skp1_ext = {x(k+1|k), P(k+1|k)}, + * on behalf of a single observation z[j]. + * actual observation z[j] is not given here, + * just computing the gain vector. i'th member of gain vector + * gives effect of innovation on i'th member of kalman filter state. + */ + VectorXd gain1(ref::rp const & skp1_ext, + uint32_t j) const; - /* compute correction to extrapolated filter state skp1_ext = {x(k+1|k), P(k+1|k)}, - * for a single observation z(k+1, j) = .input.z()[j] - */ - ref::rp correct1(ref::rp const & skp1_ext, - uint32_t j); + /* compute correction to extrapolated filter state {x(k+1|k), P(k+1|k)}, + * for observation z(k+1) = .input.z() + */ + ref::rp correct(ref::rp const & skp1_ext); - void display(std::ostream & os) const; - std::string display_string() const; + /* compute correction to extrapolated filter state skp1_ext = {x(k+1|k), P(k+1|k)}, + * for a single observation z(k+1, j) = .input.z()[j] + */ + ref::rp correct1(ref::rp const & skp1_ext, + uint32_t j); - private: - /* system state: timestamp, estimated process state, process covariance - * asof beginning of this step - */ - ref::rp state_; - /* input: observations at time t(k+1) */ - KalmanFilterInputPtr input_; - }; /*KalmanFilterStep*/ + void display(std::ostream & os) const; + std::string display_string() const; - inline std::ostream & - operator<<(std::ostream & os, KalmanFilterStep const & x) { - x.display(os); - return os; - } /*operator<<*/ + private: + /* system state: timestamp, estimated process state, process covariance + * asof beginning of this step + */ + ref::rp state_; + /* input: observations at time t(k+1) */ + KalmanFilterInputPtr input_; + }; /*KalmanFilterStep*/ - } /*namespace kalman*/ + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterStep const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace kalman*/ } /*namespace xo*/ /* end KalmanFilterStep.hpp */ diff --git a/src/kalmanfilter/CMakeLists.txt b/src/kalmanfilter/CMakeLists.txt index b7036526..882db194 100644 --- a/src/kalmanfilter/CMakeLists.txt +++ b/src/kalmanfilter/CMakeLists.txt @@ -21,11 +21,10 @@ set(SELF_SRCS xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- -# internal dependencies +# Dependencies +# +# REMINDER: must coordinate with find_dependency() calls in +# [xo-kalmanfilter/cmake/xo_kalmanfilterConfig.cmake.in] xo_dependency(${SELF_LIB} reactor) - -# ---------------------------------------------------------------- -# external dependencies - xo_external_target_dependency(${SELF_LIB} Eigen3 Eigen3::Eigen) From d42d29fa52fe1f1f7d3e9dbd9f38f9d56c638807 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:52:24 -0400 Subject: [PATCH 0678/2693] indentlog: allow for multiply STRINGIFY macro defs --- include/xo/indentlog/print/tag.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/xo/indentlog/print/tag.hpp b/include/xo/indentlog/print/tag.hpp index 7d4a0abc..de6a9d75 100644 --- a/include/xo/indentlog/print/tag.hpp +++ b/include/xo/indentlog/print/tag.hpp @@ -9,7 +9,9 @@ #include // STRINGIFY(xyz) -> "xyz" -#define STRINGIFY(x) #x +#ifndef STRINGIFY +# define STRINGIFY(x) #x +#endif // TAG(xyz) -> tag("xyz", xyz) #define TAG(x) xo::make_tag(STRINGIFY(x), x) From 8bbf32680585b25da2a629f8df1519600f30799a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:54:05 -0400 Subject: [PATCH 0679/2693] lsp: prevent some false complaints from LSP clang seeing gcc headers --- include/xo/indentlog/machdep/machdep.hpp | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 include/xo/indentlog/machdep/machdep.hpp diff --git a/include/xo/indentlog/machdep/machdep.hpp b/include/xo/indentlog/machdep/machdep.hpp new file mode 100644 index 00000000..11c2f3e9 --- /dev/null +++ b/include/xo/indentlog/machdep/machdep.hpp @@ -0,0 +1,26 @@ +/* @file machdep.hpp */ + +#pragma once + +/** Carveout for LSP (language server process): + LSP uses clang, but with the same compiler flags as primary build. + This triggers a handful of false alarms, in which clang complains about + gcc builtins. + + Replace these with something innocuous. Ok since LSP stops + once parsing completes and does not generate code + **/ +#if __clang__ && __GNUG__ + +extern "C" { + /* never defined! must not ever generate code that relies on these */ + unsigned int fake_mm_getcsr(); + unsigned int fake_mm_setcsr(unsigned int a); +} + +#define _mm_getcsr(a) fake_mm_getcsr() +#define _mm_setcsr(a) fake_mm_setcsr(a) + +#endif + +/* end machdep.hpp */ From 20774158ad3cd2f5feadecf9b994972443538ef7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Apr 2024 18:01:24 -0400 Subject: [PATCH 0680/2693] xo-indentlog: + XTAG() macro --- include/xo/indentlog/print/tag.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/xo/indentlog/print/tag.hpp b/include/xo/indentlog/print/tag.hpp index de6a9d75..0973506d 100644 --- a/include/xo/indentlog/print/tag.hpp +++ b/include/xo/indentlog/print/tag.hpp @@ -17,6 +17,9 @@ #define TAG(x) xo::make_tag(STRINGIFY(x), x) #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 // From 5688582f4d83587a50066c2769bec77951ca316f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Apr 2024 18:33:40 -0400 Subject: [PATCH 0681/2693] initial commit --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..d0133c7d --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# xo-unit From 2f911bc10995085baf7993612748c901a5d26107 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Apr 2024 18:52:36 -0400 Subject: [PATCH 0682/2693] xo-unit: adopt dim-checking unit impl from xo-observable --- include/xo/unit/dim_util.hpp | 59 ++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 include/xo/unit/dim_util.hpp diff --git a/include/xo/unit/dim_util.hpp b/include/xo/unit/dim_util.hpp new file mode 100644 index 00000000..222f9c9e --- /dev/null +++ b/include/xo/unit/dim_util.hpp @@ -0,0 +1,59 @@ +/* @file dim_util.hpp */ + +#pragma once + +#include "stringliteral.hpp" + +namespace xo { + namespace obs { + enum class dim { + /** weight. native unit = 1 gram **/ + mass, + /** distance. native unit = 1 meter **/ + distance, + /** time. native unit = 1 second **/ + time, + /** a currency amount. native unit depends on actual currency. + * For USD: one US dollar. + * + * NOTE: unit system isn't suitable for multicurrency work: + * (1usd + 1eur) is well-defined, but (1sec + 1m) is not. + **/ + currency, + /** a screen price. dimensionless **/ + price, + }; + + enum class native_unit_id { + gram, + meter, + second, + currency, + price + }; + + template + struct native_unit_for; + + template <> + struct native_unit_for { static constexpr auto value = native_unit_id::gram; }; + + template <> + struct native_unit_for { static constexpr auto value = native_unit_id::meter; }; + + template <> + struct native_unit_for { static constexpr auto value = native_unit_id::second; }; + + template <> + struct native_unit_for { static constexpr auto value = native_unit_id::currency; }; + + template <> + struct native_unit_for { static constexpr auto value = native_unit_id::price; }; + + template + constexpr auto native_unit_for_v = native_unit_for::value; + } /*namespace obs*/ +} /*namespace xo*/ + + +/* end dim_util.hpp */ From 3c1f8389c73c88dd9806e8dff51add0cbde2ef42 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Apr 2024 19:34:04 -0400 Subject: [PATCH 0683/2693] build: move unit+quantity feature from xo-observable -> xo-unit --- CMakeLists.txt | 56 + cmake/xo-bootstrap-macros.cmake | 14 + cmake/xo_unitConfig.cmake.in | 17 + docs/CMakeLists.txt | 60 + docs/Doxyfile.in | 2814 ++++++++++++++++++++++++ include/xo/unit/basis_unit.hpp | 86 + include/xo/unit/dimension_concept.hpp | 89 + include/xo/unit/dimension_impl.hpp | 516 +++++ include/xo/unit/native_bpu.hpp | 259 +++ include/xo/unit/native_bpu_concept.hpp | 42 + include/xo/unit/quantity.hpp | 501 +++++ include/xo/unit/quantity_concept.hpp | 21 + include/xo/unit/ratio_concept.hpp | 13 + include/xo/unit/ratio_util.hpp | 242 ++ include/xo/unit/stringliteral.hpp | 170 ++ include/xo/unit/unit.hpp | 394 ++++ utest/CMakeLists.txt | 25 + utest/quantity.test.cpp | 653 ++++++ utest/unit.test.cpp | 346 +++ utest/unit_utest_main.cpp | 6 + 20 files changed, 6324 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/xo-bootstrap-macros.cmake create mode 100644 cmake/xo_unitConfig.cmake.in create mode 100644 docs/CMakeLists.txt create mode 100644 docs/Doxyfile.in create mode 100644 include/xo/unit/basis_unit.hpp create mode 100644 include/xo/unit/dimension_concept.hpp create mode 100644 include/xo/unit/dimension_impl.hpp create mode 100644 include/xo/unit/native_bpu.hpp create mode 100644 include/xo/unit/native_bpu_concept.hpp create mode 100644 include/xo/unit/quantity.hpp create mode 100644 include/xo/unit/quantity_concept.hpp create mode 100644 include/xo/unit/ratio_concept.hpp create mode 100644 include/xo/unit/ratio_util.hpp create mode 100644 include/xo/unit/stringliteral.hpp create mode 100644 include/xo/unit/unit.hpp create mode 100644 utest/CMakeLists.txt create mode 100644 utest/quantity.test.cpp create mode 100644 utest/unit.test.cpp create mode 100644 utest/unit_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..a879e31f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,56 @@ +# xo-unit/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_unit VERSION 1.0) +enable_language(CXX) + +# common XO cmake macros (see proj/xo-cmake) +include(cmake/xo-bootstrap-macros.cmake) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() +# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) +add_code_coverage() +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) + +# ---------------------------------------------------------------- +# c++ settings + +# one-time project-specific c++ flags. usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- + +#add_subdirectory(src/unit) +add_subdirectory(utest) +add_subdirectory(docs) + +# ---------------------------------------------------------------- +# provide find_package() support for reactor customers + +set(SELF_LIB xo_unit) +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) + +# ---------------------------------------------------------------- +# dependencies + +#xo_headeronly_dependency(${SELF_LIB} randomgen) +# etc.. + +# end CMakeLists.txt diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..96592216 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,14 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) diff --git a/cmake/xo_unitConfig.cmake.in b/cmake/xo_unitConfig.cmake.in new file mode 100644 index 00000000..e5ee1778 --- /dev/null +++ b/cmake/xo_unitConfig.cmake.in @@ -0,0 +1,17 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# note: changes to find_dependency() calls here +# must coordinate with xo_dependency() calls +# in xo-reactor/src/reactor/CMakeLists.txt +# +#find_dependency(reflect) +#find_dependency(subsys) +#find_dependency(Eigen3) +#find_dependency(webutil) +#find_dependency(printjson) +#find_dependency(callback) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt new file mode 100644 index 00000000..99a1fdcc --- /dev/null +++ b/docs/CMakeLists.txt @@ -0,0 +1,60 @@ +# xo-unit/docs/CMakeLists.txt + +# copied from xo-observable/docs/CMakeLists.txt + +set(DOX_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) + +#set(DOX_DEPS ${PROJECT_SOURCE_DIR}/mainpage.dox) # not yet +set(DOX_INPUT_DIR ${PROJECT_SOURCE_DIR}) +set(DOX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/dox) + +set(DOX_INDEX_FILE ${DOX_OUTPUT_DIR}/html/index.html) + +#set(SPHINX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/sphinx/html) +#set(SPHINX_INDEX_FILE ${SPHINX_OUTPUT_DIR}/index.html) +# +## sphinx .rst files reachable from cmake-examples/docs +#file(GLOB_RECURSE SPHINX_RST_FILES ${CMAKE_CURRENT_SOURCE_DIR} *.rst) + +set(ALL_LIBRARY_TARGETS xo_unit) # todo: automate this from xo-cmake macros +#set(ALL_UTEST_TARGETS "") # todo: automate this from xo-cmake macros + +# look for doxygen executable +find_program(DOXYGEN_EXECUTABLE NAMES doxygen REQUIRED) +message("-- DOXYGEN_EXECUTABLE=${DOXYGEN_EXECUTABLE}") + +if (XO_SUBMODULE_BUILD) + # in submodule build, rely on toplevel docs/CMakeLists.txt file instead +else() + # build docs starting from here only in standalone build. + # otherwise use top-level doxygen setup instead. + + # TODO: + # 1. move Doxyfile.in to xo-cmake project + # 2. replace this command section with xo-cmake macro + # + configure_file( + Doxyfile.in ${DOX_CONFIG_FILE} + FILE_PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE + @ONLY) + + file(MAKE_DIRECTORY ${DOX_OUTPUT_DIR}) + add_custom_command( + OUTPUT ${DOX_INDEX_FILE} + DEPENDS ${DOX_DEPS} ${ALL_LIBRARY_TARGETS} ${ALL_UTEST_TARGETS} + COMMAND "${DOXYGEN_EXECUTABLE}" ${DOX_CONFIG_FILE} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + MAIN_DEPENDENCY ${DOX_CONFIG_FILE} + COMMENT "Generating docs (doxygen)") + + # To build this target + # $ cmake --build .build -j -- doxygen + # or + # $ cd .build + # $ make doxygen + # + add_custom_target( + doxygen + DEPENDS ${DOX_INDEX_FILE} + ) +endif() diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in new file mode 100644 index 00000000..67b45f8c --- /dev/null +++ b/docs/Doxyfile.in @@ -0,0 +1,2814 @@ +# If filename is Doxyfile.in: +# template (to be expanded by cmake) for real doxyfile +# @SOMEVAR@ expands to value of cmake variable SOMEVAR +# +# expressions to be expanded include: +# @DOX_INPUT_DIR@ +# @DOX_OUTPUT_DIR@ +# +# if filename is Doxyfile: +# expanded template in build directory, to configure doxygen + +# Doxyfile 1.9.7 +# + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "Cmake Examples" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = @DOX_OUTPUT_DIR@ + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = @DOX_INPUT_DIR@ + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN Use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0. and GITHUB Use the lower case version of title +# with any whitespace replaced by '-' and punctations characters removed.. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = YES + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = YES + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = YES + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. + +CASE_SENSE_NAMES = SYSTEM + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about +# undocumented enumeration values. If set to NO, doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = @DOX_INPUT_DIR@ + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding +# "INPUT_ENCODING" for further information on supported encodings. + +INPUT_FILE_ENCODING = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, +# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C +# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, +# *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.l \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = */utest/* + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# ANamespace::AClass, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generate light mode output, DARK always +# generate dark mode output, AUTO_LIGHT automatically set the mode according to +# the user preference, use light mode if no preference is set (the default), +# AUTO_DARK automatically set the mode according to the user preference, use +# dark mode if no preference is set and TOGGLE allow to user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /

); + static_assert(bpu_list_concept); + + /** For example: + * using b1 = basis_power_unit>; + * using b2 = basis_power_unit>; + * using foo = dimension_impl>; + * + * then + * foo::lookup_bpu<0> -> b1 + * foo::lookup_bpu<1> -> b2 + * foo::lookup_bpu<2> -> not defined + **/ + using front_type = P; + using rest_type = D; + + static constexpr std::uint32_t n_dimension = rest_type::n_dimension + 1; + }; + + /** @class dimension + + @brief represent a composite dimension + **/ + template + struct bpu_node { + static_assert(native_bpu_concept); + static_assert(bpu_list_concept); + + using front_type = P0; + using rest_type = void; + + /** For example: + * using b1 = basis_power_unit>; + * using foo = dimension_impl; + * then + * foo::lookup_bpu<0> --> b1 + * foo::lookup_bpu<1> --> not defined + **/ + + /** number of dimensions represented by this struct **/ + static constexpr std::uint32_t n_dimension = 1; + }; + + // ----- bpu_cartesian_product ----- + + /** Require: + * - B isa native_bpu type + * - DI_Front is a native_bpu type + * - DI_Rest is a dimension_impl type + * + * Promise: + * - type isa dimension_impl type + **/ + template < typename B, + typename DI_Front, + typename DI_Rest, + bool MatchesFront = (B::c_native_dim == DI_Front::c_native_dim) > + struct bpu_cartesian_product_helper; + + /** require: + * - B isa native_bpu type + * - DI isa (bpu_list | void) type + **/ + template < typename B, typename DI > + struct bpu_cartesian_product { + static_assert(native_bpu_concept); + static_assert(bpu_list_concept); + + using _tmp = bpu_cartesian_product_helper; + + using outer_scalefactor_type = typename _tmp::outer_scalefactor_type; + static constexpr double c_outer_scalefactor_inexact = _tmp::c_outer_scalefactor_inexact; + + using bpu_list_type = typename _tmp::bpu_list_type; + + static_assert(ratio_concept); + static_assert(bpu_list_concept); + }; + + /** Reminder: void represents the 'no dimension' of a dimensionless quantity **/ + template < typename B > + struct bpu_cartesian_product { + using outer_scalefactor_type = std::ratio<1>; + static constexpr double c_outer_scalefactor_inexact = 1.0; + + using bpu_list_type = bpu_node; + + static_assert(ratio_concept); + static_assert(bpu_list_concept); + }; + + /* specialize for matching front */ + template + struct bpu_cartesian_product_helper { + static_assert(native_bpu_concept); + static_assert(native_bpu_concept); + static_assert(bpu_list_concept); + + /* _mult_type may have zero exponent (power_type); + * in that case bpu_smart_cons will collapse to DI_Rest + */ + using _front_mult_type = bpu_product; + + using _front_type = typename _front_mult_type::native_bpu_type; + using _rest_type = DI_Rest; + + using outer_scalefactor_type = typename _front_mult_type::outer_scalefactor_type; + static constexpr double c_outer_scalefactor_inexact = _front_mult_type::c_outer_scalefactor_inexact; + + using bpu_list_type = bpu_smart_cons_t<_front_type, DI_Rest>; + + static_assert(ratio_concept); + static_assert(bpu_list_concept); + }; + + /* specialize for not-matching-front */ + template + struct bpu_cartesian_product_helper { + static_assert(native_bpu_concept); + static_assert(native_bpu_concept); + static_assert(bpu_list_concept); + + using _rest_mult_type = bpu_cartesian_product< B, DI_Rest >; + + using _front_type = DI_Front; + using _rest_type = typename _rest_mult_type::bpu_list_type; + + using outer_scalefactor_type = typename _rest_mult_type::outer_scalefactor_type; + static constexpr double c_outer_scalefactor_inexact = _rest_mult_type::c_outer_scalefactor_inexact; + + using bpu_list_type = bpu_node; + + static_assert(ratio_concept); + static_assert(bpu_list_concept); + }; + + // ----- di_cartesian_product ----- + + template < typename D1, typename D2 > struct di_cartesian_product; + + // ----- bpu_cartesian_product1 ----- + + template < typename B1, typename R1, typename D2 > + struct di_cartesian_product1 { + static_assert(native_bpu_concept); + static_assert(bpu_list_concept); + static_assert(bpu_list_concept); + + using _tmp1_mult_type = bpu_cartesian_product; + using _tmp1_scalefactor_type = _tmp1_mult_type::outer_scalefactor_type; + using _tmp1_bpu_list_type = _tmp1_mult_type::bpu_list_type; + + using _tmp2_mult_type = di_cartesian_product; + using _tmp2_scalefactor_type = _tmp2_mult_type::outer_scalefactor_type; + using _tmp2_bpu_list_type = _tmp2_mult_type::bpu_list_type; + + using outer_scalefactor_type = std::ratio_multiply< + _tmp1_scalefactor_type, + _tmp2_scalefactor_type>; + static constexpr double c_outer_scalefactor_inexact = (_tmp1_mult_type::c_outer_scalefactor_inexact + * _tmp2_mult_type::c_outer_scalefactor_inexact); + + using bpu_list_type = _tmp2_bpu_list_type; + + static_assert(ratio_concept); + static_assert(bpu_list_concept); + }; + + template < typename B1, typename D2 > + struct di_cartesian_product1 { + static_assert(native_bpu_concept); + static_assert(bpu_list_concept); + + using _tmp_mult_type = bpu_cartesian_product; + + using outer_scalefactor_type = _tmp_mult_type::outer_scalefactor_type; + static constexpr double c_outer_scalefactor_inexact = _tmp_mult_type::c_outer_scalefactor_inexact; + + using bpu_list_type = _tmp_mult_type::bpu_list_type; + }; + + // ----- di_invert ----- + + /* note: rescaling never required here, + * since not combining basis dimensions. + */ + template + struct di_invert; + + template <> + struct di_invert { + using type = void; + }; + + template + struct di_invert { + using type = bpu_node< + typename bpu_invert::type, + typename di_invert::type + >; + }; + + // ----- di_cartesian_product ----- + + template < typename D1, typename D2 > + struct di_cartesian_product { + static_assert(bpu_list_concept); + static_assert(bpu_list_concept); + + using _mult_type = di_cartesian_product1< + typename D1::front_type, + typename D1::rest_type, + D2>; + + using outer_scalefactor_type = _mult_type::outer_scalefactor_type; + static constexpr double c_outer_scalefactor_inexact = _mult_type::c_outer_scalefactor_inexact; + + using bpu_list_type = _mult_type::bpu_list_type; + + static_assert(ratio_concept); + static_assert(bpu_list_concept); + }; + + template < typename D2 > + struct di_cartesian_product< void, D2 > { + static_assert(bpu_list_concept); + + using outer_scalefactor_type = std::ratio<1>; + static constexpr double c_outer_scalefactor_inexact = 1.0; + using bpu_list_type = D2; + }; + + template + struct di_cartesian_product< D1, void > { + static_assert(bpu_list_concept); + + using outer_scalefactor_type = std::ratio<1>; + static constexpr double c_outer_scalefactor_inexact = 1.0; + using bpu_list_type = D1; + }; + + // ----- di_assemble_abbrev ----- + + /* reminder: can't partially specialize a template function -> need struct wrapper */ + template < typename DI > + struct di_assemble_abbrev; + + /** Expect: + * - P isa native_bpu type + * - P::power_type = std::ratio<..> + * - P::c_native_dim :: dim + * - P::c_num :: int + * - P::c_den :: int + * - D isa dimension_impl type + * - D::front_type = native_bpu<..> + * - D::rest_type = dimension_impl<..> + * - D::n_dimension :: int + **/ + template + struct di_assemble_abbrev_helper { + static_assert(native_bpu_concept

); + static_assert(bpu_list_concept); + + static constexpr auto _prefix = bpu_assemble_abbrev

(); + static constexpr auto _suffix = di_assemble_abbrev::value; + + static constexpr auto value = stringliteral_concat(_prefix.value_, + ".", + _suffix.value_); + }; + + template + struct di_assemble_abbrev_helper { + static constexpr auto value = bpu_assemble_abbrev

(); + }; + + template < typename DI > + struct di_assemble_abbrev { + static_assert(bpu_list_concept); + + using _helper_type = di_assemble_abbrev_helper ; + + static constexpr auto value = _helper_type::value; + }; + + template <> + struct di_assemble_abbrev { + static constexpr auto value = stringliteral(""); + }; + + // ----- canonical_impl ----- + + template + struct canonical_impl { + /* + * bwp_front::c_index + * bwp_front::c_native_dim + */ + using _bwp_front = native_lo_bwp_of::bwp_type; + + using _front_type = typename lookup_bpu::power_unit_type; + using _rest0_type = typename without_elt::dim_type; + using _rest_type = canonical_impl<_rest0_type>::dim_type; + + using dim_type = bpu_node<_front_type, _rest_type>; + }; + + /** compute canonical renumbering of a dimension + **/ + template <> + struct canonical_impl { + using dim_type = void; + }; + + template + using canonical_t = canonical_impl::dim_type; + + } /*namespace obs*/ +} /*namespace xo*/ + +/* end dimension_impl.hpp */ diff --git a/include/xo/unit/native_bpu.hpp b/include/xo/unit/native_bpu.hpp new file mode 100644 index 00000000..a3e716a0 --- /dev/null +++ b/include/xo/unit/native_bpu.hpp @@ -0,0 +1,259 @@ +/* @file native_bpu.hpp */ + +#pragma once + +#include "native_bpu_concept.hpp" +#include "basis_unit.hpp" +#include + +namespace xo { + namespace obs { + // ----- native_bpu ----- + + /** @class native_bpu + + @brief represent product of a compile-time scale-factor with a rational power of a native unit + + Example: + native_bpu, ratio<-1,2>> represents unit of 1/sqrt(t) + **/ + template< + dim DimId, + typename InnerScale, + typename Power = std::ratio<1> > + struct bpu : basis_unit, InnerScale> { + static_assert(ratio_concept); + + /* native_unit provides + * - scalefactor_type --> std::ratio + * - c_native_dim :: dim + * - c_native_unit :: native_unit + */ + + using power_type = Power; + + static const int c_num = Power::num; + static const int c_den = Power::den; + }; + + /** @class bpu_assemble_abbrev + * + * @brief generate abbreviation literal. + * + * Abbreviation literal ignores outer scale factor; + * (outer scale factor should be multiplied by run-time scale when printing a quantity) + * + * Separate template from native_bpu so that abbrev can independently be specialized + **/ + template < dim dim_id, + typename InnerScale, + typename Power = std::ratio<1> > + constexpr auto bpu_assemble_abbrev_helper() { + static_assert(ratio_concept); + + return stringliteral_concat(units::scaled_native_unit_abbrev_v.value_, + stringliteral_from_exponent().value_); + }; + + /** Expect: + * - BPU is a native_bpu type: + * - BPU::scalefactor_type = std::ratio<..> + * - BPU::c_native_dim :: dim + * - BPU::power_type = std::ratio<..> + * - BPU::c_num :: int + * - BPU::c_den :: int + **/ + template < typename BPU > + constexpr auto bpu_assemble_abbrev() { + static_assert(native_bpu_concept); + + return bpu_assemble_abbrev_helper< BPU::c_native_dim, + typename BPU::scalefactor_type, + typename BPU::power_type >(); + }; + + // ----- bpu_rescale ----- + + /** + * Part I + * ------ + * We have B satisfying native_bpu_concept: + * B represents a basis-power-unit + * p + * (b.u) + * + * with + * b = B::scalefactor_type, e.g. 60 for a 1-minute unit + * u = B::dim, e.g. 1second for time + * p = B::power_type + * + * We want to construct something with similar form: + * + * p + * a'.(b'.u) + * + * representing the same dimensioned unit, + * i.e. + * p p' + * (b.u) = a'.(b'.u) + * + * with NewInnerScale -> b' + * + * p p p p + * (b.u) = (b/b') . (b'.u) = a'.(b'.u) + * + * p + * with a' = (b/b') + * + * For example: if we have B(b=60,u=time,p=2), NewInnerScale=1: + * then we want a'=3600, B'(b=1,u=time,p=2) + * + * Result represented with + * bpu_rescale::outer_scalefactor_type -> 'a + * bpu_rescale::native_bpu_type -> B' + * + * Part II + * ------- + * Want ability to rescale when p is a non-integer rational. + * In that case a' = (b/b')^p won't in general be exactly-representable, + * so we are forced to accept some loss of precision. + * + * Want to write: + * p as p' + q' with: + * p' = integer part of p + * q' = fractional part of p + * Then we can write + * a' as c'.d' with: + * c' = (b/b')^p' [exactly represented] + * d' = (b/b')^q' [floating point] + **/ + template + struct bpu_rescale { + static_assert(native_bpu_concept); + static_assert(ratio_concept); + + /* TODO: + * - native_unit::c_scale -> std::ratio, call it c_inner_scalefactor + * - ++ native_bpu::c_outer_scalefactor, will be a std::ratio + */ + + /* b/b' */ + using _t1_type = std::ratio_divide + < typename B::scalefactor_type, NewInnerScale >; + + /* p' */ + using p1_type = ratio_floor_t; + /* q' */ + using q1_type = ratio_frac_t; + + /** require p must be integral **/ + static_assert(p1_type::den == 1); + + /* note: constexpr from c++26, but already present in earlier gcc */ + static constexpr double c_outer_scalefactor_inexact = ::pow(from_ratio(), + from_ratio()); + + /** p + * a' = (b/b') + **/ + using outer_scalefactor_type = ratio_power_t< _t1_type, p1_type::num >; + + /** + * p + * (b'.u) + **/ + using native_bpu_type = bpu < B::c_native_dim, + NewInnerScale, + typename B::power_type >; + }; + + // ----- bpu_invert ----- + + /** invert a native bpu: create type for space 1/B **/ + template + struct bpu_invert { + using type = bpu < + B::c_native_dim, + typename B::scalefactor_type, + std::ratio_multiply, typename B::power_type> + >; + }; + + // ----- bpu_product ----- + + /** Suppose we have two native_bpu's {B1, B2} that scale the same native basis unit u. + * B1,B2 may be using different units {b1,b2} for u + * + * p1 + * B1 (b1, u, p1) = (b1.u) + * + * p2 + * B2 (b2, u, p2) = (b2.u) + * + * we want a representation in similar form: + * + * p' + * a' . B' (b', u, p') = a'.(b'.u) + * + * for the product (B1 x B2), i.e. + * + * p' p1 p2 + * a'.(b'.u) = (b1.u) (b2.u) + * + * We can use bpu_rescale to rewrite B2 in the form + * + * p2 + * B2' = (c'.d').(b1.u) + * + * where c' is exact, d' is inexact. + * (note however d' will be exactly 1.0 whenever p2 is integral) + * + * so we have + * + * p1 p2 + * (B1 x B2) = (b1.u) (c'.d').(b1.u) + * + * p1+p2 + * = (c'.d').(b1.u) + * + **/ + template < typename B1, typename B2 > + struct bpu_product { + static_assert(native_bpu_concept); + static_assert(native_bpu_concept); + static_assert(B1::c_native_dim == B2::c_native_dim); + + /* c'.d'.B2' = c'.d'.(b1.u)^p2 + * + * _b2p_rescaled_type::native_bpu_type -> B2' (b1, u, p2) [same basis scalefactor as B1] + * _b2p_rescaled_type::outer_scalefactor_type -> c' [exact factor] + * _b2p_rescaled_type::c_outer_scalefactor_type -> d' [inexact factor, from fractional powers] + */ + using _b2p_rescaled_type = bpu_rescale; + /* (b1.u)^p2 */ + using _b2p_sf_bpu_type = _b2p_rescaled_type::native_bpu_type; + + /* p1+p2 */ + using _p_type = std::ratio_add< + typename B1::power_type, + typename B2::power_type + >; + + /* c' */ + using outer_scalefactor_type = _b2p_rescaled_type::outer_scalefactor_type; + /* d' */ + static constexpr double c_outer_scalefactor_inexact = _b2p_rescaled_type::c_outer_scalefactor_inexact; + + /* (b1.u)^(p1+p2) */ + using native_bpu_type = bpu < + B1::c_native_dim, + typename B1::scalefactor_type, + _p_type /*Power*/ >; + }; + + } /*namespace obs*/ +} /*namespace xo*/ + +/* end native_bpu.hpp */ diff --git a/include/xo/unit/native_bpu_concept.hpp b/include/xo/unit/native_bpu_concept.hpp new file mode 100644 index 00000000..776e18bf --- /dev/null +++ b/include/xo/unit/native_bpu_concept.hpp @@ -0,0 +1,42 @@ +/* @file native_bpu_concept.hpp */ + +#pragma once + +#include "ratio_concept.hpp" +#include "dim_util.hpp" +#include + +namespace xo { + namespace obs { + /** + * e.g. see native_bpu> + * + * bpu short for 'basis power unit'. + * + * NOTE: in typical c++ use, there won't be a reason to declare + * a variable of type NativeBpu. Instead will appear + * as a template argument. + **/ + template + concept native_bpu_concept = requires(NativeBpu bpu) + { + typename NativeBpu::scalefactor_type; + typename NativeBpu::power_type; + + // NativeBpu::c_native_dim :: native_dim_id + // NativeBpu::c_scale :: std::intmax_t + // NativeBpu::num :: int + // NativeBpu::den :: int + } + && ((std::is_same_v) + && ratio_concept + && ratio_concept + && (std::is_signed_v) + && (std::is_signed_v)) + // && std::copyable + ; + } /*namespace obs*/ +} /*namespace xo*/ + + +/* end native_bpu_concept.hpp */ diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp new file mode 100644 index 00000000..286a9e16 --- /dev/null +++ b/include/xo/unit/quantity.hpp @@ -0,0 +1,501 @@ +/* @file quantity.hpp */ + +#pragma once + +#include "quantity_concept.hpp" +#include "unit.hpp" +//#include "xo/reflect/Reflect.hpp" +//#include "xo/indentlog/scope.hpp" + +namespace xo { + namespace obs { + /** @class promoter + * + * Aux class assister for quantity::promote() + **/ + template > + struct promoter; + + // ----- quantity ----- + + /** @class quantity + * + * @brief represets a scalar quantity; enforces dimensional consistency at compile time + * + * Repr representation. + * Unit unit + * Assert use to specify required unit dimension + * + * Require: + * - Repr copyable, assignable + * - Repr = 0 + * - Repr = 1 + * - Repr + Repr -> Repr + * - Repr - Repr -> Repr + * - Repr * Repr -> Repr + * - Repr / Repr -> Repr + **/ + template //, typename RequiredDimensionType = Unit> + class quantity { + public: + using unit_type = Unit; + using repr_type = Repr; + + /* non-unity compile-time scale factors can arise during unit conversion; + * for example see method quantity::in_units_of() + */ + static_assert(std::same_as< typename Unit::scalefactor_type, std::ratio<1> >); + static_assert(std::same_as< typename Unit::canon_type, typename Unit::canon_type >); + + public: + constexpr quantity() = default; + constexpr quantity(quantity const & x) = default; + constexpr quantity(quantity && x) = default; + + template + using find_bpu_t = unit_find_bpu_t; + + /** + * For example: + * auto q = qty::milliseconds(5) * qty::seconds(1); + * then + * q.basis_power -> 2 + * q.basis_power -> 0 + **/ + template + static constexpr PowerRepr c_basis_power = from_ratio::power_type>(); + + /** @brief get scale value (relative to unit) (@ref scale_) **/ + constexpr Repr scale() const { return scale_; } + /** @brief abbreviation for this quantity's units **/ + static constexpr char const * unit_cstr() { return unit_abbrev_v.c_str(); } + /** @brief return unit quantity -- amount with this Unit that has representation = 1 **/ + static constexpr quantity unit_quantity() { return quantity(1); } + /** @brief promote representation to quantity. Same as multiplying by Unit **/ + static constexpr auto promote(Repr x) { + //std::cerr << "quantity::promote: x=" << x << ", R=" << reflect::Reflect::require()->canonical_name() << std::endl; + return promoter::promote(x); + } + + template + auto multiply(Quantity2 y) const { + //constexpr bool c_debug_flag = false; + //using Reflect = xo::reflect::Reflect; + + //scope log(XO_DEBUG(c_debug_flag)); + + static_assert(quantity_concept); + + /* unit: may have non-unit scalefactor_type */ + using unit_product_type = unit_cartesian_product; + using exact_unit_type = unit_product_type::exact_unit_type; + using norm_unit_type = normalize_unit_t; + + using exact_scalefactor_type = exact_unit_type::scalefactor_type; + constexpr double c_scalefactor_inexact = unit_product_type::c_scalefactor_inexact; + + using repr_type = std::common_type_t; + + repr_type r_scale = ((scale() * y.scale() * c_scalefactor_inexact * exact_scalefactor_type::num) + / exact_scalefactor_type::den); + +# ifdef NOT_USING_DEBUG + log && log(xtag("unit_product_type", Reflect::require()->canonical_name())); + log && log(xtag("exact_unit_type", Reflect::require()->canonical_name())); + log && log(xtag("norm_unit_type", Reflect::require()->canonical_name())); + log && log(xtag("exact_scalefactor_type", Reflect::require()->canonical_name())); + log && log(xtag("c_scalefactor_inexact", c_scalefactor_inexact)); + log && log(xtag("repr_type", Reflect::require()->canonical_name())); + log && log(xtag("repr_type", Reflect::require()->canonical_name())); +# endif + + return quantity::promote(r_scale); + } + + template + auto divide(Quantity2 y) const { + using unit_divide_type = unit_divide; + using exact_unit_type = unit_divide_type::exact_unit_type; + using norm_unit_type = normalize_unit_t; + + using exact_scalefactor_type = exact_unit_type::scalefactor_type; + constexpr double c_scalefactor_inexact = unit_divide_type::c_scalefactor_inexact; + + using repr_type = std::common_type_t; + + repr_type r_scale = ((scale() * c_scalefactor_inexact * unit_type::scalefactor_type::num) + / (y.scale() * unit_type::scalefactor_type::den)); + + return quantity::promote(r_scale); + } + + // quantity operator*=() + // quantity operator/=() + + /** + * scale by dimensionless number + **/ + template + auto scale_by(Repr2 x) const { + static_assert(!quantity_concept); + + using r_repr_type = std::common_type_t; + + r_repr_type r_scale = this->scale_ * x; + + //std::cerr << "quantity::scale_by: scale=" << scale << ", repr_type=" << reflect::Reflect::require()->canonical_name() << std::endl; + + return quantity::promote(r_scale); + } + + /** + * divide by dimensionless number + **/ + template + auto divide_by(Repr2 x) const { + using r_repr_type = std::common_type_t; + + r_repr_type r_scale = this->scale_ / x; + + return quantity::promote(r_scale); + } + + /** + * divide dimensionless number by this quantity + **/ + template + auto divide_into(Repr2 x) const { + using r_unit_type = unit_invert_t; + using r_repr_type = std::common_type_t; + + r_repr_type r_scale = ((x * r_unit_type::scalefactor_type::num) + / (this->scale_ * r_unit_type::scalefactor_type::den)); + + return quantity::promote(r_scale); + } + + template + Repr2 in_units_of() const { + // static_assert(dimension_of == dimension_of); // discard all the scaling values + + static_assert(same_dimension_v); + + using _convert_to_u2_type = unit_cartesian_product>; + + using exact_scalefactor_type = _convert_to_u2_type::exact_unit_type::scalefactor_type; + constexpr double c_scalefactor_inexact = _convert_to_u2_type::c_scalefactor_inexact; + + // _convert_u2_type + // - scalefactor_type + // - dim_type + // - canon_type + + /* if _convert_u2_type isn't dimensionless, then {Unit2, Unit} have different dimensions */ + + return ((this->scale_ * c_scalefactor_inexact * exact_scalefactor_type::num) / exact_scalefactor_type::den); + } + + template + quantity operator+=(Quantity2 y) { + static_assert(std::same_as< + typename unit_type::canon_type, + typename Quantity2::unit_type::canon_type >); + + /* relying on assignment that correctly converts-to-lhs-units */ + quantity y2 = y; + + this->scale_ += y2.scale(); + + return *this; + } + + template + quantity operator-=(Quantity2 y) { + static_assert(std::same_as< + typename unit_type::canon_type, + typename Quantity2::unit_type::canon_type >); + + quantity y2 = y; + + /* relying on assignment that correctly converts-to-lhs-units */ + this->scale_ -= y2.scale(); + + return *this; + } + + /* convert to quantity with same dimension, different {unit_type, repr_type} */ + template + operator Quantity2 () const { + /* avoid truncating precision when converting: + * use best available representation + */ + using tmp_repr_type = std::common_type_t; + + return Quantity2::promote(this->in_units_of()); + } + + void display(std::ostream & os) const { + os << this->scale() << unit_cstr(); + } + + quantity & operator=(quantity const & x) = default; + quantity & operator=(quantity && x) = default; + + private: + explicit constexpr quantity(Repr x) : scale_{x} {} + + friend class promoter; + friend class promoter; + + private: + /** @brief quantity represents this multiple of a unit (that has compile-time outer-scalefactor of 1) **/ + Repr scale_ = 0; + }; /*quantity*/ + + // ----- promoter ----- + + /* collapse dimensionless quantity to its repr_type> */ + template + struct promoter { + static constexpr Repr promote(Repr x) { return x; }; + }; + + template + struct promoter { + static constexpr quantity promote(Repr x) { return quantity(x); } + }; + + // ----- operator+ ----- + + template + inline Quantity1 operator+ (Quantity1 x, Quantity2 y) { + static_assert(same_dimension_v); + + /* convert y to match units used by x; + * would fail at compile time if this isn't well-defined + */ + Quantity1 y2 = y; + + return Quantity1::promote(x.scale() + y2.scale()); + } + + template + inline Quantity1 operator- (Quantity1 x, Quantity2 y) { + static_assert(std::same_as + < typename Quantity1::unit_type::dimension_type::canon_type, + typename Quantity2::unit_type::dimension_type::canon_type >); + + /* convert y to match units used by x */ + Quantity1 y2 = y; + + return Quantity1::promote(x.scale() - y2.scale()); + } + + template + inline Quantity operator- (Quantity x) { + return Quantity::promote(- x.scale()); + } + + template + inline auto operator* (Quantity1 x, Quantity2 y) { + static_assert(quantity_concept); + static_assert(quantity_concept); + + return x.multiply(y); + } + + /** e.g. DECLARE_LH_MULT(int32_t) **/ +# define DECLARE_LH_MULT(lhtype) \ + template \ + inline auto \ + operator* (lhtype x, Quantity y) { \ + static_assert(quantity_concept); \ + return y.scale_by(x); \ + } + + DECLARE_LH_MULT(int8_t); + DECLARE_LH_MULT(uint8_t); + DECLARE_LH_MULT(int16_t); + DECLARE_LH_MULT(uint16_t); + DECLARE_LH_MULT(int32_t); + DECLARE_LH_MULT(uint32_t); + DECLARE_LH_MULT(int64_t); + DECLARE_LH_MULT(uint64_t); + DECLARE_LH_MULT(float); + DECLARE_LH_MULT(double); +# undef DECLARE_LH_MULT + + /** e.g. DECLARE_RH_MULT(int32_t) **/ +# define DECLARE_RH_MULT(rhtype) \ + template \ + inline auto \ + operator* (Quantity x, rhtype y) { \ + static_assert(quantity_concept); \ + return x.scale_by(y); \ + } + + DECLARE_RH_MULT(int8_t); + DECLARE_RH_MULT(uint8_t); + DECLARE_RH_MULT(int16_t); + DECLARE_RH_MULT(uint16_t); + DECLARE_RH_MULT(int32_t); + DECLARE_RH_MULT(uint32_t); + DECLARE_RH_MULT(int64_t); + DECLARE_RH_MULT(uint64_t); + DECLARE_RH_MULT(float); + DECLARE_RH_MULT(double); +# undef DECLARE_LH_MULT + + template + inline auto operator/ (Quantity1 x, Quantity2 y) { + static_assert(quantity_concept); + static_assert(quantity_concept); + + return x.divide(y); + } + +# define DECLARE_LH_DIV(lhtype) \ + template \ + inline auto \ + operator/ (lhtype x, Quantity y) { \ + static_assert(quantity_concept); \ + return y.divide_into(x); \ + } + + DECLARE_LH_DIV(int8_t); + DECLARE_LH_DIV(uint8_t); + DECLARE_LH_DIV(int16_t); + DECLARE_LH_DIV(uint16_t); + DECLARE_LH_DIV(int32_t); + DECLARE_LH_DIV(uint32_t); + DECLARE_LH_DIV(int64_t); + DECLARE_LH_DIV(uint64_t); + DECLARE_LH_DIV(float); + DECLARE_LH_DIV(double); +# undef DECLARE_LH_DIV + +# define DECLARE_RH_DIV(rhtype) \ + template \ + inline auto \ + operator/ (Quantity x, rhtype y) { \ + static_assert(quantity_concept); \ + return x.divide_by(y); \ + } + + DECLARE_RH_DIV(int8_t) + DECLARE_RH_DIV(uint8_t) + DECLARE_RH_DIV(int16_t) + DECLARE_RH_DIV(uint16_t) + DECLARE_RH_DIV(int32_t) + DECLARE_RH_DIV(uint32_t) + DECLARE_RH_DIV(int64_t) + DECLARE_RH_DIV(uint64_t) + DECLARE_RH_DIV(float) + DECLARE_RH_DIV(double) +# undef DECLARE_RH_DIV + + template + inline std::ostream & + operator<< (std::ostream & os, quantity const & x) { + x.display(os); + return os; + } + + namespace qty { + // ----- mass ----- + + template + inline auto milligrams(Repr x) -> quantity { + return quantity::promote(x); + }; + + template + inline auto grams(Repr x) -> quantity { + return quantity::promote(x); + }; + + template + inline auto kilograms(Repr x) -> quantity { + return quantity::promote(x); + }; + + // ----- distance ----- + + template + inline auto millimeters(Repr x) -> quantity { + return quantity::promote(x); + } + + template + inline auto meters(Repr x) -> quantity { + return quantity::promote(x); + } + + template + inline auto kilometers(Repr x) -> quantity { + return quantity::promote(x); + } + + // ----- time ----- + + template + inline auto nanoseconds(Repr x) -> quantity { + return quantity::promote(x); + } + + template + inline auto microseconds(Repr x) -> quantity { + return quantity::promote(x); + } + + template + inline auto milliseconds(Repr x) -> quantity { + return quantity::promote(x); + } + + template + inline auto seconds(Repr x) -> quantity { + return quantity::promote(x); + } + + template + inline auto minutes(Repr x) -> quantity { + return quantity::promote(x); + } + + template + inline auto hours(Repr x) -> quantity { + return quantity::promote(x); + } + + template + inline auto days(Repr x) -> quantity { + return quantity::promote(x); + } + + // ----- time/volatility ----- + + /** quantity in units of 1/sqrt(1dy) **/ + template + inline auto volatility1d(Repr x) -> quantity { + return quantity::promote(x); + } + + /** quantity in units of 1/sqrt(30days) + **/ + template + inline auto volatility30d(Repr x) -> quantity { + return quantity::promote(x); + } + + /** quantity in units of 1/sqrt(250days) + **/ + template + inline auto volatility250d(Repr x) -> quantity { + return quantity::promote(x); + } + } /*namespace qty*/ + } /*namespace obs*/ +} /*namespace xo*/ + +/* end quantity.hpp */ diff --git a/include/xo/unit/quantity_concept.hpp b/include/xo/unit/quantity_concept.hpp new file mode 100644 index 00000000..963a0246 --- /dev/null +++ b/include/xo/unit/quantity_concept.hpp @@ -0,0 +1,21 @@ +/** @file quantity_concept.hpp **/ + +#pragma once + +#include + +namespace xo { + namespace obs { + template + concept quantity_concept = requires(Quantity qty, typename Quantity::repr_type repr) + { + typename Quantity::unit_type; + typename Quantity::repr_type; + + { qty.scale() } -> std::same_as; + { Quantity::unit_cstr() } -> std::same_as; + { Quantity::unit_quantity() } -> std::same_as; + { Quantity::promote(repr) } -> std::same_as; + }; + } /*namespace obs*/ +} /*namespace xo*/ diff --git a/include/xo/unit/ratio_concept.hpp b/include/xo/unit/ratio_concept.hpp new file mode 100644 index 00000000..3308b1f1 --- /dev/null +++ b/include/xo/unit/ratio_concept.hpp @@ -0,0 +1,13 @@ +/* @file ratio_concept.hpp */ + +#pragma once + +#include + +namespace xo { + namespace obs { + template + concept ratio_concept = (std::is_signed_v + && std::is_signed_v); + } /*namespace obs*/ +} /*namespace xo*/ diff --git a/include/xo/unit/ratio_util.hpp b/include/xo/unit/ratio_util.hpp new file mode 100644 index 00000000..7695db3d --- /dev/null +++ b/include/xo/unit/ratio_util.hpp @@ -0,0 +1,242 @@ +/* @file ratio_util.hpp */ + +#pragma once + +#include "ratio_concept.hpp" +#include "stringliteral.hpp" +#include +#include + +namespace xo { + namespace obs { + // ----- ratio_floor ----- + + template + struct ratio_floor { + using type = std::ratio; + }; + + template + using ratio_floor_t = ratio_floor::type; + + // ----- ratio_frac ----- + + template + struct ratio_frac { + static_assert(ratio_concept); + + using type = std::ratio_subtract>::type; + }; + + template + using ratio_frac_t = ratio_frac::type; + + // ----- ratio_power ----- + + /** ratio to an integer power + * + * ratio_power::type + * + * yields a std::ratio representing Base^Power. + **/ + template + struct ratio_power_aux; + + template + struct ratio_power_aux { + + /** std::ratio representing Base^(-Power) **/ + using _inverse_ratio_type = typename ratio_power_aux::type; + + /** Base^(-Power)^-1 = Base^Power **/ + using type = std::ratio_divide, _inverse_ratio_type>; + + static_assert(ratio_concept); + }; + + template + struct ratio_power_aux { + using type = std::ratio<1>; + }; + + template + struct ratio_power_aux { + using type = Base; + }; + + template + struct ratio_power_aux { + /** Base^(Power/2) **/ + using _p2_ratio_type = ratio_power_aux::type; + /** Base^(Power%2) : + * - Base^0 if Power is even + * - Base^1 if Power is odd + **/ + using _x_ratio_type = ratio_power_aux::type; + + using type = std::ratio_multiply< _x_ratio_type, + std::ratio_multiply<_p2_ratio_type, + _p2_ratio_type> >; + + static_assert(ratio_concept); + }; + + // ----- ratio_power_v ----- + + /** compute Base^Power at compile time **/ + template + using ratio_power_t = ratio_power_aux::type; + + // ----- from_ratio ----- + + /** Example: + * from_ratio --> 0.1 + * from_ratio --> 0.8 + **/ + template + constexpr Repr from_ratio() { + static_assert(ratio_concept); + + return Ratio::num / static_cast(Ratio::den); + } + + // ----- ratio2str_aux ----- + + /* note: using struct wrapper because partial specialization of function templates + * is not allowed + */ + template + struct ratio2str_aux; + + template + struct ratio2str_aux { + static constexpr auto value = stringliteral_concat("(", + stringliteral_from_int_v().value_, + "/", + stringliteral_from_int_v().value_, + ")"); + }; + + template + struct ratio2str_aux { + /* note: using struct wrapper because partial specialization of function templates + * is not allowed + */ + static constexpr auto value = stringliteral_concat("-(", + stringliteral_from_int_v<-Num>().value_, + "/", + stringliteral_from_int_v().value_, + ")"); + }; + + template + struct ratio2str_aux { + static constexpr auto value = stringliteral_concat("-", + stringliteral_from_int_v<-Num>().value_); + }; + + template + struct ratio2str_aux { + static constexpr auto value = stringliteral_from_int_v(); + }; + + template <> + struct ratio2str_aux<1, 1, false> { + static constexpr auto value = stringliteral(""); + }; + + // ----- stringliteral_from_ratio ----- + + /** Example: + * - stringliteral_from_ratio -> "^(2,3)" + **/ + template + constexpr auto stringliteral_from_ratio() { + static_assert(ratio_concept); + + using lowest_terms_type = Ratio::type; + + return ratio2str_aux().value; + }; + + template + constexpr char const * cstr_from_ratio() { + static_assert(ratio_concept); + + using lowest_terms_type = Ratio::type; + + return ratio2str_aux().value.c_str(); + }; + + // ----- exponent2str_aux ----- + + /* note: using struct wrapper because partial specialization of function templates + * is not allowed + */ + template + struct exponent2str_aux; + + template + struct exponent2str_aux { + static constexpr auto value = stringliteral_concat("^(", + stringliteral_from_int_v().value_, + "/", + stringliteral_from_int_v().value_, + ")"); + }; + + template + struct exponent2str_aux { + /* note: using struct wrapper because partial specialization of function templates + * is not allowed + */ + static constexpr auto value = stringliteral_concat("^-(", + stringliteral_from_int_v<-Num>().value_, + "/", + stringliteral_from_int_v().value_, + ")"); + }; + + template + struct exponent2str_aux { + static constexpr auto value = stringliteral_concat("^", + stringliteral_from_int_v().value_); + }; + + template + struct exponent2str_aux { + /* Example: + * - exponent2str_aux<-1, 1, true> --> "^-1" + * - exponent2str_aux<-2, 1, true> --> "^-2" + */ + static constexpr auto value = stringliteral_concat("^", + stringliteral_from_int_v().value_); + }; + + template <> + struct exponent2str_aux<1, 1, false> { + static constexpr auto value = stringliteral(""); + }; + + // ----- stringliteral_from_exponent ----- + + template + constexpr auto stringliteral_from_exponent() { + static_assert(ratio_concept); + + return exponent2str_aux().value; + }; + + } /*namespace obs*/ +} /*namespace xo*/ + +/* end ratio_util.hpp */ diff --git a/include/xo/unit/stringliteral.hpp b/include/xo/unit/stringliteral.hpp new file mode 100644 index 00000000..c6bc6243 --- /dev/null +++ b/include/xo/unit/stringliteral.hpp @@ -0,0 +1,170 @@ +/* @file stringliteral.hpp */ + +#pragma once + +#include +#include +#include +#include + +namespace xo { + namespace obs { + + // ----- stringliteral ----- + + /** @class stringliteral + * + * @brief class to represent a literal string at compile time, for use as template argument + **/ + template + struct stringliteral { + constexpr stringliteral() { if (N > 0) value_[0] = '\0'; } + constexpr stringliteral(const char (&str)[N]) { std::copy_n(str, N, value_); } + constexpr int size() const { return N; } + + constexpr char const * c_str() const { return value_; } + + char value_[N]; + }; + + template < typename First, typename... Rest > + constexpr auto + all_same_v = std::conjunction_v< std::is_same... >; + + /** args and result must be stringliteral **/ + template < typename... Ts> + constexpr auto + stringliteral_concat(Ts && ... args) + { +#ifdef NOT_USING + static_assert(all_same_v...>, + "string must share the same char type"); + + using char_type = std::remove_const_t< std::remove_pointer_t < std::common_type_t < Ts... > > >; +#endif + using char_type = char; + + /** n1: total number of bytes used by arguments **/ + constexpr size_t n1 = (sizeof(Ts) + ...); + /** z1: each string arg has a null terminator included in its size, + * z1 is the number of arguments in parameter pack Ts, + * which equals the number of null terminators used + **/ + constexpr size_t z1 = sizeof...(Ts); + + /** n: number of chars in concatenated string. +1 for final null **/ + constexpr size_t n + = (n1 / sizeof(char_type)) - z1 + 1; + + stringliteral result; + + size_t pos = 0; + + auto detail_concat = [ &pos, &result ](auto && arg) { + constexpr auto count = (sizeof(arg) - sizeof(char_type)) / sizeof(char_type); + + std::copy_n(arg, count, result.value_ + pos); + pos += count; + }; + + (detail_concat(args), ...); + + return result; + } + + template + constexpr auto + stringliteral_compare(stringliteral && s1, stringliteral && s2) + { + return std::string_view(s1.value_) <=> std::string_view(s2.value_); + } + + // ----- literal_size ----- + + /** @brief compute number of chars needed to stringify an int **/ + template < int d, bool signbit = std::signbit(d) > + struct literal_size; + + template < int d > + struct literal_size { + /* d < 0 */ + static constexpr int size = 1 + literal_size<-d, false>::size; + }; + + template < int d > + struct literal_size { + /* d >= 0 */ + static constexpr int size = 1 + literal_size::size; + }; + + template <> struct literal_size<0, false> { static constexpr int size = 1; }; + template <> struct literal_size<1, false> { static constexpr int size = 1; }; + template <> struct literal_size<2, false> { static constexpr int size = 1; }; + template <> struct literal_size<3, false> { static constexpr int size = 1; }; + template <> struct literal_size<4, false> { static constexpr int size = 1; }; + template <> struct literal_size<5, false> { static constexpr int size = 1; }; + template <> struct literal_size<6, false> { static constexpr int size = 1; }; + template <> struct literal_size<7, false> { static constexpr int size = 1; }; + template <> struct literal_size<8, false> { static constexpr int size = 1; }; + template <> struct literal_size<9, false> { static constexpr int size = 1; }; + + template < int d > + constexpr int literal_size_v = literal_size::size; + + // ----- literal_from_digit ----- + + constexpr auto /*stringliteral<2>*/ stringliteral_from_digit( int d ) { + return stringliteral({ static_cast('0' + d), '\0' }); + } + +#ifdef NOT_YET_22mar24 + template < int d > + struct literal_from_digit; + + template <> struct literal_from_digit<0> { static constexpr auto value = stringliteral("0"); }; + template <> struct literal_from_digit<1> { static constexpr auto value = stringliteral("1"); }; + template <> struct literal_from_digit<2> { static constexpr auto value = stringliteral("2"); }; + template <> struct literal_from_digit<3> { static constexpr auto value = stringliteral("3"); }; + template <> struct literal_from_digit<4> { static constexpr auto value = stringliteral("4"); }; + template <> struct literal_from_digit<5> { static constexpr auto value = stringliteral("5"); }; + template <> struct literal_from_digit<6> { static constexpr auto value = stringliteral("6"); }; + template <> struct literal_from_digit<7> { static constexpr auto value = stringliteral("7"); }; + template <> struct literal_from_digit<8> { static constexpr auto value = stringliteral("8"); }; + template <> struct literal_from_digit<9> { static constexpr auto value = stringliteral("9"); }; + + template < int d > + constexpr auto literal_from_digit_v() { return literal_from_digit::value; } +#endif + + // ----- stringliteral_from_int ----- + + template < int D, int N = literal_size_v, bool signbit = std::signbit(D) > + struct stringliteral_from_int; + + template < int D, int N = literal_size_v, bool signbit = std::signbit(D) > + constexpr auto stringliteral_from_int_v() { return stringliteral_from_int::value; } + + template < int D > + struct stringliteral_from_int< D, 1, false > { + static constexpr auto value = stringliteral_from_digit(D); + }; + + template < int D, int N > + struct stringliteral_from_int< D, N, false > { + static constexpr auto _prefix = stringliteral_from_int_v< D / 10, N - 1, false >(); + static constexpr auto _suffix = stringliteral_from_digit(D % 10); + + static constexpr auto value = stringliteral_concat(_prefix.value_, _suffix.value_); + }; + + template < int D, int N > + struct stringliteral_from_int< D, N, true > { + static constexpr auto _suffix = stringliteral_from_int_v< -D, N - 1, false>(); + + static constexpr auto value = stringliteral_concat("-", _suffix.value_); + }; + + } /*namespace obs*/ +} /*namespace xo*/ + +/* end stringliteral.hpp */ diff --git a/include/xo/unit/unit.hpp b/include/xo/unit/unit.hpp new file mode 100644 index 00000000..d081077e --- /dev/null +++ b/include/xo/unit/unit.hpp @@ -0,0 +1,394 @@ +/* @file dimension.hpp */ + +#pragma once + +#include "dimension_impl.hpp" + +namespace xo { + namespace obs { + // ----- wrap_unit ----- + + template + struct wrap_unit { + static_assert(ratio_concept); + static_assert(bpu_list_concept); + + //using _norm_type = bpu_normalize; + + using scalefactor_type = Scalefactor; + using dim_type = BpuList; + + /* canon_type just orders dimensions by increasing native_dim_id */ + using canon_type = canonical_impl::dim_type; + + static_assert(bpu_list_concept); + }; + + // ----- normalize_unit ----- + + template + struct normalize_unit { + static_assert(unit_concept); + + using type = wrap_unit, typename Unit::dim_type>; + }; + + template + using normalize_unit_t = normalize_unit::type; + + // ----- dimensionless_v ----- + + template + constexpr auto dimensionless_v = std::same_as; + + // ----- unit_find_bpu ----- + + /** @brief find basis-power-unit matching native_dim_id + * + * Constructs dimensionless native_bpu if no match + **/ + template + struct unit_find_bpu { + using type = di_find_bpu::type; + }; + + template + using unit_find_bpu_t = unit_find_bpu::type; + + // ----- unit_abbrev_v ----- + + /** @brief canonical stringliteral abbreviation for dimension D. **/ + template + constexpr auto unit_abbrev_v = di_assemble_abbrev::value; + + // ----- unit_invert ----- + + template + struct unit_invert { + static_assert(unit_concept); + + using _sf = std::ratio_divide, typename U::scalefactor_type>; + using _di = di_invert< typename U::dim_type >::type; + + using type = wrap_unit< _sf, _di >; + + static_assert(unit_concept); + }; + + template + using unit_invert_t = unit_invert::type; + + // ----- unit_cartesian_product ----- + + template + struct unit_cartesian_product { + /* when a basis dimension (represented by a bpu<..>::c_native_dim) + * is present in both {U1, U2}, we need to pick a common unit + * (represented by bpu<..>::scalefactor_type). + * + * scale factors from such conversions are collected in: + * 1a. _mult_type::outer_scalefactor_type (compile-time exact representation using std::ratio) + * 1b. _mult_type::outer_scalefactor_inexact (compile-time constexpr) + */ + + static_assert(unit_concept); + static_assert(unit_concept); + + /* _mult_type -> describes product dimension */ + using _mult_type = di_cartesian_product< + typename U1::dim_type, + typename U2::dim_type>; + + /* compile-time exact scalefactor for product dimension + * (distilled from any forced rescaling) + */ + using _mult_sf_type = _mult_type::outer_scalefactor_type; + /* bpulist specifying basis factors (possibly to rational powers) in product dimension */ + using _mult_di_type = _mult_type::bpu_list_type; + + /* note: inexact scalefactor doesn't come up here. + * It's not present in unit types, only appears as byproduct + * of products/ratios of units + */ + using _sf1_type = typename std::ratio_multiply< + typename U1::scalefactor_type, + typename U2::scalefactor_type>::type; + + using _sf_type = typename std::ratio_multiply<_mult_sf_type, _sf1_type>::type; + + /* note: we can compute inexact scale factor, + * but can't make it a template argument + */ + using exact_unit_type = wrap_unit< _sf_type, _mult_di_type >; + + static constexpr double c_scalefactor_inexact = _mult_type::c_outer_scalefactor_inexact; + + static_assert(unit_concept); + }; + + /* WARNING: omits inexact scalefactor */ + template + using unit_cartesian_product_t = unit_cartesian_product::exact_unit_type; + + // ----- unit_divide ----- + + template + struct unit_divide { + static_assert(unit_concept); + static_assert(unit_concept); + + using _mult_type = unit_cartesian_product>; + using exact_unit_type = _mult_type::exact_unit_type; + + static constexpr double c_scalefactor_inexact = _mult_type::c_scalefactor_inexact; + }; + + /* WARNING: omits inexact scalefactor */ + template + using unit_divide_t = unit_divide::exact_unit_type; + + // ----- same_dimension ----- + + /* true iff U1 and U2 represent the same dimension, i.e. differ only in dimensionless scaling factor + * + * To verify scale also, use same_unit instead + */ + template + struct same_dimension { + static_assert(unit_concept); + static_assert(unit_concept); + + using _unit_ratio_type = typename unit_cartesian_product>::exact_unit_type; + + static_assert(std::same_as); + + static constexpr bool value = std::same_as; + }; + + template + constexpr bool same_dimension_v = same_dimension::value; + + // ----- same_unit ----- + + template + struct same_unit { + static_assert(unit_concept); + static_assert(unit_concept); + + using _unit_ratio_type = unit_cartesian_product>; + using _unit_exact_type = typename _unit_ratio_type::exact_unit_type; + using _unit_scalefactor_type = _unit_exact_type::scalefactor_type; + static constexpr double c_unit_ratio_inexact = _unit_ratio_type::c_scalefactor_inexact; + + static_assert(std::same_as<_unit_scalefactor_type, std::ratio<1>>); + static_assert(std::same_as); + + static constexpr bool value = (std::same_as<_unit_scalefactor_type, std::ratio<1>> + && (c_unit_ratio_inexact == 1.0) + && std::same_as); + }; + + template + constexpr bool same_unit_v = same_unit::value; + + // ----- unit_conversion_factor ----- + + template + struct unit_conversion_factor { + static_assert(same_dimension_v); + + using _unit_ratio_type = typename unit_cartesian_product>::exact_unit_type; + using type = _unit_ratio_type::scalefactor_type; + static constexpr double c_scalefactor_inexact = _unit_ratio_type::c_scalefactor_inexact; + }; + + /** conversion factor from U1 to U2: + * U1 = x.U2 + * with: + * x = R::num / R::den + * R = unit_conversion_factor_t + * + * WARNING: omits inexact scalefactor unit_conversion_factor::c_scalefactor_inexact + **/ + template + using unit_conversion_factor_t = unit_conversion_factor::type; + + // ----- units ----- + + namespace units { + /* computing abbreviations: + * - unit_abbrev_v :: stringliteral<...> + * - unit_abbrev_v.c_str() :: const char * + * + * relies on + * - di_assemble_abbrev, di_assemble_abbrev_helper [dimension_impl.hpp] + * + * - bpu_assemble_abbrev() [native_bpu.hpp] + * - bpu_assemble_abbrev_helper< native_bpu::c_native_dim, + * native_bpu::scalefactor_type, + * native_bpu::power_type > + * -> stringliteral + * + * + can specialize for specific combinations + * + * - native_unit_abbrev_helper< native_bpu::c_native_dim, + * native_bpu::power_type > + */ + + // ----- weight ----- + + using milligram = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + + template <> + struct scaled_native_unit_abbrev> { + static constexpr auto value = stringliteral("mg"); + }; + + using gram = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + using kilogram = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + + template <> + struct scaled_native_unit_abbrev> { + static constexpr auto value = stringliteral("kg"); + }; + + // ----- distance ----- + + using millimeter = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + template <> + struct scaled_native_unit_abbrev> { + static constexpr auto value = stringliteral("mm"); + }; + + using meter = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + + using kilometer = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + template <> + struct scaled_native_unit_abbrev> { + static constexpr auto value = stringliteral("km"); + }; + + // ----- time ----- + + using nanosecond = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + + template <> + struct scaled_native_unit_abbrev> { + static constexpr auto value = stringliteral("ns"); + }; + + using microsecond = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + + template <> + struct scaled_native_unit_abbrev> { + static constexpr auto value = stringliteral("us"); + }; + + using millisecond = wrap_unit< std::ratio<1>, bpu_node< bpu> > >; + + template <> + struct scaled_native_unit_abbrev> { + static constexpr auto value = stringliteral("ms"); + }; + + using second = wrap_unit< std::ratio<1>, bpu_node< bpu> > >; + using minute = wrap_unit< std::ratio<1>, bpu_node< bpu> > >; + template <> + struct scaled_native_unit_abbrev> { + static constexpr auto value = stringliteral("min"); + }; + + using hour = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + + template <> + struct scaled_native_unit_abbrev> { + static constexpr auto value = stringliteral("hr"); + }; + + using day = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + + template <> + struct scaled_native_unit_abbrev> { + static constexpr auto value = stringliteral("dy"); + }; + + using month = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + + template <> + struct scaled_native_unit_abbrev> { + static constexpr auto value = stringliteral("mo"); + }; + + using yr250 = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + + template <> + struct scaled_native_unit_abbrev> { + static constexpr auto value = stringliteral("yr250"); + }; + + // ------ volatility ------ + + /* volatility in units of 1/sqrt(1day) + * volatility^2 in units of 1/day + */ + using volatility_1d = wrap_unit< std::ratio<1>, + bpu_node< bpu, + std::ratio<-1,2>> > >; + + /* volatility in units of 1/sqrt(30day) + * volatility^2 in units of 1/(30day) + */ + using volatility_30d = wrap_unit< std::ratio<1>, + bpu_node< bpu, + std::ratio<-1,2>> > >; + + /* volatility in units of 1/sqrt(250day) + * volatility^2 in units of 1/(250day) + */ + using volatility_250d = wrap_unit< std::ratio<1>, + bpu_node< bpu, + std::ratio<-1,2>> > >; + + using currency = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + using price = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + } + + } /*namespace obs*/ +} /*namespace xo*/ + + +/* end dimension.hpp */ diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..97d1afa1 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,25 @@ +# build unittest observable/utest + +set(SELF_EXECUTABLE_NAME utest.unit) +set(SELF_SOURCE_FILES + unit_utest_main.cpp + unit.test.cpp quantity.test.cpp) + +add_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) +xo_include_options2(${SELF_EXECUTABLE_NAME}) + +add_test(NAME ${SELF_EXECUTABLE_NAME} COMMAND ${SELF_EXECUTABLE_NAME}) +target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) + +# ---------------------------------------------------------------- +# internal dependencies: logutil, ... + +xo_self_dependency(${SELF_EXECUTABLE_NAME} xo_unit) +xo_dependency(${SELF_EXECUTABLE_NAME} reflect) + +# ---------------------------------------------------------------- +# 3rd party dependency: catch2: + +xo_external_target_dependency(${SELF_EXECUTABLE_NAME} Catch2 Catch2::Catch2) + +# end CMakeLists.txt diff --git a/utest/quantity.test.cpp b/utest/quantity.test.cpp new file mode 100644 index 00000000..8274012c --- /dev/null +++ b/utest/quantity.test.cpp @@ -0,0 +1,653 @@ +/* @file quantity.test.cpp */ + +#include "xo/unit/quantity.hpp" +#include "xo/reflect/Reflect.hpp" +//#include +//#include +#include +#include +#include + +namespace xo { + using xo::obs::quantity; + + using xo::obs::qty::milliseconds; + using xo::obs::qty::seconds; + using xo::obs::qty::minutes; + using xo::obs::qty::volatility30d; + using xo::obs::qty::volatility250d; + + using xo::obs::unit_find_bpu_t; + using xo::obs::unit_conversion_factor_t; + using xo::obs::unit_cartesian_product_t; + using xo::obs::unit_cartesian_product; + using xo::obs::unit_invert_t; + using xo::obs::unit_abbrev_v; + using xo::obs::same_dimension_v; + using xo::obs::dim; + + using xo::obs::from_ratio; + using xo::obs::stringliteral_from_ratio; + using xo::obs::ratio2str_aux; + using xo::obs::cstr_from_ratio; + + using xo::reflect::Reflect; + + namespace units = xo::obs::units; + + namespace ut { + /* use 'testcase' snippet to add test cases here */ + TEST_CASE("quantity", "[quantity]") { + //constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + //scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.quantity"), xtag("foo", foo), ...); + //log && log("(A)", xtag("foo", foo)); + + quantity t = seconds(1L); + + REQUIRE(t.scale() == 1); + + static_assert(t.c_basis_power == 1); + static_assert(t.c_basis_power == 0); + } /*TEST_CASE(quantity)*/ + + TEST_CASE("add1", "[quantity]") { + constexpr bool c_debug_flag = false; + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.add1")); + + quantity t1 = seconds(1); + quantity t2 = seconds(2); + + static_assert(std::same_as); + static_assert(std::same_as); + + auto sum = t1 + t2; + + CHECK(strcmp(sum.unit_cstr(), "s") == 0); + + static_assert(std::same_as); + static_assert(t1.c_basis_power == 1); + static_assert(t2.c_basis_power == 1); + + REQUIRE(sum.scale() == 3); + + } /*TEST_CASE(add1)*/ + + TEST_CASE("add2", "[quantity]") { + constexpr bool c_debug_flag = false; + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.add2")); + + quantity t1 = seconds(1); + { + CHECK(strcmp(t1.unit_cstr(), "s") == 0); + CHECK(t1.scale() == 1); + } + + auto m2 = minutes(2); + + { + static_assert(m2.c_basis_power == 1); + + log && log(xtag("m2.scale", m2.scale()), xtag("m2.unit", m2.unit_cstr())); + + CHECK(m2.scale() == 2); + CHECK(strcmp(m2.unit_cstr(), "min") == 0); + } + + { + auto m2_sec = m2.in_units_of(); + + static_assert(std::same_as); + + log && log(XTAG(m2_sec)); + + CHECK(m2_sec == 120); + } + + quantity t2 = m2; + { + auto sum = t1 + t2; + + static_assert(std::same_as); + static_assert(sum.c_basis_power == 1); + + log && log(xtag("t1.unit", t1.unit_cstr()), xtag("t2.unit", t2.unit_cstr())); + log && log(xtag("sum.unit", sum.unit_cstr())); + + CHECK(strcmp(t2.unit_cstr(), "s") == 0); + CHECK(strcmp(sum.unit_cstr(), "s") == 0); + CHECK(sum.scale() == 121); + } + } /*TEST_CASE(add2)*/ + + TEST_CASE("add3", "[quantity]") { + quantity t1 = seconds(1); + quantity t2 = minutes(2); + + /* sum will take unit from lhs argument to + */ + auto sum = t1 + t2; + + static_assert(sum.c_basis_power == 1); + static_assert(std::same_as); + + REQUIRE(sum.scale() == 121); + } /*TEST_CASE(add3)*/ + + TEST_CASE("add4", "[quantity]") { + constexpr bool c_debug_flag = false; + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.add4")); + + using u_kgps_result = unit_cartesian_product>; + using u_kgps = u_kgps_result::exact_unit_type; + using u_gpm_result = unit_cartesian_product>; + using u_gpm = u_gpm_result::exact_unit_type; + { + static_assert(u_kgps_result::c_scalefactor_inexact == 1.0); + + static_assert(std::same_as::power_type, std::ratio<1>>); + static_assert(std::same_as::power_type, std::ratio<-1>>); + static_assert(std::same_as::power_type, std::ratio<1>>); + static_assert(std::same_as::power_type, std::ratio<-1>>); + + log && log(xtag("u_kgps", unit_abbrev_v.c_str())); + log && log(xtag("u_gpm", unit_abbrev_v.c_str())); + + CHECK(strcmp(unit_abbrev_v.c_str(), "kg.s^-1") == 0); + CHECK(strcmp(unit_abbrev_v.c_str(), "g.min^-1") == 0); + + static_assert(same_dimension_v); + } + + using convert_type = unit_conversion_factor_t; + { + log && log(xtag("u_kgps->u_gpm", cstr_from_ratio())); + + CHECK(strcmp(cstr_from_ratio(), "60000") == 0); + CHECK(from_ratio() == 60000); + } + + /* note: in practice probably write + * kilograms(0.1) / seconds(1); + * but + * 1. don't want to exercise quantity {*,/} here; + * 2. want to force unit representation + */ + auto q1 = quantity::promote(0.1); + auto q2 = quantity(); + { + q2 = q1; + + static_assert(q1.c_basis_power == 1); + static_assert(q1.c_basis_power == -1); + static_assert(q2.c_basis_power == 1); + static_assert(q2.c_basis_power == -1); + + log && log(XTAG(q1), XTAG(q2)); + + CHECK(strcmp(q1.unit_cstr(), "kg.s^-1") == 0); + CHECK(q1.scale() == 0.1); + + CHECK(strcmp(q2.unit_cstr(), "g.min^-1") == 0); + CHECK(q2.scale() == 6000.0); + } + } /*TEST_CASE(add4)*/ + + TEST_CASE("add5", "[quantity][fractional_dimension]") { + constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.add5")); + //log && log("(A)", xtag("foo", foo)); + + auto vol_250d = volatility250d(0.2); + { + log && log(xtag("vol_250d", vol_250d)); + + CHECK(strcmp(vol_250d.unit_cstr(), "yr250^-(1/2)") == 0); + CHECK(vol_250d.scale() == 0.2); + } + + /* scaling factor from 30-day vol to 250-day vol is sqrt(250/30) ~ 2.88675 + * so 0.1 -> 0.288675 + */ + auto vol_30d = volatility30d(0.1); + { + log && log(xtag("vol_30d", vol_30d)); + + CHECK(strcmp(vol_30d.unit_cstr(), "mo^-(1/2)") == 0); + CHECK(vol_30d.scale() == Approx(0.1).epsilon(1e-6)); + } + + /* conversion from monthly vol to (250-day) annual vol */ + + using u_vol250d = units::volatility_250d; + { + quantity q = vol_30d; + + log && log(xtag("q", q)); + + CHECK(strcmp(q.unit_cstr(), "yr250^-(1/2)") == 0); + CHECK(q.scale() == Approx(0.288675).epsilon(1e-6)); + + } + + { + auto sum = vol_250d + vol_30d; + + static_assert(sum.c_basis_power == -0.5); + + log && log(XTAG(sum)); + + CHECK(strcmp(sum.unit_cstr(), "yr250^-(1/2)") == 0); + /* 0.1mo^-(1/2) ~ 0.288675yr250^-(1/2) */ + CHECK(sum.scale() == Approx(0.4886751).epsilon(1e-6)); + } + } /*TEST_CASE(add5)*/ + + + TEST_CASE("mult1", "[quantity]") { + constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.mult1")); + //log && log("(A)", xtag("foo", foo)); + + auto q0 = milliseconds(5); + auto q1 = seconds(60); + auto q2 = minutes(1); + + { + auto r = q0 * q1; + + static_assert(r.c_basis_power == 2); + + log && log(xtag("q0", q0), xtag("q1", q1), xtag("q0*q1", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* taking unit from LHS */ + REQUIRE(strcmp(r.unit_cstr(), "ms^2") == 0); + REQUIRE(r.scale() == 300000); + } + + { + auto r = q1 * q2; + + static_assert(r.c_basis_power == 2); + + log && log(xtag("q1", q1), xtag("q2", q2), xtag("q1*q2", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* taking unit from LHS */ + REQUIRE(strcmp(r.unit_cstr(), "s^2") == 0); + REQUIRE(r.scale() == 3600); + } + + { + auto r = q2 * q1; + + static_assert(r.c_basis_power == 2); + + log && log(xtag("q1", q1), xtag("q2", q2), xtag("r=q2*q1", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* taking unit from LHS */ + CHECK(strcmp(r.unit_cstr(), "min^2") == 0); + CHECK(r.scale() == 1); + } + + { + auto r = q2 * 60; + + static_assert(r.c_basis_power == 1); + static_assert(std::same_as); + + log && log(xtag("q2*60", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* preserve units of existing quantity */ + CHECK(strcmp(r.unit_cstr(), "min") == 0); + CHECK(r.scale() == 60); + } + + { + auto r = q2 * 60U; + + static_assert(r.c_basis_power == 1); + static_assert(std::same_as); + + log && log(xtag("q2*60U", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* preserve units of existing quantity */ + CHECK(strcmp(r.unit_cstr(), "min") == 0); + CHECK(r.scale() == 60U); + } + + { + auto r = (q2 * 60.5); + + static_assert(r.c_basis_power == 1); + + /* verify dimension */ + static_assert(std::same_as); + + log && log(xtag("q2*60.5", q2*60.5)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* preserve units of existing quantity */ + REQUIRE(strcmp(r.unit_cstr(), "min") == 0); + REQUIRE(r.scale() == 60.5); + } + + { + log && log(xtag("q2*60.5f", q2*60.5f)); + + auto r = (q2 * 60.5f); + + /* verify dimension */ + static_assert(r.c_basis_power == 1); + static_assert(std::same_as); + + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* preserve units of existing quantity */ + REQUIRE(strcmp(r.unit_cstr(), "min") == 0); + REQUIRE(r.scale() == 60.5f); + } + + { + auto r = 60 * q2; + + static_assert(r.c_basis_power == 1); + static_assert(std::same_as); + + log && log(xtag("60*q2", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* preserve units of existing quantity */ + CHECK(strcmp(r.unit_cstr(), "min") == 0); + CHECK(r.scale() == 60); + } + + { + log && log(xtag("60.5*q2", 60.5*q2)); + + auto r = 60.5 * q2; + + static_assert(r.c_basis_power == 1); + + log && log(xtag("r.type", Reflect::require()->canonical_name())); + static_assert(std::same_as); + + /* preserve units of existing quantity */ + CHECK(strcmp(r.unit_cstr(), "min") == 0); + CHECK(r.scale() == 60.5); + } + + { + log && log(xtag("60.5f*q2", 60.5f*q2)); + + auto r = 60.5f * q2; + + static_assert(r.c_basis_power == 1); + static_assert(std::same_as); + + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* preserve units of existing quantity */ + CHECK(strcmp(r.unit_cstr(), "min") == 0); + CHECK(r.scale() == 60.5); + } + } /*TEST_CASE(mult1)*/ + + TEST_CASE("div1", "[quantity]") { + constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.div1")); + //log && log("(A)", xtag("foo", foo)); + + auto q0 = milliseconds(5); + auto q1 = milliseconds(10); + + { + /* repr_type adopts argument to milliseconds() */ + static_assert(std::same_as); + static_assert(std::same_as); + + auto r = q0/q1; + + log && log(xtag("q0", q0), xtag("q1", q1), xtag("q0/q1", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* verify dimensionless + no type promotion */ + static_assert(std::same_as); + /* verify scale (truncate)*/ + REQUIRE(r == 0); + } + + auto q0p = milliseconds(5.0); + + { + static_assert(std::same_as); + + auto r = q0p/q1; + static_assert(std::same_as); + + log && log(XTAG(q0p), xtag("q0p/q1", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* verify dimension */ + static_assert(std::same_as); + + /* verify scale */ + REQUIRE(r == 0.5); + } + + auto r1 = 1.0 / q0; + + { + log && log(XTAG(q0), xtag("r1=1.0/q0", r1)); + + /* verify dimension */ + static_assert(r1.c_basis_power == -1); + + /* verify scale */ + REQUIRE(r1.scale() == 0.2); + } + } /*TEST_CASE(div1)*/ + + TEST_CASE("div2", "[quantity]") { + constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.div2")); + + auto q0 = milliseconds(5); + auto q1 = milliseconds(20.0); + + { + auto r = q0/q1; + + log && log(xtag("q0", q0), xtag("q1", q1), xtag("q0/q1", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* verify dimension */ + static_assert(std::same_as); + + /* verify scale */ + REQUIRE(r == 0.25); + } + + { + auto r = q1/q0; + + log && log(xtag("q0", q0), xtag("q1", q1), xtag("q1/q0", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* verify dimension */ + static_assert(std::same_as); + + /* verify scale */ + REQUIRE(r == 4.0); + } + + { + auto r = q0/(q1*q1); + + log && log(xtag("q0", q0), xtag("q1", q1), xtag("q0/(q1*q1)", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* verify dimension */ + static_assert(r.c_basis_power == -1); + + /* verify scale */ + REQUIRE(r.scale() == 0.0125); + } + + { + auto r = (q0*q0)/q1; + + log && log(xtag("q0", q0), xtag("q1", q1), xtag("(q0*q0)/q1", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* verify dimension */ + static_assert(r.c_basis_power == 1); + + /* verify scale */ + REQUIRE(r.scale() == 1.25); + } + + } /*TEST_CASE(div2)*/ + + TEST_CASE("div3", "[quantity]") { + constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.div3")); + + auto q0 = milliseconds(5); + auto q1 = milliseconds(20.0); + + { + auto r = q0/q1; + + log && log(XTAG(q0), XTAG(q1), xtag("q0/q1", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* verify dimension */ + static_assert(std::same_as); + + /* verify scale */ + REQUIRE(r == 0.25); + } + + { + auto r = q1/q0; + + log && log(xtag("q0", q0), xtag("q1", q1), xtag("q1/q0", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* verify dimension */ + static_assert(std::same_as); + + /* verify scale */ + REQUIRE(r == 4.0); + } + + { + auto r = q0/(q1*q1); + + log && log(xtag("q0", q0), xtag("q1", q1), xtag("q0/(q1*q1)", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* verify dimension */ + static_assert(r.c_basis_power == -1); + + /* verify scale */ + REQUIRE(r.scale() == 0.0125); + } + + { + auto r = (q0*q0)/q1; + + log && log(xtag("q0", q0), xtag("q1", q1), xtag("(q0*q0)/q1", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* verify dimension */ + static_assert(r.c_basis_power == 1); + + /* verify scale */ + REQUIRE(r.scale() == 1.25); + } + + } /*TEST_CASE(div3)*/ + + TEST_CASE("div4", "[quantity]") { + constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.mult2")); + //log && log("(A)", xtag("foo", foo)); + + auto q1 = volatility250d(0.2); + auto q2 = volatility30d(0.1); + + auto r = q1/q2; + + /* 0.1/sqrt(30dy) ~ 0.288675/sqrt(250dy), + * so q1/q2 ~ 0.6928 + */ + + log && log(XTAG(q1), XTAG(q2), XTAG(q1/q2)); + + /* verify dimensionless result */ + static_assert(std::same_as); + + /* verify scale of result */ + CHECK(r == Approx(0.692820323).epsilon(1e-6)); + + } /*TEST_CASE(div4)*/ + } /*namespace ut*/ +} /*namespace xo*/ + +/* end quantity.test.cpp */ diff --git a/utest/unit.test.cpp b/utest/unit.test.cpp new file mode 100644 index 00000000..bccc5d47 --- /dev/null +++ b/utest/unit.test.cpp @@ -0,0 +1,346 @@ +/* @file dimension.test.cpp */ + +#include "xo/unit/unit.hpp" +#include "xo/reflect/Reflect.hpp" +#include "xo/cxxutil/demangle.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/tag.hpp" +#include +#include + +namespace xo { + namespace ut { + /* compile-time tests */ + + using xo::reflect::Reflect; + + using xo::obs::dim; + using xo::obs::native_unit_abbrev_v; + using xo::obs::units::scaled_native_unit_abbrev_v; + //using xo::obs::native_dim_abbrev; + using xo::obs::stringliteral_compare; + using xo::obs::literal_size_v; + using xo::obs::stringliteral_from_digit; + using xo::obs::stringliteral_from_int_v; + using xo::obs::stringliteral; +#ifndef __clang__ + using xo::obs::stringliteral_concat; + using xo::obs::stringliteral_from_ratio; + using xo::obs::bpu_assemble_abbrev_helper; + using xo::obs::bpu_assemble_abbrev; +#endif + using xo::obs::bpu_node; + using xo::obs::wrap_unit; + using xo::obs::unit_abbrev_v; + //using xo::obs::dim_abbrev_v; + using xo::obs::di_cartesian_product; + using xo::obs::di_cartesian_product1; + using xo::obs::unit_cartesian_product_t; + using xo::obs::bpu_cartesian_product; + using xo::obs::bpu_cartesian_product_helper; + using xo::obs::unit_invert_t; + using xo::obs::units::gram; + using xo::obs::units::second; + using xo::print::ccs; + + template + int unused() + { + return 1; + } + + template + constexpr bool unused_same(typename std::enable_if_t::value, bool> result = true) + { + return result; + } + + TEST_CASE("native_unit_abbrev", "[native_dim_abbrev]") { + constexpr bool c_debug_flag = true; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.native_dim_abbrev")); + //log && log("(A)", xtag("foo", foo)); + + /* NOTE: the .value_ expression below will fail to compile if missing specialization for + * native_dim_abbrev on native_dim_id::foo; that's the point :) + */ + + REQUIRE(strcmp(scaled_native_unit_abbrev_v>.value_, "g") == 0); + REQUIRE(strcmp(scaled_native_unit_abbrev_v>.value_, "s") == 0); + REQUIRE(strcmp(scaled_native_unit_abbrev_v>.value_, "ccy") == 0); + REQUIRE(strcmp(scaled_native_unit_abbrev_v>.value_, "px") == 0); + +#ifdef OBSOLETE + REQUIRE(strcmp(native_dim_abbrev().value_, "") != 0); + REQUIRE(strcmp(native_dim_abbrev().value_, "") != 0); + REQUIRE(strcmp(native_dim_abbrev().value_, "") != 0); + REQUIRE(strcmp(native_dim_abbrev().value_, "") != 0); +#endif + + static_assert(stringliteral_compare(stringliteral_from_digit(0), stringliteral("0")) == 0); + static_assert(stringliteral_compare(stringliteral_from_digit(1), stringliteral("1")) == 0); + static_assert(stringliteral_compare(stringliteral_from_digit(9), stringliteral("9")) == 0); + + static_assert(literal_size_v<0> == 1); + static_assert(literal_size_v<10> == 2); + static_assert(literal_size_v<99> == 2); + static_assert(literal_size_v<100> == 3); + static_assert(literal_size_v<999> == 3); + + static_assert(stringliteral_compare(stringliteral_from_int_v<0>(), stringliteral("0")) == 0); + static_assert(stringliteral_compare(stringliteral_from_int_v<9>(), stringliteral("9")) == 0); + + static_assert(stringliteral_compare(stringliteral_from_int_v<1, 1, false>(), stringliteral("1")) == 0); + + + static_assert(stringliteral_compare(stringliteral_from_int_v<9, 1, false>(), stringliteral("9")) == 0); + static_assert(stringliteral_compare(stringliteral_from_int_v<9>(), stringliteral("9")) == 0); + + /* NOTE: clang16 complains starting here; gcc is fine */ + +#ifndef __clang__ + if constexpr (stringliteral_concat("a", "b").size() == 3) { + REQUIRE(true); + } else { + REQUIRE(false); + } + + static_assert(stringliteral_compare(stringliteral_concat("hello", " ", "world"), + stringliteral("hello world")) == 0); + + static_assert(stringliteral_compare(stringliteral_from_int_v<10, 2, false>(), stringliteral("10")) == 0); + static_assert(stringliteral_compare(stringliteral_from_int_v<10>(), stringliteral("10")) == 0); + + static_assert(stringliteral_compare(stringliteral_from_int_v<99, 2, false>(), stringliteral("99")) == 0); + static_assert(stringliteral_compare(stringliteral_from_int_v<99>(), stringliteral("99")) == 0); + + static_assert(stringliteral_compare(stringliteral_from_int_v<100, 3, false>(), stringliteral("100")) == 0); + static_assert(stringliteral_compare(stringliteral_from_int_v<100>(), stringliteral("100")) == 0); + + static_assert(stringliteral_compare(stringliteral_from_int_v<999, 3, false>(), stringliteral("999")) == 0); + static_assert(stringliteral_compare(stringliteral_from_int_v<999>(), stringliteral("999")) == 0); + + //std::cerr << "test=" << stringliteral_from_int_v<-1, 2, true>().value_ << std::endl; + + static_assert(stringliteral_compare(stringliteral_from_int_v<-1, 2, true>(), stringliteral("-1")) == 0); + static_assert(stringliteral_compare(stringliteral_from_int_v<-1>(), stringliteral("-1")) == 0); + + static_assert(stringliteral_compare(stringliteral_from_int_v<-9, 2, true>(), stringliteral("-9")) == 0); + static_assert(stringliteral_compare(stringliteral_from_int_v<-9>(), stringliteral("-9")) == 0); + + static_assert(stringliteral_compare(stringliteral_from_int_v<-10, 3, true>(), stringliteral("-10")) == 0); + static_assert(stringliteral_compare(stringliteral_from_int_v<-10>(), stringliteral("-10")) == 0); + + static_assert(stringliteral_compare(stringliteral_from_int_v<-99, 3, true>(), stringliteral("-99")) == 0); + static_assert(stringliteral_compare(stringliteral_from_int_v<-99>(), stringliteral("-99")) == 0); + + static_assert(stringliteral_compare(stringliteral_from_int_v<-100, 4, true>(), stringliteral("-100")) == 0); + static_assert(stringliteral_compare(stringliteral_from_int_v<-100>(), stringliteral("-100")) == 0); + + static_assert(stringliteral_compare(stringliteral_from_int_v<-999, 4, true>(), stringliteral("-999")) == 0); + static_assert(stringliteral_compare(stringliteral_from_int_v<-999>(), stringliteral("-999")) == 0); + + static_assert(stringliteral_compare(stringliteral_from_ratio>(), stringliteral("(2/3)")) == 0); + static_assert(stringliteral_compare(stringliteral_from_ratio>(), stringliteral("(2/3)")) == 0); + + static_assert(stringliteral_compare(stringliteral_from_ratio>(), stringliteral("-1")) == 0); + static_assert(stringliteral_compare(stringliteral_from_ratio>(), stringliteral("-2")) == 0); + static_assert(stringliteral_compare(stringliteral_from_ratio>(), stringliteral("-2")) == 0); + static_assert(stringliteral_compare(stringliteral_from_ratio>(), stringliteral("-(3/2)")) == 0); + static_assert(stringliteral_compare(stringliteral_from_ratio>(), stringliteral("-(3/2)")) == 0); + static_assert(stringliteral_compare(stringliteral_from_ratio>(), stringliteral("-(1/2)")) == 0); + static_assert(stringliteral_compare(stringliteral_from_ratio>(), stringliteral("(1/2)")) == 0); + static_assert(stringliteral_compare(stringliteral_from_ratio>(), stringliteral("(3/2)")) == 0); + + //log && log(xtag("ratio<2>", stringliteral_from_ratio>().c_str())); + static_assert(stringliteral_compare(stringliteral_from_ratio>(), stringliteral("2")) == 0); + + static_assert(stringliteral_compare(bpu_assemble_abbrev_helper, std::ratio<1>>(), stringliteral("g")) == 0); + //log && log(xtag("s^(-1/2)", bpu_assemble_abbrev_helper, std::ratio<-1,2>>().c_str())); + static_assert(stringliteral_compare(bpu_assemble_abbrev_helper, std::ratio<-1,2>>(), stringliteral("s^-(1/2)")) == 0); + //stringliteral_compare(stringliteral_from_ratio>(), stringliteral("^2")) == 0); +#endif + + //static_assert(stringliteral_compare(stringliteral_from_int_v<10>(), obs::stringliteral("10")) == 0); + + //REQUIRE(strcmp(obs::stringliteral_from_digit(1).value_, "1") == 0); + //REQUIRE(strcmp(obs::ratio2str>().value_, "") == 0); + + } /*TEST_CASE(native_dim_abbrev)*/ + + TEST_CASE("dimension", "[dimension]") { + constexpr bool c_debug_flag = true; + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.dimension")); + //log && log("(A)", xtag("foo", foo)); + + using t1 = obs::bpu>; + + static_assert(t1::c_native_dim == obs::dim::currency); + static_assert(t1::power_type::num == 1); + static_assert(t1::power_type::den == 1); + + using t2 = obs::bpu, std::ratio<-1,2>>; + + static_assert(t2::c_native_dim == obs::dim::time); + static_assert(t2::power_type::num == -1); + static_assert(t2::power_type::den == 2); + + using dim1 = wrap_unit, bpu_node>; + using d1 = dim1::dim_type; /* ccy */ + REQUIRE(unused_same()); + REQUIRE(unused_same::power_unit_type, t1>()); +#ifdef NOT_USING + static_assert(obs::lo_basis_elt_of::c_lo_basis == t1::c_basis); +#endif + + static_assert(obs::native_lo_bwp_of::bwp_type::c_index == 0); + static_assert(obs::native_lo_bwp_of::bwp_type::c_basis == obs::dim::currency); + + + using dim2 = wrap_unit, bpu_node>; + using d2 = dim2::dim_type; /* t^(-1/2) */ + REQUIRE(unused_same()); + REQUIRE(unused_same::power_unit_type, t2>()); + static_assert(obs::native_lo_bwp_of::bwp_type::c_index == 0); + static_assert(obs::native_lo_bwp_of::bwp_type::c_basis == obs::dim::time); + + using dim3 = wrap_unit, bpu_node>>; + using d3 = dim3::dim_type; /* ccy.t^(-1/2) */ + REQUIRE(unused_same::power_unit_type, t1>()); + + { + using type = obs::lookup_bpu::power_unit_type; + //std::cerr << "obs::power_unit_of::power_unit_type" << xtag("type", reflect::type_name()) << std::endl; + + REQUIRE(unused_same()); + } + +#ifdef NOT_USING + static_assert(obs::lo_basis_elt_of::c_lo_basis == t2::c_basis); +#endif + + /* lowest is in pos 1, beacuse t2=time before t1=currency */ + static_assert(obs::native_lo_bwp_of::bwp_type::c_index == 1); + + static_assert(unused_same::dim_type, d2>()); + //using type = obs::without_elt::dim_type; + //std::cerr << "obs::without_elt::dim_type" << xtag("type", reflect::type_name()) << std::endl; + static_assert(unused_same::dim_type, d1>()); + + + using d3b = wrap_unit, + bpu_node>>::dim_type; /* t^(-1/2).ccy */ + //using d3b = obs::dimension_impl>; /* t^(-1/2).ccy */ + REQUIRE(unused_same::power_unit_type, t2>()); + REQUIRE(unused_same::power_unit_type, t1>()); + + /* lowest is in pos 0 */ + static_assert(obs::native_lo_bwp_of::bwp_type::c_index == 0); + + static_assert(unused_same::dim_type, d1>()); + static_assert(unused_same::dim_type, d2>()); + + static_assert(unused_same, obs::canonical_t>()); + + log && log(xtag("d1.abbrev", unit_abbrev_v.c_str())); + log && log(xtag("d2.abbrev", unit_abbrev_v.c_str())); + log && log(xtag("d3.abbrev", unit_abbrev_v.c_str())); + } + + TEST_CASE("dimension2", "[dimension2]") { + constexpr bool c_debug_flag = true; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.dimension2")); + //log && log("(A)", xtag("foo", foo)); + + using di = di_cartesian_product; + + log && log(xtag("di", Reflect::require()->canonical_name())); + log && log(xtag("di::outer_scalefactor_type", Reflect::require()->canonical_name())); + log && log(xtag("di::bpu_list_type", Reflect::require()->canonical_name())); + + using u1 = unit_cartesian_product_t; + + log && log(xtag("u1", Reflect::require()->canonical_name())); + + log && log(xtag("u1", ccs(unit_abbrev_v.value_))); + } /*TEST_CASE(dimension2)*/ + + TEST_CASE("dimension3", "[dimension3]") { + constexpr bool c_debug_flag = true; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.dimension3")); + //log && log("(A)", xtag("foo", foo)); + + using u1 = unit_invert_t; + + log && log(xtag("second^-1", Reflect::require()->canonical_name())); + log && log(xtag("u1", unit_abbrev_v.c_str())); + + REQUIRE(strcmp(unit_abbrev_v.c_str(), "s^-1") == 0); + + using u2 = second; + + log && log(xtag("second", Reflect::require()->canonical_name())); + log && log(xtag("u2", unit_abbrev_v.c_str())); + + using u1u2 = unit_cartesian_product_t; + + log && log(xtag("u1u2", Reflect::require()->canonical_name())); + +#ifdef NOT_USING + using di1 = d1::dim_type; + using di2 = d2::dim_type; + using di1di2 = di_cartesian_product::type; + + log && log(xtag("di1di2", Reflect::require()->canonical_name())); +#endif + + using f1 = u1::dim_type::front_type; + using r1 = u1::dim_type::rest_type; + using tmp = di_cartesian_product1; + + log && log(xtag("f1", Reflect::require()->canonical_name())); + log && log(xtag("r1", Reflect::require()->canonical_name())); + log && log(xtag("(f1.r1).outer_scalefactor_type", Reflect::require()->canonical_name())); + log && log(xtag("(f1.r1).bpu_list_type", Reflect::require()->canonical_name())); + + using tmp2 = bpu_cartesian_product; + + log && log(xtag("(f1.u2).outer_scalefactor_type", Reflect::require()->canonical_name())); + log && log(xtag("(f1.u2).bpu_list_type", Reflect::require()->canonical_name())); + + using f2 = u2::dim_type::front_type; + log && log(xtag("f2", Reflect::require()->canonical_name())); + + using tmp3 = bpu_cartesian_product_helper; + log && log(xtag("(f1.f2).outer_scalefactor_type", Reflect::require()->canonical_name())); + log && log(xtag("(f1.f2).bpu_list_type", Reflect::require()->canonical_name())); + } /*TEST_CASE(dimension3)*/ + + + } /*namespace ut*/ + +} /*namespace xo*/ + + +/* end dimension.test.cpp */ diff --git a/utest/unit_utest_main.cpp b/utest/unit_utest_main.cpp new file mode 100644 index 00000000..81f2ade0 --- /dev/null +++ b/utest/unit_utest_main.cpp @@ -0,0 +1,6 @@ +/* file unit_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end unit_utest_main.cpp */ From b480e4a0e657bf22a7af219414d778640e243c96 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Apr 2024 19:43:30 -0400 Subject: [PATCH 0684/2693] doc: + README.md --- README.md | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d0133c7d..68174012 100644 --- a/README.md +++ b/README.md @@ -1 +1,38 @@ -# xo-unit +# unit library + +Provides compile-time dimension checking and scaling. + +Similar to `boost::units`, but: +1. streamlined: assumes modern (c++20) support +2. supports fractional dimensions (rational powers) + +## Getting Started + +### build + install dependencies + +- [github/Rconybea/reflect](https://github.com/Rconybea/reflect) + +### build + install +``` +$ cd xo-unit +$ mkdir .build +$ cd .build +$ PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} .. +$ make +$ make install +``` + +### build for unit test coverage +``` +$ cd xo-unit +$ mkdir .build-ccov +$ cd .build-ccov +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + +### LSP support +``` +$ cd xo-unit +$ ln -s .build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree +``` From b8f2360b899739b72272036324a6997aa9169c8f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Apr 2024 19:47:48 -0400 Subject: [PATCH 0685/2693] .gitignore: + compile_commands.json --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 24e5b0a1..7b6765fd 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .build +compile_commands.json From 83810d6c0048424a527e93484bf328d5deb0ce89 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Apr 2024 19:49:51 -0400 Subject: [PATCH 0686/2693] build: comments in CMakeLists.txt --- src/kalmanfilter/CMakeLists.txt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/kalmanfilter/CMakeLists.txt b/src/kalmanfilter/CMakeLists.txt index b7036526..2dbdcff0 100644 --- a/src/kalmanfilter/CMakeLists.txt +++ b/src/kalmanfilter/CMakeLists.txt @@ -21,11 +21,10 @@ set(SELF_SRCS xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- -# internal dependencies - +# Dependencies +# +# REMINDER: these must coodinate with find_dependency() calls in +# [xo-kalmanfilter/cmake/xo_kalmanfilterConfig.cmake.in] +# xo_dependency(${SELF_LIB} reactor) - -# ---------------------------------------------------------------- -# external dependencies - xo_external_target_dependency(${SELF_LIB} Eigen3 Eigen3::Eigen) From 35c4fbdca8c86a9b262bed9298af5596b2786a4d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Apr 2024 19:55:20 -0400 Subject: [PATCH 0687/2693] build: suppress cmake messages in standalone build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From ad8c028e73cad88dcec03c5b4eef74c89e40b015 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Apr 2024 20:11:11 -0400 Subject: [PATCH 0688/2693] + .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..13c0afb7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json From d6342f7269274bfeaa71bd81ad741cdac906608e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Apr 2024 20:11:20 -0400 Subject: [PATCH 0689/2693] github: + ubuntu base CI workflow --- .github/workflows/ubuntu-main.yml | 239 ++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 .github/workflows/ubuntu-main.yml diff --git a/.github/workflows/ubuntu-main.yml b/.github/workflows/ubuntu-main.yml new file mode 100644 index 00000000..0acda62e --- /dev/null +++ b/.github/workflows/ubuntu-main.yml @@ -0,0 +1,239 @@ +name: build xo-unit + dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install dependencies + run: | + echo "::group::install catch2" + # install catch2. see + # [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + sudo apt-get install -y catch2 + echo "::endgroup" + + #echo "::group::install pybind11" + #sudo apt-get install -y pybind11-dev + #echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: build xo-cmake + run: | + XONAME=xo-cmake + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/xo-indentlog + + - name: build xo-indentlog + run: | + XONAME=xo-indentlog + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/xo-refcnt + + - name: build xo-refcnt + run: | + XONAME=xo-refcnt + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/xo-subsys + + - name: build xo-subsys + run: | + XONAME=xo-subsys + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/xo-reflect + + - name: build xo-reflect + run: | + XONAME=xo-reflect + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: build self (xo-unit) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: | + XONAME=xo-unit + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + (cd ${BUILDDIR} && ctest -C ${{env.BUILD_TYPE}}) From 2e1214efbe1dd003bfbb3dc32936dfbd09adb0b6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Apr 2024 20:13:51 -0400 Subject: [PATCH 0690/2693] github: + doxygen dep --- .github/workflows/ubuntu-main.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ubuntu-main.yml b/.github/workflows/ubuntu-main.yml index 0acda62e..11cd66e5 100644 --- a/.github/workflows/ubuntu-main.yml +++ b/.github/workflows/ubuntu-main.yml @@ -23,12 +23,17 @@ jobs: - name: Install dependencies run: | - echo "::group::install catch2" - # install catch2. see + # install catch2, doxygen. see # [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + + echo "::group::install catch2" sudo apt-get install -y catch2 echo "::endgroup" + echo "::group::install doxygen" + sudo apt-get install -y doxygen + echo "::endgroup" + #echo "::group::install pybind11" #sudo apt-get install -y pybind11-dev #echo "::endgroup" From 06a7e938e452bd300aac7a45dfccd85a49c86e19 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Apr 2024 20:16:46 -0400 Subject: [PATCH 0691/2693] build: drop stray #include (did you do that, lsp?) --- include/xo/unit/dimension_impl.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/unit/dimension_impl.hpp b/include/xo/unit/dimension_impl.hpp index 49262655..7a8e7aff 100644 --- a/include/xo/unit/dimension_impl.hpp +++ b/include/xo/unit/dimension_impl.hpp @@ -6,7 +6,7 @@ #include "native_bpu.hpp" #include "dim_util.hpp" #include "ratio_util.hpp" -#include +//#include #include #include #include From 7e61533cafc32c98066da786beef779e5e54e28d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 2 Apr 2024 17:28:15 -0400 Subject: [PATCH 0692/2693] xo-unit: move types: xo::obs:: -> xo::unit:: --- include/xo/unit/basis_unit.hpp | 8 +- include/xo/unit/dim_util.hpp | 6 +- include/xo/unit/dimension_concept.hpp | 5 +- include/xo/unit/dimension_impl.hpp | 4 +- include/xo/unit/native_bpu.hpp | 4 +- include/xo/unit/native_bpu_concept.hpp | 5 +- include/xo/unit/quantity.hpp | 8 +- include/xo/unit/quantity_concept.hpp | 4 +- include/xo/unit/ratio_concept.hpp | 4 +- include/xo/unit/ratio_util.hpp | 4 +- include/xo/unit/stringliteral.hpp | 4 +- include/xo/unit/unit.hpp | 19 +++-- utest/quantity.test.cpp | 76 +++++++++++++----- utest/unit.test.cpp | 104 ++++++++++++------------- 14 files changed, 144 insertions(+), 111 deletions(-) diff --git a/include/xo/unit/basis_unit.hpp b/include/xo/unit/basis_unit.hpp index cd0b2a02..37281553 100644 --- a/include/xo/unit/basis_unit.hpp +++ b/include/xo/unit/basis_unit.hpp @@ -4,7 +4,7 @@ #include "ratio_util.hpp" namespace xo { - namespace obs { + namespace unit { /** @class basis_unit * * @brief A dimensionless multiple with natively-specified (i.e. at compile-time) dimension @@ -56,9 +56,9 @@ namespace xo { template constexpr auto native_unit_abbrev_v = native_unit_abbrev_helper::value; - // ----- scaled_native_unit_abbrev_helper ----- - namespace units { + // ----- scaled_native_unit_abbrev_helper ----- + /* Require: InnerScale is ratio type; InnerScale >= 1 */ template struct scaled_native_unit_abbrev; @@ -80,7 +80,7 @@ namespace xo { template constexpr auto scaled_native_unit_abbrev_v = scaled_native_unit_abbrev::value; } - } /*namespace obs*/ + } /*namespace unit*/ } /*namespace xo*/ /** end basis_unit.hpp **/ diff --git a/include/xo/unit/dim_util.hpp b/include/xo/unit/dim_util.hpp index 222f9c9e..ad2f3496 100644 --- a/include/xo/unit/dim_util.hpp +++ b/include/xo/unit/dim_util.hpp @@ -2,10 +2,11 @@ #pragma once + #include "stringliteral.hpp" namespace xo { - namespace obs { + namespace unit { enum class dim { /** weight. native unit = 1 gram **/ mass, @@ -52,8 +53,7 @@ namespace xo { template constexpr auto native_unit_for_v = native_unit_for::value; - } /*namespace obs*/ + } /*namespace unit*/ } /*namespace xo*/ - /* end dim_util.hpp */ diff --git a/include/xo/unit/dimension_concept.hpp b/include/xo/unit/dimension_concept.hpp index c1514e40..6277b621 100644 --- a/include/xo/unit/dimension_concept.hpp +++ b/include/xo/unit/dimension_concept.hpp @@ -3,10 +3,9 @@ #pragma once #include "native_bpu_concept.hpp" -//#include namespace xo { - namespace obs { + namespace unit { /** checks most non-empty BPU (basis power unit) node types; * cannot check BpuList::rest_type, because concept definition * can't (as of c++23) be recursive. @@ -83,7 +82,7 @@ namespace xo { && (ratio_concept && bpu_list_concept && bpu_list_concept); - } /*namespace obs*/ + } /*namespace unit*/ } /*namespace xo*/ /* end dimension_concept.hpp */ diff --git a/include/xo/unit/dimension_impl.hpp b/include/xo/unit/dimension_impl.hpp index 7a8e7aff..d51c655c 100644 --- a/include/xo/unit/dimension_impl.hpp +++ b/include/xo/unit/dimension_impl.hpp @@ -17,7 +17,7 @@ namespace xo { * - bpu_list -> bpu_node */ - namespace obs { + namespace unit { // ----- lookup_bpu ----- /** @@ -510,7 +510,7 @@ namespace xo { template using canonical_t = canonical_impl::dim_type; - } /*namespace obs*/ + } /*namespace unit*/ } /*namespace xo*/ /* end dimension_impl.hpp */ diff --git a/include/xo/unit/native_bpu.hpp b/include/xo/unit/native_bpu.hpp index a3e716a0..44e1a54d 100644 --- a/include/xo/unit/native_bpu.hpp +++ b/include/xo/unit/native_bpu.hpp @@ -7,7 +7,7 @@ #include namespace xo { - namespace obs { + namespace unit { // ----- native_bpu ----- /** @class native_bpu @@ -253,7 +253,7 @@ namespace xo { _p_type /*Power*/ >; }; - } /*namespace obs*/ + } /*namespace unit*/ } /*namespace xo*/ /* end native_bpu.hpp */ diff --git a/include/xo/unit/native_bpu_concept.hpp b/include/xo/unit/native_bpu_concept.hpp index 776e18bf..f72c60ca 100644 --- a/include/xo/unit/native_bpu_concept.hpp +++ b/include/xo/unit/native_bpu_concept.hpp @@ -7,7 +7,7 @@ #include namespace xo { - namespace obs { + namespace unit { /** * e.g. see native_bpu> * @@ -35,8 +35,7 @@ namespace xo { && (std::is_signed_v)) // && std::copyable ; - } /*namespace obs*/ + } /*namespace unit*/ } /*namespace xo*/ - /* end native_bpu_concept.hpp */ diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index 286a9e16..adb6cf80 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -8,7 +8,7 @@ //#include "xo/indentlog/scope.hpp" namespace xo { - namespace obs { + namespace unit { /** @class promoter * * Aux class assister for quantity::promote() @@ -35,7 +35,7 @@ namespace xo { * - Repr * Repr -> Repr * - Repr / Repr -> Repr **/ - template //, typename RequiredDimensionType = Unit> + template class quantity { public: using unit_type = Unit; @@ -415,7 +415,7 @@ namespace xo { }; template - inline auto kilograms(Repr x) -> quantity { + inline auto kilograms(Repr x) -> quantity { return quantity::promote(x); }; @@ -495,7 +495,7 @@ namespace xo { return quantity::promote(x); } } /*namespace qty*/ - } /*namespace obs*/ + } /*namespace unit*/ } /*namespace xo*/ /* end quantity.hpp */ diff --git a/include/xo/unit/quantity_concept.hpp b/include/xo/unit/quantity_concept.hpp index 963a0246..09d33446 100644 --- a/include/xo/unit/quantity_concept.hpp +++ b/include/xo/unit/quantity_concept.hpp @@ -5,7 +5,7 @@ #include namespace xo { - namespace obs { + namespace unit { template concept quantity_concept = requires(Quantity qty, typename Quantity::repr_type repr) { @@ -17,5 +17,5 @@ namespace xo { { Quantity::unit_quantity() } -> std::same_as; { Quantity::promote(repr) } -> std::same_as; }; - } /*namespace obs*/ + } /*namespace unit*/ } /*namespace xo*/ diff --git a/include/xo/unit/ratio_concept.hpp b/include/xo/unit/ratio_concept.hpp index 3308b1f1..ecfeb4bf 100644 --- a/include/xo/unit/ratio_concept.hpp +++ b/include/xo/unit/ratio_concept.hpp @@ -5,9 +5,9 @@ #include namespace xo { - namespace obs { + namespace unit { template concept ratio_concept = (std::is_signed_v && std::is_signed_v); - } /*namespace obs*/ + } /*namespace unit*/ } /*namespace xo*/ diff --git a/include/xo/unit/ratio_util.hpp b/include/xo/unit/ratio_util.hpp index 7695db3d..1d7e3199 100644 --- a/include/xo/unit/ratio_util.hpp +++ b/include/xo/unit/ratio_util.hpp @@ -8,7 +8,7 @@ #include namespace xo { - namespace obs { + namespace unit { // ----- ratio_floor ----- template @@ -236,7 +236,7 @@ namespace xo { return exponent2str_aux().value; }; - } /*namespace obs*/ + } /*namespace unit*/ } /*namespace xo*/ /* end ratio_util.hpp */ diff --git a/include/xo/unit/stringliteral.hpp b/include/xo/unit/stringliteral.hpp index c6bc6243..b3e68fe8 100644 --- a/include/xo/unit/stringliteral.hpp +++ b/include/xo/unit/stringliteral.hpp @@ -8,7 +8,7 @@ #include namespace xo { - namespace obs { + namespace unit { // ----- stringliteral ----- @@ -164,7 +164,7 @@ namespace xo { static constexpr auto value = stringliteral_concat("-", _suffix.value_); }; - } /*namespace obs*/ + } /*namespace unit*/ } /*namespace xo*/ /* end stringliteral.hpp */ diff --git a/include/xo/unit/unit.hpp b/include/xo/unit/unit.hpp index d081077e..b8b8c1b9 100644 --- a/include/xo/unit/unit.hpp +++ b/include/xo/unit/unit.hpp @@ -1,11 +1,11 @@ -/* @file dimension.hpp */ +/* @file unit.hpp */ #pragma once #include "dimension_impl.hpp" namespace xo { - namespace obs { + namespace unit { // ----- wrap_unit ----- template @@ -26,6 +26,7 @@ namespace xo { // ----- normalize_unit ----- + /* like Unit, but with scalefactor 1 */ template struct normalize_unit { static_assert(unit_concept); @@ -239,7 +240,7 @@ namespace xo { using milligram = wrap_unit< std::ratio<1>, bpu_node< bpu> > >; + std::ratio<1, 1000>> > >; template <> struct scaled_native_unit_abbrev> { @@ -248,10 +249,10 @@ namespace xo { using gram = wrap_unit< std::ratio<1>, bpu_node< bpu> > >; + std::ratio<1>> > >; using kilogram = wrap_unit< std::ratio<1>, bpu_node< bpu> > >; + std::ratio<1000>> > >; template <> struct scaled_native_unit_abbrev> { @@ -385,10 +386,8 @@ namespace xo { using price = wrap_unit< std::ratio<1>, bpu_node< bpu> > >; - } - - } /*namespace obs*/ + } /*namespace units*/ + } /*namespace unit*/ } /*namespace xo*/ - -/* end dimension.hpp */ +/* end unit.hpp */ diff --git a/utest/quantity.test.cpp b/utest/quantity.test.cpp index 8274012c..352ca2c5 100644 --- a/utest/quantity.test.cpp +++ b/utest/quantity.test.cpp @@ -9,31 +9,32 @@ #include namespace xo { - using xo::obs::quantity; + using xo::unit::quantity; - using xo::obs::qty::milliseconds; - using xo::obs::qty::seconds; - using xo::obs::qty::minutes; - using xo::obs::qty::volatility30d; - using xo::obs::qty::volatility250d; + using xo::unit::qty::kilograms; + using xo::unit::qty::milliseconds; + using xo::unit::qty::seconds; + using xo::unit::qty::minutes; + using xo::unit::qty::volatility30d; + using xo::unit::qty::volatility250d; - using xo::obs::unit_find_bpu_t; - using xo::obs::unit_conversion_factor_t; - using xo::obs::unit_cartesian_product_t; - using xo::obs::unit_cartesian_product; - using xo::obs::unit_invert_t; - using xo::obs::unit_abbrev_v; - using xo::obs::same_dimension_v; - using xo::obs::dim; + using xo::unit::unit_find_bpu_t; + using xo::unit::unit_conversion_factor_t; + using xo::unit::unit_cartesian_product_t; + using xo::unit::unit_cartesian_product; + using xo::unit::unit_invert_t; + using xo::unit::unit_abbrev_v; + using xo::unit::same_dimension_v; + using xo::unit::dim; - using xo::obs::from_ratio; - using xo::obs::stringliteral_from_ratio; - using xo::obs::ratio2str_aux; - using xo::obs::cstr_from_ratio; + using xo::unit::from_ratio; + using xo::unit::stringliteral_from_ratio; + using xo::unit::ratio2str_aux; + using xo::unit::cstr_from_ratio; using xo::reflect::Reflect; - namespace units = xo::obs::units; + namespace units = xo::unit::units; namespace ut { /* use 'testcase' snippet to add test cases here */ @@ -626,7 +627,7 @@ namespace xo { //auto rng = xo::rng::xoshiro256ss(seed); - scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.mult2")); + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.div4")); //log && log("(A)", xtag("foo", foo)); auto q1 = volatility250d(0.2); @@ -647,6 +648,41 @@ namespace xo { CHECK(r == Approx(0.692820323).epsilon(1e-6)); } /*TEST_CASE(div4)*/ + + TEST_CASE("muldiv5", "[quantity]") { + constexpr bool c_debug_flag = true; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.muldiv5")); + //log && log("(A)", xtag("foo", foo)); + + auto t = milliseconds(10); + auto m = kilograms(2.5); + + auto a = m / (t * t); + + /* 0.1/sqrt(30dy) ~ 0.288675/sqrt(250dy), + * so q1/q2 ~ 0.6928 + */ + + log && log(XTAG(m), XTAG(t), xtag("a=m.t^-2", a)); + + /* verify dimensions of result + sticky units */ + CHECK(strcmp(t.unit_cstr(), "ms") == 0); + CHECK(strcmp(m.unit_cstr(), "kg") == 0); + CHECK(strcmp(a.unit_cstr(), "kg.ms^-2") == 0); + + CHECK(a.scale() == 0.025); + + /* verify scale of result */ + //CHECK(r == Approx(0.692820323).epsilon(1e-6)); + + } /*TEST_CASE(div4)*/ } /*namespace ut*/ } /*namespace xo*/ diff --git a/utest/unit.test.cpp b/utest/unit.test.cpp index bccc5d47..d3ea40bf 100644 --- a/utest/unit.test.cpp +++ b/utest/unit.test.cpp @@ -14,33 +14,33 @@ namespace xo { using xo::reflect::Reflect; - using xo::obs::dim; - using xo::obs::native_unit_abbrev_v; - using xo::obs::units::scaled_native_unit_abbrev_v; - //using xo::obs::native_dim_abbrev; - using xo::obs::stringliteral_compare; - using xo::obs::literal_size_v; - using xo::obs::stringliteral_from_digit; - using xo::obs::stringliteral_from_int_v; - using xo::obs::stringliteral; + using xo::unit::dim; + using xo::unit::native_unit_abbrev_v; + using xo::unit::units::scaled_native_unit_abbrev_v; + //using xo::unit::native_dim_abbrev; + using xo::unit::stringliteral_compare; + using xo::unit::literal_size_v; + using xo::unit::stringliteral_from_digit; + using xo::unit::stringliteral_from_int_v; + using xo::unit::stringliteral; #ifndef __clang__ - using xo::obs::stringliteral_concat; - using xo::obs::stringliteral_from_ratio; - using xo::obs::bpu_assemble_abbrev_helper; - using xo::obs::bpu_assemble_abbrev; + using xo::unit::stringliteral_concat; + using xo::unit::stringliteral_from_ratio; + using xo::unit::bpu_assemble_abbrev_helper; + using xo::unit::bpu_assemble_abbrev; #endif - using xo::obs::bpu_node; - using xo::obs::wrap_unit; - using xo::obs::unit_abbrev_v; - //using xo::obs::dim_abbrev_v; - using xo::obs::di_cartesian_product; - using xo::obs::di_cartesian_product1; - using xo::obs::unit_cartesian_product_t; - using xo::obs::bpu_cartesian_product; - using xo::obs::bpu_cartesian_product_helper; - using xo::obs::unit_invert_t; - using xo::obs::units::gram; - using xo::obs::units::second; + using xo::unit::bpu_node; + using xo::unit::wrap_unit; + using xo::unit::unit_abbrev_v; + //using xo::unit::dim_abbrev_v; + using xo::unit::di_cartesian_product; + using xo::unit::di_cartesian_product1; + using xo::unit::unit_cartesian_product_t; + using xo::unit::bpu_cartesian_product; + using xo::unit::bpu_cartesian_product_helper; + using xo::unit::unit_invert_t; + using xo::unit::units::gram; + using xo::unit::units::second; using xo::print::ccs; template @@ -180,74 +180,74 @@ namespace xo { scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.dimension")); //log && log("(A)", xtag("foo", foo)); - using t1 = obs::bpu>; + using t1 = unit::bpu>; - static_assert(t1::c_native_dim == obs::dim::currency); + static_assert(t1::c_native_dim == unit::dim::currency); static_assert(t1::power_type::num == 1); static_assert(t1::power_type::den == 1); - using t2 = obs::bpu, std::ratio<-1,2>>; + using t2 = unit::bpu, std::ratio<-1,2>>; - static_assert(t2::c_native_dim == obs::dim::time); + static_assert(t2::c_native_dim == unit::dim::time); static_assert(t2::power_type::num == -1); static_assert(t2::power_type::den == 2); using dim1 = wrap_unit, bpu_node>; using d1 = dim1::dim_type; /* ccy */ REQUIRE(unused_same()); - REQUIRE(unused_same::power_unit_type, t1>()); + REQUIRE(unused_same::power_unit_type, t1>()); #ifdef NOT_USING - static_assert(obs::lo_basis_elt_of::c_lo_basis == t1::c_basis); + static_assert(unit::lo_basis_elt_of::c_lo_basis == t1::c_basis); #endif - static_assert(obs::native_lo_bwp_of::bwp_type::c_index == 0); - static_assert(obs::native_lo_bwp_of::bwp_type::c_basis == obs::dim::currency); + static_assert(unit::native_lo_bwp_of::bwp_type::c_index == 0); + static_assert(unit::native_lo_bwp_of::bwp_type::c_basis == unit::dim::currency); using dim2 = wrap_unit, bpu_node>; using d2 = dim2::dim_type; /* t^(-1/2) */ REQUIRE(unused_same()); - REQUIRE(unused_same::power_unit_type, t2>()); - static_assert(obs::native_lo_bwp_of::bwp_type::c_index == 0); - static_assert(obs::native_lo_bwp_of::bwp_type::c_basis == obs::dim::time); + REQUIRE(unused_same::power_unit_type, t2>()); + static_assert(unit::native_lo_bwp_of::bwp_type::c_index == 0); + static_assert(unit::native_lo_bwp_of::bwp_type::c_basis == unit::dim::time); using dim3 = wrap_unit, bpu_node>>; using d3 = dim3::dim_type; /* ccy.t^(-1/2) */ - REQUIRE(unused_same::power_unit_type, t1>()); + REQUIRE(unused_same::power_unit_type, t1>()); { - using type = obs::lookup_bpu::power_unit_type; - //std::cerr << "obs::power_unit_of::power_unit_type" << xtag("type", reflect::type_name()) << std::endl; + using type = unit::lookup_bpu::power_unit_type; + //std::cerr << "unit::power_unit_of::power_unit_type" << xtag("type", reflect::type_name()) << std::endl; REQUIRE(unused_same()); } #ifdef NOT_USING - static_assert(obs::lo_basis_elt_of::c_lo_basis == t2::c_basis); + static_assert(unit::lo_basis_elt_of::c_lo_basis == t2::c_basis); #endif /* lowest is in pos 1, beacuse t2=time before t1=currency */ - static_assert(obs::native_lo_bwp_of::bwp_type::c_index == 1); + static_assert(unit::native_lo_bwp_of::bwp_type::c_index == 1); - static_assert(unused_same::dim_type, d2>()); - //using type = obs::without_elt::dim_type; - //std::cerr << "obs::without_elt::dim_type" << xtag("type", reflect::type_name()) << std::endl; - static_assert(unused_same::dim_type, d1>()); + static_assert(unused_same::dim_type, d2>()); + //using type = unit::without_elt::dim_type; + //std::cerr << "unit::without_elt::dim_type" << xtag("type", reflect::type_name()) << std::endl; + static_assert(unused_same::dim_type, d1>()); using d3b = wrap_unit, bpu_node>>::dim_type; /* t^(-1/2).ccy */ - //using d3b = obs::dimension_impl>; /* t^(-1/2).ccy */ - REQUIRE(unused_same::power_unit_type, t2>()); - REQUIRE(unused_same::power_unit_type, t1>()); + //using d3b = unit::dimension_impl>; /* t^(-1/2).ccy */ + REQUIRE(unused_same::power_unit_type, t2>()); + REQUIRE(unused_same::power_unit_type, t1>()); /* lowest is in pos 0 */ - static_assert(obs::native_lo_bwp_of::bwp_type::c_index == 0); + static_assert(unit::native_lo_bwp_of::bwp_type::c_index == 0); - static_assert(unused_same::dim_type, d1>()); - static_assert(unused_same::dim_type, d2>()); + static_assert(unused_same::dim_type, d1>()); + static_assert(unused_same::dim_type, d2>()); - static_assert(unused_same, obs::canonical_t>()); + static_assert(unused_same, unit::canonical_t>()); log && log(xtag("d1.abbrev", unit_abbrev_v.c_str())); log && log(xtag("d2.abbrev", unit_abbrev_v.c_str())); From d9868870e4790b414493495c81410c7d936ee4d2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 2 Apr 2024 17:29:10 -0400 Subject: [PATCH 0693/2693] xo-unit: docs: + sphinx + breathe + expand docs --- docs/CMakeLists.txt | 83 ++++++++++++++++++++++--------- docs/examples.rst | 117 ++++++++++++++++++++++++++++++++++++++++++++ docs/glossary.rst | 26 ++++++++++ docs/index.rst | 40 +++++++++++++++ docs/install.rst | 62 +++++++++++++++++++++++ 5 files changed, 305 insertions(+), 23 deletions(-) create mode 100644 docs/examples.rst create mode 100644 docs/glossary.rst create mode 100644 docs/index.rst create mode 100644 docs/install.rst diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 99a1fdcc..bc00ca12 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -1,34 +1,41 @@ # xo-unit/docs/CMakeLists.txt -# copied from xo-observable/docs/CMakeLists.txt - -set(DOX_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) - -#set(DOX_DEPS ${PROJECT_SOURCE_DIR}/mainpage.dox) # not yet -set(DOX_INPUT_DIR ${PROJECT_SOURCE_DIR}) -set(DOX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/dox) - -set(DOX_INDEX_FILE ${DOX_OUTPUT_DIR}/html/index.html) - -#set(SPHINX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/sphinx/html) -#set(SPHINX_INDEX_FILE ${SPHINX_OUTPUT_DIR}/index.html) -# -## sphinx .rst files reachable from cmake-examples/docs -#file(GLOB_RECURSE SPHINX_RST_FILES ${CMAKE_CURRENT_SOURCE_DIR} *.rst) - -set(ALL_LIBRARY_TARGETS xo_unit) # todo: automate this from xo-cmake macros -#set(ALL_UTEST_TARGETS "") # todo: automate this from xo-cmake macros - -# look for doxygen executable -find_program(DOXYGEN_EXECUTABLE NAMES doxygen REQUIRED) -message("-- DOXYGEN_EXECUTABLE=${DOXYGEN_EXECUTABLE}") - if (XO_SUBMODULE_BUILD) # in submodule build, rely on toplevel docs/CMakeLists.txt file instead else() # build docs starting from here only in standalone build. # otherwise use top-level doxygen setup instead. + set(ALL_LIBRARY_TARGETS xo_unit) # todo: automate this from xo-cmake macros + set(ALL_UTEST_TARGETS utest.unit) # todo: automate this from xo-cmake macros + + # look for doxygen executable + find_program(DOXYGEN_EXECUTABLE NAMES doxygen REQUIRED) + message("-- DOXYGEN_EXECUTABLE=${DOXYGEN_EXECUTABLE}") + + # look for sphinx-build executable + find_program(SPHINX_EXECUTABLE NAMES sphinx-build REQUIRED) + message("-- SPHINX_EXECUTABLE=${SPHINX_EXECUTABLE}") + + set(DOX_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) + + #set(DOX_DEPS ${PROJECT_SOURCE_DIR}/mainpage.dox) # not yet + set(DOX_INPUT_DIR ${PROJECT_SOURCE_DIR}) + set(DOX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/dox) + + set(DOX_INDEX_FILE ${DOX_OUTPUT_DIR}/html/index.html) + + set(SPHINX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/sphinx/html) + set(SPHINX_INDEX_FILE ${SPHINX_OUTPUT_DIR}/index.html) + # + # sphinx .rst files reachable from cmake-examples/docs + # + # REMINDER: will need to re-run cmake when the set of .rst files changes + # + file(GLOB_RECURSE SPHINX_RST_FILES_GLOB ${CMAKE_CURRENT_SOURCE_DIR} *.rst) + + set(SPHINX_RST_FILES index.rst install.rst examples.rst classes.rst glossary.rst) + # TODO: # 1. move Doxyfile.in to xo-cmake project # 2. replace this command section with xo-cmake macro @@ -57,4 +64,34 @@ else() doxygen DEPENDS ${DOX_INDEX_FILE} ) + + # root of sphinx doc tree + set(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}) + + add_custom_command( + OUTPUT ${SPHINX_INDEX_FILE} + DEPENDS doxygen conf.py ${SPHINX_RST_FILES} ${SPHINX_RST_FILES_GLOB} + COMMAND ${SPHINX_EXECUTABLE} + -b html -Dbreathe_projects.xodoxxml=${CMAKE_CURRENT_BINARY_DIR}/dox/xml + ${SPHINX_SOURCE} ${SPHINX_OUTPUT_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating docs (sphinx) -> [${SPHINX_OUTPUT_DIR}]") + + # make sphinx --> generate sphinx documentation + # + add_custom_target( + sphinx + DEPENDS ${SPHINX_INDEX_FILE}) + + # - html docs generated in build/docs/sphinx + # - copy the doc tree to share/doc/xo_unit/html + # + # OPTIONAL: install directory tree if it exists, + # but don't complain if it's missing + install( + DIRECTORY ${SPHINX_OUTPUT_DIR} + FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/doc/${PROJECT_NAME} + COMPONENT Documentation + OPTIONAL) endif() diff --git a/docs/examples.rst b/docs/examples.rst new file mode 100644 index 00000000..1aba11ed --- /dev/null +++ b/docs/examples.rst @@ -0,0 +1,117 @@ +.. _examples: + +.. toctree + :maxdepth: 2 + +Examples +======== + +Compile-time unit inference +--------------------------- + +.. code-block:: cpp + + #include "xo/unit/quantity.hpp" + #include + + int main() { + namespace u = xo::unit; + namespace qty = u::qty; + using namespace std; + + auto t = qty::milliseconds(10); + auto m = qty::kilograms(2.5); + auto a = m / (t * t); + + static_assert(same_as>); + static_assert(same_as>); + static_assert(sizeof(t) == sizeof(int)); + static_assert(sizeof(m) == sizeof(double)); + static_assert(sizeof(a) == sizeof(double)); + + cerr << "t: " << t << ", m: " << m << ", a: " << a << endl; + } + +with output: + +.. code-block:: + + t: 10ms, m: 2.5kg, m.t^-2: 0.025kg.ms^-2 + +Remarks: + +* The ``xo-unit`` system runs entirely at compile time; there's no runtime overhead. +* No runtime overhead includes construction of literal strings such as ``kg.ms^-2`` + (this is once place implementation requires c++20) +* Units are sticky: since we expressed ``t`` in milliseconds and ``m`` in kilograms, result is in the same terms. +* Unit ordering is sticky. Mass appears on the left in printed value of ``a`` because it was on the left-hand side of ``operator/`` +* Example omits verifying ``decltype(a)``, to keep output small. + +Scale conversion triggered by assignment +---------------------------------------- + +One way to convert units is by assignment: + +.. code-block:: cpp + :linenos: + :emphasize-lines: 9-10 + + #include "xo/unit/quantity.hpp" + #include + + int main() { + namespace u = xo::unit; + namespace qty = xo::units::qty; + using namespace std; + + quantity t = qty::milliseconds(10); + quantity m = qty::kilograms(2.5); + auto a = m / (t * t); + + cerr << "t: " << t << ", m: " << m << ", a: " << a << endl; + } + +with output: + +.. code-block:: + + t: 0.01s, m: 2500g, m.t^-2: 2.5e+07g.s^-2 + +Remarks: + +* Assignment to ``t`` converted to representation ``double``. + We could have used :code:`quantity` to convert (possibly rounding down) + representation to `int`. + +Scale conversion triggered by arithmetic +---------------------------------------- + +In representing a particular quantity, +xo-unit uses at most one scale for each :term:`basis dimension` associated with the unit for that quantity. +When an arithmetic operator encounters basis units involving two different scales, +the operator will adopt the scale provided by the left-hand argument: + +.. code-block:: cpp + :linenos: + :emphasize-lines: 11 + + #include "xo/unit/quantity.hpp" + #include + + int main() { + namespace u = xo::unit; + namespace qty = xo::units::qty; + using namespace std; + + auto t1 = qty::milliseconds(1); + auto t2 = qty::minutes(1); + auto p = t1 * t2; + + cerr << "t1: " << t1 << ", t2: " << t2 << ", p: " << p << endl; + } + +with output: + +.. code-block:: + + t1: 1ms, t2: 1min, t1*t2: 60000ms^2 diff --git a/docs/glossary.rst b/docs/glossary.rst new file mode 100644 index 00000000..5d594f81 --- /dev/null +++ b/docs/glossary.rst @@ -0,0 +1,26 @@ +.. _glossary: + +Glossary +-------- + +.. glossary:: + basis dimension + Orthogonal directions associated with basis units, for example *mass*, *length*, *time*. + In xo-unit these are represented by the enum ``xo::unit::dim``. + + basis unit + An implementation type representing a quantity (with associated scale) in the direction of a single :term:`basis dimension`. + For example *milliseconds*, *seconds*, and *hours* stand for different basis units with the *time* dimension. + In xo-unit these are represented by the template type ``xo::unit::basis_unit``. + + bpu + A rational power of a (single) basis unit. For example *kg.m.s\ :sup:-2* or *hr\ :sup:-(1/2)*. + In xo-unit these are represented by the template type ``xo::unit::bpu``. + + XO + A set of integrated c++ libraries for complex event processing, with browser and python integration. + `xo documentation here`_ + +.. _xo documentation here: https://rconybea.github.io/web/sw/xo.html + +.. toctree:: diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..108d97d4 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,40 @@ +.. xo-unit-examples documentation master file, created by + sphinx-quickstart on Wed Mar 6 23:32:27 2024. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +xo-unit documentation +===================== + +xo-unit is a lightweight header-only library that provides compile-time +dimension checking and unit conversion. + +Functionality is similar in spirit to that provided by ``boost::units``; +however there are some important differences: + +First, implementation relies on c++20 features +like class-instance template parameters to efficiently assemble string constants at compile time. + +Second, ``xo-unit`` supports fractional dimensions. This allows using it to naturally handle +concepts like volatility (dimension 1/sqrt(time)), for example. + +Finally, ``xo-unit`` is written with the expectation of providing +python integration via pybind11. This requires a parallel set of data structures that can work at +runtime (since we can't construct new c++ types at runtime). + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + install + examples + classes + +Indices and Tables +------------------ + +* :ref:`glossary` +* :ref:`genindex` +* :ref:`search` + +.. toctree:: diff --git a/docs/install.rst b/docs/install.rst new file mode 100644 index 00000000..d8e0f9bd --- /dev/null +++ b/docs/install.rst @@ -0,0 +1,62 @@ +.. _install: + +.. toctree + :maxdepth: 2 + +Install +======= + +`xo-unit source`_ lives on github. + +.. _xo-unit source: https://github.com/rconybea/xo-unit + +Implementation relies on some c++20 features (for example class-instances as template arguments). +Tested with gcc 12.3 + +Include as submodule +-------------------- + +.. code-block:: bash + + cd myproject + git submodule add -b main https://github.com/rconybea/xo-unit ext/xo-unit + git submodule update --init + +This assumes you organize directly-incorporated dependencies under directory ``myproject/ext``. +You would then add ``myproject/ext/xo-unit/include`` to your compiler's include path, +and add + +.. code-block:: c++ + + #include + +to c++ source files that rely on xo-unit + +Supported compilers +------------------- + +* developed with gcc 12.3.0; github CI using gcc 11.4.0 (asof March 2024) + +Building from source +-------------------- + +Although the xo-unit library is header-only, unit tests have some dependencies. +Example instructions (github CI) for build starting from stock ubuntu are in `ubuntu-main.yml`_ + +.. _ubuntu-main.yml: https://github.com/Rconybea/xo-unit/blob/main/.github/workflows/ubuntu-main.yml + +Unit test dependencies: + +* `catch2`_ header-only unit-test framework +* `xo-cmake`_ cmake macros +* `xo-indentlog`_ logging with call-structure indenting +* `xo-refcnt`_ intrusive reference counting (needed by xo-reflect) +* `xo-subsys`_ plugin initialization support (needed by xo-reflect) +* `xo-reflect`_ c++ introspection library + +.. _catch2: https://github.com/catchorg/Catch2 +.. _xo-cmake: https://github.com/rconybea/xo-cmake +.. _xo-indentlog: https://github.com/rconybea/indentlog +.. _xo-refcnt: https://github.com/rconybea/refcnt +.. _xo-subsys: https://github.com/rconybea/subsys +.. _xo-reflect: https://github.com/rconybea/reflect From 0d9fd75b0c4462b5a46590861094cdfcb38a5967 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 2 Apr 2024 17:29:35 -0400 Subject: [PATCH 0694/2693] xo-unit: docs: + example programs --- CMakeLists.txt | 1 + example/CMakeLists.txt | 3 +++ example/ex1/CMakeLists.txt | 14 ++++++++++++++ example/ex1/ex1.cpp | 18 ++++++++++++++++++ example/ex2/CMakeLists.txt | 14 ++++++++++++++ example/ex2/ex2.cpp | 20 ++++++++++++++++++++ example/ex3/CMakeLists.txt | 14 ++++++++++++++ example/ex3/ex3.cpp | 19 +++++++++++++++++++ 8 files changed, 103 insertions(+) create mode 100644 example/CMakeLists.txt create mode 100644 example/ex1/CMakeLists.txt create mode 100644 example/ex1/ex1.cpp create mode 100644 example/ex2/CMakeLists.txt create mode 100644 example/ex2/ex2.cpp create mode 100644 example/ex3/CMakeLists.txt create mode 100644 example/ex3/ex3.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a879e31f..fc595582 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,7 @@ xo_toplevel_compile_options() # ---------------------------------------------------------------- #add_subdirectory(src/unit) +add_subdirectory(example) add_subdirectory(utest) add_subdirectory(docs) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 00000000..ed03faf2 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(ex1) +add_subdirectory(ex2) +add_subdirectory(ex3) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt new file mode 100644 index 00000000..0d415a06 --- /dev/null +++ b/example/ex1/CMakeLists.txt @@ -0,0 +1,14 @@ +# xo-unit/example/ex1/CMakeLists.txt + +set(SELF_EXE xo_unit_ex1) +set(SELF_SRCS ex1.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +# ---------------------------------------------------------------- +# dependencies.. + +xo_self_dependency(${SELF_EXE} xo_unit) + +# end CMakeLists.txt diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp new file mode 100644 index 00000000..74d0dc10 --- /dev/null +++ b/example/ex1/ex1.cpp @@ -0,0 +1,18 @@ +/** @file ex1.cpp **/ + +#include "xo/unit/quantity.hpp" +#include + +int +main () { + namespace qty = xo::obs::qty; + using namespace std; + + auto t = qty::milliseconds(10); + auto m = qty::kilograms(2.5); + auto a = m / (t*t); + + cerr << "t: " << t << ", m: " << m << ", m.t^-2: " << a << endl; +} + +/** end ex1.cpp **/ diff --git a/example/ex2/CMakeLists.txt b/example/ex2/CMakeLists.txt new file mode 100644 index 00000000..fa98d33b --- /dev/null +++ b/example/ex2/CMakeLists.txt @@ -0,0 +1,14 @@ +# xo-unit/example/ex2/CMakeLists.txt + +set(SELF_EXE xo_unit_ex2) +set(SELF_SRCS ex2.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +# ---------------------------------------------------------------- +# dependencies.. + +xo_self_dependency(${SELF_EXE} xo_unit) + +# end CMakeLists.txt diff --git a/example/ex2/ex2.cpp b/example/ex2/ex2.cpp new file mode 100644 index 00000000..96e574ed --- /dev/null +++ b/example/ex2/ex2.cpp @@ -0,0 +1,20 @@ +/** @file ex2.cpp **/ + +#include "xo/unit/quantity.hpp" +#include + +int +main () { + namespace u = xo::obs::units; + namespace qty = xo::obs::qty; + using xo::obs::quantity; + using namespace std; + + quantity t = qty::milliseconds(10); + quantity m = qty::kilograms(2.5); + auto a = m / (t*t); + + cerr << "t: " << t << ", m: " << m << ", m.t^-2: " << a << endl; +} + +/** end ex2.cpp **/ diff --git a/example/ex3/CMakeLists.txt b/example/ex3/CMakeLists.txt new file mode 100644 index 00000000..e6f40fc1 --- /dev/null +++ b/example/ex3/CMakeLists.txt @@ -0,0 +1,14 @@ +# xo-unit/example/ex3/CMakeLists.txt + +set(SELF_EXE xo_unit_ex3) +set(SELF_SRCS ex3.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +# ---------------------------------------------------------------- +# dependencies.. + +xo_self_dependency(${SELF_EXE} xo_unit) + +# end CMakeLists.txt diff --git a/example/ex3/ex3.cpp b/example/ex3/ex3.cpp new file mode 100644 index 00000000..d7939957 --- /dev/null +++ b/example/ex3/ex3.cpp @@ -0,0 +1,19 @@ +/** @file ex3.cpp **/ + +#include "xo/unit/quantity.hpp" +#include + +int +main () { + namespace u = xo::obs::units; + namespace qty = xo::obs::qty; + using xo::obs::quantity; + using namespace std; + + auto t1 = qty::milliseconds(1); + auto t2 = qty::minutes(1); + + cerr << "t1: " << t1 << ", t2: " << t2 << ", t1*t2: " << t1*t2 << endl; +} + +/** end ex3.cpp **/ From 183da6b29b48fbec6671f04358d3dde1c1c7bc58 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 2 Apr 2024 17:29:52 -0400 Subject: [PATCH 0695/2693] xo-unit: README: instructions for building sphinx docs --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 68174012..5af84f03 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,13 @@ $ make $ make install ``` +### build documentation +``` +$ cd xo-unit +$ cmake --build .build -- sphinx +``` +When this completes, point local browser to `xo-unit/.build/docs/sphinx/index.html`. + ### build for unit test coverage ``` $ cd xo-unit From 13f4746f524312fb41910a2bf793d66032a93fa5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 2 Apr 2024 17:30:16 -0400 Subject: [PATCH 0696/2693] xo-unit: docs: bugfix: missing sphinx conf.py --- docs/conf.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 docs/conf.py diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..a590afbe --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,35 @@ +# 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 unit documentation' +copyright = '2024, Roland Conybeare' +author = 'Roland Conybeare' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +#extensions = [] +extensions = [ "breathe", + "sphinx.ext.autodoc" # generate info from docstrings + ] + +# 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'] From eff31256f98b94026a917bde514dc4b2e3fc3c8a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 2 Apr 2024 17:30:35 -0400 Subject: [PATCH 0697/2693] xo-unit: docs: bugfix: fix xo::unit::quantity ref --- docs/classes.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 docs/classes.rst diff --git a/docs/classes.rst b/docs/classes.rst new file mode 100644 index 00000000..1e3ac342 --- /dev/null +++ b/docs/classes.rst @@ -0,0 +1,10 @@ +.. _classes: + +.. toctree + :maxdepth: 2 + +Template Classes +================ + +.. doxygenclass:: xo::unit::quantity + :members: From 204c889281006c99261398bd43681dfd2c648cec Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 2 Apr 2024 17:30:52 -0400 Subject: [PATCH 0698/2693] xo-unit: + LICENSE --- LICENSE | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..cae3cb5d --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2024 Roland Conybeare , All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Please also refer to the file .github/CONTRIBUTING.md, which clarifies licensing of +external contributions to this project including patches, pull requests, etc. From eb7ae8a564afc799936efc0f329b64b4d2c074ba Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 12:50:29 -0400 Subject: [PATCH 0699/2693] xo-unit: + nixpkgs candidate + flake.nix --- flake.nix | 514 +++++++++++++++++++++++++++++++++++++++++++++++ pkgs/xo-unit.nix | 37 ++++ 2 files changed, 551 insertions(+) create mode 100644 flake.nix create mode 100644 pkgs/xo-unit.nix diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..ed2b35f1 --- /dev/null +++ b/flake.nix @@ -0,0 +1,514 @@ +{ + description = "xo-unit: c++ compile-time dimension checking and unit conversion"; + + # Adapted from xo-nix2/flake.nix + + # MANIFESTO + # No build instructions in flake.nix + # - Following Jade Lovelace's advice + # - Build instructions are in pkgs/*.nix + # - Each pkgs/*.nix is intended to work 'like a .nix file in nixpkgs' + # I'm being lazy about source hashes, since flake.nix supplies them. + # + # Motivation (per JL) versus doing everything in flake.nix: + # - nixpkgs-ready + # - parameterized + # - overridable + # - still works if cross-compiling + # + # Instead: using flake.nix as entry point: + # - pin nixpkgs to a specific revision, for reproducibility + # - pin our candidate packages (pkgs/*.nix), for the same reason. + + # to determine specific hash for nixpkgs: + # 1. $ cd ~/proj/nixpkgs + # 2. $ git checkout release-23.05 + # 3. $ git fetch + # 4. $ git pull + # 5. $ git log -1 + # take this hash, then substitue for ${hash} in: + # inputs.nixpkgs.url = "https://github.com/NixOS/nixpkgs/archive/${hash}.tar.gz"; + # below + #inputs.nixpkgs.url = "https://github.com/NixOS/nixpkgs/archive/9a333eaa80901efe01df07eade2c16d183761fa3.tar.gz"; + + # as sbove but instead of {release-23.05} use {release-23.11} + # gcc -> 12.3.0 + # python -> 3.11.6 + # + inputs.nixpkgs.url = "https://github.com/NixOS/nixpkgs/archive/217b3e910660fbf603b0995a6d2c3992aef4cc37.tar.gz"; # asof 10mar2024 + #inputs.nixpkgs.url = "https://github.com/NixOS/nixpkgs/archive/4dd376f7943c64b522224a548d9cab5627b4d9d6.tar.gz"; + + # inputs.nixpkgs.url + # = "https://github.com/NixOS/nixpkgs/archive/fac3684647cc9d6dfb2a39f3f4b7cf5fc89c96b6.tar.gz"; # asof 8feb2024 + # fac3684647.. asof 17oct2023 + # instead of + # inputs.nixpkgs.url = "github:nixos/nixpkgs/23.05"; + + inputs.flake-utils.url = "github:numtide/flake-utils"; + + # To add a new package, visit placeholder-A .. placeholder-E + + inputs.xo-cmake-path = { type = "github"; owner = "Rconybea"; repo = "xo-cmake"; flake = false; }; + inputs.xo-indentlog-path = { type = "github"; owner = "Rconybea"; repo = "indentlog"; flake = false; }; + inputs.xo-refcnt-path = { type = "github"; owner = "Rconybea"; repo = "refcnt"; flake = false; }; + inputs.xo-subsys-path = { type = "github"; owner = "Rconybea"; repo = "subsys"; flake = false; }; + inputs.xo-randomgen-path = { type = "github"; owner = "Rconybea"; repo = "randomgen"; flake = false; }; + inputs.xo-ordinaltree-path = { type = "github"; owner = "Rconybea"; repo = "xo-ordinaltree"; flake = false; }; + inputs.xo-pyutil-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyutil"; flake = false; }; + inputs.xo-reflect-path = { type = "github"; owner = "Rconybea"; repo = "reflect"; flake = false; }; + inputs.xo-pyreflect-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyreflect"; flake = false; }; + inputs.xo-printjson-path = { type = "github"; owner = "Rconybea"; repo = "xo-printjson"; flake = false; }; + inputs.xo-pyprintjson-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyprintjson"; flake = false; }; + inputs.xo-callback-path = { type = "github"; owner = "Rconybea"; repo = "xo-callback"; flake = false; }; + inputs.xo-webutil-path = { type = "github"; owner = "Rconybea"; repo = "xo-webutil"; flake = false; }; + inputs.xo-pywebutil-path = { type = "github"; owner = "Rconybea"; repo = "xo-pywebutil"; flake = false; }; + inputs.xo-reactor-path = { type = "github"; owner = "Rconybea"; repo = "xo-reactor"; flake = false; }; + inputs.xo-pyreactor-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyreactor"; flake = false; }; + inputs.xo-simulator-path = { type = "github"; owner = "Rconybea"; repo = "xo-simulator"; flake = false; }; + inputs.xo-pysimulator-path = { type = "github"; owner = "Rconybea"; repo = "xo-pysimulator"; flake = false; }; + inputs.xo-distribution-path = { type = "github"; owner = "Rconybea"; repo = "xo-distribution"; flake = false; }; + inputs.xo-pydistribution-path = { type = "github"; owner = "Rconybea"; repo = "xo-pydistribution"; flake = false; }; + inputs.xo-process-path = { type = "github"; owner = "Rconybea"; repo = "xo-process"; flake = false; }; + inputs.xo-pyprocess-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyprocess"; flake = false; }; + inputs.xo-statistics-path = { type = "github"; owner = "Rconybea"; repo = "xo-statistics"; flake = false; }; + inputs.xo-kalmanfilter-path = { type = "github"; owner = "Rconybea"; repo = "xo-kalmanfilter"; flake = false; }; + inputs.xo-pykalmanfilter-path = { type = "github"; owner = "Rconybea"; repo = "xo-pykalmanfilter"; flake = false; }; + inputs.xo-websock-path = { type = "github"; owner = "Rconybea"; repo = "xo-websock"; flake = false; }; + inputs.xo-pywebsock-path = { type = "github"; owner = "Rconybea"; repo = "xo-pywebsock"; flake = false; }; + # placeholder-A + + outputs + = { self, + nixpkgs, + flake-utils, + xo-cmake-path, + xo-indentlog-path, + xo-refcnt-path, + xo-subsys-path, + xo-reflect-path, + xo-pyreflect-path, + xo-randomgen-path, + xo-ordinaltree-path, + xo-pyutil-path, + xo-printjson-path, + xo-pyprintjson-path, + xo-callback-path, + xo-webutil-path, + xo-pywebutil-path, + xo-reactor-path, + xo-pyreactor-path, + xo-simulator-path, + xo-pysimulator-path, + xo-distribution-path, + xo-pydistribution-path, + xo-process-path, + xo-pyprocess-path, + xo-statistics-path, + xo-kalmanfilter-path, + xo-pykalmanfilter-path, + xo-websock-path, + xo-pywebsock-path, + # placeholder-B + } : + # out :: system -> {packages, devShells} + let + out + = system : + let + pkgs = nixpkgs.legacyPackages.${system}; + + # could try using + # appliedOverlay = (pkgs.extend self.overlays.default) + # but it doesn't seem to work the way I expect, + # For example, wants to pickup 2.7.11 python for xo-pyutil ! + # + appliedOverlay = self.overlays.default pkgs pkgs; + + in + { + #xo-cmake-dir = "${self.packages.${system}.xo-cmake}/share/cmake"; + + # reminder: + # 'packages' comprises the output of this flake; + # each defn invokes a build + # ./pkgs/$example.nix + # using + # cmake-examples-$example-path + # above for source code + + packages.xo-cmake = appliedOverlay.xo-cmake; + packages.xo-indentlog = appliedOverlay.xo-indentlog; + packages.xo-refcnt = appliedOverlay.xo-refcnt; + packages.xo-subsys = appliedOverlay.xo-subsys; + packages.xo-randomgen = appliedOverlay.xo-randomgen; + packages.xo-ordinaltree = appliedOverlay.xo-ordinaltree; + packages.xo-pyutil = appliedOverlay.xo-pyutil; + packages.xo-reflect = appliedOverlay.xo-reflect; + packages.xo-pyreflect = appliedOverlay.xo-pyreflect; + packages.xo-printjson = appliedOverlay.xo-printjson; + packages.xo-pyprintjson = appliedOverlay.xo-pyprintjson; + packages.xo-callback = appliedOverlay.xo-callback; + packages.xo-webutil = appliedOverlay.xo-webutil; + packages.xo-pywebutil = appliedOverlay.xo-pywebutil; + packages.xo-reactor = appliedOverlay.xo-reactor; + packages.xo-pyreactor = appliedOverlay.xo-pyreactor; + packages.xo-simulator = appliedOverlay.xo-simulator; + packages.xo-pysimulator = appliedOverlay.xo-pysimulator; + packages.xo-distribution = appliedOverlay.xo-distribution; + packages.xo-pydistribution = appliedOverlay.xo-pydistribution; + packages.xo-process = appliedOverlay.xo-process; + packages.xo-pyprocess = appliedOverlay.xo-pyprocess; + packages.xo-statistics = appliedOverlay.xo-statistics; + packages.xo-kalmanfilter = appliedOverlay.xo-kalmanfilter; + packages.xo-pykalmanfilter = appliedOverlay.xo-pykalmanfilter; + packages.xo-websock = appliedOverlay.xo-websock; + packages.xo-pywebsock = appliedOverlay.xo-pywebsock; + # placeholder-C + + packages.xo-userenv = appliedOverlay.xo-userenv; + + devShells = appliedOverlay.devShells; + }; + in + flake-utils.lib.eachDefaultSystem + out + // + { + # introduce overlay to extend nixpkgs with our local packages, + # (which ofc are not present in nixpkgs, though same form would work if they were present) + # + overlays.default = final: prev: + ( + let + # can use + # $ nix-env -qaP | grep \.boost # show known boost versions + # $ nix-env -qaP | grep \.python.*Packages # show known python versions + + stdenv = prev.stdenv; + + boost = prev.boost182; + python = prev.python311Full; + pythonPackages = prev.python311Packages; + #doxygen = prev.doxygen; + + pybind11 = pythonPackages.pybind11; + #breathe = python3Packages.breathe; + #sphinx = python3Packages.sphinx; + #sphinx-rtd-theme = python3Packages.sphinx-rtd-theme; + + #extras1 = { boost = boost; }; + #extras2 = { boost = boost; python3Packages = python3Packages; pybind11 = pybind11; }; + #extras3 = { boost = boost; python3Packages = python3Packages; pybind11 = pybind11; doxygen = doxygen; }; + #extras4 = extras3 // { breathe = breathe; }; + + xo-cmake = + (prev.callPackage ./pkgs/xo-cmake.nix {}).overrideAttrs + (old: { src = xo-cmake-path; }); + + xo-indentlog = + (prev.callPackage ./pkgs/xo-indentlog.nix { xo-cmake = xo-cmake; }).overrideAttrs + (old: { src = xo-indentlog-path; }); + + xo-subsys = + (prev.callPackage ./pkgs/xo-subsys.nix { xo-cmake = xo-cmake; }).overrideAttrs + (old: { src = xo-subsys-path; }); + + xo-refcnt = + (prev.callPackage ./pkgs/xo-refcnt.nix { xo-cmake = xo-cmake; + xo-indentlog = xo-indentlog; }).overrideAttrs + (old: { src = xo-refcnt-path; }); + + xo-randomgen = + (prev.callPackage ./pkgs/xo-randomgen.nix { xo-cmake = xo-cmake; + xo-indentlog = xo-indentlog; }).overrideAttrs + (old: { src = xo-randomgen-path; }); + + xo-ordinaltree = + (prev.callPackage ./pkgs/xo-ordinaltree.nix { xo-cmake = xo-cmake; + xo-refcnt = xo-refcnt; + xo-randomgen = xo-randomgen; }).overrideAttrs + (old: { src = xo-ordinaltree-path; }); + + xo-pyutil = + (prev.callPackage ./pkgs/xo-pyutil.nix { xo-cmake = xo-cmake; + xo-refcnt = xo-refcnt; + python = python; + pybind11 = pybind11; + }).overrideAttrs + (old: { src = xo-pyutil-path; }); + + xo-reflect = + (prev.callPackage ./pkgs/xo-reflect.nix { xo-cmake = xo-cmake; + xo-subsys = xo-subsys; + xo-refcnt = xo-refcnt; }).overrideAttrs + (old: { src = xo-reflect-path; }); + + xo-pyreflect = + (prev.callPackage ./pkgs/xo-pyreflect.nix { xo-cmake = xo-cmake; + xo-refcnt = xo-refcnt; + xo-pyutil = xo-pyutil; + xo-reflect = xo-reflect; }).overrideAttrs + (old: { src = xo-pyreflect-path; }); + + xo-unit = + (prev.callPackage ./pkgs/xo-unit.nix { xo-cmake = xo-cmake; + xo-reflect = xo-reflect; }).overrideAttrs + (old: { src = ./.; }); + + xo-printjson = + (prev.callPackage ./pkgs/xo-printjson.nix { xo-cmake = xo-cmake; + xo-reflect = xo-reflect; }).overrideAttrs + (old: { src = xo-printjson-path; }); + + xo-pyprintjson = + (prev.callPackage ./pkgs/xo-pyprintjson.nix { xo-cmake = xo-cmake; + xo-pyutil = xo-pyutil; + xo-printjson = xo-printjson; + xo-pyreflect = xo-pyreflect; }).overrideAttrs + (old: { src = xo-pyprintjson-path; }); + + xo-callback = + (prev.callPackage ./pkgs/xo-callback.nix { xo-cmake = xo-cmake; + xo-reflect = xo-reflect; }).overrideAttrs + (old: { src = xo-callback-path; }); + + xo-webutil = + (prev.callPackage ./pkgs/xo-webutil.nix { xo-cmake = xo-cmake; + xo-reflect = xo-reflect; + xo-callback = xo-callback; }).overrideAttrs + (old: { src = xo-webutil-path; }); + + xo-pywebutil = + (prev.callPackage ./pkgs/xo-pywebutil.nix { xo-cmake = xo-cmake; + xo-webutil = xo-webutil; + xo-pyutil = xo-pyutil; }).overrideAttrs + (old: { src = xo-pywebutil-path; }); + + xo-reactor = + (prev.callPackage ./pkgs/xo-reactor.nix { xo-cmake = xo-cmake; + xo-randomgen = xo-randomgen; + xo-webutil = xo-webutil; + xo-printjson = xo-printjson; + xo-ordinaltree = xo-ordinaltree; }).overrideAttrs + (old: { src = xo-reactor-path; }); + + xo-pyreactor = + (prev.callPackage ./pkgs/xo-pyreactor.nix { xo-cmake = xo-cmake; + xo-reactor = xo-reactor; + xo-pyutil = xo-pyutil; + xo-pyreflect = xo-pyreflect; + xo-pyprintjson = xo-pyprintjson; + }).overrideAttrs + (old: { src = xo-pyreactor-path; }); + + xo-simulator = + (prev.callPackage ./pkgs/xo-simulator.nix { xo-cmake = xo-cmake; + xo-reactor = xo-reactor; + }).overrideAttrs + (old: { src = xo-simulator-path; }); + + xo-pysimulator = + (prev.callPackage ./pkgs/xo-pysimulator.nix { xo-cmake = xo-cmake; + xo-simulator = xo-simulator; + xo-pyutil = xo-pyutil; + xo-pyreactor = xo-pyreactor; + }).overrideAttrs + (old: { src = xo-pysimulator-path; }); + + xo-distribution = + (prev.callPackage ./pkgs/xo-distribution.nix { xo-cmake = xo-cmake; + xo-refcnt = xo-refcnt; + }).overrideAttrs + (old: { src = xo-distribution-path; }); + + xo-pydistribution = + (prev.callPackage ./pkgs/xo-pydistribution.nix { xo-cmake = xo-cmake; + xo-distribution = xo-distribution; + xo-pyutil = xo-pyutil; + }).overrideAttrs + (old: { src = xo-pydistribution-path; }); + + xo-process = + (prev.callPackage ./pkgs/xo-process.nix { xo-cmake = xo-cmake; + xo-simulator = xo-simulator; + xo-randomgen = xo-randomgen; + }).overrideAttrs + (old: { src = xo-process-path; }); + + xo-pyprocess = + (prev.callPackage ./pkgs/xo-pyprocess.nix { xo-cmake = xo-cmake; + xo-process = xo-process; + xo-pyutil = xo-pyutil; + xo-pywebutil = xo-pywebutil; + xo-pyreactor = xo-pyreactor; + }).overrideAttrs + (old: { src = xo-pyprocess-path; }); + + xo-statistics = + (prev.callPackage ./pkgs/xo-statistics.nix { xo-cmake = xo-cmake; + #xo-reactor = xo-reactor; + }).overrideAttrs + (old: { src = xo-statistics-path; }); + + xo-kalmanfilter = + (prev.callPackage ./pkgs/xo-kalmanfilter.nix { xo-cmake = xo-cmake; + xo-statistics = xo-statistics; + xo-reactor = xo-reactor; + }).overrideAttrs + (old: { src = xo-kalmanfilter-path; }); + + + xo-pykalmanfilter = + (prev.callPackage ./pkgs/xo-pykalmanfilter.nix { xo-cmake = xo-cmake; + xo-pyutil = xo-pyutil; + xo-kalmanfilter = xo-kalmanfilter; + xo-pyreactor = xo-pyreactor; + }).overrideAttrs + (old: { src = xo-pykalmanfilter-path; }); + + xo-websock = + (prev.callPackage ./pkgs/xo-websock.nix { xo-cmake = xo-cmake; + xo-reactor = xo-reactor; + }).overrideAttrs + (old: { src = xo-websock-path; }); + + xo-pywebsock = + (prev.callPackage ./pkgs/xo-pywebsock.nix { xo-cmake = xo-cmake; + xo-websock = xo-websock; + xo-pyutil = xo-pyutil; + xo-pywebutil = xo-pywebutil; + }).overrideAttrs + (old: { src = xo-pywebsock-path; }); + + # placeholder-D + + # user environment with all xo libraries present + xo-userenv = + (prev.callPackage ./pkgs/xo-userenv.nix { xo-cmake = xo-cmake; + xo-indentlog = xo-indentlog; + xo-callback = xo-callback; + xo-subsys = xo-subsys; + xo-refcnt = xo-refcnt; + xo-randomgen = xo-randomgen; + xo-ordinaltree = xo-ordinaltree; + xo-pyutil = xo-pyutil; + xo-reflect = xo-reflect; + xo-pyreflect = xo-pyreflect; + xo-unit = xo-unit; + xo-printjson = xo-printjson; + xo-pyprintjson = xo-pyprintjson; + xo-webutil = xo-webutil; + xo-pywebutil = xo-pywebutil; + xo-reactor = xo-reactor; + xo-pyreactor = xo-pyreactor; + xo-simulator = xo-simulator; + xo-pysimulator = xo-pysimulator; + xo-distribution = xo-distribution; + xo-pydistribution = xo-pydistribution; + xo-process = xo-process; + xo-pyprocess = xo-pyprocess; + xo-statistics = xo-statistics; + xo-kalmanfilter = xo-kalmanfilter; + xo-pykalmanfilter = xo-pykalmanfilter; + xo-websock = xo-websock; + xo-pywebsock = xo-pywebsock; + }).overrideAttrs(old: {}); + + + in + # attrs in this set provide derivations with all overlay changes applied. + # + # REMINDER: need expression like + # packages.xo-foo = appliedOverlay.xo-foo; + # above to export + { + xo-cmake = xo-cmake; + xo-indentlog = xo-indentlog; + xo-subsys = xo-subsys; + xo-refcnt = xo-refcnt; + xo-randomgen = xo-randomgen; + xo-ordinaltree = xo-ordinaltree; + xo-pyutil = xo-pyutil; + xo-reflect = xo-reflect; + xo-pyreflect = xo-pyreflect; + xo-unit = xo-unit; + xo-printjson = xo-printjson; + xo-pyprintjson = xo-pyprintjson; + xo-callback = xo-callback; + xo-webutil = xo-webutil; + xo-pywebutil = xo-pywebutil; + xo-reactor = xo-reactor; + xo-pyreactor = xo-pyreactor; + xo-simulator = xo-simulator; + xo-pysimulator = xo-pysimulator; + xo-distribution = xo-distribution; + xo-pydistribution = xo-pydistribution; + xo-process = xo-process; + xo-pyprocess = xo-pyprocess; + xo-statistics = xo-statistics; + xo-kalmanfilter = xo-kalmanfilter; + xo-pykalmanfilter = xo-pykalmanfilter; + xo-websock = xo-websock; + xo-pywebsock = xo-pywebsock; + # placeholder-E + + xo-userenv = xo-userenv; + + devShells = { + default = prev.mkShell.override + # but may need prev.clang16Stdenv instead of prev.stdenv here on macos + { stdenv = prev.stdenv; } + + { packages + = [ python + pybind11 + pythonPackages.coverage + pythonPackages.sphinx + pythonPackages.sphinx-rtd-theme + pythonPackages.breathe + # pythonPackages.pyarrow + boost # really for filemerge + + prev.llvmPackages_16.clang-unwrapped + + prev.anki + prev.mesa + prev.egl-wayland + + prev.emacs29 + prev.notmuch + prev.emacsPackages.notmuch + prev.inconsolata-lgc + + prev.doxygen + prev.graphviz + + prev.ditaa + prev.semgrep + prev.ripgrep + prev.git + prev.openssh + prev.cmake + prev.gdb + prev.which + prev.man + prev.man-pages + prev.less + prev.tree + prev.nix-tree + prev.lcov + + prev.arrow-cpp + prev.libwebsockets + prev.jsoncpp + prev.eigen + prev.catch2 + prev.pkg-config + prev.zlib + ]; + }; + }; + }); + }; + +} diff --git a/pkgs/xo-unit.nix b/pkgs/xo-unit.nix new file mode 100644 index 00000000..093c3091 --- /dev/null +++ b/pkgs/xo-unit.nix @@ -0,0 +1,37 @@ +{ + # nixpkgs dependencies + stdenv, cmake, catch2, # ... other deps here + + # xo dependencies + xo-cmake, xo-reflect, + + # args + + # attrset for fetching source code. + # { type, owner, repo, ref } + # + # e.g. type="github", owner="rconybea", repo="cmake-examples", ref="ex1b" + # + # see [[../flake.nix]] + # + #cmake-examples-ex1-path + + # someconfigurationoption ? false +} : + +stdenv.mkDerivation (finalattrs: + { + name = "xo-unit"; + + src = (fetchGit { + url = "https://github.com/rconybea/xo-unit"; + version = "1.0"; + #ref = "ex1"; + #rev = "c0472c9d7e4d2c53bfb977d3182380832fe96645"; + }); + + cmakeFlags = ["-DCMAKE_MODULE_PATH=${xo-cmake}/share/cmake"]; + doCheck = true; + nativeBuildInputs = [ cmake catch2 ]; + propagatedBuildInputs = [ ]; + }) From 0f202575f9dcf3b434314128418311297e2b8bd3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 12:52:19 -0400 Subject: [PATCH 0700/2693] + pkgs/xo-cmake.nix --- pkgs/xo-cmake.nix | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 pkgs/xo-cmake.nix diff --git a/pkgs/xo-cmake.nix b/pkgs/xo-cmake.nix new file mode 100644 index 00000000..91a8cbd2 --- /dev/null +++ b/pkgs/xo-cmake.nix @@ -0,0 +1,38 @@ +{ + # dependencies + stdenv, cmake, # ... other deps here + + # args + + # attrset for fetching source code. + # { type, owner, repo, ref } + # + # e.g. type="github", owner="rconybea", repo="cmake-examples", ref="ex1b" + # + # see [[../flake.nix]] + # + #cmake-examples-ex1-path + + # someconfigurationoption ? false +} : + +stdenv.mkDerivation (finalattrs: + { + name = "xo-cmake"; + +# src = cmake-examples-ex1-path; + + src = (fetchGit { + url = "https://github.com/rconybea/xo-cmake"; + version = "1.0"; + #ref = "ex1"; + #rev = "c0472c9d7e4d2c53bfb977d3182380832fe96645"; + }); + + nativeBuildInputs = [ cmake ]; + +# installPhase = '' +# mkdir -p $out +# echo 'This project intentionally has no install phase' +# ''; + }) From ac6b55c4478b70e6aedcd76892494304e6ad2c4d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 12:53:48 -0400 Subject: [PATCH 0701/2693] xo-unit: + ./pkgs/xo-indentlog.nix for nix CI --- pkgs/xo-indentlog.nix | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 pkgs/xo-indentlog.nix diff --git a/pkgs/xo-indentlog.nix b/pkgs/xo-indentlog.nix new file mode 100644 index 00000000..8277d455 --- /dev/null +++ b/pkgs/xo-indentlog.nix @@ -0,0 +1,36 @@ +{ + # nixpkgs dependencies + stdenv, cmake, catch2, # ... other deps here + + # xo dependencies + xo-cmake, + + # args + + # attrset for fetching source code. + # { type, owner, repo, ref } + # + # e.g. type="github", owner="rconybea", repo="cmake-examples", ref="ex1b" + # + # see [[../flake.nix]] + # + #cmake-examples-ex1-path + + # someconfigurationoption ? false +} : + +stdenv.mkDerivation (finalattrs: + { + name = "xo-indentlog"; + + src = (fetchGit { + url = "https://github.com/rconybea/indentlog"; + version = "1.0"; + #ref = "ex1"; + #rev = "c0472c9d7e4d2c53bfb977d3182380832fe96645"; + }); + + cmakeFlags = ["-DCMAKE_MODULE_PATH=${xo-cmake}/share/cmake"]; + doCheck = true; + nativeBuildInputs = [ cmake catch2 ]; + }) From 4cfad335784233d8bf99793f7ef8d626b94e3489 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 12:54:09 -0400 Subject: [PATCH 0702/2693] xo-unit: + ./pkgs/xo-refcnt.nix for nix CI --- pkgs/xo-refcnt.nix | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 pkgs/xo-refcnt.nix diff --git a/pkgs/xo-refcnt.nix b/pkgs/xo-refcnt.nix new file mode 100644 index 00000000..a571dd04 --- /dev/null +++ b/pkgs/xo-refcnt.nix @@ -0,0 +1,37 @@ +{ + # nixpkgs dependencies + stdenv, cmake, catch2, # ... other deps here + + # xo dependencies + xo-cmake, xo-indentlog + + # args + + # attrset for fetching source code. + # { type, owner, repo, ref } + # + # e.g. type="github", owner="rconybea", repo="cmake-examples", ref="ex1b" + # + # see [[../flake.nix]] + # + #cmake-examples-ex1-path + + # someconfigurationoption ? false +} : + +stdenv.mkDerivation (finalattrs: + { + name = "xo-refcnt"; + + src = (fetchGit { + url = "https://github.com/rconybea/refcnt"; + version = "1.0"; + #ref = "ex1"; + #rev = "c0472c9d7e4d2c53bfb977d3182380832fe96645"; + }); + + cmakeFlags = ["-DCMAKE_MODULE_PATH=${xo-cmake}/share/cmake"]; + doCheck = true; + nativeBuildInputs = [ cmake catch2 ]; + propagatedBuildInputs = [ xo-indentlog ]; + }) From f439108352deae1a61176e574694a3c500865155 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 12:54:31 -0400 Subject: [PATCH 0703/2693] xo-unit: + ./pkgs/xo-subsys.nix for nix CI --- pkgs/xo-subsys.nix | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 pkgs/xo-subsys.nix diff --git a/pkgs/xo-subsys.nix b/pkgs/xo-subsys.nix new file mode 100644 index 00000000..791fc341 --- /dev/null +++ b/pkgs/xo-subsys.nix @@ -0,0 +1,36 @@ +{ + # nixpkgs dependencies + stdenv, cmake, catch2, # ... other deps here + + # xo dependencies + xo-cmake, + + # args + + # attrset for fetching source code. + # { type, owner, repo, ref } + # + # e.g. type="github", owner="rconybea", repo="cmake-examples", ref="ex1b" + # + # see [[../flake.nix]] + # + #cmake-examples-ex1-path + + # someconfigurationoption ? false +} : + +stdenv.mkDerivation (finalattrs: + { + name = "xo-subsys"; + + src = (fetchGit { + url = "https://github.com/rconybea/subsys"; + version = "1.0"; + #ref = "ex1"; + #rev = "c0472c9d7e4d2c53bfb977d3182380832fe96645"; + }); + + cmakeFlags = ["-DCMAKE_MODULE_PATH=${xo-cmake}/share/cmake"]; + #doCheck = true; + nativeBuildInputs = [ cmake catch2 ]; + }) From e3325e09260be6304d1eadb9c8def017daacb7e6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 12:58:01 -0400 Subject: [PATCH 0704/2693] xo-unit: build: + pkgs/xo-reflect.nix for nix CI --- flake.nix | 264 +------------------------------------------- pkgs/xo-reflect.nix | 37 +++++++ 2 files changed, 40 insertions(+), 261 deletions(-) create mode 100644 pkgs/xo-reflect.nix diff --git a/flake.nix b/flake.nix index ed2b35f1..a54b4e28 100644 --- a/flake.nix +++ b/flake.nix @@ -52,29 +52,10 @@ inputs.xo-indentlog-path = { type = "github"; owner = "Rconybea"; repo = "indentlog"; flake = false; }; inputs.xo-refcnt-path = { type = "github"; owner = "Rconybea"; repo = "refcnt"; flake = false; }; inputs.xo-subsys-path = { type = "github"; owner = "Rconybea"; repo = "subsys"; flake = false; }; - inputs.xo-randomgen-path = { type = "github"; owner = "Rconybea"; repo = "randomgen"; flake = false; }; - inputs.xo-ordinaltree-path = { type = "github"; owner = "Rconybea"; repo = "xo-ordinaltree"; flake = false; }; - inputs.xo-pyutil-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyutil"; flake = false; }; + #inputs.xo-pyutil-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyutil"; flake = false; }; inputs.xo-reflect-path = { type = "github"; owner = "Rconybea"; repo = "reflect"; flake = false; }; - inputs.xo-pyreflect-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyreflect"; flake = false; }; - inputs.xo-printjson-path = { type = "github"; owner = "Rconybea"; repo = "xo-printjson"; flake = false; }; - inputs.xo-pyprintjson-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyprintjson"; flake = false; }; - inputs.xo-callback-path = { type = "github"; owner = "Rconybea"; repo = "xo-callback"; flake = false; }; - inputs.xo-webutil-path = { type = "github"; owner = "Rconybea"; repo = "xo-webutil"; flake = false; }; - inputs.xo-pywebutil-path = { type = "github"; owner = "Rconybea"; repo = "xo-pywebutil"; flake = false; }; - inputs.xo-reactor-path = { type = "github"; owner = "Rconybea"; repo = "xo-reactor"; flake = false; }; - inputs.xo-pyreactor-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyreactor"; flake = false; }; - inputs.xo-simulator-path = { type = "github"; owner = "Rconybea"; repo = "xo-simulator"; flake = false; }; - inputs.xo-pysimulator-path = { type = "github"; owner = "Rconybea"; repo = "xo-pysimulator"; flake = false; }; - inputs.xo-distribution-path = { type = "github"; owner = "Rconybea"; repo = "xo-distribution"; flake = false; }; - inputs.xo-pydistribution-path = { type = "github"; owner = "Rconybea"; repo = "xo-pydistribution"; flake = false; }; - inputs.xo-process-path = { type = "github"; owner = "Rconybea"; repo = "xo-process"; flake = false; }; - inputs.xo-pyprocess-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyprocess"; flake = false; }; - inputs.xo-statistics-path = { type = "github"; owner = "Rconybea"; repo = "xo-statistics"; flake = false; }; - inputs.xo-kalmanfilter-path = { type = "github"; owner = "Rconybea"; repo = "xo-kalmanfilter"; flake = false; }; - inputs.xo-pykalmanfilter-path = { type = "github"; owner = "Rconybea"; repo = "xo-pykalmanfilter"; flake = false; }; - inputs.xo-websock-path = { type = "github"; owner = "Rconybea"; repo = "xo-websock"; flake = false; }; - inputs.xo-pywebsock-path = { type = "github"; owner = "Rconybea"; repo = "xo-pywebsock"; flake = false; }; + #inputs.xo-pyreflect-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyreflect"; flake = false; }; + # placeholder-A outputs @@ -86,28 +67,6 @@ xo-refcnt-path, xo-subsys-path, xo-reflect-path, - xo-pyreflect-path, - xo-randomgen-path, - xo-ordinaltree-path, - xo-pyutil-path, - xo-printjson-path, - xo-pyprintjson-path, - xo-callback-path, - xo-webutil-path, - xo-pywebutil-path, - xo-reactor-path, - xo-pyreactor-path, - xo-simulator-path, - xo-pysimulator-path, - xo-distribution-path, - xo-pydistribution-path, - xo-process-path, - xo-pyprocess-path, - xo-statistics-path, - xo-kalmanfilter-path, - xo-pykalmanfilter-path, - xo-websock-path, - xo-pywebsock-path, # placeholder-B } : # out :: system -> {packages, devShells} @@ -140,29 +99,7 @@ packages.xo-indentlog = appliedOverlay.xo-indentlog; packages.xo-refcnt = appliedOverlay.xo-refcnt; packages.xo-subsys = appliedOverlay.xo-subsys; - packages.xo-randomgen = appliedOverlay.xo-randomgen; - packages.xo-ordinaltree = appliedOverlay.xo-ordinaltree; - packages.xo-pyutil = appliedOverlay.xo-pyutil; packages.xo-reflect = appliedOverlay.xo-reflect; - packages.xo-pyreflect = appliedOverlay.xo-pyreflect; - packages.xo-printjson = appliedOverlay.xo-printjson; - packages.xo-pyprintjson = appliedOverlay.xo-pyprintjson; - packages.xo-callback = appliedOverlay.xo-callback; - packages.xo-webutil = appliedOverlay.xo-webutil; - packages.xo-pywebutil = appliedOverlay.xo-pywebutil; - packages.xo-reactor = appliedOverlay.xo-reactor; - packages.xo-pyreactor = appliedOverlay.xo-pyreactor; - packages.xo-simulator = appliedOverlay.xo-simulator; - packages.xo-pysimulator = appliedOverlay.xo-pysimulator; - packages.xo-distribution = appliedOverlay.xo-distribution; - packages.xo-pydistribution = appliedOverlay.xo-pydistribution; - packages.xo-process = appliedOverlay.xo-process; - packages.xo-pyprocess = appliedOverlay.xo-pyprocess; - packages.xo-statistics = appliedOverlay.xo-statistics; - packages.xo-kalmanfilter = appliedOverlay.xo-kalmanfilter; - packages.xo-pykalmanfilter = appliedOverlay.xo-pykalmanfilter; - packages.xo-websock = appliedOverlay.xo-websock; - packages.xo-pywebsock = appliedOverlay.xo-pywebsock; # placeholder-C packages.xo-userenv = appliedOverlay.xo-userenv; @@ -218,200 +155,27 @@ xo-indentlog = xo-indentlog; }).overrideAttrs (old: { src = xo-refcnt-path; }); - xo-randomgen = - (prev.callPackage ./pkgs/xo-randomgen.nix { xo-cmake = xo-cmake; - xo-indentlog = xo-indentlog; }).overrideAttrs - (old: { src = xo-randomgen-path; }); - - xo-ordinaltree = - (prev.callPackage ./pkgs/xo-ordinaltree.nix { xo-cmake = xo-cmake; - xo-refcnt = xo-refcnt; - xo-randomgen = xo-randomgen; }).overrideAttrs - (old: { src = xo-ordinaltree-path; }); - - xo-pyutil = - (prev.callPackage ./pkgs/xo-pyutil.nix { xo-cmake = xo-cmake; - xo-refcnt = xo-refcnt; - python = python; - pybind11 = pybind11; - }).overrideAttrs - (old: { src = xo-pyutil-path; }); - xo-reflect = (prev.callPackage ./pkgs/xo-reflect.nix { xo-cmake = xo-cmake; xo-subsys = xo-subsys; xo-refcnt = xo-refcnt; }).overrideAttrs (old: { src = xo-reflect-path; }); - xo-pyreflect = - (prev.callPackage ./pkgs/xo-pyreflect.nix { xo-cmake = xo-cmake; - xo-refcnt = xo-refcnt; - xo-pyutil = xo-pyutil; - xo-reflect = xo-reflect; }).overrideAttrs - (old: { src = xo-pyreflect-path; }); - xo-unit = (prev.callPackage ./pkgs/xo-unit.nix { xo-cmake = xo-cmake; xo-reflect = xo-reflect; }).overrideAttrs (old: { src = ./.; }); - xo-printjson = - (prev.callPackage ./pkgs/xo-printjson.nix { xo-cmake = xo-cmake; - xo-reflect = xo-reflect; }).overrideAttrs - (old: { src = xo-printjson-path; }); - - xo-pyprintjson = - (prev.callPackage ./pkgs/xo-pyprintjson.nix { xo-cmake = xo-cmake; - xo-pyutil = xo-pyutil; - xo-printjson = xo-printjson; - xo-pyreflect = xo-pyreflect; }).overrideAttrs - (old: { src = xo-pyprintjson-path; }); - - xo-callback = - (prev.callPackage ./pkgs/xo-callback.nix { xo-cmake = xo-cmake; - xo-reflect = xo-reflect; }).overrideAttrs - (old: { src = xo-callback-path; }); - - xo-webutil = - (prev.callPackage ./pkgs/xo-webutil.nix { xo-cmake = xo-cmake; - xo-reflect = xo-reflect; - xo-callback = xo-callback; }).overrideAttrs - (old: { src = xo-webutil-path; }); - - xo-pywebutil = - (prev.callPackage ./pkgs/xo-pywebutil.nix { xo-cmake = xo-cmake; - xo-webutil = xo-webutil; - xo-pyutil = xo-pyutil; }).overrideAttrs - (old: { src = xo-pywebutil-path; }); - - xo-reactor = - (prev.callPackage ./pkgs/xo-reactor.nix { xo-cmake = xo-cmake; - xo-randomgen = xo-randomgen; - xo-webutil = xo-webutil; - xo-printjson = xo-printjson; - xo-ordinaltree = xo-ordinaltree; }).overrideAttrs - (old: { src = xo-reactor-path; }); - - xo-pyreactor = - (prev.callPackage ./pkgs/xo-pyreactor.nix { xo-cmake = xo-cmake; - xo-reactor = xo-reactor; - xo-pyutil = xo-pyutil; - xo-pyreflect = xo-pyreflect; - xo-pyprintjson = xo-pyprintjson; - }).overrideAttrs - (old: { src = xo-pyreactor-path; }); - - xo-simulator = - (prev.callPackage ./pkgs/xo-simulator.nix { xo-cmake = xo-cmake; - xo-reactor = xo-reactor; - }).overrideAttrs - (old: { src = xo-simulator-path; }); - - xo-pysimulator = - (prev.callPackage ./pkgs/xo-pysimulator.nix { xo-cmake = xo-cmake; - xo-simulator = xo-simulator; - xo-pyutil = xo-pyutil; - xo-pyreactor = xo-pyreactor; - }).overrideAttrs - (old: { src = xo-pysimulator-path; }); - - xo-distribution = - (prev.callPackage ./pkgs/xo-distribution.nix { xo-cmake = xo-cmake; - xo-refcnt = xo-refcnt; - }).overrideAttrs - (old: { src = xo-distribution-path; }); - - xo-pydistribution = - (prev.callPackage ./pkgs/xo-pydistribution.nix { xo-cmake = xo-cmake; - xo-distribution = xo-distribution; - xo-pyutil = xo-pyutil; - }).overrideAttrs - (old: { src = xo-pydistribution-path; }); - - xo-process = - (prev.callPackage ./pkgs/xo-process.nix { xo-cmake = xo-cmake; - xo-simulator = xo-simulator; - xo-randomgen = xo-randomgen; - }).overrideAttrs - (old: { src = xo-process-path; }); - - xo-pyprocess = - (prev.callPackage ./pkgs/xo-pyprocess.nix { xo-cmake = xo-cmake; - xo-process = xo-process; - xo-pyutil = xo-pyutil; - xo-pywebutil = xo-pywebutil; - xo-pyreactor = xo-pyreactor; - }).overrideAttrs - (old: { src = xo-pyprocess-path; }); - - xo-statistics = - (prev.callPackage ./pkgs/xo-statistics.nix { xo-cmake = xo-cmake; - #xo-reactor = xo-reactor; - }).overrideAttrs - (old: { src = xo-statistics-path; }); - - xo-kalmanfilter = - (prev.callPackage ./pkgs/xo-kalmanfilter.nix { xo-cmake = xo-cmake; - xo-statistics = xo-statistics; - xo-reactor = xo-reactor; - }).overrideAttrs - (old: { src = xo-kalmanfilter-path; }); - - - xo-pykalmanfilter = - (prev.callPackage ./pkgs/xo-pykalmanfilter.nix { xo-cmake = xo-cmake; - xo-pyutil = xo-pyutil; - xo-kalmanfilter = xo-kalmanfilter; - xo-pyreactor = xo-pyreactor; - }).overrideAttrs - (old: { src = xo-pykalmanfilter-path; }); - - xo-websock = - (prev.callPackage ./pkgs/xo-websock.nix { xo-cmake = xo-cmake; - xo-reactor = xo-reactor; - }).overrideAttrs - (old: { src = xo-websock-path; }); - - xo-pywebsock = - (prev.callPackage ./pkgs/xo-pywebsock.nix { xo-cmake = xo-cmake; - xo-websock = xo-websock; - xo-pyutil = xo-pyutil; - xo-pywebutil = xo-pywebutil; - }).overrideAttrs - (old: { src = xo-pywebsock-path; }); - # placeholder-D # user environment with all xo libraries present xo-userenv = (prev.callPackage ./pkgs/xo-userenv.nix { xo-cmake = xo-cmake; xo-indentlog = xo-indentlog; - xo-callback = xo-callback; xo-subsys = xo-subsys; xo-refcnt = xo-refcnt; - xo-randomgen = xo-randomgen; - xo-ordinaltree = xo-ordinaltree; - xo-pyutil = xo-pyutil; xo-reflect = xo-reflect; - xo-pyreflect = xo-pyreflect; xo-unit = xo-unit; - xo-printjson = xo-printjson; - xo-pyprintjson = xo-pyprintjson; - xo-webutil = xo-webutil; - xo-pywebutil = xo-pywebutil; - xo-reactor = xo-reactor; - xo-pyreactor = xo-pyreactor; - xo-simulator = xo-simulator; - xo-pysimulator = xo-pysimulator; - xo-distribution = xo-distribution; - xo-pydistribution = xo-pydistribution; - xo-process = xo-process; - xo-pyprocess = xo-pyprocess; - xo-statistics = xo-statistics; - xo-kalmanfilter = xo-kalmanfilter; - xo-pykalmanfilter = xo-pykalmanfilter; - xo-websock = xo-websock; - xo-pywebsock = xo-pywebsock; }).overrideAttrs(old: {}); @@ -426,30 +190,8 @@ xo-indentlog = xo-indentlog; xo-subsys = xo-subsys; xo-refcnt = xo-refcnt; - xo-randomgen = xo-randomgen; - xo-ordinaltree = xo-ordinaltree; - xo-pyutil = xo-pyutil; xo-reflect = xo-reflect; - xo-pyreflect = xo-pyreflect; xo-unit = xo-unit; - xo-printjson = xo-printjson; - xo-pyprintjson = xo-pyprintjson; - xo-callback = xo-callback; - xo-webutil = xo-webutil; - xo-pywebutil = xo-pywebutil; - xo-reactor = xo-reactor; - xo-pyreactor = xo-pyreactor; - xo-simulator = xo-simulator; - xo-pysimulator = xo-pysimulator; - xo-distribution = xo-distribution; - xo-pydistribution = xo-pydistribution; - xo-process = xo-process; - xo-pyprocess = xo-pyprocess; - xo-statistics = xo-statistics; - xo-kalmanfilter = xo-kalmanfilter; - xo-pykalmanfilter = xo-pykalmanfilter; - xo-websock = xo-websock; - xo-pywebsock = xo-pywebsock; # placeholder-E xo-userenv = xo-userenv; diff --git a/pkgs/xo-reflect.nix b/pkgs/xo-reflect.nix new file mode 100644 index 00000000..9f151c5f --- /dev/null +++ b/pkgs/xo-reflect.nix @@ -0,0 +1,37 @@ +{ + # nixpkgs dependencies + stdenv, cmake, catch2, # ... other deps here + + # xo dependencies + xo-cmake, xo-refcnt, xo-subsys, + + # args + + # attrset for fetching source code. + # { type, owner, repo, ref } + # + # e.g. type="github", owner="rconybea", repo="cmake-examples", ref="ex1b" + # + # see [[../flake.nix]] + # + #cmake-examples-ex1-path + + # someconfigurationoption ? false +} : + +stdenv.mkDerivation (finalattrs: + { + name = "xo-reflect"; + + src = (fetchGit { + url = "https://github.com/rconybea/reflect"; + version = "1.0"; + #ref = "ex1"; + #rev = "c0472c9d7e4d2c53bfb977d3182380832fe96645"; + }); + + cmakeFlags = ["-DCMAKE_MODULE_PATH=${xo-cmake}/share/cmake"]; + doCheck = true; + nativeBuildInputs = [ cmake catch2 ]; + propagatedBuildInputs = [ xo-subsys xo-refcnt ]; + }) From cfd77c388856e313cd8f454af4d5596c0ade00e6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 13:00:30 -0400 Subject: [PATCH 0705/2693] xo-unit: + ./pkgs/xo-userenv for nix CI --- pkgs/xo-userenv.nix | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 pkgs/xo-userenv.nix diff --git a/pkgs/xo-userenv.nix b/pkgs/xo-userenv.nix new file mode 100644 index 00000000..9a622b55 --- /dev/null +++ b/pkgs/xo-userenv.nix @@ -0,0 +1,24 @@ +{ + # nixpkgs dependencies + buildFHSUserEnv, # ... other deps here + + # xo dependencies + xo-cmake, xo-indentlog, xo-subsys, xo-refcnt, xo-reflect, xo-unit, + + # other args + + # someconfigurationoption ? false +} : + +buildFHSUserEnv { + name = "xo-userenv"; + targetPkgs = pkgs: [ xo-cmake + xo-indentlog + xo-subsys + xo-refcnt + xo-reflect + xo-unit + ]; + # runScript = ...; + # profile = ...; +} From 9ddee6c61cff937320a7c9442a4dc71caefc79ef Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 13:01:10 -0400 Subject: [PATCH 0706/2693] xo-unit: + flake.lock file --- flake.lock | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 flake.lock diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..c4d40ddd --- /dev/null +++ b/flake.lock @@ -0,0 +1,141 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "narHash": "sha256-ci7ghtn0YKXw68Wkufou0DK3pwTmkfWeFOYkRsnLagc=", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/217b3e910660fbf603b0995a6d2c3992aef4cc37.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/217b3e910660fbf603b0995a6d2c3992aef4cc37.tar.gz" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "xo-cmake-path": "xo-cmake-path", + "xo-indentlog-path": "xo-indentlog-path", + "xo-refcnt-path": "xo-refcnt-path", + "xo-reflect-path": "xo-reflect-path", + "xo-subsys-path": "xo-subsys-path" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "xo-cmake-path": { + "flake": false, + "locked": { + "lastModified": 1712015268, + "narHash": "sha256-+nu4z5LA1nSE8NwQ0l0AhhykUO9B2b5BvEs1mLNiI3Y=", + "owner": "Rconybea", + "repo": "xo-cmake", + "rev": "b8f2360b899739b72272036324a6997aa9169c8f", + "type": "github" + }, + "original": { + "owner": "Rconybea", + "repo": "xo-cmake", + "type": "github" + } + }, + "xo-indentlog-path": { + "flake": false, + "locked": { + "lastModified": 1712008884, + "narHash": "sha256-WGe9fQq6PM9CbZuA0maJOxOErN71l9J1/cHPigkLJoU=", + "owner": "Rconybea", + "repo": "indentlog", + "rev": "20774158ad3cd2f5feadecf9b994972443538ef7", + "type": "github" + }, + "original": { + "owner": "Rconybea", + "repo": "indentlog", + "type": "github" + } + }, + "xo-refcnt-path": { + "flake": false, + "locked": { + "lastModified": 1711737792, + "narHash": "sha256-Grel2sXne5LUGNqlcxcrbhqstXXAQxhQsXpd5Rcjn7E=", + "owner": "Rconybea", + "repo": "refcnt", + "rev": "cffffdae7c15273dfad8e04acf64193e644bda81", + "type": "github" + }, + "original": { + "owner": "Rconybea", + "repo": "refcnt", + "type": "github" + } + }, + "xo-reflect-path": { + "flake": false, + "locked": { + "lastModified": 1710545203, + "narHash": "sha256-L3421dvkTtibSGchOjj4RdCITtnlX8+4rU3XEnakf1s=", + "owner": "Rconybea", + "repo": "reflect", + "rev": "fe986a320a2b1993a8399602cb3a3b6dc8c86253", + "type": "github" + }, + "original": { + "owner": "Rconybea", + "repo": "reflect", + "type": "github" + } + }, + "xo-subsys-path": { + "flake": false, + "locked": { + "lastModified": 1711737221, + "narHash": "sha256-7VjuMxme3wmht32nzRjw6XhzfXu4szmNZjErDqCp6E8=", + "owner": "Rconybea", + "repo": "subsys", + "rev": "06bd4324305996db9b24a98c010a63c4593c1e21", + "type": "github" + }, + "original": { + "owner": "Rconybea", + "repo": "subsys", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} From 3f9e5f11cae0acc3c2a54efe84ea1bec4895839f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 13:02:29 -0400 Subject: [PATCH 0707/2693] xo-unit: bugfix: must not commit flake.lock for self --- flake.lock | 141 ----------------------------------------------------- 1 file changed, 141 deletions(-) delete mode 100644 flake.lock diff --git a/flake.lock b/flake.lock deleted file mode 100644 index c4d40ddd..00000000 --- a/flake.lock +++ /dev/null @@ -1,141 +0,0 @@ -{ - "nodes": { - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "narHash": "sha256-ci7ghtn0YKXw68Wkufou0DK3pwTmkfWeFOYkRsnLagc=", - "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/217b3e910660fbf603b0995a6d2c3992aef4cc37.tar.gz" - }, - "original": { - "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/217b3e910660fbf603b0995a6d2c3992aef4cc37.tar.gz" - } - }, - "root": { - "inputs": { - "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs", - "xo-cmake-path": "xo-cmake-path", - "xo-indentlog-path": "xo-indentlog-path", - "xo-refcnt-path": "xo-refcnt-path", - "xo-reflect-path": "xo-reflect-path", - "xo-subsys-path": "xo-subsys-path" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "xo-cmake-path": { - "flake": false, - "locked": { - "lastModified": 1712015268, - "narHash": "sha256-+nu4z5LA1nSE8NwQ0l0AhhykUO9B2b5BvEs1mLNiI3Y=", - "owner": "Rconybea", - "repo": "xo-cmake", - "rev": "b8f2360b899739b72272036324a6997aa9169c8f", - "type": "github" - }, - "original": { - "owner": "Rconybea", - "repo": "xo-cmake", - "type": "github" - } - }, - "xo-indentlog-path": { - "flake": false, - "locked": { - "lastModified": 1712008884, - "narHash": "sha256-WGe9fQq6PM9CbZuA0maJOxOErN71l9J1/cHPigkLJoU=", - "owner": "Rconybea", - "repo": "indentlog", - "rev": "20774158ad3cd2f5feadecf9b994972443538ef7", - "type": "github" - }, - "original": { - "owner": "Rconybea", - "repo": "indentlog", - "type": "github" - } - }, - "xo-refcnt-path": { - "flake": false, - "locked": { - "lastModified": 1711737792, - "narHash": "sha256-Grel2sXne5LUGNqlcxcrbhqstXXAQxhQsXpd5Rcjn7E=", - "owner": "Rconybea", - "repo": "refcnt", - "rev": "cffffdae7c15273dfad8e04acf64193e644bda81", - "type": "github" - }, - "original": { - "owner": "Rconybea", - "repo": "refcnt", - "type": "github" - } - }, - "xo-reflect-path": { - "flake": false, - "locked": { - "lastModified": 1710545203, - "narHash": "sha256-L3421dvkTtibSGchOjj4RdCITtnlX8+4rU3XEnakf1s=", - "owner": "Rconybea", - "repo": "reflect", - "rev": "fe986a320a2b1993a8399602cb3a3b6dc8c86253", - "type": "github" - }, - "original": { - "owner": "Rconybea", - "repo": "reflect", - "type": "github" - } - }, - "xo-subsys-path": { - "flake": false, - "locked": { - "lastModified": 1711737221, - "narHash": "sha256-7VjuMxme3wmht32nzRjw6XhzfXu4szmNZjErDqCp6E8=", - "owner": "Rconybea", - "repo": "subsys", - "rev": "06bd4324305996db9b24a98c010a63c4593c1e21", - "type": "github" - }, - "original": { - "owner": "Rconybea", - "repo": "subsys", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} From f8e337c783acd3b68bdb1f079ba651ca22d2aa36 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 13:03:33 -0400 Subject: [PATCH 0708/2693] xo-unit: build: .gitignore ++ flake.lock --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 13c0afb7..e1c0d400 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# flake.nix builds source in this tree. +# must not commit flake.lock that would need hash from this repo +flake.lock # clangd working space (see emacs+lsp) .cache # typical cmake build directory (source-tree-nephew) From 554edcd045fe48e201797f959eedeb771f50cbdd Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 13:04:46 -0400 Subject: [PATCH 0709/2693] looks like nix flakes needs not-git-ignored flake.lock --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index e1c0d400..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,3 @@ -# flake.nix builds source in this tree. -# must not commit flake.lock that would need hash from this repo -flake.lock # clangd working space (see emacs+lsp) .cache # typical cmake build directory (source-tree-nephew) From d446f0671c9e007bc9f3faa6d7ed0165b6e4754f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 13:07:20 -0400 Subject: [PATCH 0710/2693] xo-unit: bugfix in flake.nix, must export xo-unit --- flake.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/flake.nix b/flake.nix index a54b4e28..c5ea5b37 100644 --- a/flake.nix +++ b/flake.nix @@ -100,6 +100,7 @@ packages.xo-refcnt = appliedOverlay.xo-refcnt; packages.xo-subsys = appliedOverlay.xo-subsys; packages.xo-reflect = appliedOverlay.xo-reflect; + packages.xo-unit = appliedOverlay.xo-unit; # placeholder-C packages.xo-userenv = appliedOverlay.xo-userenv; From 4446fc3e1e31fc96f2736985a2af6b216254a11c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 14:58:11 -0400 Subject: [PATCH 0711/2693] xo-unit: docs: + _static dir --- docs/_static/README | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/_static/README 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 From 7432d3798cb6848d5b4262173486f5a878978477 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 14:59:20 -0400 Subject: [PATCH 0712/2693] xo-unit: bugfix: xo::obs -> xo::unit in examples --- example/ex1/ex1.cpp | 2 +- example/ex2/ex2.cpp | 6 +++--- example/ex3/ex3.cpp | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 74d0dc10..2e286e7c 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -5,7 +5,7 @@ int main () { - namespace qty = xo::obs::qty; + namespace qty = xo::unit::qty; using namespace std; auto t = qty::milliseconds(10); diff --git a/example/ex2/ex2.cpp b/example/ex2/ex2.cpp index 96e574ed..2caf4ee3 100644 --- a/example/ex2/ex2.cpp +++ b/example/ex2/ex2.cpp @@ -5,9 +5,9 @@ int main () { - namespace u = xo::obs::units; - namespace qty = xo::obs::qty; - using xo::obs::quantity; + namespace u = xo::unit::units; + namespace qty = xo::unit::qty; + using xo::unit::quantity; using namespace std; quantity t = qty::milliseconds(10); diff --git a/example/ex3/ex3.cpp b/example/ex3/ex3.cpp index d7939957..77fcf03e 100644 --- a/example/ex3/ex3.cpp +++ b/example/ex3/ex3.cpp @@ -5,9 +5,9 @@ int main () { - namespace u = xo::obs::units; - namespace qty = xo::obs::qty; - using xo::obs::quantity; + namespace u = xo::unit::units; + namespace qty = xo::unit::qty; + using xo::unit::quantity; using namespace std; auto t1 = qty::milliseconds(1); From bde506df1c3974ae605c82ac715962027bd41179 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 15:00:01 -0400 Subject: [PATCH 0713/2693] xo-unit: build: + custom 'docs' target, same as 'sphinx' --- docs/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index bc00ca12..8c7b9b92 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -94,4 +94,9 @@ else() DESTINATION ${CMAKE_INSTALL_PREFIX}/share/doc/${PROJECT_NAME} COMPONENT Documentation OPTIONAL) + + # make docs --> generate sphinx documentation + add_custom_target( + docs + DEPENDS sphinx) endif() From 439c2d03c2078f976441d2ab4ac24e6a327a0938 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 15:00:26 -0400 Subject: [PATCH 0714/2693] xo-unit: docs: Helvetica -> HelveticaNeue --- docs/Doxyfile.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in index 67b45f8c..8d1f25ba 100644 --- a/docs/Doxyfile.in +++ b/docs/Doxyfile.in @@ -2503,7 +2503,7 @@ DOT_NUM_THREADS = 0 # The default value is: fontname=Helvetica,fontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" +DOT_COMMON_ATTR = "fontname=HelveticaNeue,fontsize=10" # DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can # add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. Date: Wed, 3 Apr 2024 15:01:42 -0400 Subject: [PATCH 0715/2693] xo-unit: nix: build docs --- flake.nix | 11 ++++++---- pkgs/xo-unit.nix | 52 ++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/flake.nix b/flake.nix index c5ea5b37..44369377 100644 --- a/flake.nix +++ b/flake.nix @@ -130,9 +130,9 @@ #doxygen = prev.doxygen; pybind11 = pythonPackages.pybind11; - #breathe = python3Packages.breathe; - #sphinx = python3Packages.sphinx; - #sphinx-rtd-theme = python3Packages.sphinx-rtd-theme; + #breathe = pythonPackages.breathe; + #sphinx = pythonPackages.sphinx; + #sphinx-rtd-theme = pythonPackages.sphinx-rtd-theme; #extras1 = { boost = boost; }; #extras2 = { boost = boost; python3Packages = python3Packages; pybind11 = pybind11; }; @@ -164,7 +164,10 @@ xo-unit = (prev.callPackage ./pkgs/xo-unit.nix { xo-cmake = xo-cmake; - xo-reflect = xo-reflect; }).overrideAttrs + xo-reflect = xo-reflect; + python = python; + pythonPackages = pythonPackages; + }).overrideAttrs (old: { src = ./.; }); # placeholder-D diff --git a/pkgs/xo-unit.nix b/pkgs/xo-unit.nix index 093c3091..e3ee6d43 100644 --- a/pkgs/xo-unit.nix +++ b/pkgs/xo-unit.nix @@ -1,8 +1,27 @@ { - # nixpkgs dependencies + # 1. nixpkgs dependencies + # 1.1. python + python, pythonPackages, + + # 1.2. particular python packages + sphinx ? pythonPackages.sphinx, + sphinx-rtd-theme ? pythonPackages.sphinx-rtd-theme, + breathe ? pythonPackages.breathe, + + # 1.3. document-generation packages + # use of makeFontsConf adapted from nixpkgs/development/libraries/gtkmm/4.x.nix + # + doxygen, graphviz, + #fontconfig, + makeFontsConf, + + # used for graphviz (dot) see docs/Doxyfile.in + helvetica-neue-lt-std, + + # 1.4. c++ build/utest chain stdenv, cmake, catch2, # ... other deps here - # xo dependencies + # 2. xo dependencies xo-cmake, xo-reflect, # args @@ -31,7 +50,32 @@ stdenv.mkDerivation (finalattrs: }); cmakeFlags = ["-DCMAKE_MODULE_PATH=${xo-cmake}/share/cmake"]; + + FONTCONFIG_FILE = makeFontsConf { fontDirectories = [ helvetica-neue-lt-std ]; }; + + # move HOME so fontconfig can do sensible things + buildPhase = '' + set -x + + echo "FONTCONFIG_FILE=$FONTCONFIG_FILE" + + #export FONTCONFIG_FILE=$fontconfig.out/etc/fonts/fonts.conf + export HOME=$TMPDIR + export XDG_CONFIG_HOME=$TMPDIR + + mkdir $XDG_CONFIG_HOME/fontconfig + + #grep xdg $FONTCONFIG_FILE + + #$fontconfig.out/bin/fc-cache -v + + make && make docs + ''; + + # NOTE: helvetic-neue-lt-std has unfree license + #FONTCONFIG_FILE = makeFontsConf { fontDirectories = [ helvetica-neue-lt-std ]; }; + doCheck = true; - nativeBuildInputs = [ cmake catch2 ]; - propagatedBuildInputs = [ ]; + nativeBuildInputs = [ cmake catch2 doxygen graphviz sphinx sphinx-rtd-theme breathe ]; + propagatedBuildInputs = [ xo-reflect ]; }) From b6467d0013b96073a27838c11c9735c9b5b8bd8c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 15:09:07 -0400 Subject: [PATCH 0716/2693] github: + nix workflow --- .github/workflows/nix-main.yml | 50 ++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/workflows/nix-main.yml diff --git a/.github/workflows/nix-main.yml b/.github/workflows/nix-main.yml new file mode 100644 index 00000000..7154fc3c --- /dev/null +++ b/.github/workflows/nix-main.yml @@ -0,0 +1,50 @@ +# Workflow to build xo-unit using custom docker container; +# container provides nix support +# +# NOTES +# 1. GIT_TOKEN granted automatically by github. +# has read permission on public resources + read/write permission on this repo +# +# 2. container built from [[https:github.com:rconybea/docker-nix-builder]] +# Includes dependencies: +# - nix +# - compiler toolchain: gcc, binutils, bash, etc +# - git +# - cmake +# - catch2 +# - pybind11 + python +# - libwebsockets +# - jsoncpp +# +name: xo-unit nix builder + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + BUILD_TYPE: Release + +jobs: + build_job: + name: xo-unit nix build on docker-nix-builder + runs-on: ubuntu-latest + container: + # custom docker image. see github.com:rconybea/docker-nix-builder for definition + image: ghcr.io/rconybea/docker-nix-builder:v1 + + steps: + # not using usual checkout actions: they don't work out-of-the-box from within a container + + - name: xo-unix + run: | + echo "::group::clone xo-unit repo" + mkdir -p repo + GIT_SSL_NO_VERIFY=true git clone https://${{env.GIT_USER}}:${{env.GIT_TOKEN}}@github.com/rconybea/xo-unix.git repo + echo "::endgroup" + + echo "::group::build xo-unit with nix" + (cd repo/xo-unit && nix build -L --print-build-logs .#xo-unit && tree ./result) + echo "::endgroup" From fc7a2564f09edcc32f7808401ab30eda15bcc43b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 15:10:32 -0400 Subject: [PATCH 0717/2693] github: lame workflow typo, sheesh! --- .github/workflows/nix-main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nix-main.yml b/.github/workflows/nix-main.yml index 7154fc3c..83d8af73 100644 --- a/.github/workflows/nix-main.yml +++ b/.github/workflows/nix-main.yml @@ -38,11 +38,11 @@ jobs: steps: # not using usual checkout actions: they don't work out-of-the-box from within a container - - name: xo-unix + - name: xo-unit run: | echo "::group::clone xo-unit repo" mkdir -p repo - GIT_SSL_NO_VERIFY=true git clone https://${{env.GIT_USER}}:${{env.GIT_TOKEN}}@github.com/rconybea/xo-unix.git repo + GIT_SSL_NO_VERIFY=true git clone https://${{env.GIT_USER}}:${{env.GIT_TOKEN}}@github.com/rconybea/xo-unit.git repo echo "::endgroup" echo "::group::build xo-unit with nix" From 52f8aec04f6ebaa01fa34986ada79bd88db8a97a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 15:12:00 -0400 Subject: [PATCH 0718/2693] github: bugfix in nix workflow --- .github/workflows/nix-main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nix-main.yml b/.github/workflows/nix-main.yml index 83d8af73..590f5f84 100644 --- a/.github/workflows/nix-main.yml +++ b/.github/workflows/nix-main.yml @@ -42,7 +42,7 @@ jobs: run: | echo "::group::clone xo-unit repo" mkdir -p repo - GIT_SSL_NO_VERIFY=true git clone https://${{env.GIT_USER}}:${{env.GIT_TOKEN}}@github.com/rconybea/xo-unit.git repo + GIT_SSL_NO_VERIFY=true git clone https://${{env.GIT_USER}}:${{env.GIT_TOKEN}}@github.com/rconybea/xo-unit.git repo/xo-unit echo "::endgroup" echo "::group::build xo-unit with nix" From d2a8d9bbe55a77a1b707193e23ff92f63aae4e81 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 15:14:26 -0400 Subject: [PATCH 0719/2693] xo-unit: github: allow unfree helvetica-neue font --- .github/workflows/nix-main.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nix-main.yml b/.github/workflows/nix-main.yml index 590f5f84..f0ae63e3 100644 --- a/.github/workflows/nix-main.yml +++ b/.github/workflows/nix-main.yml @@ -46,5 +46,6 @@ jobs: echo "::endgroup" echo "::group::build xo-unit with nix" - (cd repo/xo-unit && nix build -L --print-build-logs .#xo-unit && tree ./result) + export NIXPKGS_ALLOW_UNFREE=1 + (cd repo/xo-unit && nix build --impure -L --print-build-logs .#xo-unit && tree ./result) echo "::endgroup" From 0f7a739e2fdb51354a28f34546fe7de56713b389 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 17:23:05 -0400 Subject: [PATCH 0720/2693] xo-unit: docs: switch doxygen label font Helvetica->lato --- docs/Doxyfile.in | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in index 8d1f25ba..13dfb0fa 100644 --- a/docs/Doxyfile.in +++ b/docs/Doxyfile.in @@ -2503,7 +2503,8 @@ DOT_NUM_THREADS = 0 # The default value is: fontname=Helvetica,fontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_COMMON_ATTR = "fontname=HelveticaNeue,fontsize=10" +DOT_COMMON_ATTR = "fontname=lato,fontsize=10" +#DOT_COMMON_ATTR = "fontname=HelveticaNeue,fontsize=10" # DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can # add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. Date: Wed, 3 Apr 2024 17:23:35 -0400 Subject: [PATCH 0721/2693] xo-unit: bugfix: quantity/quantity --- include/xo/unit/quantity.hpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index adb6cf80..9a86baba 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -123,8 +123,20 @@ namespace xo { using repr_type = std::common_type_t; - repr_type r_scale = ((scale() * c_scalefactor_inexact * unit_type::scalefactor_type::num) - / (y.scale() * unit_type::scalefactor_type::den)); + repr_type r_scale = ((scale() * c_scalefactor_inexact * exact_scalefactor_type::num) + / (y.scale() * exact_scalefactor_type::den)); + +# ifdef NOT_USING_DEBUG + using xo::reflect::Reflect; + scope log(XO_DEBUG(true /*c_debug_flag*/)); + log && log(xtag("unit_divide_type", Reflect::require()->canonical_name())); + log && log(xtag("exact_unit_type", Reflect::require()->canonical_name())); + log && log(xtag("norm_unit_type", Reflect::require()->canonical_name())); + log && log(xtag("exact_scalefactor_type", Reflect::require()->canonical_name())); + log && log(xtag("c_scalefactor_inexact", c_scalefactor_inexact)); + log && log(xtag("r_scale", r_scale)); + log && log(xtag("repr_type", Reflect::require()->canonical_name())); +# endif return quantity::promote(r_scale); } From bb5fc1cb4e5e4bbfc3b71a75d23a65fb2be93dfa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 17:23:56 -0400 Subject: [PATCH 0722/2693] xo-unit: quantity: + with_unit() + with_repr() convenience --- include/xo/unit/quantity.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index 9a86baba..38fa44d8 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -77,6 +77,12 @@ namespace xo { return promoter::promote(x); } + template + constexpr quantity with_unit() const { return *this; } + + template + constexpr quantity with_repr() const { return quantity::promote(scale_); } + template auto multiply(Quantity2 y) const { //constexpr bool c_debug_flag = false; From 7c7373427ed6c980f78ff7e5eb874387a910e9d7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 17:24:55 -0400 Subject: [PATCH 0723/2693] xo-unit: refactor: c_basis_power<> -> basis_power<> --- example/ex1/CMakeLists.txt | 1 + example/ex2/CMakeLists.txt | 1 + example/ex3/CMakeLists.txt | 1 + example/ex3/ex3.cpp | 1 - include/xo/unit/quantity.hpp | 2 +- utest/quantity.test.cpp | 95 ++++++++++++++++++++++++------------ 6 files changed, 69 insertions(+), 32 deletions(-) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt index 0d415a06..679f538f 100644 --- a/example/ex1/CMakeLists.txt +++ b/example/ex1/CMakeLists.txt @@ -10,5 +10,6 @@ xo_include_options2(${SELF_EXE}) # dependencies.. xo_self_dependency(${SELF_EXE} xo_unit) +xo_dependency(${SELF_EXE} reflect) # end CMakeLists.txt diff --git a/example/ex2/CMakeLists.txt b/example/ex2/CMakeLists.txt index fa98d33b..e762d370 100644 --- a/example/ex2/CMakeLists.txt +++ b/example/ex2/CMakeLists.txt @@ -10,5 +10,6 @@ xo_include_options2(${SELF_EXE}) # dependencies.. xo_self_dependency(${SELF_EXE} xo_unit) +xo_dependency(${SELF_EXE} reflect) # end CMakeLists.txt diff --git a/example/ex3/CMakeLists.txt b/example/ex3/CMakeLists.txt index e6f40fc1..8f78979b 100644 --- a/example/ex3/CMakeLists.txt +++ b/example/ex3/CMakeLists.txt @@ -10,5 +10,6 @@ xo_include_options2(${SELF_EXE}) # dependencies.. xo_self_dependency(${SELF_EXE} xo_unit) +xo_dependency(${SELF_EXE} reflect) # end CMakeLists.txt diff --git a/example/ex3/ex3.cpp b/example/ex3/ex3.cpp index 77fcf03e..61a1d49c 100644 --- a/example/ex3/ex3.cpp +++ b/example/ex3/ex3.cpp @@ -7,7 +7,6 @@ int main () { namespace u = xo::unit::units; namespace qty = xo::unit::qty; - using xo::unit::quantity; using namespace std; auto t1 = qty::milliseconds(1); diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index 38fa44d8..232583b4 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -63,7 +63,7 @@ namespace xo { * q.basis_power -> 0 **/ template - static constexpr PowerRepr c_basis_power = from_ratio::power_type>(); + static constexpr PowerRepr basis_power = from_ratio::power_type>(); /** @brief get scale value (relative to unit) (@ref scale_) **/ constexpr Repr scale() const { return scale_; } diff --git a/utest/quantity.test.cpp b/utest/quantity.test.cpp index 352ca2c5..75adba87 100644 --- a/utest/quantity.test.cpp +++ b/utest/quantity.test.cpp @@ -54,8 +54,8 @@ namespace xo { REQUIRE(t.scale() == 1); - static_assert(t.c_basis_power == 1); - static_assert(t.c_basis_power == 0); + static_assert(t.basis_power == 1); + static_assert(t.basis_power == 0); } /*TEST_CASE(quantity)*/ TEST_CASE("add1", "[quantity]") { @@ -74,8 +74,8 @@ namespace xo { CHECK(strcmp(sum.unit_cstr(), "s") == 0); static_assert(std::same_as); - static_assert(t1.c_basis_power == 1); - static_assert(t2.c_basis_power == 1); + static_assert(t1.basis_power == 1); + static_assert(t2.basis_power == 1); REQUIRE(sum.scale() == 3); @@ -95,7 +95,7 @@ namespace xo { auto m2 = minutes(2); { - static_assert(m2.c_basis_power == 1); + static_assert(m2.basis_power == 1); log && log(xtag("m2.scale", m2.scale()), xtag("m2.unit", m2.unit_cstr())); @@ -118,7 +118,7 @@ namespace xo { auto sum = t1 + t2; static_assert(std::same_as); - static_assert(sum.c_basis_power == 1); + static_assert(sum.basis_power == 1); log && log(xtag("t1.unit", t1.unit_cstr()), xtag("t2.unit", t2.unit_cstr())); log && log(xtag("sum.unit", sum.unit_cstr())); @@ -136,7 +136,7 @@ namespace xo { /* sum will take unit from lhs argument to + */ auto sum = t1 + t2; - static_assert(sum.c_basis_power == 1); + static_assert(sum.basis_power == 1); static_assert(std::same_as); REQUIRE(sum.scale() == 121); @@ -187,10 +187,10 @@ namespace xo { { q2 = q1; - static_assert(q1.c_basis_power == 1); - static_assert(q1.c_basis_power == -1); - static_assert(q2.c_basis_power == 1); - static_assert(q2.c_basis_power == -1); + static_assert(q1.basis_power == 1); + static_assert(q1.basis_power == -1); + static_assert(q2.basis_power == 1); + static_assert(q2.basis_power == -1); log && log(XTAG(q1), XTAG(q2)); @@ -249,7 +249,7 @@ namespace xo { { auto sum = vol_250d + vol_30d; - static_assert(sum.c_basis_power == -0.5); + static_assert(sum.basis_power == -0.5); log && log(XTAG(sum)); @@ -279,7 +279,7 @@ namespace xo { { auto r = q0 * q1; - static_assert(r.c_basis_power == 2); + static_assert(r.basis_power == 2); log && log(xtag("q0", q0), xtag("q1", q1), xtag("q0*q1", r)); log && log(xtag("r.type", Reflect::require()->canonical_name())); @@ -292,7 +292,7 @@ namespace xo { { auto r = q1 * q2; - static_assert(r.c_basis_power == 2); + static_assert(r.basis_power == 2); log && log(xtag("q1", q1), xtag("q2", q2), xtag("q1*q2", r)); log && log(xtag("r.type", Reflect::require()->canonical_name())); @@ -305,7 +305,7 @@ namespace xo { { auto r = q2 * q1; - static_assert(r.c_basis_power == 2); + static_assert(r.basis_power == 2); log && log(xtag("q1", q1), xtag("q2", q2), xtag("r=q2*q1", r)); log && log(xtag("r.type", Reflect::require()->canonical_name())); @@ -318,7 +318,7 @@ namespace xo { { auto r = q2 * 60; - static_assert(r.c_basis_power == 1); + static_assert(r.basis_power == 1); static_assert(std::same_as); log && log(xtag("q2*60", r)); @@ -332,7 +332,7 @@ namespace xo { { auto r = q2 * 60U; - static_assert(r.c_basis_power == 1); + static_assert(r.basis_power == 1); static_assert(std::same_as); log && log(xtag("q2*60U", r)); @@ -346,7 +346,7 @@ namespace xo { { auto r = (q2 * 60.5); - static_assert(r.c_basis_power == 1); + static_assert(r.basis_power == 1); /* verify dimension */ static_assert(std::same_as); @@ -365,7 +365,7 @@ namespace xo { auto r = (q2 * 60.5f); /* verify dimension */ - static_assert(r.c_basis_power == 1); + static_assert(r.basis_power == 1); static_assert(std::same_as); log && log(xtag("r.type", Reflect::require()->canonical_name())); @@ -378,7 +378,7 @@ namespace xo { { auto r = 60 * q2; - static_assert(r.c_basis_power == 1); + static_assert(r.basis_power == 1); static_assert(std::same_as); log && log(xtag("60*q2", r)); @@ -394,7 +394,7 @@ namespace xo { auto r = 60.5 * q2; - static_assert(r.c_basis_power == 1); + static_assert(r.basis_power == 1); log && log(xtag("r.type", Reflect::require()->canonical_name())); static_assert(std::same_as); @@ -409,7 +409,7 @@ namespace xo { auto r = 60.5f * q2; - static_assert(r.c_basis_power == 1); + static_assert(r.basis_power == 1); static_assert(std::same_as); log && log(xtag("r.type", Reflect::require()->canonical_name())); @@ -475,7 +475,7 @@ namespace xo { log && log(XTAG(q0), xtag("r1=1.0/q0", r1)); /* verify dimension */ - static_assert(r1.c_basis_power == -1); + static_assert(r1.basis_power == -1); /* verify scale */ REQUIRE(r1.scale() == 0.2); @@ -529,7 +529,7 @@ namespace xo { log && log(xtag("r.type", Reflect::require()->canonical_name())); /* verify dimension */ - static_assert(r.c_basis_power == -1); + static_assert(r.basis_power == -1); /* verify scale */ REQUIRE(r.scale() == 0.0125); @@ -542,7 +542,7 @@ namespace xo { log && log(xtag("r.type", Reflect::require()->canonical_name())); /* verify dimension */ - static_assert(r.c_basis_power == 1); + static_assert(r.basis_power == 1); /* verify scale */ REQUIRE(r.scale() == 1.25); @@ -597,7 +597,7 @@ namespace xo { log && log(xtag("r.type", Reflect::require()->canonical_name())); /* verify dimension */ - static_assert(r.c_basis_power == -1); + static_assert(r.basis_power == -1); /* verify scale */ REQUIRE(r.scale() == 0.0125); @@ -610,7 +610,7 @@ namespace xo { log && log(xtag("r.type", Reflect::require()->canonical_name())); /* verify dimension */ - static_assert(r.c_basis_power == 1); + static_assert(r.basis_power == 1); /* verify scale */ REQUIRE(r.scale() == 1.25); @@ -619,6 +619,8 @@ namespace xo { } /*TEST_CASE(div3)*/ TEST_CASE("div4", "[quantity]") { + /* test with exact scalefactor */ + constexpr bool c_debug_flag = false; // can get bits from /dev/random by uncommenting the 2nd line below @@ -630,6 +632,39 @@ namespace xo { scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.div4")); //log && log("(A)", xtag("foo", foo)); + auto q1 = milliseconds(1); + auto q2 = minutes(1); + + auto r = q1 / q2.with_repr(); + + /* 0.1/sqrt(30dy) ~ 0.288675/sqrt(250dy), + * so q1/q2 ~ 0.6928 + */ + + log && log(XTAG(q1), XTAG(q2), xtag("q1/q2", r)); + + /* verify dimensionless result */ + static_assert(std::same_as); + + /* verify scale of result */ + CHECK(r == Approx(0.00001666667).epsilon(1e-6)); + + } /*TEST_CASE(div4)*/ + + TEST_CASE("div5", "[quantity]") { + /* test with inexact scalefactor */ + + constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.div5")); + //log && log("(A)", xtag("foo", foo)); + auto q1 = volatility250d(0.2); auto q2 = volatility30d(0.1); @@ -647,10 +682,10 @@ namespace xo { /* verify scale of result */ CHECK(r == Approx(0.692820323).epsilon(1e-6)); - } /*TEST_CASE(div4)*/ + } /*TEST_CASE(div5)*/ TEST_CASE("muldiv5", "[quantity]") { - constexpr bool c_debug_flag = true; + constexpr bool c_debug_flag = false; // can get bits from /dev/random by uncommenting the 2nd line below //uint64_t seed = xxx; @@ -682,7 +717,7 @@ namespace xo { /* verify scale of result */ //CHECK(r == Approx(0.692820323).epsilon(1e-6)); - } /*TEST_CASE(div4)*/ + } /*TEST_CASE(muldiv5)*/ } /*namespace ut*/ } /*namespace xo*/ From ba31d2c1122ab9f4ab242497269f3134160f354f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 17:25:20 -0400 Subject: [PATCH 0724/2693] xo-unit: example: + ex4 .. ex6 --- example/CMakeLists.txt | 3 +++ example/ex4/CMakeLists.txt | 15 +++++++++++++++ example/ex4/ex4.cpp | 26 ++++++++++++++++++++++++++ example/ex5/CMakeLists.txt | 15 +++++++++++++++ example/ex5/ex5.cpp | 23 +++++++++++++++++++++++ example/ex6/CMakeLists.txt | 15 +++++++++++++++ example/ex6/ex6.cpp | 19 +++++++++++++++++++ 7 files changed, 116 insertions(+) create mode 100644 example/ex4/CMakeLists.txt create mode 100644 example/ex4/ex4.cpp create mode 100644 example/ex5/CMakeLists.txt create mode 100644 example/ex5/ex5.cpp create mode 100644 example/ex6/CMakeLists.txt create mode 100644 example/ex6/ex6.cpp diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index ed03faf2..86f0d7e6 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1,3 +1,6 @@ add_subdirectory(ex1) add_subdirectory(ex2) add_subdirectory(ex3) +add_subdirectory(ex4) +add_subdirectory(ex5) +add_subdirectory(ex6) diff --git a/example/ex4/CMakeLists.txt b/example/ex4/CMakeLists.txt new file mode 100644 index 00000000..5e88f779 --- /dev/null +++ b/example/ex4/CMakeLists.txt @@ -0,0 +1,15 @@ +# xo-unit/example/ex4/CMakeLists.txt + +set(SELF_EXE xo_unit_ex4) +set(SELF_SRCS ex4.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +# ---------------------------------------------------------------- +# dependencies.. + +xo_self_dependency(${SELF_EXE} xo_unit) +xo_dependency(${SELF_EXE} reflect) + +# end CMakeLists.txt diff --git a/example/ex4/ex4.cpp b/example/ex4/ex4.cpp new file mode 100644 index 00000000..6db7f145 --- /dev/null +++ b/example/ex4/ex4.cpp @@ -0,0 +1,26 @@ +/** @file ex4.cpp **/ + +#include "xo/unit/quantity.hpp" +#include + +int +main () { + namespace u = xo::unit::units; + namespace qty = xo::unit::qty; + using namespace std; + + /* 20% volatility over 250 days (approx number of trading days in one year) */ + auto t1 = qty::milliseconds(1); + /* 10% volatility over 30 days */ + auto t2 = qty::minutes(1); + + auto r1 = t1 / t2.with_repr(); + auto r2 = t2 / t1.with_repr(); + + static_assert(same_as); + static_assert(same_as); + + cerr << "t1: " << t1 << ", t2: " << t2 << ", t1/t2: " << r1 << ", t2/t1: " << r2 << endl; +} + +/** end ex4.cpp */ diff --git a/example/ex5/CMakeLists.txt b/example/ex5/CMakeLists.txt new file mode 100644 index 00000000..583a7cff --- /dev/null +++ b/example/ex5/CMakeLists.txt @@ -0,0 +1,15 @@ +# xo-unit/example/ex5/CMakeLists.txt + +set(SELF_EXE xo_unit_ex5) +set(SELF_SRCS ex5.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +# ---------------------------------------------------------------- +# dependencies.. + +xo_self_dependency(${SELF_EXE} xo_unit) +xo_dependency(${SELF_EXE} reflect) + +# end CMakeLists.txt diff --git a/example/ex5/ex5.cpp b/example/ex5/ex5.cpp new file mode 100644 index 00000000..2f0dbb9e --- /dev/null +++ b/example/ex5/ex5.cpp @@ -0,0 +1,23 @@ +/** @file ex5.cpp **/ + +#include "xo/unit/quantity.hpp" +#include + +int +main () { + namespace u = xo::unit::units; + namespace qty = xo::unit::qty; + using namespace std; + + /* 20% volatility over 250 days (approx number of trading days in one year) */ + auto q1 = qty::volatility250d(0.2); + /* 10% volatility over 30 days */ + auto q2 = qty::volatility30d(0.1); + + auto sum = q1 + q2; + auto prod = q1 * q2; + + cerr << "q1: " << q1 << ", q2: " << q2 << ", q1+q2: " << sum << ", q1*q2: " << prod << endl; +} + +/** end ex5.cpp */ diff --git a/example/ex6/CMakeLists.txt b/example/ex6/CMakeLists.txt new file mode 100644 index 00000000..cff85d53 --- /dev/null +++ b/example/ex6/CMakeLists.txt @@ -0,0 +1,15 @@ +# xo-unit/example/ex6/CMakeLists.txt + +set(SELF_EXE xo_unit_ex6) +set(SELF_SRCS ex6.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +# ---------------------------------------------------------------- +# dependencies.. + +xo_self_dependency(${SELF_EXE} xo_unit) +xo_dependency(${SELF_EXE} reflect) + +# end CMakeLists.txt diff --git a/example/ex6/ex6.cpp b/example/ex6/ex6.cpp new file mode 100644 index 00000000..9046f331 --- /dev/null +++ b/example/ex6/ex6.cpp @@ -0,0 +1,19 @@ +/** @file ex6.cpp **/ + +#include "xo/unit/quantity.hpp" +#include + +int +main () { + namespace u = xo::unit::units; + namespace qty = xo::unit::qty; + using namespace std; + + auto t1 = qty::milliseconds(25.0); + auto t1_usec = t1.with_unit(); + auto t1_sec = t1.with_unit(); + + cerr << "t1: " << t1 << ", t1_usec: " << t1_usec << ", t1_sec: " << t1_sec << endl; +} + +/** end ex6.cpp */ From 715205f97b9c377daac80169aea180ba9d1490e9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 17:25:34 -0400 Subject: [PATCH 0725/2693] xo-unit: docs: + README --- docs/README | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 docs/README diff --git a/docs/README b/docs/README new file mode 100644 index 00000000..599096ac --- /dev/null +++ b/docs/README @@ -0,0 +1,30 @@ +map + + index.rst + +- install.rst + +- examples.rst + +- classes.rst + +- glossary.rst + +examples + +.. doxygenclass:: ${c++ class name} + :project: + :path: + :members: + :protected-members: + :private-members: + :undoc-members: + :member-groups: + :members-only: + :outline: + :no-link: + :allow-dot-graphs: + +.. doxygendefine:: ${c preprocessor define} + +.. doxygenconcept:: ${c++ concept definition} + +.. doxygenenum:: ${c++ enum definition} + +.. doxygenfunction:: ${c++ function name} From fdd80f9d5f1de906ed3fb19a44beeacda932f6e8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 17:25:55 -0400 Subject: [PATCH 0726/2693] xo-unit: docs: + written-out examples --- docs/examples.rst | 111 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-) diff --git a/docs/examples.rst b/docs/examples.rst index 1aba11ed..d9beb387 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -46,6 +46,40 @@ Remarks: * Units are sticky: since we expressed ``t`` in milliseconds and ``m`` in kilograms, result is in the same terms. * Unit ordering is sticky. Mass appears on the left in printed value of ``a`` because it was on the left-hand side of ``operator/`` * Example omits verifying ``decltype(a)``, to keep output small. +* Conversion factors are exact (provided dimensions are limited to integer powers). + Exact conversion involves no loss of precision. + +Explicit scale conversion +------------------------- + +Can convert between compatible units explictly: + +.. code-block:: cpp + :linenos: + :emphasize-lines: 11-12 + + #include "xo/unit/quantity.hpp" + #include + + int + main () { + namespace u = xo::unit::units; + namespace qty = xo::unit::qty; + using namespace std; + + auto t1 = qty::milliseconds(25.0); + auto t1_usec = t1.with_unit(); + auto t1_sec = t1.with_unit(); + + cerr << "t1: " << t1 << ", t1_usec: " << t1_usec << ", t1_sec: " << t1_sec << endl; + } + +with output: + +.. code-block:: + + t1: 25ms, t1_usec: 25000us, t1_sec: 0.025s + Scale conversion triggered by assignment ---------------------------------------- @@ -86,7 +120,7 @@ Remarks: Scale conversion triggered by arithmetic ---------------------------------------- -In representing a particular quantity, +When representing a particular quantity, xo-unit uses at most one scale for each :term:`basis dimension` associated with the unit for that quantity. When an arithmetic operator encounters basis units involving two different scales, the operator will adopt the scale provided by the left-hand argument: @@ -115,3 +149,78 @@ with output: .. code-block:: t1: 1ms, t2: 1min, t1*t2: 60000ms^2 + +Dimensionless quantities collapse automatically +----------------------------------------------- + +.. code-block:: cpp + :linenos: + :emphasize-lines: 14-15 + + #include "xo/unit/quantity.hpp" + #include + + int main() { + namespace u = xo::unit; + namespace qty = xo::units::qty; + using namespace std; + + auto t1 = qty::milliseconds(1); + auto t2 = qty::minutes(1); + auto r1 = t1 / t2.with_repr(); + auto r2 = t2 / t1.with_repr(); + + static_assert); + static_assert); + + cerr << "t1: " << t1 << ", t2: " << t2 << ", t1/t2: " << r1 << ", t2/t1: " << r2 << endl; + } + +with output: + +.. code-block:: + + t1: 1ms, t2: 1min, t1/t2: 1.66667e-05, t2/t1: 60000 + + +Fractional dimension +-------------------- + +Fractional dimensions are supported; they work in the same way as familiar integral dimensions. + +Only caveat is that converting between fractional units with different scales creates a floating-point conversion factor, +which may incur loss of precision based on floating-point roundoff. + +.. code-block:: cpp + :linenos: + :emphasize-lines: 15 + + #include "xo/unit/quantity.hpp" + #include + + int + main () { + namespace u = xo::unit::units; + namespace qty = xo::unit::qty; + using namespace std; + + /* 20% volatility over 250 days (approx number of trading days in one year) */ + auto q1 = qty::volatility250d(0.2); + /* 10% volatility over 30 days */ + auto q2 = qty::volatility30d(0.1); + + static_assert(q2.basis_power == 0.5); + + auto sum = q1 + q2; + auto prod = q1 * q2; + + static_assert(prod.basis_power == 1); + + cerr << "q1: " << q1 << ", q2: " << q2 << ", q1+q2: " << sum << ", q1*q2" << prod << endl; + } + +with output: + +.. code-block:: + + q1: 0.2yr250^-(1/2), q2: 0.1mo^-(1/2), q1+q2: 0.488675yr250^(1/2), q1*q2: 0.057735yr250^-1 From 4d823595fed8c28a4b4c8d7975795ef7f6013c89 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:36:09 -0400 Subject: [PATCH 0727/2693] xo-unit: ex1: + static assert --- example/ex1/ex1.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 2e286e7c..8909e188 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -5,13 +5,17 @@ int main () { + namespace u = xo::unit::units; namespace qty = xo::unit::qty; + using xo::unit::quantity; using namespace std; auto t = qty::milliseconds(10); auto m = qty::kilograms(2.5); auto a = m / (t*t); + static_assert(same_as>); + cerr << "t: " << t << ", m: " << m << ", m.t^-2: " << a << endl; } From b4c3ba4dda9409887d04a100524d1dac899b9914 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:37:06 -0400 Subject: [PATCH 0728/2693] xo-unit: + numeric_concept + unit_concept --- include/xo/unit/numeric_concept.hpp | 38 +++++++++++++++++++++ include/xo/unit/quantity_concept.hpp | 3 +- include/xo/unit/unit_concept.hpp | 49 ++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 include/xo/unit/numeric_concept.hpp create mode 100644 include/xo/unit/unit_concept.hpp diff --git a/include/xo/unit/numeric_concept.hpp b/include/xo/unit/numeric_concept.hpp new file mode 100644 index 00000000..720099a3 --- /dev/null +++ b/include/xo/unit/numeric_concept.hpp @@ -0,0 +1,38 @@ +/* @file numeric_concept.hpp */ + +#pragma once + +#include + +namespace xo { + namespace unit { + /** @concept numeric_concept + * @brief Concept for values that participate in arithmetic operations (+,-,*,/) and comparisons + * + * Intended to include at least: + * - built-in integral and floating-point types + * - boost::rational + * - std::complex + * - xo::unit::quantity + * + * This implies we don't require T to be totally ordered, + * and don't require (<,<=,>=,>) operators. + * + * Intend numeric_concept to apply to types suitable for + * xo::unit::quantity::repr_type. + **/ + template + concept numeric_concept = requires(T x, U y) + { + { -x }; + { x - y }; + { x + y }; + { x * y }; + { x / y }; + { x == y }; + { x != y }; + }; + } /*namespace unit*/ +} /*namespace xo*/ + +/* end numeric_concept.hpp */ diff --git a/include/xo/unit/quantity_concept.hpp b/include/xo/unit/quantity_concept.hpp index 09d33446..1235a277 100644 --- a/include/xo/unit/quantity_concept.hpp +++ b/include/xo/unit/quantity_concept.hpp @@ -2,7 +2,8 @@ #pragma once -#include +#include "unit_concept.hpp" +#include "numeric_concept.hpp" namespace xo { namespace unit { diff --git a/include/xo/unit/unit_concept.hpp b/include/xo/unit/unit_concept.hpp new file mode 100644 index 00000000..8c5fe59c --- /dev/null +++ b/include/xo/unit/unit_concept.hpp @@ -0,0 +1,49 @@ +/* @file unit_concept.hpp */ + +#pragma once + +#include "dimension_concept.hpp" + +namespace xo { + namespace unit { + /** @brief concept for a Unit type, suitable for use with the quantity template + * + * Example: + * @code + * using namespace xo::unit; + * static_assert(unit_concept); + * @endcode + **/ + template + concept unit_concept = requires(Unit unit) + { + typename Unit::scalefactor_type; + typename Unit::dim_type; + typename Unit::canon_type; + } + && (ratio_concept + && bpu_list_concept + && bpu_list_concept); + + + /** @brief concept for a Unit type, that contains exactly one basis dimension + * + * Example: + * @code + * using namespace xo::unit + * static_assert(basis_unit_concept); + * @endcode + **/ + template + concept basis_unit_concept = requires(Unit unit) + { + typename Unit::dim_type; + typename Unit::dim_type::rest_type; + } + && (std::same_as) + && (unit_concept); + } /*namespace unit*/ +} /*namespace xo*/ + + +/* end unit_concept.hpp */ From 2cea0d3dfa896010d14280c9e40f61ef0ac789cd Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:37:29 -0400 Subject: [PATCH 0729/2693] xo-unit: quantity_concept: + req concept sat for nested types --- include/xo/unit/quantity_concept.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/xo/unit/quantity_concept.hpp b/include/xo/unit/quantity_concept.hpp index 1235a277..1edb2c59 100644 --- a/include/xo/unit/quantity_concept.hpp +++ b/include/xo/unit/quantity_concept.hpp @@ -17,6 +17,7 @@ namespace xo { { Quantity::unit_cstr() } -> std::same_as; { Quantity::unit_quantity() } -> std::same_as; { Quantity::promote(repr) } -> std::same_as; - }; + } && (unit_concept + && numeric_concept); } /*namespace unit*/ } /*namespace xo*/ From 8fb3b11e000320df6c6f1157c1f34731eb0b6aa2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:38:09 -0400 Subject: [PATCH 0730/2693] xo-unit: removed unit_concept from dimension_concept.hpp --- include/xo/unit/dimension_concept.hpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/include/xo/unit/dimension_concept.hpp b/include/xo/unit/dimension_concept.hpp index 6277b621..c670c3e1 100644 --- a/include/xo/unit/dimension_concept.hpp +++ b/include/xo/unit/dimension_concept.hpp @@ -70,18 +70,6 @@ namespace xo { && bpu_list_concept ); - // ---------------------------------------------------------------- - - template - concept unit_concept = requires(Unit unit) - { - typename Unit::scalefactor_type; - typename Unit::dim_type; - typename Unit::canon_type; - } - && (ratio_concept - && bpu_list_concept - && bpu_list_concept); } /*namespace unit*/ } /*namespace xo*/ From 34214f571ddae67f7592eaf6a6f830a06433dd72 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:38:36 -0400 Subject: [PATCH 0731/2693] xo-unit: + di_replace_basis_scale --- include/xo/unit/dimension_impl.hpp | 44 +++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/include/xo/unit/dimension_impl.hpp b/include/xo/unit/dimension_impl.hpp index d51c655c..d241f9ec 100644 --- a/include/xo/unit/dimension_impl.hpp +++ b/include/xo/unit/dimension_impl.hpp @@ -180,7 +180,7 @@ namespace xo { using dim_type = typename Dim::rest_type; }; - // ----- bpu_list ----- + // ----- bpu_node ----- /** Represents the cartesian product of a list of 'native basis power units'; * represents something with dimensions @@ -234,6 +234,48 @@ namespace xo { static constexpr std::uint32_t n_dimension = 1; }; + // ----- di_replace_basis_scale ----- + + /** + * @brief Replace BpuList member with matching BasisDim, preserving everything except (inner) scalefactor + **/ + template + struct di_replace_basis_scale; + + template + struct di_replace_basis_scale_aux; + + /** specialization for non-empty BpuList **/ + template + struct di_replace_basis_scale { + using type = di_replace_basis_scale_aux::type; + }; + + /** specialization for empty BpuList -- error not found **/ + template + struct di_replace_basis_scale {}; + + /** specialization for matching front **/ + template + struct di_replace_basis_scale_aux { + using _replace_bpu_type = bpu; + + static_assert(native_bpu_concept<_replace_bpu_type>); + + /* NewBpu replaces Front */ + using type = bpu_node<_replace_bpu_type, Rest>; + }; + + template + struct di_replace_basis_scale_aux { + /* keep Front, replace NewBpu in rest */ + using type = bpu_node::type>; + }; + // ----- bpu_cartesian_product ----- /** Require: From fcdf0c8dfa6bf2c0f0e08bd2dab2c8b14a55b500 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:39:08 -0400 Subject: [PATCH 0732/2693] xo-unit: bugfix:fix meter, kilometer units --- include/xo/unit/unit.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/xo/unit/unit.hpp b/include/xo/unit/unit.hpp index b8b8c1b9..56157c94 100644 --- a/include/xo/unit/unit.hpp +++ b/include/xo/unit/unit.hpp @@ -270,12 +270,12 @@ namespace xo { }; using meter = wrap_unit< std::ratio<1>, - bpu_node< bpu> > >; + bpu_node< bpu> > >; using kilometer = wrap_unit< std::ratio<1>, - bpu_node< bpu> > >; + bpu_node< bpu> > >; template <> struct scaled_native_unit_abbrev> { static constexpr auto value = stringliteral("km"); From ccdaace50cb050b75490548114346a4f90482b4f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:39:41 -0400 Subject: [PATCH 0733/2693] xo-unit: unit.hpp: need unit_concept.hpp #incl --- include/xo/unit/unit.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/xo/unit/unit.hpp b/include/xo/unit/unit.hpp index 56157c94..d93ead8d 100644 --- a/include/xo/unit/unit.hpp +++ b/include/xo/unit/unit.hpp @@ -2,6 +2,7 @@ #pragma once +#include "unit_concept.hpp" #include "dimension_impl.hpp" namespace xo { From 859fdfd2c494bc0b60fc106b64fc29fac953b13d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:41:24 -0400 Subject: [PATCH 0734/2693] xo-unit: + unit_qty namespace w/ unit qty vars --- include/xo/unit/quantity.hpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index 232583b4..457afd4e 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -513,6 +513,38 @@ namespace xo { return quantity::promote(x); } } /*namespace qty*/ + + namespace unit_qty { + /** @brief quantity with mass dimension, representing 1mg (1 milligram = 10^-3 grams) **/ + static constexpr auto milligram = qty::milligrams(1.0); + /** @brief quantity with mass dimension, representing 1g (1 gram) **/ + static constexpr auto gram = qty::grams(1.0); + /** @brief quantity with mass dimension, representing 1kg (1 kilogram = 1000 grams) **/ + static constexpr auto kilogram = qty::kilograms(1.0); + + /** @brief quantity with length dimension representing 1mm (10^-3 meters) **/ + static constexpr auto millimeter = qty::millimeters(1.0); + /** @brief quantity with length dimension representing 1m (1 meter) **/ + static constexpr auto meter = qty::meters(1.0); + /** @brief quantity with length dimension representing 1km (1 kilometer = 1000 meters) **/ + static constexpr auto kilometer = qty::kilometers(1.0); + + /** @brief quantity with time dimension representing 1ns (1 nanosecond = 10^-9 seconds) **/ + static constexpr auto nanosecond = qty::microseconds(1); + /** @brief quantity with time dimension representing 1us (1 microsecond = 10^-6 seconds) **/ + static constexpr auto microsecond = qty::microseconds(1); + /** @brief quantity with time dimension representing 1ms (1 milliseconds = 10^-3 seconds) **/ + static constexpr auto millisecond = qty::milliseconds(1); + /** @brief quantity with time dimension representing 1s (1 second) **/ + static constexpr auto second = qty::seconds(1); + /** @brief quantity with time dimension representing 1min (1 minute = 60 seconds) **/ + static constexpr auto minute = qty::minutes(1); + /** @brief quantity with time dimension representing 1hr (1 hour = 60 minutes) **/ + static constexpr auto hour = qty::hours(1); + /** @brief quantity with time dimension representing 1dy (1 day = 24 hours) **/ + static constexpr auto day = qty::days(1); + } + } /*namespace unit*/ } /*namespace xo*/ From 1a7321c98f0ae251eb6c8527258ad77ea51065fe Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:42:26 -0400 Subject: [PATCH 0735/2693] xo-unit: quantity: ++ constexpr where appropriate --- include/xo/unit/quantity.hpp | 61 ++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index 457afd4e..8085bcd5 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -286,7 +286,7 @@ namespace xo { // ----- operator+ ----- template - inline Quantity1 operator+ (Quantity1 x, Quantity2 y) { + inline constexpr Quantity1 operator+ (Quantity1 x, Quantity2 y) { static_assert(same_dimension_v); /* convert y to match units used by x; @@ -298,7 +298,7 @@ namespace xo { } template - inline Quantity1 operator- (Quantity1 x, Quantity2 y) { + inline constexpr Quantity1 operator- (Quantity1 x, Quantity2 y) { static_assert(std::same_as < typename Quantity1::unit_type::dimension_type::canon_type, typename Quantity2::unit_type::dimension_type::canon_type >); @@ -315,7 +315,7 @@ namespace xo { } template - inline auto operator* (Quantity1 x, Quantity2 y) { + inline constexpr auto operator* (Quantity1 x, Quantity2 y) { static_assert(quantity_concept); static_assert(quantity_concept); @@ -325,7 +325,7 @@ namespace xo { /** e.g. DECLARE_LH_MULT(int32_t) **/ # define DECLARE_LH_MULT(lhtype) \ template \ - inline auto \ + inline constexpr auto \ operator* (lhtype x, Quantity y) { \ static_assert(quantity_concept); \ return y.scale_by(x); \ @@ -346,7 +346,7 @@ namespace xo { /** e.g. DECLARE_RH_MULT(int32_t) **/ # define DECLARE_RH_MULT(rhtype) \ template \ - inline auto \ + inline constexpr auto \ operator* (Quantity x, rhtype y) { \ static_assert(quantity_concept); \ return x.scale_by(y); \ @@ -365,7 +365,7 @@ namespace xo { # undef DECLARE_LH_MULT template - inline auto operator/ (Quantity1 x, Quantity2 y) { + inline constexpr auto operator/ (Quantity1 x, Quantity2 y) { static_assert(quantity_concept); static_assert(quantity_concept); @@ -374,7 +374,7 @@ namespace xo { # define DECLARE_LH_DIV(lhtype) \ template \ - inline auto \ + inline constexpr auto \ operator/ (lhtype x, Quantity y) { \ static_assert(quantity_concept); \ return y.divide_into(x); \ @@ -394,7 +394,7 @@ namespace xo { # define DECLARE_RH_DIV(rhtype) \ template \ - inline auto \ + inline constexpr auto \ operator/ (Quantity x, rhtype y) { \ static_assert(quantity_concept); \ return x.divide_by(y); \ @@ -422,72 +422,85 @@ namespace xo { namespace qty { // ----- mass ----- + /** @brief create quantity representing @p x milligrams **/ template - inline auto milligrams(Repr x) -> quantity { + inline constexpr auto milligrams(Repr x) -> quantity { return quantity::promote(x); }; + /** @brief create quantity representing @p x grams **/ template - inline auto grams(Repr x) -> quantity { + inline constexpr auto grams(Repr x) -> quantity { return quantity::promote(x); }; + /** @brief create quantity representing @p x kilograms **/ template - inline auto kilograms(Repr x) -> quantity { + inline constexpr auto kilograms(Repr x) -> quantity { return quantity::promote(x); }; // ----- distance ----- + /** @brief create quantity representing @p x millimeters **/ template - inline auto millimeters(Repr x) -> quantity { + inline constexpr auto millimeters(Repr x) -> quantity { return quantity::promote(x); } + /** @brief create quantity representing @p x meters **/ template - inline auto meters(Repr x) -> quantity { + inline constexpr auto meters(Repr x) -> quantity { return quantity::promote(x); } + /** @brief create quantity representing @p x kilometers **/ template - inline auto kilometers(Repr x) -> quantity { + inline constexpr auto kilometers(Repr x) -> quantity { return quantity::promote(x); } // ----- time ----- + /** @brief create quantity representing @p x nanoseconds **/ template - inline auto nanoseconds(Repr x) -> quantity { + inline constexpr auto nanoseconds(Repr x) -> quantity { return quantity::promote(x); } + /** @brief create quantity representing @p x microseconds **/ template - inline auto microseconds(Repr x) -> quantity { + inline constexpr auto microseconds(Repr x) -> quantity { return quantity::promote(x); } + /** @brief create quantity representing @p x milliseconds **/ template - inline auto milliseconds(Repr x) -> quantity { + inline constexpr auto milliseconds(Repr x) -> quantity { return quantity::promote(x); } + /** @brief create quantity representing @p x seconds **/ template - inline auto seconds(Repr x) -> quantity { + inline constexpr auto seconds(Repr x) -> quantity { return quantity::promote(x); } + /** @brief create quantity representing @p x minutes **/ template - inline auto minutes(Repr x) -> quantity { + inline constexpr auto minutes(Repr x) -> quantity { return quantity::promote(x); } + /** @brief create quantity representing @p x hours **/ template - inline auto hours(Repr x) -> quantity { + inline constexpr auto hours(Repr x) -> quantity { return quantity::promote(x); } + /** @brief create quantity representing @p x days (1 day = exactly 24 hours) **/ template - inline auto days(Repr x) -> quantity { + inline constexpr auto days(Repr x) -> quantity { return quantity::promote(x); } @@ -495,21 +508,21 @@ namespace xo { /** quantity in units of 1/sqrt(1dy) **/ template - inline auto volatility1d(Repr x) -> quantity { + inline constexpr auto volatility1d(Repr x) -> quantity { return quantity::promote(x); } /** quantity in units of 1/sqrt(30days) **/ template - inline auto volatility30d(Repr x) -> quantity { + inline constexpr auto volatility30d(Repr x) -> quantity { return quantity::promote(x); } /** quantity in units of 1/sqrt(250days) **/ template - inline auto volatility250d(Repr x) -> quantity { + inline constexpr auto volatility250d(Repr x) -> quantity { return quantity::promote(x); } } /*namespace qty*/ From 0c355d3e8b57b0e7d26195e36f3e2ec70daf0f55 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:43:52 -0400 Subject: [PATCH 0736/2693] xo-unit: ++ doxygen decors on quantity members --- include/xo/unit/quantity.hpp | 352 +++++++++++++++++++++++++++++------ include/xo/unit/unit.hpp | 1 + 2 files changed, 291 insertions(+), 62 deletions(-) diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index 8085bcd5..bab5655c 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -20,26 +20,27 @@ namespace xo { /** @class quantity * - * @brief represets a scalar quantity; enforces dimensional consistency at compile time + * @brief represents a scalar quantity; enforces dimensional consistency at compile time. * - * Repr representation. - * Unit unit - * Assert use to specify required unit dimension + * - @p Unit is a type identifying dimension and scale attaching to this quantity. + * Unit must satisfy @c unit_concept + * - @p Repr is a type used to represent quantity values, scaled by @p Unit. + * Repr must satisfy @c numeric_concept * - * Require: - * - Repr copyable, assignable - * - Repr = 0 - * - Repr = 1 - * - Repr + Repr -> Repr - * - Repr - Repr -> Repr - * - Repr * Repr -> Repr - * - Repr / Repr -> Repr + * A quantity's run-time state consists of exactly one @p Repr + * instance: + * @c sizeof(quantity) == sizeof(Repr) **/ template class quantity { public: + /** @defgroup quantity-traits **/ + ///@{ + /** @brief type capturing the units (and dimension) of this quantity **/ using unit_type = Unit; + /** @brief type used for representation of this quantity **/ using repr_type = Repr; + ///@} /* non-unity compile-time scale factors can arise during unit conversion; * for example see method quantity::in_units_of() @@ -48,41 +49,232 @@ namespace xo { static_assert(std::same_as< typename Unit::canon_type, typename Unit::canon_type >); public: + /** @defgroup quantity-ctors constructors + **/ + ///@{ constexpr quantity() = default; constexpr quantity(quantity const & x) = default; constexpr quantity(quantity && x) = default; + ///@} - template - using find_bpu_t = unit_find_bpu_t; - - /** - * For example: - * auto q = qty::milliseconds(5) * qty::seconds(1); - * then - * q.basis_power -> 2 - * q.basis_power -> 0 + /** @defgroup quantity-named-ctors named constructors + **/ + ///@{ + /** @brief construct a unit quantity using @c unit_type + * + * @code + * auto q = qty::milliseconds(17) / qty::kilometers(23.0); + * q::unit_quantity(); // 1ms.km^-1 + * @endcode **/ - template - static constexpr PowerRepr basis_power = from_ratio::power_type>(); - - /** @brief get scale value (relative to unit) (@ref scale_) **/ - constexpr Repr scale() const { return scale_; } - /** @brief abbreviation for this quantity's units **/ - static constexpr char const * unit_cstr() { return unit_abbrev_v.c_str(); } - /** @brief return unit quantity -- amount with this Unit that has representation = 1 **/ static constexpr quantity unit_quantity() { return quantity(1); } - /** @brief promote representation to quantity. Same as multiplying by Unit **/ + /** @brief promote representation to quantity. Same as multiplying by Unit + **/ static constexpr auto promote(Repr x) { //std::cerr << "quantity::promote: x=" << x << ", R=" << reflect::Reflect::require()->canonical_name() << std::endl; return promoter::promote(x); } + ///@} - template - constexpr quantity with_unit() const { return *this; } + /** @addtogroup quantity-traits **/ + ///@{ + /** @brief report this quantity's basis-power-unit type for a given basis dimension + * + * Example: + * @code + * auto q = 1.0 / (qty::milliseconds(5) * qty::seconds(100.0)); + * q.unit_cstr(); // "ms^-2" + * + * using tmp = q.find_bpu_t; + * + * tmp::c_native_dim; // dim::time + * tmp::c_native_unit; // native_unit_id::second + * tmp::scalefactor_type::num; // 1 + * tmp::scalefactor_type::den; // 1000 + * tmp::power_type::num; // -2 + * tmp::pwoer_type::den; // 1 + * @endcode + **/ + template + using find_bpu_t = unit_find_bpu_t; + + /** @brief report this quantity's scalefactor type for given basis dimension **/ + template + using basis_scale_type = typename find_bpu_t::scalefactor_type::type; + + ///@} + + /** @defgroup quantity-access-methods **/ + ///@{ + /** @brief get scale value (relative to unit) (@ref scale_) **/ + constexpr Repr scale() const { return scale_; } + /** @brief abbreviation for this quantity's units + * + * This string literal is constructed at compile-time by concatenating + * abbreviations for each basis-power-unit. + * For implementation see: + * * @c xo::unit::native_unit_abbrev_helper + * (in xo/unit/basis_unit.hpp) for each native dimension + * * @c xo::unit::scaled_native_unit_abbrev + * (in xo/unit/basis_unit.hpp) last-resort handling for scaled native dimensions + * * @c xo::unit::scaled_native_unit_abbrev + * (in xo/unit/unit.hpp) specializations for scaled native dimensions + **/ + static constexpr char const * unit_cstr() { return unit_abbrev_v.c_str(); } + ///@} + + /** @defgroup quantity-constants constants + **/ + ///@{ + /** @brief report exponent of @p BasisDim in dimension of this quantity + * + * For example: + * @code + * auto q = qty::milliseconds(5) * qty::seconds(1); + * int p1 = q.basis_power; // p1 == 2 + * int p2 = q.basis_power; // p2 == 0 + * @endcode + **/ + template + static constexpr PowerRepr basis_power = from_ratio::power_type>(); + ///@} + + /** @defgroup quantity-unit-conversion **/ + ///@{ + /** @brief convert to quantity representing the same amount, but changing units and perhaps representation. + * + * These two expressions are equivalent: + * @code + * q.with_unit(); + * quantity(q); + * @endcode + * + **/ + template + constexpr quantity with_unit() const { return *this; } + + /** + * @brief produce quantity scaled according to @p BasisUnit2, representing the same value as @c *this. + * + * For example: + * + * @code{.cpp} + * auto q1 = 1.0 / minutes(1) * kilograms(2.5); // q1 = 2.5kg.min^-1 + * auto q2 = q1.with_basis_unit(); // q2 in kg.ms^-1 + * @endcode + * + * Motivation is ability to chain rescaling to reach desired compound unit + * + * @code + * auto q3 = q1.with_basis_unit() + * .with_basis_unit(); // q3 in g.s^-1 + * @endcode + **/ + template + constexpr auto with_basis_unit() const { + static_assert(basis_unit_concept); + + using new_bpu_type = BasisUnit2::dim_type::front_type; + using old_bpulist_type = unit_type::dim_type; + using new_bpulist_type = di_replace_basis_scale::type; + using new_unit_type = wrap_unit, new_bpulist_type>; + +# ifdef NOT_USING_DEBUG + using xo::reflect::Reflect; + scope log(XO_DEBUG(true /*c_debug_flag*/)); + log && log(xtag("old_unit_type", Reflect::require()->canonical_name())); + log && log(xtag("new_unit_type", Reflect::require()->canonical_name())); +# endif + + return this->with_unit(); + } + + /** + * @brief express this quantity in the same units as @p q + * + * @pre @c *this and @p q must have the same dimension + * + * @param q take units from @c q::unit_type, ignoring @c q.scale() + * @return this amount, but expressed using the same units as @p q + **/ + template + auto with_units_from(Quantity q) const { + return this->with_units(); + } + + /** + * @brief express this quantity in units of @p Unit2. + * + * @p Unit2 specifies new units + * @p Repr2 specifies representation + * @return this amount, but expressed as a multiple of @p Unit2 + **/ + template + auto with_units() const { + Repr2 x = this->in_units_of(); + + return quantity::promote(x); + } + + /** + * @brief compute scale with respect to @p Unit2 + * + * @pre @c *this must have the same dimension as @p Unit2 + * + * @p Unit2 rescale in terms of this unit. + * @p Repr2 compute scale in this representation + * @return scale to use for @c quantity representing the same amount as @c *this. + **/ + template + Repr2 in_units_of() const { + // static_assert(dimension_of == dimension_of); // discard all the scaling values + + static_assert(same_dimension_v); + + using _convert_to_u2_type = unit_cartesian_product>; + + using exact_scalefactor_type = _convert_to_u2_type::exact_unit_type::scalefactor_type; + constexpr double c_scalefactor_inexact = _convert_to_u2_type::c_scalefactor_inexact; + + // _convert_u2_type + // - scalefactor_type + // - dim_type + // - canon_type + + /* if _convert_u2_type isn't dimensionless, then {Unit2, Unit} have different dimensions */ + + return ((this->scale_ * c_scalefactor_inexact * exact_scalefactor_type::num) / exact_scalefactor_type::den); + } + + /** + * @brief convert to quantity with representation @p Repr2 + * + * @return a quantity representing the same amount as @c *this, but using representation @p Repr2 + **/ template constexpr quantity with_repr() const { return quantity::promote(scale_); } + ///@} + + /** @defgroup quantity-arithmeticsupport **/ + ///@{ + /** + * @brief multiply this quantity *x* by another quantity *y*. + * + * Result will propagate dimension and units appropriately. + * If *x* and *y* use conflicting scale factors for a dimension, + * adopt scalefactor from *x*. + * + * note: result will be a dimensionless value (e.g. type @c double) + * if units cancel. + * + * @pre @p Quantity2 must satisfy @c quantity_concept + * + * @param y multiply by this amount + * @return x.multiply(y) returns amount representing x*y + **/ template auto multiply(Quantity2 y) const { //constexpr bool c_debug_flag = false; @@ -118,6 +310,21 @@ namespace xo { return quantity::promote(r_scale); } + /** + * @brief multiply this quantity *x* by another quantity *y* + * + * Result will propagate dimension and units appropriately. + * If *x* and *y* use conflicting scale factors for a dimension, + * adopt scalefactor from *x*. + * + * note: result will be a dimensionless value (e.g. type @c double) + * if units cancel. + * + * @pre @p Quantity2 must satisfy @c quantity_concept + * + * @param y divide by this amount + * @return x.divide(y) returns amount representing x/y + **/ template auto divide(Quantity2 y) const { using unit_divide_type = unit_divide; @@ -151,15 +358,17 @@ namespace xo { // quantity operator/=() /** - * scale by dimensionless number + * @brief scale this quantity *x* by dimensionless amount @p y + * + * @return quantity representing @c x*y **/ template - auto scale_by(Repr2 x) const { + auto scale_by(Repr2 y) const { static_assert(!quantity_concept); using r_repr_type = std::common_type_t; - r_repr_type r_scale = this->scale_ * x; + r_repr_type r_scale = this->scale_ * y; //std::cerr << "quantity::scale_by: scale=" << scale << ", repr_type=" << reflect::Reflect::require()->canonical_name() << std::endl; @@ -167,7 +376,9 @@ namespace xo { } /** - * divide by dimensionless number + * @brief divide this quantity *x* by dimensionless amount @p y + * + * @return quantity representing @c x/y **/ template auto divide_by(Repr2 x) const { @@ -179,7 +390,9 @@ namespace xo { } /** - * divide dimensionless number by this quantity + * @brief divide dimensionless number @p x by this quantity @c y + * + * @return quantity representing @c x/y **/ template auto divide_into(Repr2 x) const { @@ -191,30 +404,19 @@ namespace xo { return quantity::promote(r_scale); } + ///@} - template - Repr2 in_units_of() const { - // static_assert(dimension_of == dimension_of); // discard all the scaling values - - static_assert(same_dimension_v); - - using _convert_to_u2_type = unit_cartesian_product>; - - using exact_scalefactor_type = _convert_to_u2_type::exact_unit_type::scalefactor_type; - constexpr double c_scalefactor_inexact = _convert_to_u2_type::c_scalefactor_inexact; - - // _convert_u2_type - // - scalefactor_type - // - dim_type - // - canon_type - - /* if _convert_u2_type isn't dimensionless, then {Unit2, Unit} have different dimensions */ - - return ((this->scale_ * c_scalefactor_inexact * exact_scalefactor_type::num) / exact_scalefactor_type::den); - } - + /** @defgroup quantity-arithmetic **/ + ///@{ + /** @brief add quantity in-place + * + * @pre @p y must have the same dimension as @c *this. + * + * @param y quantity to add + * @retval this quantity after adding y + **/ template - quantity operator+=(Quantity2 y) { + quantity & operator+=(Quantity2 y) { static_assert(std::same_as< typename unit_type::canon_type, typename Quantity2::unit_type::canon_type >); @@ -227,8 +429,15 @@ namespace xo { return *this; } + /** @brief subtract quantity in-place + * + * @pre @p y must have the same dimensions as @c *this + * + * @param y quantity to subtract + * @retval this quantity after subtracting y + **/ template - quantity operator-=(Quantity2 y) { + quantity & operator-=(Quantity2 y) { static_assert(std::same_as< typename unit_type::canon_type, typename Quantity2::unit_type::canon_type >); @@ -240,10 +449,16 @@ namespace xo { return *this; } + ///@} - /* convert to quantity with same dimension, different {unit_type, repr_type} */ + /** @addtogroup quantity-unit-conversion **/ + ///@{ + /** @brief convert to quantity with same dimension, different {unit_type, repr_type} + * + * @pre @c Quantity2 must have the same dimension as @c *this. + **/ template - operator Quantity2 () const { + constexpr operator Quantity2 () const { /* avoid truncating precision when converting: * use best available representation */ @@ -251,13 +466,26 @@ namespace xo { return Quantity2::promote(this->in_units_of()); } + ///@} + /** @defgroup quantity-print-support **/ + ///@{ + /** @brief write printed representation on stream + * + * @param os write on this output stream + **/ void display(std::ostream & os) const { os << this->scale() << unit_cstr(); } + ///@} + /** @defgroup quantity-assignment **/ + ///@{ + /** @brief copy constructor **/ quantity & operator=(quantity const & x) = default; + /** @brief move constructor **/ quantity & operator=(quantity && x) = default; + ///@} private: explicit constexpr quantity(Repr x) : scale_{x} {} diff --git a/include/xo/unit/unit.hpp b/include/xo/unit/unit.hpp index d93ead8d..54c8fffc 100644 --- a/include/xo/unit/unit.hpp +++ b/include/xo/unit/unit.hpp @@ -239,6 +239,7 @@ namespace xo { // ----- weight ----- + /** @brief a unit type representing 1mg (10^-3 grams) **/ using milligram = wrap_unit< std::ratio<1>, bpu_node< bpu> > >; From 0ff7b7dc11e775ac83cd33e796d9884d3a8e5a70 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:44:19 -0400 Subject: [PATCH 0737/2693] xo-unit: quantity: check template args sat concepts --- include/xo/unit/quantity.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index bab5655c..f8d5dd80 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -42,6 +42,8 @@ namespace xo { using repr_type = Repr; ///@} + static_assert(unit_concept); + static_assert(numeric_concept); /* non-unity compile-time scale factors can arise during unit conversion; * for example see method quantity::in_units_of() */ From 1740a232945eb45b4cb6cbdde839991561d53f34 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:44:44 -0400 Subject: [PATCH 0738/2693] xo-unit: utest: + quantity rescale tests --- utest/quantity.test.cpp | 71 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/utest/quantity.test.cpp b/utest/quantity.test.cpp index 75adba87..83cf18ce 100644 --- a/utest/quantity.test.cpp +++ b/utest/quantity.test.cpp @@ -12,9 +12,14 @@ namespace xo { using xo::unit::quantity; using xo::unit::qty::kilograms; + + using xo::unit::qty::meters; + using xo::unit::qty::kilometers; + using xo::unit::qty::milliseconds; using xo::unit::qty::seconds; using xo::unit::qty::minutes; + using xo::unit::qty::hours; using xo::unit::qty::volatility30d; using xo::unit::qty::volatility250d; @@ -718,6 +723,72 @@ namespace xo { //CHECK(r == Approx(0.692820323).epsilon(1e-6)); } /*TEST_CASE(muldiv5)*/ + + TEST_CASE("rescale", "[quantity]") { + constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.rescale")); + //log && log("(A)", xtag("foo", foo)); + + auto q = kilograms(150.0) / minutes(1); /* 150.0kg.min^-1 */ + + CHECK(strcmp(q.unit_cstr(), "kg.min^-1") == 0); + CHECK(q.scale() == 150.0); + + log && log(XTAG(q)); + + namespace u = xo::unit::units; + + auto q1 = q.with_basis_unit(); /* 0.0025kg.ms^-1 */ + + CHECK(strcmp(q1.unit_cstr(), "kg.ms^-1") == 0); + CHECK(q1.scale() == 0.0025); + + log && log(XTAG(q1)); + + auto q2 = q1.with_basis_unit(); /* 2.5g.ms^-1 */ + + CHECK(strcmp(q2.unit_cstr(), "g.ms^-1") == 0); + CHECK(q2.scale() == 2.5); + + log && log(XTAG(q2)); + + auto q3 = q2.with_basis_unit(); /* 2500g.s^-1 */ + + CHECK(strcmp(q3.unit_cstr(), "g.s^-1") == 0); + CHECK(q3.scale() == 2500.0); + + log && log(XTAG(q3)); + } /*TEST_CASE(rescale)*/ + + TEST_CASE("rescale2", "[quantity]") { + constexpr bool c_debug_flag = true; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.rescale2")); + //log && log("(A)", xtag("foo", foo)); + + namespace u = xo::unit::unit_qty; + + auto q1 = kilometers(150.0) / u::hour; + auto q2 = q1.with_units_from(u::meter / u::second); + + log && log(XTAG(q1), XTAG(q2)); + } /*TEST_CASE(rescale2)*/ + + + } /*namespace ut*/ } /*namespace xo*/ From c25930b7f50179ac1f54370a1ef8c9258e999c7d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:45:17 -0400 Subject: [PATCH 0739/2693] xo-unit: docs: cmakefile deps ++ cover quantity features --- docs/CMakeLists.txt | 22 ++++--- docs/README | 1 + docs/classes.rst | 10 ---- docs/examples.rst | 12 ++-- docs/index.rst | 8 +-- docs/quantity-class.rst | 70 ++++++++++++++++++++++ docs/quantity-factoryfunctions.rst | 27 +++++++++ docs/quantity-reference.rst | 12 ++++ docs/quantity-unitvars.rst | 45 +++++++++++++++ docs/unit-concept.rst | 12 ++++ docs/unit-quantities.rst | 93 ++++++++++++++++++++++++++++++ docs/unit-reference.rst | 10 ++++ 12 files changed, 296 insertions(+), 26 deletions(-) delete mode 100644 docs/classes.rst create mode 100644 docs/quantity-class.rst create mode 100644 docs/quantity-factoryfunctions.rst create mode 100644 docs/quantity-reference.rst create mode 100644 docs/quantity-unitvars.rst create mode 100644 docs/unit-concept.rst create mode 100644 docs/unit-quantities.rst create mode 100644 docs/unit-reference.rst diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 8c7b9b92..35be8468 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -7,7 +7,7 @@ else() # otherwise use top-level doxygen setup instead. set(ALL_LIBRARY_TARGETS xo_unit) # todo: automate this from xo-cmake macros - set(ALL_UTEST_TARGETS utest.unit) # todo: automate this from xo-cmake macros + set(ALL_UTEST_TARGETS utest.unit xo_unit_ex1 xo_unit_ex2 xo_unit_ex3 xo_unit_ex4 xo_unit_ex5 xo_unit_ex6) # todo: automate this from xo-cmake macros # look for doxygen executable find_program(DOXYGEN_EXECUTABLE NAMES doxygen REQUIRED) @@ -19,22 +19,27 @@ else() set(DOX_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) - #set(DOX_DEPS ${PROJECT_SOURCE_DIR}/mainpage.dox) # not yet set(DOX_INPUT_DIR ${PROJECT_SOURCE_DIR}) set(DOX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/dox) set(DOX_INDEX_FILE ${DOX_OUTPUT_DIR}/html/index.html) + # .hpp files reachable from xo-unit/include + # + # REMINDER: for reliability will need to re-run cmake when the set of .hpp files changes + # + file(GLOB_RECURSE DOX_HPP_FILES_GLOB ${PROJECT_SOURCE_DIR}/include *.hpp) + set(SPHINX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/sphinx/html) set(SPHINX_INDEX_FILE ${SPHINX_OUTPUT_DIR}/index.html) # # sphinx .rst files reachable from cmake-examples/docs # - # REMINDER: will need to re-run cmake when the set of .rst files changes + # REMINDER: for reliability will need to re-run cmake when the set of .rst files changes # file(GLOB_RECURSE SPHINX_RST_FILES_GLOB ${CMAKE_CURRENT_SOURCE_DIR} *.rst) - set(SPHINX_RST_FILES index.rst install.rst examples.rst classes.rst glossary.rst) + set(SPHINX_RST_FILES index.rst install.rst examples.rst unit-quantities.rst quantity-class.rst quantity-factoryfunctions.rst quantity-unitvars.rst unit-reference.rst unit-concept.rst glossary.rst) # TODO: # 1. move Doxyfile.in to xo-cmake project @@ -45,10 +50,12 @@ else() FILE_PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE @ONLY) + set(DOX_DEPS ${ALL_LIBRARY_TARGETS} ${ALL_UTEST_TARGETS} ${DOX_HPP_FILES_GLOB}) + file(MAKE_DIRECTORY ${DOX_OUTPUT_DIR}) add_custom_command( OUTPUT ${DOX_INDEX_FILE} - DEPENDS ${DOX_DEPS} ${ALL_LIBRARY_TARGETS} ${ALL_UTEST_TARGETS} + DEPENDS ${DOX_DEPS} COMMAND "${DOXYGEN_EXECUTABLE}" ${DOX_CONFIG_FILE} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} MAIN_DEPENDENCY ${DOX_CONFIG_FILE} @@ -62,15 +69,16 @@ else() # add_custom_target( doxygen - DEPENDS ${DOX_INDEX_FILE} + DEPENDS ${DOX_INDEX_FILE} ${DOX_DEPS} ) # root of sphinx doc tree set(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}) + set(SPHINX_DEPS doxygen conf.py ${SPHINX_RST_FILES} ${SPHINX_RST_FILES_GLOB} ${DOX_DEPS}) add_custom_command( OUTPUT ${SPHINX_INDEX_FILE} - DEPENDS doxygen conf.py ${SPHINX_RST_FILES} ${SPHINX_RST_FILES_GLOB} + DEPENDS ${SPHINX_DEPS} COMMAND ${SPHINX_EXECUTABLE} -b html -Dbreathe_projects.xodoxxml=${CMAKE_CURRENT_BINARY_DIR}/dox/xml ${SPHINX_SOURCE} ${SPHINX_OUTPUT_DIR} diff --git a/docs/README b/docs/README index 599096ac..078db7c8 100644 --- a/docs/README +++ b/docs/README @@ -3,6 +3,7 @@ map index.rst +- install.rst +- examples.rst + +- unit-quantities.rst +- classes.rst +- glossary.rst diff --git a/docs/classes.rst b/docs/classes.rst deleted file mode 100644 index 1e3ac342..00000000 --- a/docs/classes.rst +++ /dev/null @@ -1,10 +0,0 @@ -.. _classes: - -.. toctree - :maxdepth: 2 - -Template Classes -================ - -.. doxygenclass:: xo::unit::quantity - :members: diff --git a/docs/examples.rst b/docs/examples.rst index d9beb387..f9ddda45 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -15,16 +15,17 @@ Compile-time unit inference #include int main() { - namespace u = xo::unit; - namespace qty = u::qty; + namespace u = xo::unit::units; + namespace qty = xo::unit::qty; + using xo::unit::quantity; using namespace std; auto t = qty::milliseconds(10); auto m = qty::kilograms(2.5); auto a = m / (t * t); - static_assert(same_as>); - static_assert(same_as>); + static_assert(same_as>); + static_assert(same_as>); static_assert(sizeof(t) == sizeof(int)); static_assert(sizeof(m) == sizeof(double)); static_assert(sizeof(a) == sizeof(double)); @@ -88,7 +89,7 @@ One way to convert units is by assignment: .. code-block:: cpp :linenos: - :emphasize-lines: 9-10 + :emphasize-lines: 10-11 #include "xo/unit/quantity.hpp" #include @@ -96,6 +97,7 @@ One way to convert units is by assignment: int main() { namespace u = xo::unit; namespace qty = xo::units::qty; + using xo::unit::quantity; using namespace std; quantity t = qty::milliseconds(10); diff --git a/docs/index.rst b/docs/index.rst index 108d97d4..12ffda2e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -24,11 +24,13 @@ runtime (since we can't construct new c++ types at runtime). .. toctree:: :maxdepth: 2 - :caption: Contents: + :caption: xo-unit contents: install examples - classes + unit-quantities + quantity-reference + unit-reference Indices and Tables ------------------ @@ -36,5 +38,3 @@ Indices and Tables * :ref:`glossary` * :ref:`genindex` * :ref:`search` - -.. toctree:: diff --git a/docs/quantity-class.rst b/docs/quantity-class.rst new file mode 100644 index 00000000..2945026e --- /dev/null +++ b/docs/quantity-class.rst @@ -0,0 +1,70 @@ +.. _quantity-class: + +Quantity +======== + +.. code-block:: cpp + + #include + +The primary data structure for interacting with xo-unit is the +template class ``xo::unit::quantity``. + +.. doxygenclass:: xo::unit::quantity + +Type Traits +----------- + +.. doxygengroup:: quantity-traits + :content-only: + +Constructors +------------ + +.. doxygengroup:: quantity-ctors + :content-only: + +.. doxygengroup:: quantity-named-ctors + :content-only: + +The simplest way to create a quantity instance is to use either + +* factory functions in ``xo::unit::qty``, see :doc:`quantity-factoryfunctions` +* unit variables in ``xo::unit::units``, see :doc:`quantity-unitvars` + +Access Methods +-------------- + +.. doxygengroup:: quantity-access-methods + :content-only: + +Constants +--------- + +.. doxygengroup:: quantity-constants + :content-only: + +Conversion Methods +------------------ + +Amount-preserving conversion to quantities with different units and/or representation. + +.. doxygengroup:: quantity-unit-conversion + :content-only: + +Arithmetic +---------- + +.. doxygengroup:: quantity-arithmetic + :content-only: + +Support methods for arithmetic operations + +.. doxygengroup:: quantity-arithmeticsupport + :content-only: + +Assignment +---------- + +.. doxygengroup:: quantity-assignment + :content-only: diff --git a/docs/quantity-factoryfunctions.rst b/docs/quantity-factoryfunctions.rst new file mode 100644 index 00000000..f73d9745 --- /dev/null +++ b/docs/quantity-factoryfunctions.rst @@ -0,0 +1,27 @@ +.. _quantity_factoryfunctions: + +.. toctree:: + :maxdepth: 2 + +Quantity Factory Functions +========================== + +.. code-block:: cpp + + #include + +.. doxygenfunction:: xo::unit::qty::milligrams +.. doxygenfunction:: xo::unit::qty::grams +.. doxygenfunction:: xo::unit::qty::kilograms + +.. doxygenfunction:: xo::unit::qty::millimeters +.. doxygenfunction:: xo::unit::qty::meters +.. doxygenfunction:: xo::unit::qty::kilometers + +.. doxygenfunction:: xo::unit::qty::nanoseconds +.. doxygenfunction:: xo::unit::qty::microseconds +.. doxygenfunction:: xo::unit::qty::milliseconds +.. doxygenfunction:: xo::unit::qty::seconds +.. doxygenfunction:: xo::unit::qty::minutes +.. doxygenfunction:: xo::unit::qty::hours +.. doxygenfunction:: xo::unit::qty::days diff --git a/docs/quantity-reference.rst b/docs/quantity-reference.rst new file mode 100644 index 00000000..1f3b4496 --- /dev/null +++ b/docs/quantity-reference.rst @@ -0,0 +1,12 @@ +.. _quantity-reference: + +Quantity Reference +================== + +.. toctree:: + :maxdepth: 2 + :caption: Quantity Reference + + quantity-class + quantity-factoryfunctions + quantity-unitvars diff --git a/docs/quantity-unitvars.rst b/docs/quantity-unitvars.rst new file mode 100644 index 00000000..77c09946 --- /dev/null +++ b/docs/quantity-unitvars.rst @@ -0,0 +1,45 @@ +.. _quantity-unitvars: + +.. toctree:: + :maxdepth: 2 + +Quantity Unit Variables +======================= + +.. code-block:: cpp + + #include + +The ``xo::unit::unit_qty`` namespace contains unit quantities in each dimension. +Can use these to request unit conversion involving multiple dimensions, for example: + +.. code-block:: cpp + + namespace qty = xo::unit::qty; + namespace u = xo::unit::unit_qty; + + auto q1 = (qty::kilometers(150.0) / qty::hours(0.5); + auto q2 = q1.with_units_from(u:meter / u:second); + + +Mass +---- +.. doxygenvariable:: xo::unit::unit_qty::milligram +.. doxygenvariable:: xo::unit::unit_qty::gram +.. doxygenvariable:: xo::unit::unit_qty::kilogram + +Distance +-------- +.. doxygenvariable:: xo::unit::unit_qty::millimeter +.. doxygenvariable:: xo::unit::unit_qty::meter +.. doxygenvariable:: xo::unit::unit_qty::kilometer + +Time +---- +.. doxygenvariable:: xo::unit::unit_qty::nanosecond +.. doxygenvariable:: xo::unit::unit_qty::microsecond +.. doxygenvariable:: xo::unit::unit_qty::millisecond +.. doxygenvariable:: xo::unit::unit_qty::second +.. doxygenvariable:: xo::unit::unit_qty::minute +.. doxygenvariable:: xo::unit::unit_qty::hour +.. doxygenvariable:: xo::unit::unit_qty::day diff --git a/docs/unit-concept.rst b/docs/unit-concept.rst new file mode 100644 index 00000000..387289d8 --- /dev/null +++ b/docs/unit-concept.rst @@ -0,0 +1,12 @@ +.. _unit-concept: + +Unit Concepts +============= + +.. code-block:: cpp + + #include + +.. doxygenconcept:: xo::unit::unit_concept + +.. doxygenconcept:: xo::unit::basis_unit_concept diff --git a/docs/unit-quantities.rst b/docs/unit-quantities.rst new file mode 100644 index 00000000..30e57de6 --- /dev/null +++ b/docs/unit-quantities.rst @@ -0,0 +1,93 @@ +.. _unit-quantities: + +.. toctree + :maxdepth: 2 + +Unit Quantities +=============== + +Xo-unit uses the type system to represent units. +This is great for eliminating runtime overhead. + +One place where we face some awkwardness is conversions involving multiple dimensions. +We'd like to write something concise like + +.. code-block:: cpp + + meter / (second * second); + +The difficulty arises because xo-unit represents `meter` and `second` by types +(``xo::unit::units::meter`` and ``xo::unit::units::second``); operators like `*` and `/` +apply to *values*, not types. + +We'll present various ways to express rescaling below + +Converting units +---------------- + +First, xo-unit provides constexpr unit quantities in namespace ``xo::unit::unit_qty``: + +.. code-block:: cpp + :linenos: + + static constexpr auto meter = qty::meters(1); + static constexpr auto kilometer = qty::kilometers(1); + // etc + +Second, a method ``quantity::with_units_from`` that takes units (only) from its argument: +``quantity::with_units_from`` just extracts its argument's unit_type to call ``quantity::with_units``. + +.. code-block:: cpp + :linenos: + + template + template + auto quantity::with_units_from(Quantity q) { + return this->with_units(); + } + +Motivation is that it's easier to express an argument to `with_units_from` +than to express template arguments to `with_units`. + +Prefer + +.. code-block:: cpp + :linenos: + :emphasize-lines: 5 + + namespace u = xo::unit::unit_qty; // u::meter is a value + namespace qty = xo::unit::qty; + + auto q1 = qty::kilometers(150.0) / qty::hours(0.5); + auto q2 = q1.with_units_from(u::meter / u::second); + +instead of the more verbose: + +.. code-block:: cpp + :linenos: + :emphasize-lines: 5-6 + + namespace u = xo::unit::units; // u::meter is a type + + auto q1 = qty::kilometers(150.0) / qty::hours(0.5); + + auto q2 = q1.with_units>>(); + +Using basis units +----------------- + +An alternative way to request multidimensional unit conversion is with basis units + +.. code-block:: cpp + :linenos: + :emphasize-lines: 4-5 + + namespace u = xo::unit::units; // u::meter is a type + + auto q1 = qty::kilometers(150.0) / qty::hours(0.5); + auto q2 = q1.with_basis_unit(); // q2 in km.s^-1 + auto q3 = q2.with_basis_unit(); // q3 in m.s^-1 + +With this technique we don't have to supply the basis dimension's exponent. +Instead we're just giving scale. diff --git a/docs/unit-reference.rst b/docs/unit-reference.rst new file mode 100644 index 00000000..a329f486 --- /dev/null +++ b/docs/unit-reference.rst @@ -0,0 +1,10 @@ +.. _unit-reference: + +Unit Reference +============== + +.. toctree:: + :maxdepth: 2 + :caption: Unit Reference + + unit-concept From eea10d8fe1f6d6b4fd99cd6ef9a99b79faf50a50 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 02:00:05 -0400 Subject: [PATCH 0740/2693] xo-unit: refactor: move promoter aux class to detail/ subdir --- include/xo/unit/detail/promoter.hpp | 40 +++++++++++++++++++++++++++++ include/xo/unit/quantity.hpp | 29 +++++---------------- 2 files changed, 47 insertions(+), 22 deletions(-) create mode 100644 include/xo/unit/detail/promoter.hpp diff --git a/include/xo/unit/detail/promoter.hpp b/include/xo/unit/detail/promoter.hpp new file mode 100644 index 00000000..1dc34c78 --- /dev/null +++ b/include/xo/unit/detail/promoter.hpp @@ -0,0 +1,40 @@ +/** @file promoter.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "unit.hpp" + +namespace xo { + namespace unit { + /** @class promoter + * + * Auxiliary class driver for quantity::promote(). + * promoter has two specializations: + * 1. if Unit is dimensionless, @c promoter::promote() is the identity function. + * This has the effect of collapsing dimensionless quantities to their representation. + * 2. if Unit has at least one non-empty dimension, + * @c promoter::promote() builds an xo::unit::quantity instance + **/ + template > + struct promoter; + + template + class quantity; + + /* collapse dimensionless quantity to its repr_type> */ + template + struct promoter { + static constexpr Repr promote(Repr x) { return x; }; + }; + + template + struct promoter { + static constexpr quantity promote(Repr x) { return quantity(x); } + }; + } /*namespace unit*/ +} /*namespace xo*/ + +/** end promoter.hpp **/ diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index f8d5dd80..138565d1 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -4,18 +4,12 @@ #include "quantity_concept.hpp" #include "unit.hpp" +#include "detail/promoter.hpp" //#include "xo/reflect/Reflect.hpp" //#include "xo/indentlog/scope.hpp" namespace xo { namespace unit { - /** @class promoter - * - * Aux class assister for quantity::promote() - **/ - template > - struct promoter; - // ----- quantity ----- /** @class quantity @@ -72,10 +66,7 @@ namespace xo { static constexpr quantity unit_quantity() { return quantity(1); } /** @brief promote representation to quantity. Same as multiplying by Unit **/ - static constexpr auto promote(Repr x) { - //std::cerr << "quantity::promote: x=" << x << ", R=" << reflect::Reflect::require()->canonical_name() << std::endl; - return promoter::promote(x); - } + static constexpr auto promote(Repr x); ///@} /** @addtogroup quantity-traits **/ @@ -500,18 +491,12 @@ namespace xo { Repr scale_ = 0; }; /*quantity*/ - // ----- promoter ----- - - /* collapse dimensionless quantity to its repr_type> */ template - struct promoter { - static constexpr Repr promote(Repr x) { return x; }; - }; - - template - struct promoter { - static constexpr quantity promote(Repr x) { return quantity(x); } - }; + constexpr auto + quantity::promote(Repr x) { + //std::cerr << "quantity::promote: x=" << x << ", R=" << reflect::Reflect::require()->canonical_name() << std::endl; + return promoter::promote(x); + } // ----- operator+ ----- From 75799f46529124848862e5e42335ac422fa57dfe Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 12 Apr 2024 20:45:04 -0400 Subject: [PATCH 0741/2693] initial commit --- CMakeLists.txt | 57 +++++++++++ cmake/xo-bootstrap-macros.cmake | 26 +++++ cmake/xo_stringliteralConfig.cmake.in | 17 ++++ example/CMakeLists.txt | 1 + example/ex1/CMakeLists.txt | 15 +++ example/ex1/ex1.cpp | 38 ++++++++ .../xo/stringliteral/string_view_concat.hpp | 30 ++++++ include/xo/stringliteral/stringliteral.hpp | 96 +++++++++++++++++++ .../stringliteral/stringliteral_iostream.hpp | 36 +++++++ 9 files changed, 316 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/xo-bootstrap-macros.cmake create mode 100644 cmake/xo_stringliteralConfig.cmake.in create mode 100644 example/CMakeLists.txt create mode 100644 example/ex1/CMakeLists.txt create mode 100644 example/ex1/ex1.cpp create mode 100644 include/xo/stringliteral/string_view_concat.hpp create mode 100644 include/xo/stringliteral/stringliteral.hpp create mode 100644 include/xo/stringliteral/stringliteral_iostream.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..b7b5d586 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,57 @@ +# xo-stringliteral/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_stringliteral VERSION 1.0) +enable_language(CXX) + +# common XO cmake macros (see proj/xo-cmake) +include(cmake/xo-bootstrap-macros.cmake) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() +# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) +add_code_coverage() + +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) + +# ---------------------------------------------------------------- +# c++ settings + +# one-time project-specific c++ flags. usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- + +add_subdirectory(example) +#add_subdirectory(utest) +#add_subdirectory(docs) + +# ---------------------------------------------------------------- +# provide find_package() support for projects using this library + +set(SELF_LIB xo_stringliteral) +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) + +# ---------------------------------------------------------------- +# dependencies + +#xo_headeronly_dependency(${SELF_LIB} randomgen) +# etc.. + +# end CMakeLists.txt diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..0b6a916a --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,26 @@ +# ---------------------------------------------------------------- +# for example use +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will set +# CMAKE_MODULE_PATH = /usr/local/share/cmake +# and expect .cmake macros in +# /usr/local/share/cmake/xo_macros/xo-project-macros.cmake +# ---------------------------------------------------------------- + +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +if (NOT XO_SUBMODULE_BUILD) + message("-- GUESSED_CMAKE_CMD=cmake -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -B ${CMAKE_BINARY_DIR}") + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) diff --git a/cmake/xo_stringliteralConfig.cmake.in b/cmake/xo_stringliteralConfig.cmake.in new file mode 100644 index 00000000..e5ee1778 --- /dev/null +++ b/cmake/xo_stringliteralConfig.cmake.in @@ -0,0 +1,17 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# note: changes to find_dependency() calls here +# must coordinate with xo_dependency() calls +# in xo-reactor/src/reactor/CMakeLists.txt +# +#find_dependency(reflect) +#find_dependency(subsys) +#find_dependency(Eigen3) +#find_dependency(webutil) +#find_dependency(printjson) +#find_dependency(callback) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 00000000..4151ec21 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(ex1) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt new file mode 100644 index 00000000..abe34974 --- /dev/null +++ b/example/ex1/CMakeLists.txt @@ -0,0 +1,15 @@ +# xo-stringliteral/example/ex1/CMakeLists.txt + +set(SELF_EXE xo_stringliteral_ex1) +set(SELF_SRCS ex1.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +# ---------------------------------------------------------------- +# dependencies.. + +xo_self_dependency(${SELF_EXE} xo_stringliteral) +#xo_dependency(${SELF_EXE} reflect) + +# end CMakeLists.txt diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp new file mode 100644 index 00000000..dcb2a459 --- /dev/null +++ b/example/ex1/ex1.cpp @@ -0,0 +1,38 @@ +/* @file ex1.cpp */ + +#include "xo/stringliteral/stringliteral.hpp" +#include "xo/stringliteral/stringliteral_iostream.hpp" +#include "xo/stringliteral/string_view_concat.hpp" +#include + +int +main() { + using namespace std; + using xo::stringliteral; + using xo::stringliteral_compare; + +#ifdef NOT_USING + constexpr stringliteral s1("hello"); + + static_assert(stringliteral_compare(s1, s1) == 0); + + cerr << s1 << endl; + + constexpr stringliteral s2 = stringliteral_concat(stringliteral("hello"), + stringliteral(", world")); + +#endif + + static constexpr string_view hello("hello"); + static constexpr string_view world(" world"); + + static constexpr auto s2 = concat_v; + + static constexpr string_view hello_world("hello world"); + + static_assert(s2 == hello_world); + + cerr << hello_world << endl; +} + +/* end ex1.cpp */ diff --git a/include/xo/stringliteral/string_view_concat.hpp b/include/xo/stringliteral/string_view_concat.hpp new file mode 100644 index 00000000..4105420d --- /dev/null +++ b/include/xo/stringliteral/string_view_concat.hpp @@ -0,0 +1,30 @@ +#include +#include + +template +struct sv_concat +{ + static constexpr auto impl() noexcept { + constexpr std::size_t n = (Strings.size() + ... + 0); + + std::array arr{}; + + auto append = [i=0, &arr](const auto & s) mutable { + for (auto c : s) + arr[i++] = c; + }; + (append(Strings), ...); + arr[n] = '\0'; + + return arr; + } + + static constexpr auto arr = impl(); + static constexpr std::string_view value { + arr.data(), + arr.size() - 1 + }; +}; + +template +static constexpr auto concat_v = sv_concat::value; diff --git a/include/xo/stringliteral/stringliteral.hpp b/include/xo/stringliteral/stringliteral.hpp new file mode 100644 index 00000000..72235727 --- /dev/null +++ b/include/xo/stringliteral/stringliteral.hpp @@ -0,0 +1,96 @@ +/** @file stringliteral.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include +#include +#include + +namespace xo { + /** @class stringliteral + * + * @brief class to represent a literal string at compile time, for use as template argument + **/ + template + struct stringliteral { + constexpr stringliteral() { if (N > 0) value_[0] = '\0'; } + constexpr stringliteral(const char (&str)[N]) { std::copy_n(str, N, value_); } + constexpr int size() const { return N; } + + constexpr char const * c_str() const { return value_; } + + char value_[N]; + }; + + /** @brief all_same_v is true iff types T1 = .. = Tn + **/ + template < typename First, typename... Rest > + constexpr auto + all_same_v = std::conjunction_v< std::is_same... >; + + /** @brief concatenate string literals + * + * NOTE: this isn't constexpr in clang16 + **/ + template < typename... Ts> + constexpr auto + stringliteral_concat(Ts && ... args) + { +#ifdef NOT_USING + static_assert(all_same_v...>, + "string must share the same char type"); + + using char_type = std::remove_const_t< std::remove_pointer_t < std::common_type_t < Ts... > > >; +#endif + using char_type = char; + + /** n1: total number of bytes used by arguments **/ + constexpr size_t n1 = (sizeof(Ts) + ...); + /** z1: each string arg has a null terminator included in its size, + * z1 is the number of arguments in parameter pack Ts, + * which equals the number of null terminators used + **/ + constexpr size_t z1 = sizeof...(Ts); + + /** n: number of chars in concatenated string. +1 for final null **/ + constexpr size_t n + = (n1 / sizeof(char_type)) - z1 + 1; + + stringliteral result; + size_t pos = 0; + + auto detail_concat = [ &pos, &result ](auto && arg) { + constexpr auto count = (sizeof(arg) - sizeof(char_type)) / sizeof(char_type); + + std::copy_n(arg.c_str(), count, result.value_ + pos); + pos += count; + }; + + (detail_concat(args), ...); + + //return stringliteral(""); + return result; + } + +#ifdef NOT_USING + template + constexpr auto + stringliteral_compare(stringliteral && s1, stringliteral && s2) + { + return std::string_view(s1.value_) <=> std::string_view(s2.value_); + } +#endif + + template + constexpr auto + stringliteral_compare(const stringliteral & s1, const stringliteral & s2) + { + return std::string_view(s1.value_) <=> std::string_view(s2.value_); + } +} /*namespace xo*/ + + +/** end stringliteral.hpp **/ diff --git a/include/xo/stringliteral/stringliteral_iostream.hpp b/include/xo/stringliteral/stringliteral_iostream.hpp new file mode 100644 index 00000000..18a371a5 --- /dev/null +++ b/include/xo/stringliteral/stringliteral_iostream.hpp @@ -0,0 +1,36 @@ +/** @file stringliteral_iostream.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "stringliteral.hpp" +#include +//#include + +namespace xo { + /** @brief print stringliteral on stream os. + * + **/ + template + void + print_stringliteral (std::ostream & os, const stringliteral & x) { + os << x.c_str(); + } + + /** @brief print stringliteral x on stream os. + * + * Example + * @code + * cout << stringliteral("foo"); // outputs "foo" + **/ + template + inline std::ostream & + operator<< (std::ostream & os, const stringliteral & x) { + print_stringliteral(os, x); + return os; + } +} /*namespace xo*/ + +/** end stringliteral_iostream.hpp **/ From c8ac0ceb493c03af3edbcb008503b001bcba50be Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 16 Apr 2024 13:30:16 -0400 Subject: [PATCH 0742/2693] cmake: + xo_self_headeronly_dependency() --- cmake/xo_macros/xo_cxx.cmake | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index e63762f8..3fcf3e2f 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -736,6 +736,19 @@ macro(xo_self_dependency target dep) target_link_libraries(${target} PUBLIC ${dep}) endmacro() +# dependency on target provided from this codebase. +# +# Similar comments as for xo_self_dependency() +# 1. don't need find_package() in this case, since details of dep targets +# must be known to cmake for it to build them. +# 2. in any case, can't use find_package() when cmake runs, +# because supporting .cmake files haven't been generated yet +# 3. need to use INTERFACE instead of PUBLIC for a header-only dep +# +macro(xo_self_headeronly_dependency target dep) + target_link_libraries(${target} INTERFACE ${dep}) +endmacro() + # ---------------------------------------------------------------- # need this when linking pybind11-generated libraries # From a958217c38470c5e49a67770c3d9abb8c0338fc6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 16 Apr 2024 17:25:14 -0400 Subject: [PATCH 0743/2693] xo-flatstring: + README + docs + test coverage --- CMakeLists.txt | 46 +- README.md | 59 + cmake/gen-ccov.in | 20 + cmake/lcov-harness | 114 + cmake/xo-bootstrap-macros.cmake | 5 +- ....cmake.in => xo_flatstringConfig.cmake.in} | 0 docs/CMakeLists.txt | 110 + docs/Doxyfile.in | 2816 +++++++++++++++++ docs/_static/README | 1 + docs/conf.py | 35 + docs/flatstring-class.rst | 60 + docs/flatstring-functions.rst | 15 + docs/flatstring-reference.rst | 11 + docs/index.rst | 38 + docs/install.rst | 56 + docs/lessons.rst | 44 + example/ex1/ex1.cpp | 149 +- include/xo/flatstring/flatstring.hpp | 414 +++ include/xo/flatstring/flatstring_iostream.hpp | 37 + .../string_view_concat.hpp | 0 include/xo/stringliteral/stringliteral.hpp | 96 - .../stringliteral/stringliteral_iostream.hpp | 36 - utest/.#flatstring_utest_main.cpp | 1 + utest/CMakeLists.txt | 21 + utest/flatstring.test.cpp | 131 + utest/flatstring_utest_main.cpp | 6 + 26 files changed, 4163 insertions(+), 158 deletions(-) create mode 100644 README.md create mode 100644 cmake/gen-ccov.in create mode 100755 cmake/lcov-harness rename cmake/{xo_stringliteralConfig.cmake.in => xo_flatstringConfig.cmake.in} (100%) create mode 100644 docs/CMakeLists.txt create mode 100644 docs/Doxyfile.in create mode 100644 docs/_static/README create mode 100644 docs/conf.py create mode 100644 docs/flatstring-class.rst create mode 100644 docs/flatstring-functions.rst create mode 100644 docs/flatstring-reference.rst create mode 100644 docs/index.rst create mode 100644 docs/install.rst create mode 100644 docs/lessons.rst create mode 100644 include/xo/flatstring/flatstring.hpp create mode 100644 include/xo/flatstring/flatstring_iostream.hpp rename include/xo/{stringliteral => flatstring}/string_view_concat.hpp (100%) delete mode 100644 include/xo/stringliteral/stringliteral.hpp delete mode 100644 include/xo/stringliteral/stringliteral_iostream.hpp create mode 120000 utest/.#flatstring_utest_main.cpp create mode 100644 utest/CMakeLists.txt create mode 100644 utest/flatstring.test.cpp create mode 100644 utest/flatstring_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b7b5d586..79d62595 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,8 @@ # xo-stringliteral/CMakeLists.txt -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.25) -project(xo_stringliteral VERSION 1.0) +project(xo_flatstring VERSION 1.0) enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) @@ -12,17 +12,37 @@ include(cmake/xo-bootstrap-macros.cmake) # unit test setup enable_testing() -# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) -add_code_coverage() -# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. -# we're not interested in code coverage for these sources. -# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; -# rather, want coverage on the code that the unit tests exercise. +# ---------------------------------------------------------------- +# cmake -DCMAKE_BUILD_TYPE=coverage + +if (NOT DEFINED PROJECT_CXX_FLAGS_COVERAGE) + # note: for clang would use -fprofile-instr-generate -fcoverage-mapping here instead and also at link time + set(PROJECT_CXX_FLAGS_COVERAGE ${PROJECT_CXX_FLAGS} -ggdb -Og -fprofile-arcs -ftest-coverage + CACHE STRING "coverage c++ compiler flags") +endif() +message("-- PROJECT_CXX_FLAGS_COVERAGE: coverage c++ flags are [${PROJECT_CXX_FLAGS_COVERAGE}]") + +add_compile_options("$<$:${PROJECT_CXX_FLAGS_COVERAGE}>") +# when -DCMAKE_BUILD_TYPE=coverage, link executables with gcov +link_libraries("$<$:gcov>") + +find_program(LCOV_EXECUTABLE NAMES lcov) +find_program(GENHTML_EXECUTABLE NAMES genhtml) + +# with coverage build: +# 1. invoke instrumented executables for which you want coverage: +# (cd path/to/build && ctest) +# 2. post-process low-level coverage data +# (path/to/build/gen-ccov) +# 3. point browser to generated html data +# file:///path/to/build/ccov/html/index.html # -# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target -# -add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) +configure_file( + ${PROJECT_SOURCE_DIR}/cmake/gen-ccov.in + ${PROJECT_BINARY_DIR}/gen-ccov) + +file(CHMOD ${PROJECT_BINARY_DIR}/gen-ccov PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) # ---------------------------------------------------------------- # c++ settings @@ -37,8 +57,8 @@ xo_toplevel_compile_options() # ---------------------------------------------------------------- add_subdirectory(example) -#add_subdirectory(utest) -#add_subdirectory(docs) +add_subdirectory(utest) +add_subdirectory(docs) # ---------------------------------------------------------------- # provide find_package() support for projects using this library diff --git a/README.md b/README.md new file mode 100644 index 00000000..856b61fb --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# flatstring library + +Fixed-length no-allocation string implementation. + +Features: +- char array representation with maximum size set at compile time. +- compile time construction from char array and string concatenation +- pointer-free implementation, instances can be used as template arguments +- To the extent practical, provides the same api as `std::string` + +Limitations: +- requires c++20 +- not resizable. +- does not support wide characters. + +## Getting started + +### build + install +``` +$ cd xo-flatstring +$ mkdir .build +$ PREFIX=/usr/local # for example +$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -B .build +$ cmake --build .build +$ cmake --install .build +``` + +### build documentation +``` +$ cd xo-flatstring +$ cmake --build .build -- docs +``` +When complete, point local browser to `xo-flatstring/.build/docs/sphinx/index.html` + +### build with test coverage +``` +$ cd xo-flatstring +$ mkdir .build-ccov +$ cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug -B .build-ccov +$ cmake --build .build-ccov +``` + +run coverage-enabled unit tests +``` +$ (cd .build-ccov && ctest) +``` + +generate html+text coverage report +``` +$ .build-ccov/gen-ccov +``` + +browse to `.build-ccov/ccov/html/index.html` + +### LSP support +``` +$ cd xo-flatstring +$ ln -s .build/compile_commands.json +``` diff --git a/cmake/gen-ccov.in b/cmake/gen-ccov.in new file mode 100644 index 00000000..e335aed4 --- /dev/null +++ b/cmake/gen-ccov.in @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +srcdir=@PROJECT_SOURCE_DIR@ +builddir=@PROJECT_BINARY_DIR@ +lcov=@LCOV_EXECUTABLE@ +genhtml=@GENHTML_EXECUTABLE@ + +if [[ $lcov == "LCOV_EXECUTABLE-NOTFOUND" ]]; then + echo "gen-ccov: lcov executable not found" + exit 1 +fi + +if [[ $genhtml == "GENHTML_EXECUTABLE-NOTFOUND" ]]; then + echo "gen-ccov: genhtml executable not found" + exit 1 +fi + +mkdir $builddir/ccov + +$srcdir/cmake/lcov-harness $srcdir $builddir $builddir/ccov/out $lcov $genhtml diff --git a/cmake/lcov-harness b/cmake/lcov-harness new file mode 100755 index 00000000..27ac8be9 --- /dev/null +++ b/cmake/lcov-harness @@ -0,0 +1,114 @@ +#!/usr/bin/env bash + +srcdir=$1 +builddir=$2 +outputstem=$3 +lcov=$4 +genhtml=$5 + +if [[ -z "${srcdir}" ]]; then + echo "lcov-harness: expected non-empty srcdir" + exit 1 +fi + +if [[ -z ${builddir} ]]; then + echo "lcov-harness: expected non-empty builddir" + exit 1 +fi + +if [[ -z ${outputstem} ]]; then + echo "lcov-harness: expected non-empty outputstem" + exit 1 +fi + +if [[ -z ${lcov} ]]; then + echo "lcov-harness: exepcted non-empty lcov" + exit 1 +fi + +if [[ -z ${genhtml} ]]; then + echo "lcov-harness: expected non-empty genhtml" + exit 1 +fi + +# directory stems for location of {.gcda, gcno} coverage information, +# +# if we have source tree: +# +# ${srcdir} +# +- foo +# | \- foo.cpp +# \- bar +# \- quux +# +- quux.cpp +# \- quux_main.cpp +# +# then we expect build tree: +# +# ${builddir} +# +- foo +# | \- CMakeFiles +# | \- foo_target.dir +# | +- foo.cpp.gcda +# | \- foo.cpp.gcno +# +- bar +# \- quux +# \- CMakeFiles +# \- target4quux.dir +# +- quux.cpp.gcda +# +- quux.cpp.gcno +# +- quux_main.cpp.gcda +# \- quux_main.cpp.gcno +# +# in which case will have cmd_body: +# +# ${primarydirs} +# ./foo/CMakeFiles/foo_target.dir +# ./bar/quux/CMakeFiles/target4quux.dir +# +# here foo_target, quux_target are whatever build is using for corresponding cmake target names. +# +# We want to invoke lcov like: +# +# lcov --capture \ +# --output ${builddir}/ccov \ +# --exclude /utest/ \ +# --base-directory ${srcdir}/foo --directory ${builddir}/foo/CMakeFiles/foo_target.dir \ +# --base-directory ${srcdir}/bar/quux --directory ${builddir}/bar/quux/CMakeFiles/target4quux.dir +# +primarydirs=$(cd ${builddir} && find -name '*.gcno' \ + | xargs --replace=xx dirname xx \ + | uniq \ + | sed -e 's:^\./::') + +#echo "primarydirs=${primarydirs}" + +cmd="${lcov} --output ${outputstem}.info --capture --ignore-errors source" + +for bdir in ${primarydirs}; do + sdir=$(dirname $(dirname ${bdir})) + + cmd="${cmd} --base-directory ${srcdir}/${sdir} --directory ${builddir}/${bdir}" +done + +#echo cmd=${cmd} + +set -x + +# capture +${cmd} + +# keep only files with paths under source tree +# (don't want coverage for external libraries such as libstdc++ etc) +${lcov} --extract ${outputstem}.info "${srcdir}/*" --output ${outputstem}2.info + +# remove unit test dirs +# (we're interested in coverage of our installed code, not of the unit tests that exercise it) +${lcov} --remove ${outputstem}2.info '*/utest/*' --output ${outputstem}3.info + +# generate .html tree +mkdir -p ${builddir}/ccov/html +${genhtml} --ignore-errors source --show-details --prefix ${srcdir} --output-directory ${builddir}/ccov/html ${outputstem}3.info + +# also send report to stdout +${lcov} --list ${outputstem}3.info diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 0b6a916a..83f4a39b 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -15,7 +15,7 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL pre endif() if (NOT XO_SUBMODULE_BUILD) - message("-- GUESSED_CMAKE_CMD=cmake -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -B ${CMAKE_BINARY_DIR}") + message("-- GUESSED_CMAKE_CMD=cmake -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -B ${CMAKE_BINARY_DIR}") message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") endif() @@ -23,4 +23,5 @@ endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) # -include(xo_macros/xo-project-macros) +#include(xo_macros/xo-project-macros) +include(xo_macros/xo_cxx) # not using v1 code-coverage; testing cmake-examples impl instead diff --git a/cmake/xo_stringliteralConfig.cmake.in b/cmake/xo_flatstringConfig.cmake.in similarity index 100% rename from cmake/xo_stringliteralConfig.cmake.in rename to cmake/xo_flatstringConfig.cmake.in diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt new file mode 100644 index 00000000..0751be74 --- /dev/null +++ b/docs/CMakeLists.txt @@ -0,0 +1,110 @@ +# xo-stringliteral/docs/CMakeLists.txt + +if (XO_SUBMODULE_BUILD) + # in submodule build, rely on toplevel docs/CMakeLists.txt file instead +else() + # build docs starting from here only in standalone build. + # otherwise use top-level doxygen setup instead. + + set(ALL_LIBRARY_TARGETS xo_stringliteral) # todo: automate this from xo-cmake macros + set(ALL_UTEST_TARGETS xo_stringliteral_ex1 ) # todo: automate this from xo-cmake macros + + # look for doxygen executable + find_program(DOXYGEN_EXECUTABLE NAMES doxygen REQUIRED) + message("-- DOXYGEN_EXECUTABLE=${DOXYGEN_EXECUTABLE}") + + # look for sphinx-build executable + find_program(SPHINX_EXECUTABLE NAMES sphinx-build REQUIRED) + message("-- SPHINX_EXECUTABLE=${SPHINX_EXECUTABLE}") + + set(DOX_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) + + set(DOX_INPUT_DIR ${PROJECT_SOURCE_DIR}) + set(DOX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/dox) + + set(DOX_INDEX_FILE ${DOX_OUTPUT_DIR}/html/index.html) + + # .hpp files reachable from xo-stringliteral/include + # + # REMINDER: for reliability will need to re-run cmake when the set of .hpp files changes + # + file(GLOB_RECURSE DOX_HPP_FILES_GLOB ${PROJECT_SOURCE_DIR}/include *.hpp) + + set(SPHINX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/sphinx/html) + set(SPHINX_INDEX_FILE ${SPHINX_OUTPUT_DIR}/index.html) + # + # sphinx .rst files reachable from cmake-examples/docs + # + # REMINDER: for reliability will need to re-run cmake when the set of .rst files changes + # + file(GLOB_RECURSE SPHINX_RST_FILES_GLOB ${CMAKE_CURRENT_SOURCE_DIR} *.rst) + + set(SPHINX_RST_FILES index.rst install.rst lessons.rst flatstring-reference.rst flatstring-class.rst) + + # TODO: + # 1. move Doxyfile.in to xo-cmake project + # 2. replace this command section with xo-cmake macro + # + configure_file( + Doxyfile.in ${DOX_CONFIG_FILE} + FILE_PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE + @ONLY) + + set(DOX_DEPS ${ALL_LIBRARY_TARGETS} ${ALL_UTEST_TARGETS} ${DOX_HPP_FILES_GLOB}) + + file(MAKE_DIRECTORY ${DOX_OUTPUT_DIR}) + add_custom_command( + OUTPUT ${DOX_INDEX_FILE} + DEPENDS ${DOX_DEPS} + COMMAND "${DOXYGEN_EXECUTABLE}" ${DOX_CONFIG_FILE} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + MAIN_DEPENDENCY ${DOX_CONFIG_FILE} + COMMENT "Generating docs (doxygen)") + + # To build this target + # $ cmake --build .build -j -- doxygen + # or + # $ cd .build + # $ make doxygen + # + add_custom_target( + doxygen + DEPENDS ${DOX_INDEX_FILE} ${DOX_DEPS} + ) + + # root of sphinx doc tree + set(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}) + set(SPHINX_DEPS doxygen conf.py ${SPHINX_RST_FILES} ${SPHINX_RST_FILES_GLOB} ${DOX_DEPS}) + + add_custom_command( + OUTPUT ${SPHINX_INDEX_FILE} + DEPENDS ${SPHINX_DEPS} + COMMAND ${SPHINX_EXECUTABLE} + -b html -Dbreathe_projects.xodoxxml=${CMAKE_CURRENT_BINARY_DIR}/dox/xml + ${SPHINX_SOURCE} ${SPHINX_OUTPUT_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating docs (sphinx) -> [${SPHINX_OUTPUT_DIR}]") + + # make sphinx --> generate sphinx documentation + # + add_custom_target( + sphinx + DEPENDS ${SPHINX_INDEX_FILE}) + + # - html docs generated in build/docs/sphinx + # - copy the doc tree to share/doc/xo_unit/html + # + # OPTIONAL: install directory tree if it exists, + # but don't complain if it's missing + install( + DIRECTORY ${SPHINX_OUTPUT_DIR} + FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/doc/${PROJECT_NAME} + COMPONENT Documentation + OPTIONAL) + + # make docs --> generate sphinx documentation + add_custom_target( + docs + DEPENDS sphinx) +endif() diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in new file mode 100644 index 00000000..13dfb0fa --- /dev/null +++ b/docs/Doxyfile.in @@ -0,0 +1,2816 @@ +# If filename is Doxyfile.in: +# template (to be expanded by cmake) for real doxyfile +# @SOMEVAR@ expands to value of cmake variable SOMEVAR +# +# expressions to be expanded include: +# @DOX_INPUT_DIR@ +# @DOX_OUTPUT_DIR@ +# +# if filename is Doxyfile: +# expanded template in build directory, to configure doxygen + +# Doxyfile 1.9.7 +# + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "Cmake Examples" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = @DOX_OUTPUT_DIR@ + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = @DOX_INPUT_DIR@ + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN Use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0. and GITHUB Use the lower case version of title +# with any whitespace replaced by '-' and punctations characters removed.. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = YES + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = YES + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = YES + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. + +CASE_SENSE_NAMES = SYSTEM + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about +# undocumented enumeration values. If set to NO, doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = @DOX_INPUT_DIR@ + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding +# "INPUT_ENCODING" for further information on supported encodings. + +INPUT_FILE_ENCODING = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, +# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C +# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, +# *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.l \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = */utest/* + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# ANamespace::AClass, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generate light mode output, DARK always +# generate dark mode output, AUTO_LIGHT automatically set the mode according to +# the user preference, use light mode if no preference is set (the default), +# AUTO_DARK automatically set the mode according to the user preference, use +# dark mode if no preference is set and TOGGLE allow to user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /Node, +# Edge and Graph Attributes specification You need to make sure dot is able +# to find the font, which can be done by putting it in a standard location or by +# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. Default graphviz fontsize is 14. +# The default value is: fontname=Helvetica,fontsize=10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_COMMON_ATTR = "fontname=lato,fontsize=10" +#DOT_COMMON_ATTR = "fontname=HelveticaNeue,fontsize=10" + +# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can +# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. Complete documentation about +# arrows shapes. +# The default value is: labelfontname=Helvetica,labelfontsize=10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_EDGE_ATTR = "labelfontname=lato,labelfontsize=10" +#DOT_EDGE_ATTR = "labelfontname=HelveticaNeue,labelfontsize=10" + +# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes +# around nodes set 'shape=plain' or 'shape=plaintext' Shapes specification +# The default value is: shape=box,height=0.2,width=0.4. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" + +# You can set the path where dot can find font specified with fontname in +# DOT_COMMON_ATTR and others dot attributes. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTPATH = + +# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will +# generate a graph for each documented class showing the direct and indirect +# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and +# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case +# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the +# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used. +# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance +# relations will be shown as texts / links. +# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN. +# The default value is: YES. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# graph for each documented class showing the direct and indirect implementation +# dependencies (inheritance, containment, and class references variables) of the +# class with other documented classes. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for +# groups, showing the direct groups dependencies. See also the chapter Grouping +# in the manual. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside the +# class node. If there are many fields or methods and many nodes the graph may +# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the +# number of items for each type to make the size more manageable. Set this to 0 +# for no limit. Note that the threshold may be exceeded by 50% before the limit +# is enforced. So when you set the threshold to 10, up to 15 fields may appear, +# but if the number exceeds 15, the total amount of fields shown is limited to +# 10. +# Minimum value: 0, maximum value: 100, default value: 10. +# This tag requires that the tag UML_LOOK is set to YES. + +UML_LIMIT_NUM_FIELDS = 10 + +# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will wrapped across multiple lines. Some heuristics are apply +# to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + +# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and +# collaboration graphs will show the relations between templates and their +# instances. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +TEMPLATE_RELATIONS = NO + +# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to +# YES then doxygen will generate a graph for each documented file showing the +# direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDE_GRAPH = YES + +# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are +# set to YES then doxygen will generate a graph for each documented file showing +# the direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. Disabling a call graph can be +# accomplished by means of the command \hidecallgraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. Disabling a caller graph can be +# accomplished by means of the command \hidecallergraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# hierarchy of all classes instead of a textual one. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# dependencies a directory has on other directories in a graphical way. The +# dependency relations are determined by the #include relations between the +# files in the directories. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +DIRECTORY_GRAPH = YES + +# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels +# of child directories generated in directory dependency graphs by dot. +# Minimum value: 1, maximum value: 25, default value: 1. +# This tag requires that the tag DIRECTORY_GRAPH is set to YES. + +DIR_GRAPH_MAX_DEPTH = 1 + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. For an explanation of the image formats see the section +# output formats in the documentation of the dot tool (Graphviz (see: +# https://www.graphviz.org/)). +# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order +# to make the SVG files visible in IE 9+ (other browsers do not have this +# requirement). +# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, +# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and +# png:gdiplus:gdiplus. +# The default value is: png. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# +# Note that this requires a modern browser other than Internet Explorer. Tested +# and working are Firefox, Chrome, Safari, and Opera. +# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make +# the SVG files visible. Older versions of IE do not have SVG support. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +INTERACTIVE_SVG = NO + +# The DOT_PATH tag can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the \dotfile +# command). +# This tag requires that the tag HAVE_DOT is set to YES. + +DOTFILE_DIRS = + +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. + +DIA_PATH = + +# The DIAFILE_DIRS tag can be used to specify one or more directories that +# contain dia files that are included in the documentation (see the \diafile +# command). + +DIAFILE_DIRS = + +# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the +# path where java can find the plantuml.jar file or to the filename of jar file +# to be used. If left blank, it is assumed PlantUML is not used or called during +# a preprocessing step. Doxygen will generate a warning when it encounters a +# \startuml command in this case and will not generate output for the diagram. + +PLANTUML_JAR_PATH = + +# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for plantuml. + +PLANTUML_CFG_FILE = + +# When using plantuml, the specified paths are searched for files specified by +# the !include statement in a plantuml block. + +PLANTUML_INCLUDE_PATH = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes +# that will be shown in the graph. If the number of nodes in a graph becomes +# larger than this value, doxygen will truncate the graph, which is visualized +# by representing a node as a red box. Note that doxygen if the number of direct +# children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that +# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. +# Minimum value: 0, maximum value: 10000, default value: 50. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs +# generated by dot. A depth value of 3 means that only nodes reachable from the +# root by following a path via at most 3 edges will be shown. Nodes that lay +# further from the root node will be omitted. Note that setting this option to 1 +# or 2 may greatly reduce the computation time needed for large code bases. Also +# note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. +# Minimum value: 0, maximum value: 1000, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) support +# this, this feature is disabled by default. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# explaining the meaning of the various boxes and arrows in the dot generated +# graphs. +# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal +# graphical representation for inheritance and collaboration diagrams is used. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate +# files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc temporary +# files. +# The default value is: YES. + +DOT_CLEANUP = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will +# use a built-in version of mscgen tool to produce the charts. Alternatively, +# the MSCGEN_TOOL tag can also specify the name an external tool. For instance, +# specifying prog as the value, doxygen will call the tool as prog -T +# -o . The external tool should support +# output file formats "png", "eps", "svg", and "ismap". + +MSCGEN_TOOL = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = 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/conf.py b/docs/conf.py new file mode 100644 index 00000000..493bc187 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,35 @@ +# 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 stringlit documentation' +copyright = '2024, Roland Conybeare' +author = 'Roland Conybeare' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +#extensions = [] +extensions = [ "breathe", + "sphinx.ext.autodoc" # generate info from docstrings + ] + +# 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'] diff --git a/docs/flatstring-class.rst b/docs/flatstring-class.rst new file mode 100644 index 00000000..d2c9c546 --- /dev/null +++ b/docs/flatstring-class.rst @@ -0,0 +1,60 @@ +.. _flatstring-class: + +Flatstring +========== + +.. code-block:: cpp + + #include + +A ``flatstring`` is a thin wrapper around a character array. + +.. doxygenclass:: xo::flatstring + +Instance Variables +------------------ + +.. doxygengroup:: flatstring-instance-variables + :content-only: + +Constants +--------- + +.. doxygengroup:: flatstring-constants + :content-only: + +Constructors +------------ + +.. doxygengroup:: flatstring-ctor + :content-only: + +Properties +---------- + +.. doxygengroup:: flatstring-properties + :content-only: + +Access Methods +-------------- + +.. doxygengroup:: flatstring-access + :content-only: + +Iterators +--------- + +.. doxygengroup:: flatstring-iterators + :content-only: + +Assignment +---------- + +.. doxygengroup:: flatstring-assign + :content-only: + +Conversion +---------- + +.. doxygengroup:: flatstring-conversion-operators + :content-only: diff --git a/docs/flatstring-functions.rst b/docs/flatstring-functions.rst new file mode 100644 index 00000000..7e3c1fb1 --- /dev/null +++ b/docs/flatstring-functions.rst @@ -0,0 +1,15 @@ +.. _flatstring_functions: + +.. toctree:: + :maxdepth: 2 + +Flatstring Functions +==================== + +.. code-block:: cpp + + #include + +.. doxygenfunction:: xo::flatstring_concat + +.. doxygenfunction:: xo::flatstring_compare diff --git a/docs/flatstring-reference.rst b/docs/flatstring-reference.rst new file mode 100644 index 00000000..baf8f75e --- /dev/null +++ b/docs/flatstring-reference.rst @@ -0,0 +1,11 @@ +.. _flatstring-reference: + +Flatstring Reference +==================== + +.. toctree:: + :maxdepth: 2 + :caption: Flatstring Reference + + flatstring-class + flatstring-functions diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..430233d8 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,38 @@ +xo-flatstring documentation master file + +xo-flatstring documentation +=========================== + +xo-flatstring is a lightweight header-only library that provides a constexpr +fixed-size no-allocation string implementation. + +Why ``flatstring``? + +1. ``flatstring`` instances can be used as template arguments. [1]_ + +2. ``flatstring`` operations (construction, concatenation, ...) are ``constexpr``, so can be done at compile time. [2]_ + +3. a ``flatstring`` expression can occupy both compile-time and runtime roles. [3]_ + +.. [1] A fixed-size char array *can* be used as a template + argument, but char* pointers cannot. Automatic conversion of char arrays to pointers in various contexts + makes them difficult to work with in c++ templates. + +.. [2] Although allocation is permitted in constexpr code, it's subject to several restrictions. + it's not yet possible (as of c++23) to use ``std::string`` at compile time. + +.. [3] contrast with a solution relying on template arguments, which must then be compile-time-only. + +.. toctree:: + :maxdepth: 2 + :caption: xo-flatstring contents: + + install + lessons + flatstring-reference + +Indices and Tables +------------------ + +* :ref:`genindex` +* :ref:`search` diff --git a/docs/install.rst b/docs/install.rst new file mode 100644 index 00000000..aab059ab --- /dev/null +++ b/docs/install.rst @@ -0,0 +1,56 @@ +.. _install: + +.. toctree + :maxdepth: 2 + +Install +======= + +`xo-flatstring source`_ lives on github. + +.. _xo-flatstring source: https://github.com/rconybea/xo-flatstring + +Implementation relies on c++20 features (for example class-instances as template arguments). +Tested with gcc 13.2 + +Include as submodule +-------------------- + +.. code-block:: bash + + cd myproject + git submodule add -b main https://github.com/rconybea/xo-flatstring ext/xo-flatstring + git submodule update --init + +This assumes you organize directly-incorporated dependencies under directory ``myproject/ext``. +You would then add ``myproject/ext/xo-flatstring/include`` to your compiler's include path, +and from c++ do something like + +.. code-block:: c++ + + #include + +in c++ source files that rely on xo-flatstring + +Supported compilers +------------------- + +* developed with gcc 13.2.0; github CI using gcc 11.4.0 (asof April 2024) + +Building from source +-------------------- + +Although the xo-flatstring library is header-only, unit tests have some dependencies. +Example instructions (github CI) for build starting from stock ubuntu are in `ubuntu-main.yml`_ + +.. _ubuntu-main.yml: https://github.com/Rconybea/xo-flatstring/blob/main/.github/workflows/ubuntu-main.yml + +Unit test dependencies: + +* `catch2`_ header-only unit-test framework +* `xo-cmake`_ cmake macros +* `xo-indentlog`_ logging with call-structure indenting + +.. _catch2: https://github.com/catchorg/Catch2 +.. _xo-cmake: https://github.com/rconybea/xo-cmake +.. _xo-indentlog: https://github.com/rconybea/indentlog diff --git a/docs/lessons.rst b/docs/lessons.rst new file mode 100644 index 00000000..40c39eaf --- /dev/null +++ b/docs/lessons.rst @@ -0,0 +1,44 @@ +.. _lessons: + +.. toctree + :maxdepth: 2 + +Lessons +======= + +This is a rogue's gallery of experiments, typically unsuccessful. +One hurdle we've created for ourselves, is we need both gcc and clang to agree +that an expression can be computed at compile-time; +otherwise will get false alarms in our IDE (raised by LSP running in the background, which relies on clang). + +Must Fully Initialize Memory +---------------------------- + +Struggled for a while with the implementation of :ref:xo::flatstring_concat + +.. code-block:: cpp + + template + flatstring::flatstring() { + if (N > 0) + value_[0] = '\0'; + } + + +This implementation satisfies gcc, but not clang: in the following snippet, clang doesn't recognize ``tmp`` as constexpr: + +.. code-block:: cpp + + constexpr n = ...; + flatstring tmp; + + static_assert(tmp.size() == ...); // tmp not constexpr! + +Correction is to prove to clang that every memory address owned by an empty ``flatstring`` is initialized: + +.. code-block:: cpp + + template + flatstring::flatstring() { + std::fill_n(value_, N, '\0'); + } diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index dcb2a459..e343002e 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -1,38 +1,165 @@ /* @file ex1.cpp */ -#include "xo/stringliteral/stringliteral.hpp" -#include "xo/stringliteral/stringliteral_iostream.hpp" -#include "xo/stringliteral/string_view_concat.hpp" +#include "xo/flatstring/flatstring.hpp" +//#include "xo/stringliteral/stringliteral_iostream.hpp" +#include "xo/flatstring/experiment.hpp" +//#include "xo/stringliteral/string_view_concat.hpp" #include int main() { using namespace std; - using xo::stringliteral; + using xo::flatstring; +#ifdef WAITAMO using xo::stringliteral_compare; +#endif + + static_assert(foo1().x_ == 1); + static_assert(foo1().y_ == 2); + + constexpr foo1 s1; + + static_assert(s1.x_ == 1); + static_assert(s1.y_ == 2); + + constexpr foo2 s2; + + static_assert(s2.v_[0] == 'a'); + static_assert(s2.v_[1] == 'b'); + + constexpr foo3<2> s3; + + static_assert(s3.v_[0] == 'a'); + static_assert(s3.v_[1] == 'b'); + + constexpr foo4<6> s4("hello"); + + constexpr foo5 s5("hello"); + + static_assert(s5.v_[0] == 'h'); + static_assert(s5.v_[5] == '\0'); + + constexpr foo6 s6("hello", ", world!"); + + static_assert(s6.v_[0] == 'h'); + static_assert(s6.size() == 13); + + cerr << "s6=" << s6.c_str() << endl; + + /* z gives allocation size. string size is z-1 */ + constexpr std::size_t z = concat_size("hello", ", world!", " What's up?"); + + static_assert(z == 25); + + constexpr foo7<10> s7("Hello", ", world!", " What's up?"); + + constexpr stringlit<10> s8("0123", "45678"); + + static_assert(s8.size() == 9); + constexpr std::size_t z8 = stringlit_capacity(s8); + + + static_assert(sizeof("0123") == 5); + static_assert(sizeof("45") == 3); + static_assert(sizeof("78") == 3); + + static_assert(literal_strlen("0123") == 4); + + + static_assert(z8 == 10); #ifdef NOT_USING - constexpr stringliteral s1("hello"); + static_assert(count_size("0123") == 5); + static_assert(count_size("0123", "45") == 7); + static_assert(count_size("0123", "45", "67", "8") == 10); + + constexpr auto z9 = count_size("0123", "45", "78"); + + static_assert(z9 == 9); + + constexpr auto z10 = foofn("0123"); + + static_assert(z10 == 5); +#endif + + //constexpr auto z11 = foofn2("0123"); + + //static_assert(z9 > 22); + + constexpr auto s9 = stringlit_make("0123", "456", "78"); + //constexpr auto s9 = stringlit_makepalooza("0123", "45678"); + + static_assert(s9.size() == 9); + + constexpr auto s10 = stringlit_make("0", "123", "456", "78"); + + static_assert(s10.size() == 9); + + cerr << s10.c_str() << endl; + +#ifdef NOT_SUCCESSFUL + constexpr auto s11 = stringlit_make("0", "1", "23", "456", "78"); +#endif + + constexpr std::size_t z9 = stringlit_capacity(s9, s10); + + static_assert(z9 == 19); + + constexpr auto s12 = stringlit_cat(s9, s10); + + static_assert(s12.size() == 18); + + cerr << s12.c_str() << endl; + + constexpr auto s13 = stringlit_cat(s9, s10, s12); + + static_assert(s13.size() == 36); + + cerr << s13.c_str() << endl; + +#ifdef NOT_USING static_assert(stringliteral_compare(s1, s1) == 0); cerr << s1 << endl; - - constexpr stringliteral s2 = stringliteral_concat(stringliteral("hello"), - stringliteral(", world")); - #endif + constexpr flatstring s14 = flatstring_concat(flatstring("foo"), flatstring("bar")); + + static_assert(s14.fixed_capacity == 7); + static_assert(sizeof(s14) == 7); + + constexpr flatstring s15 = flatstring_concat(flatstring("hello"), + ", ", + flatstring("world")); + static_assert(s15.fixed_capacity == 13); + static_assert(sizeof(s15) == 13); + + constexpr auto s16 = xo::flatstring_concat("foo", "bar"); + + static_assert(s16.fixed_capacity == 7); + + constexpr auto cmp = flatstring_compare(s14, s14); + + static_assert(cmp == 0); + +#ifdef WAITAMO + constexpr stringliteral s2 = stringliteral_stringlit_make(stringliteral("hello"), + stringliteral(", world")); +#endif + +#ifdef NOT_USING static constexpr string_view hello("hello"); static constexpr string_view world(" world"); - static constexpr auto s2 = concat_v; + static constexpr auto s3 = stringlit_make_v; static constexpr string_view hello_world("hello world"); - static_assert(s2 == hello_world); + static_assert(s3 == hello_world); cerr << hello_world << endl; +#endif } /* end ex1.cpp */ diff --git a/include/xo/flatstring/flatstring.hpp b/include/xo/flatstring/flatstring.hpp new file mode 100644 index 00000000..5a68bd6a --- /dev/null +++ b/include/xo/flatstring/flatstring.hpp @@ -0,0 +1,414 @@ +/** @file flatstring.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include +#include +#include +#include + +namespace xo { + /** @class flatstring + * @brief class to represent a string with a fixed amount of storage space. + * + * - Flatstring memory layout is a fixed-size, null-terminated char array. + * - With a few exceptions, flatstring methods are noexcept. + * @c flatstring::at() may throw, for consistency with @c std::string::at() behavior + * - Construction and concatenation of flatstrings are constexpr, + * and can be done at compile time. + * We rely on this in related projects (e.g. https://github.com:rconybea/xo-unit) + * - Preserves as much of the c++23 @c std::string api as practicable + * + * @c N includes mandatory null terminator, so we require @c N > 0. + * + * @invariant all flatstring instances are null-terminated. + * @invariant sizeof(flatstring) == N + **/ + template + struct flatstring { + /** @defgroup flatstring-types template types **/ + ///@{ + using traits_type = std::char_traits; + using value_type = char; + using allocator_type = std::allocator; + using size_type = std::allocator_traits::size_type; + using difference_type = std::allocator_traits::difference_type; + using reference = value_type &; + using const_reference = const value_type &; + using pointer = std::allocator_traits::pointer; + using const_pointer = std::allocator_traits::const_pointer; + using iterator = char *; + using const_iterator = const char *; + using reverse_iterator = char *; + using const_reverse_iterator = const char *; + ///@} + + /** @defgroup flatstring-constants constants **/ + ///@{ + static constexpr const size_type npos = size_type(-1); + + /** @brief capacity of this flatstring, including final null terminator. + * + * @note not present in @c std::string api + **/ + static constexpr const std::size_t fixed_capacity = N; + ///@} + + public: + /** @defgroup flatstring-ctor constructors **/ + ///@{ + /** @brief create empty string literal. Will contain N null characters + * + * Example + * @code + * constexpr flatstring<5> s1; + * static_assert(s1.empty()); + * @endcode + **/ + constexpr flatstring() noexcept { + /* note: clang verifies that we fully initialize memory; otherwise will not recognize + * instance as constexpr + */ + std::fill_n(value_, N, '\0'); + } + + /** @brief create string literal from a correctly-sized char array + * + * Example + * @code + * constexpr flatstring s1("hello"); + * static_assert(s1.size() > 0); + * @endcode + **/ + constexpr flatstring(const char (&str)[N]) noexcept { + std::copy_n(str, N, value_); + } + ///@} + + /** @defgroup flatstring-properties property-methods **/ + ///@{ + /** @brief true if (and only if) string is empty **/ + constexpr bool empty() const noexcept { return value_[0] == '\0'; } + /** @brief returns current size of this string **/ + constexpr size_type size() const noexcept { + return this->cend() - this->cbegin(); + } + /** @brief synonym for @c size() **/ + constexpr size_type length() const noexcept { return size(); } + + constexpr size_type capacity() const noexcept { return fixed_capacity - 1; } + constexpr size_type max_size() const noexcept { return fixed_capacity - 1; } + + /** @brief contents as plain old C-style string. **/ + constexpr const char * c_str() const noexcept { return value_; } + ///@} + + /** @defgroup flatstring-access access methods **/ + ///@{ + /** @brief return char at position @p pos in this string (counting from zero). + * + * Throws @c std::out_of_range exception if @p pos >= @c N + **/ + constexpr value_type & at(size_type pos) throw() { return this->at_aux(pos); } + constexpr const value_type & at(size_type pos) const throw() { return const_cast(this)->at_aux(pos); } + + /** @brief return char at position @p pos in this string (counting from zero). + * + * Does not check bounds: undefined behavior if @p pos >= @c N + * + * @pre 0<=pos<=N-1 + **/ + constexpr value_type & operator[](size_type pos) { return value_[pos]; } + constexpr const value_type & operator[](size_type pos) const { return value_[pos]; } + ///@} + + /** @defgroup flatstring-iterators iterators **/ + ///@{ + constexpr iterator begin() { return &value_[0]; } + constexpr iterator end() { return this->last(); } + constexpr const_iterator cbegin() const { return &value_[0]; } + constexpr const_iterator cend() const { return const_cast(this)->last(); } + constexpr const_iterator begin() const { return cbegin(); } + constexpr const_iterator end() const { return cend(); } + + constexpr reverse_iterator rbegin() { return this->last(); } + constexpr reverse_iterator rend() { return &value_[0]; } + constexpr const_reverse_iterator crbegin() const { return const_cast(this)->last(); } + constexpr const_reverse_iterator crend() const { return &value_[0]; } + constexpr const_reverse_iterator rbegin() const { return crbegin(); } + constexpr const_reverse_iterator rend() const { return crend(); } + ///@} + + /** @defgroup flatstring-assign assignment **/ + ///@{ + /** @brief put string into empty state. fills entire char array with nulls **/ + void clear() { std::fill_n(value_, N, '\0'); } + + /** @brief replace contents with min(count,N-1) copies of character ch **/ + constexpr flatstring & assign(size_type count, value_type ch) { + std::size_t pos = 0; + for (; pos < std::min(count, N-1); ++pos) + value_[pos] = ch; + for (; pos < N; ++pos) + value_[pos] = '\0'; + + return *this; + } + /** @brief replace contents with first N-1 characters of str **/ + constexpr flatstring & assign(const flatstring & x) { + for (std::size_t pos = 0; pos < N-1; ++pos) + value_[pos] = x.value_[pos]; + value_[N-1] = '\0'; + return *this; + } + /** @brief replace contents with substring [pos,pos+count] of str **/ + constexpr flatstring & assign(const flatstring & x, + size_type pos, size_type count = npos) { + std::size_t i = 0; + for (; + i < std::min(std::min(count, + std::max(x.capacity-1 - pos, + 0)), + N-1); + ++i) + value_[i] = x.value_[pos+i]; + for (; i < N; ++i) + value_[i] = '\0'; + + return *this; + } + /** @brief replace contents with range [cstr, cstr + count) **/ + constexpr flatstring & assign(const value_type * cstr, size_type count) { + std::size_t i = 0; + for (; i < std::min(N-1, count); ++i) + value_[i] = cstr[i]; + for (; i < N; ++i) + value_[i] = '\0'; + + return *this; + } + /** @brief replace contents with C-style string cstr **/ + constexpr flatstring & assign(const value_type * cstr) { + std::size_t i = 0; + const value_type * p = cstr; + while ((i < N-1) && (*p != '\0')) { + value_[i] = *p; + ++i; + ++p; + } + for (; i < N; ++i) + value_[i] = '\0'; + + return *this; + } + /** @brief replace contents with iterator range [first, last) **/ + template + constexpr flatstring & assign(InputIter first, InputIter last) { + InputIter ix = first; + std::size_t i = 0; + for (; (i < N-1) && (ix != last); ++i) { + value_[i] = *ix; + } + for (; i < N; ++i) + value_[i] = '\0'; + return *this; + } + ///@} + + // insert + // insert_range + // erase + // push_back + // append + // append_range + // operator+= + // replace + // replace_with_range + // copy + // find + // rfind + // find_first_of + // find_first_not_of + // find_last_of + // find_last_not_of + // compare + // starts_with + // end_with + // contains + // substr + + /** @defgroup flatstring-conversion-operators conversion operators **/ + ///@{ + /** @brief conversion to @c std::string + * + * Example + * @code + * constexpr flatstring s("bazinga!"); + * std::string s_str{s.str()}; + * @endcode + **/ + std::string str() const { return std::string(value_); } + + /** @brief conversion operator to string_view **/ + constexpr operator std::string_view() const noexcept { return std::string_view(value_); } + + /** @brief conversion operator to C-style string. + * + * Example + * @code + * constexpr flatstring s("obey gravity.."); + * strcmp(s, "obey..."); + * @endcode + **/ + constexpr operator const char * () const { return value_; } + ///@} + + private: + constexpr value_type & at_aux(size_type pos) { + if (pos >= N) { +#ifdef NOT_USING + /* note: can't build stringstream at compile time */ + std::stringstream ss; + ss << "flatstring<" << N << ">::at: expected pos=[" << pos << "] in interval [0," << N << ")" << std::endl; +#endif + + throw std::out_of_range("at_aux: range error"); + } + + return (*this)[pos]; + } + + template + constexpr Iterator last() { + Iterator p = &value_[N-1]; + + /* search backward for first padding '\0' */ + while ((p > &value_[0]) && (*(p-1) == '\0')) + --p; + + return p; + } + + public: + /** @defgroup flatstring-instance-variables instance variables **/ + ///@{ + + /** @brief characters comprising this literal string **/ + char value_[N]; + + ///@} + }; + + /** @brief sentinel type, for forbidden stringliteral with no space for a null terminator **/ + template <> + struct flatstring<0> { flatstring() = delete; }; + + // non-member functions + // erase + // erase_if + // operator<< + // operator>> + // getline + // stoi + // stol + // stoll + // stoul + // stoull + // stof + // stod + // stold + +#ifdef NOT_USING + /** @brief all_same_v is true iff types T1 = .. = Tn + **/ + template < typename First, typename... Rest > + constexpr auto + all_same_v = std::conjunction_v< std::is_same... >; +#endif + + /** @brief Concatenate native or wrapped string literals + * + * Can mix stringliteral objects and native C-style string literals, + * and still preserve constexpr-ness. + * + * Example: + * @code + * constexpr auto s = stringliteral_concat(stringliteral("hello"), + * ", ", + * stringliteral("world")); + * static_assert(s.capacity == 13); + * @endcode + * + **/ + template < typename... Ts> + constexpr auto + flatstring_concat(Ts && ... args) noexcept + { +#ifdef NOT_USING + static_assert(all_same_v...>, + "string must share the same char type"); + + using char_type = std::remove_const_t< std::remove_pointer_t < std::common_type_t < Ts... > > >; +#endif + using value_type = char; + + /** n1: total number of bytes used by arguments **/ + constexpr std::size_t n1 = (sizeof(Ts) + ...); + /** z1: each string arg has a null terminator included in its size, + * z1 is the number of arguments in parameter pack Ts, + * which equals the number of null terminators used + **/ + constexpr std::size_t z1 = sizeof...(Ts); + + /** n: number of chars in concatenated string. +1 for final null **/ + constexpr std::size_t n + = (n1 / sizeof(value_type)) - z1 + 1; + + flatstring result; + + std::size_t pos = 0; + + auto detail_concat = [ &pos, &result ](auto && arg) { + constexpr auto count = (sizeof(arg) - sizeof(value_type)) / sizeof(value_type); + + std::copy_n(/*arg.c_str()*/ static_cast(arg), count, result.value_ + pos); + pos += count; + }; + + (detail_concat(args), ...); + + return result; + } + + /** @brief compare two string literals lexicographically. + * + * Example: + * @code + * constexpr auto cmp = stringliteral_compare(stringliteral("foo"), stringliteral("bar")); + * static_assert(cmp > 0); + * @endcode + **/ + template + constexpr auto + flatstring_compare(const flatstring & s1, + const flatstring & s2) noexcept + { + return (std::string_view(s1.value_) <=> std::string_view(s2.value_)); + } + + /** @brief 3-way compare for two stringliterals **/ + template + constexpr auto + operator<=>(const flatstring & s1, + const flatstring & s2) noexcept + { + return flatstring_compare(s1, s2); + } +} /*namespace xo*/ + +/** end stringliteral.hpp **/ diff --git a/include/xo/flatstring/flatstring_iostream.hpp b/include/xo/flatstring/flatstring_iostream.hpp new file mode 100644 index 00000000..c2d7e738 --- /dev/null +++ b/include/xo/flatstring/flatstring_iostream.hpp @@ -0,0 +1,37 @@ +/** @file flatstring_iostream.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "flatstring.hpp" +#include +//#include + +namespace xo { + /** @brief print flatstring on stream os. + * + **/ + template + void + print_flatstring (std::ostream & os, const flatstring & x) { + os << x.c_str(); + } + + /** @brief print flatstring x on stream os. + * + * Example + * @code + * cout << flatstring("foo"); // outputs "foo" + * @endcode + **/ + template + inline std::ostream & + operator<< (std::ostream & os, const flatstring & x) { + print_flatstring(os, x); + return os; + } +} /*namespace xo*/ + +/** end flatstring_iostream.hpp **/ diff --git a/include/xo/stringliteral/string_view_concat.hpp b/include/xo/flatstring/string_view_concat.hpp similarity index 100% rename from include/xo/stringliteral/string_view_concat.hpp rename to include/xo/flatstring/string_view_concat.hpp diff --git a/include/xo/stringliteral/stringliteral.hpp b/include/xo/stringliteral/stringliteral.hpp deleted file mode 100644 index 72235727..00000000 --- a/include/xo/stringliteral/stringliteral.hpp +++ /dev/null @@ -1,96 +0,0 @@ -/** @file stringliteral.hpp - * - * Author: Roland Conybeare - **/ - -#pragma once - -#include -#include -#include - -namespace xo { - /** @class stringliteral - * - * @brief class to represent a literal string at compile time, for use as template argument - **/ - template - struct stringliteral { - constexpr stringliteral() { if (N > 0) value_[0] = '\0'; } - constexpr stringliteral(const char (&str)[N]) { std::copy_n(str, N, value_); } - constexpr int size() const { return N; } - - constexpr char const * c_str() const { return value_; } - - char value_[N]; - }; - - /** @brief all_same_v is true iff types T1 = .. = Tn - **/ - template < typename First, typename... Rest > - constexpr auto - all_same_v = std::conjunction_v< std::is_same... >; - - /** @brief concatenate string literals - * - * NOTE: this isn't constexpr in clang16 - **/ - template < typename... Ts> - constexpr auto - stringliteral_concat(Ts && ... args) - { -#ifdef NOT_USING - static_assert(all_same_v...>, - "string must share the same char type"); - - using char_type = std::remove_const_t< std::remove_pointer_t < std::common_type_t < Ts... > > >; -#endif - using char_type = char; - - /** n1: total number of bytes used by arguments **/ - constexpr size_t n1 = (sizeof(Ts) + ...); - /** z1: each string arg has a null terminator included in its size, - * z1 is the number of arguments in parameter pack Ts, - * which equals the number of null terminators used - **/ - constexpr size_t z1 = sizeof...(Ts); - - /** n: number of chars in concatenated string. +1 for final null **/ - constexpr size_t n - = (n1 / sizeof(char_type)) - z1 + 1; - - stringliteral result; - size_t pos = 0; - - auto detail_concat = [ &pos, &result ](auto && arg) { - constexpr auto count = (sizeof(arg) - sizeof(char_type)) / sizeof(char_type); - - std::copy_n(arg.c_str(), count, result.value_ + pos); - pos += count; - }; - - (detail_concat(args), ...); - - //return stringliteral(""); - return result; - } - -#ifdef NOT_USING - template - constexpr auto - stringliteral_compare(stringliteral && s1, stringliteral && s2) - { - return std::string_view(s1.value_) <=> std::string_view(s2.value_); - } -#endif - - template - constexpr auto - stringliteral_compare(const stringliteral & s1, const stringliteral & s2) - { - return std::string_view(s1.value_) <=> std::string_view(s2.value_); - } -} /*namespace xo*/ - - -/** end stringliteral.hpp **/ diff --git a/include/xo/stringliteral/stringliteral_iostream.hpp b/include/xo/stringliteral/stringliteral_iostream.hpp deleted file mode 100644 index 18a371a5..00000000 --- a/include/xo/stringliteral/stringliteral_iostream.hpp +++ /dev/null @@ -1,36 +0,0 @@ -/** @file stringliteral_iostream.hpp - * - * Author: Roland Conybeare - **/ - -#pragma once - -#include "stringliteral.hpp" -#include -//#include - -namespace xo { - /** @brief print stringliteral on stream os. - * - **/ - template - void - print_stringliteral (std::ostream & os, const stringliteral & x) { - os << x.c_str(); - } - - /** @brief print stringliteral x on stream os. - * - * Example - * @code - * cout << stringliteral("foo"); // outputs "foo" - **/ - template - inline std::ostream & - operator<< (std::ostream & os, const stringliteral & x) { - print_stringliteral(os, x); - return os; - } -} /*namespace xo*/ - -/** end stringliteral_iostream.hpp **/ diff --git a/utest/.#flatstring_utest_main.cpp b/utest/.#flatstring_utest_main.cpp new file mode 120000 index 00000000..a0c5445f --- /dev/null +++ b/utest/.#flatstring_utest_main.cpp @@ -0,0 +1 @@ +roland@roly-desktop-23.717424:1712774400 \ No newline at end of file diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..11e76f63 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,21 @@ +# xo-flatstring/utest/CMakeLists.txt + +set(SELF_EXE utest.flatstring) +set(SELF_SRCS + flatstring_utest_main.cpp + flatstring.test.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) +#target_code_coverage(${SELF_EXE} AUTO ALL) + +# ---------------------------------------------------------------- +# deps: logutils, ... + +xo_self_headeronly_dependency(${SELF_EXE} xo_flatstring) +xo_dependency(${SELF_EXE} indentlog) +xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) + +# end CMakeLists.txt diff --git a/utest/flatstring.test.cpp b/utest/flatstring.test.cpp new file mode 100644 index 00000000..1b5603a7 --- /dev/null +++ b/utest/flatstring.test.cpp @@ -0,0 +1,131 @@ +/** @file flatstring.utest.cpp **/ + +#include "xo/stringliteral/stringliteral.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/tag.hpp" +#include +//#include + +namespace xo { + using namespace std; + + namespace ut { + template + void + flatstring_runtime_tests(const String & str, const char * text) { + INFO(tostr(XTAG(str), XTAG(text))); + + REQUIRE(str.fixed_capacity == strlen(text)+1); + REQUIRE(str.capacity() == strlen(text)); + REQUIRE(str.size() == strlen(text)); + REQUIRE(str.length() == strlen(text)); + REQUIRE(strcmp(str.c_str(), text) == 0); + REQUIRE(strcmp(str, text) == 0); + + /* verify range iteration visits contents in order */ + { + size_t i = 0; + for (char ch : str) { + INFO(XTAG(i)); + + CHECK(ch == text[i]); + + ++i; + } + } + } + + /* using macro here because template argument depends on size of literal C string, + * and we can't use such a string as a template argument. + * + * static_asserts: using these to verify that constexpr methods are being computed + * at compile time. + * + * REQUIRE() calls to do verification that relies on non-constexpr calls such as + * strlen(), strcmp() + */ +# define LITERAL_TEST_BODY(name, text) \ + constexpr flatstring name{text}; \ + static_assert(name[0]==text[0]); \ + static_assert(name.at(0)==text[0]); \ + static_assert(name.empty() == true || name.empty() == false); \ + static_assert(name.capacity() >= 0); \ + static_assert(name.begin() != nullptr); \ + static_assert(name.end() != nullptr); \ + static_assert(name.cbegin() != nullptr); \ + static_assert(name.cend() != nullptr); \ + static_assert(name.rbegin() != nullptr); \ + static_assert(name.rend() != nullptr); \ + static_assert(name.crbegin() != nullptr); \ + static_assert(name.crend() != nullptr); \ + static_assert(name.size() >= 0); \ + static_assert(name.c_str() != nullptr); \ + static_assert((name <=> name) == 0); \ + static_assert(name == name); \ + static_assert(name >= name); \ + static_assert(name <= name); \ + static_assert(!(name != name)); \ + static_assert(!(name > name)); \ + static_assert(!(name < name)); \ + flatstring_runtime_tests(name, text); \ + REQUIRE(name.fixed_capacity == strlen(text)+1); \ + REQUIRE(name.capacity() == strlen(text)); \ + REQUIRE(name.size() == strlen(text)); \ + REQUIRE(name.length() == strlen(text)); \ + REQUIRE(strcmp(name.c_str(), text) == 0); \ + REQUIRE(strcmp(name, text) == 0); \ + static_assert(string_view(name) == string_view(name)); \ + + + + TEST_CASE("flatstring", "[flatstring][compile-time]") { + constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.flatstring")); + //log && log("(A)", xtag("foo", foo)); + + /* mostly compile-time tests here */ + + LITERAL_TEST_BODY(s1, "h"); + LITERAL_TEST_BODY(s2, "he"); + LITERAL_TEST_BODY(s3, "hel"); + LITERAL_TEST_BODY(s4, "hell"); + LITERAL_TEST_BODY(s5, "hello"); + LITERAL_TEST_BODY(s6, "hello,"); + LITERAL_TEST_BODY(s7, "hello, "); + LITERAL_TEST_BODY(s8, "hello, w"); + LITERAL_TEST_BODY(s9, "hello, wo"); + LITERAL_TEST_BODY(s10, "hello, wor"); + LITERAL_TEST_BODY(s11, "hello, worl"); + LITERAL_TEST_BODY(s12, "hello, world"); + LITERAL_TEST_BODY(s13, "hello, world!"); + + static_assert(s1 != s2); + static_assert(s2 != s3); + static_assert(s3 != s4); + static_assert(s4 != s5); + static_assert(s12 != s13); + + static_assert(s1 < s2); + static_assert(s2 < s3); + static_assert(s3 < s4); + static_assert(s4 < s5); + static_assert(s12 < s13); + + static_assert(s2 > s1); + static_assert(s3 > s2); + static_assert(s4 > s3); + static_assert(s5 > s4); + static_assert(s13 > s12); + } /*TEST_CASE(flatstring)*/ + + } /*namespace ut*/ +} /*namespace xo*/ + +/** end flatstring.utest.cpp **/ diff --git a/utest/flatstring_utest_main.cpp b/utest/flatstring_utest_main.cpp new file mode 100644 index 00000000..e7a2c140 --- /dev/null +++ b/utest/flatstring_utest_main.cpp @@ -0,0 +1,6 @@ +/* @file flatstring_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end flatstring_utest_main.cpp */ From eca4267636808973d97c9b08a6ee95a100f6df44 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 16 Apr 2024 17:26:19 -0400 Subject: [PATCH 0744/2693] xo-flatstring: + .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..13c0afb7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json From f7c633be86bbe61c650a00c8d6ec580009637f26 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 16 Apr 2024 17:44:55 -0400 Subject: [PATCH 0745/2693] xo-flatstring: ++ doc improvements --- docs/flatstring-class.rst | 5 ++++ docs/flatstring-functions.rst | 3 +- include/xo/flatstring/flatstring.hpp | 42 ++++++++++++++++++++-------- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/docs/flatstring-class.rst b/docs/flatstring-class.rst index d2c9c546..3e34cd4c 100644 --- a/docs/flatstring-class.rst +++ b/docs/flatstring-class.rst @@ -17,6 +17,11 @@ Instance Variables .. doxygengroup:: flatstring-instance-variables :content-only: +Types +----- + +.. doxygengroup:: flatstring-types + Constants --------- diff --git a/docs/flatstring-functions.rst b/docs/flatstring-functions.rst index 7e3c1fb1..9106a2b5 100644 --- a/docs/flatstring-functions.rst +++ b/docs/flatstring-functions.rst @@ -12,4 +12,5 @@ Flatstring Functions .. doxygenfunction:: xo::flatstring_concat -.. doxygenfunction:: xo::flatstring_compare +.. doxygengroup:: flatstring-3way-compare + :content-only: diff --git a/include/xo/flatstring/flatstring.hpp b/include/xo/flatstring/flatstring.hpp index 5a68bd6a..55d9f1fd 100644 --- a/include/xo/flatstring/flatstring.hpp +++ b/include/xo/flatstring/flatstring.hpp @@ -29,20 +29,30 @@ namespace xo { **/ template struct flatstring { - /** @defgroup flatstring-types template types **/ + /** @defgroup flatstring-types template types + * @brief Template types exposed by @c flatstring + **/ ///@{ + /** @brief character traits for this flatstring **/ using traits_type = std::char_traits; + /** @brief type of each character in this flatstring **/ using value_type = char; using allocator_type = std::allocator; using size_type = std::allocator_traits::size_type; using difference_type = std::allocator_traits::difference_type; + /** @brief type of a character reference **/ using reference = value_type &; + /** @brief type of a readonly character reference **/ using const_reference = const value_type &; using pointer = std::allocator_traits::pointer; using const_pointer = std::allocator_traits::const_pointer; + /** @brief representation for a read/write iterator **/ using iterator = char *; + /** @brief representation for a readonly iterator **/ using const_iterator = const char *; + /** @brief representation for a read/write reverse iterator **/ using reverse_iterator = char *; + /** @brief representation for a readonly reverse iterator **/ using const_reverse_iterator = const char *; ///@} @@ -129,6 +139,7 @@ namespace xo { ///@{ constexpr iterator begin() { return &value_[0]; } constexpr iterator end() { return this->last(); } + constexpr const_iterator cbegin() const { return &value_[0]; } constexpr const_iterator cend() const { return const_cast(this)->last(); } constexpr const_iterator begin() const { return cbegin(); } @@ -329,16 +340,13 @@ namespace xo { all_same_v = std::conjunction_v< std::is_same... >; #endif - /** @brief Concatenate native or wrapped string literals - * - * Can mix stringliteral objects and native C-style string literals, - * and still preserve constexpr-ness. + /** @brief Concatenate flatstrings, possibly mixed with C-style char arrays * * Example: * @code - * constexpr auto s = stringliteral_concat(stringliteral("hello"), - * ", ", - * stringliteral("world")); + * constexpr auto s = flatstring_concat(flatstring("hello"), + * ", ", + * flatstring("world")); * static_assert(s.capacity == 13); * @endcode * @@ -383,11 +391,11 @@ namespace xo { return result; } - /** @brief compare two string literals lexicographically. + /** @brief compare two flatstrings lexicographically. * * Example: * @code - * constexpr auto cmp = stringliteral_compare(stringliteral("foo"), stringliteral("bar")); + * constexpr auto cmp = flatstring_compare(stringliteral("foo"), stringliteral("bar")); * static_assert(cmp > 0); * @endcode **/ @@ -400,15 +408,25 @@ namespace xo { return (std::string_view(s1.value_) <=> std::string_view(s2.value_)); } - /** @brief 3-way compare for two stringliterals **/ + /** @defgroup flatstring-3way-compare 3way-compare **/ + ///@{ + /** @brief 3-way compare for two flatstrings + * + * Example + * @code + * constexpr auto cmp = (flatstring("foo") <=> flatstring("bar")); + * static_assert(cmp != 0); + * @endcode + **/ template constexpr auto operator<=>(const flatstring & s1, const flatstring & s2) noexcept { - return flatstring_compare(s1, s2); + return (std::string_view(s1) <=> std::string_view(s2)); } + ///@} } /*namespace xo*/ /** end stringliteral.hpp **/ From ecb321f0f18bfc8084e333becb9a9433d64d5681 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 16 Apr 2024 21:37:48 -0400 Subject: [PATCH 0746/2693] tidy: cleanup stray stringlit -> flatstring naming --- CMakeLists.txt | 2 +- docs/CMakeLists.txt | 8 ++++---- docs/conf.py | 2 +- example/ex1/CMakeLists.txt | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 79d62595..af27a769 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,7 +63,7 @@ add_subdirectory(docs) # ---------------------------------------------------------------- # provide find_package() support for projects using this library -set(SELF_LIB xo_stringliteral) +set(SELF_LIB xo_flatstring) 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) diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 0751be74..c6fcc37b 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -1,4 +1,4 @@ -# xo-stringliteral/docs/CMakeLists.txt +# xo-flatstring/docs/CMakeLists.txt if (XO_SUBMODULE_BUILD) # in submodule build, rely on toplevel docs/CMakeLists.txt file instead @@ -6,8 +6,8 @@ else() # build docs starting from here only in standalone build. # otherwise use top-level doxygen setup instead. - set(ALL_LIBRARY_TARGETS xo_stringliteral) # todo: automate this from xo-cmake macros - set(ALL_UTEST_TARGETS xo_stringliteral_ex1 ) # todo: automate this from xo-cmake macros + set(ALL_LIBRARY_TARGETS xo_flatstring) # todo: automate this from xo-cmake macros + set(ALL_UTEST_TARGETS xo_flatstring_ex1 ) # todo: automate this from xo-cmake macros # look for doxygen executable find_program(DOXYGEN_EXECUTABLE NAMES doxygen REQUIRED) @@ -24,7 +24,7 @@ else() set(DOX_INDEX_FILE ${DOX_OUTPUT_DIR}/html/index.html) - # .hpp files reachable from xo-stringliteral/include + # .hpp files reachable from xo-flatstring/include # # REMINDER: for reliability will need to re-run cmake when the set of .hpp files changes # diff --git a/docs/conf.py b/docs/conf.py index 493bc187..26dc0ec0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -6,7 +6,7 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -project = 'xo stringlit documentation' +project = 'xo flatstring documentation' copyright = '2024, Roland Conybeare' author = 'Roland Conybeare' diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt index abe34974..c026a159 100644 --- a/example/ex1/CMakeLists.txt +++ b/example/ex1/CMakeLists.txt @@ -1,6 +1,6 @@ -# xo-stringliteral/example/ex1/CMakeLists.txt +# xo-flatstring/example/ex1/CMakeLists.txt -set(SELF_EXE xo_stringliteral_ex1) +set(SELF_EXE xo_flatstring_ex1) set(SELF_SRCS ex1.cpp) add_executable(${SELF_EXE} ${SELF_SRCS}) @@ -9,7 +9,7 @@ xo_include_options2(${SELF_EXE}) # ---------------------------------------------------------------- # dependencies.. -xo_self_dependency(${SELF_EXE} xo_stringliteral) +xo_self_headeronly_dependency(${SELF_EXE} xo_flatstring) #xo_dependency(${SELF_EXE} reflect) # end CMakeLists.txt From c9fa89bbdde6f48f5fe4cd6b40da7c82968832f9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 16 Apr 2024 21:38:27 -0400 Subject: [PATCH 0747/2693] xo-flatstring: build: + GNUInstallDirs --- CMakeLists.txt | 1 + cmake/xo-bootstrap-macros.cmake | 2 +- docs/CMakeLists.txt | 5 ++++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index af27a769..72b0d161 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,7 @@ project(xo_flatstring VERSION 1.0) enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) +include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 83f4a39b..1542d355 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -15,7 +15,7 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL pre endif() if (NOT XO_SUBMODULE_BUILD) - message("-- GUESSED_CMAKE_CMD=cmake -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -B ${CMAKE_BINARY_DIR}") + message("-- GUESSED_CMAKE_CMD=cmake -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_DOCDIR=${CMAKE_INSTALL_DOCDIR} -B ${CMAKE_BINARY_DIR}") message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") endif() diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index c6fcc37b..b31549e0 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -94,12 +94,15 @@ else() # - html docs generated in build/docs/sphinx # - copy the doc tree to share/doc/xo_unit/html # + # DESTINATION: CMAKE_INSTALL_DOCDIR + # => DATAROOTDIR/doc/PROJECT_NAME + # => CMAKE_INSTALL_PREFIX/share/doc/xo_flatstring # OPTIONAL: install directory tree if it exists, # but don't complain if it's missing install( DIRECTORY ${SPHINX_OUTPUT_DIR} FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ - DESTINATION ${CMAKE_INSTALL_PREFIX}/share/doc/${PROJECT_NAME} + DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT Documentation OPTIONAL) From b2f2272b32233fa5bd33a0f78c21dcb9598a7b40 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 16 Apr 2024 21:38:59 -0400 Subject: [PATCH 0748/2693] xo-flatstring: ++ README --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 856b61fb..35b6f731 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,15 @@ Features: - char array representation with maximum size set at compile time. - compile time construction from char array and string concatenation - pointer-free implementation, instances can be used as template arguments -- To the extent practical, provides the same api as `std::string` +- To the extent practical, provides the same api as `std::string`: includes iterators, + access methods, assignment, conversion operators. Limitations: - requires c++20 - not resizable. - does not support wide characters. +- (asof April 2024) missing features: `insert`, `erase`, `push_back`, `append`, `replace`, + `find`, `compare`, `starts_with`, `ends_with`, `contains`, `substr`. ## Getting started From eb11089c4a9739e6e2547e46e4360d60386eb26b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 16 Apr 2024 21:39:13 -0400 Subject: [PATCH 0749/2693] xo-flatstring: + LICENSE --- LICENSE | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..cae3cb5d --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2024 Roland Conybeare , All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Please also refer to the file .github/CONTRIBUTING.md, which clarifies licensing of +external contributions to this project including patches, pull requests, etc. From 5b9fc80258b2a5e55f9f8d00154ee7178a4964cc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 10:56:47 -0400 Subject: [PATCH 0750/2693] xo-flatstring: docs: + source code link + tweaks --- docs/install.rst | 56 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/docs/install.rst b/docs/install.rst index aab059ab..faa72baf 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -3,24 +3,46 @@ .. toctree :maxdepth: 2 -Install -======= +Source +====== -`xo-flatstring source`_ lives on github. +Source code lives on github `here`_ -.. _xo-flatstring source: https://github.com/rconybea/xo-flatstring +.. _here: https://github.com/rconybea/xo-flatstring -Implementation relies on c++20 features (for example class-instances as template arguments). -Tested with gcc 13.2 - -Include as submodule --------------------- +To clone from git: .. code-block:: bash - cd myproject - git submodule add -b main https://github.com/rconybea/xo-flatstring ext/xo-flatstring - git submodule update --init + git clone https://github.com/rconybea/xo-flatstring + +Implementation relies on c++20 features (expanded use of constexpr; class-instances as template arguments). +Tested with gcc 13.2 + +Install +======= + +Since xo-flatstring is header-only, can incorporate into another project just by copying the include directories +to somewhere convenient. + +Copy includes +------------- + +.. code-block:: bash + + # For example.. + cd myproject + mkdir -p ext/xo-flatstring + rsync -a -v path/to/xo-flatstring/include/ ext/xo-flatstring/ + +Include as git submodule +------------------------ + +.. code-block:: bash + + cd myproject + git submodule add -b main https://github.com/rconybea/xo-flatstring ext/xo-flatstring + git submodule update --init This assumes you organize directly-incorporated dependencies under directory ``myproject/ext``. You would then add ``myproject/ext/xo-flatstring/include`` to your compiler's include path, @@ -28,7 +50,7 @@ and from c++ do something like .. code-block:: c++ - #include + #include in c++ source files that rely on xo-flatstring @@ -54,3 +76,11 @@ Unit test dependencies: .. _catch2: https://github.com/catchorg/Catch2 .. _xo-cmake: https://github.com/rconybea/xo-cmake .. _xo-indentlog: https://github.com/rconybea/indentlog + +To build documentation, will also need: + +* `doxygen` +* `graphviz` +* `sphinx` +* `breathe` +* `sphinx_rtd_theme` From 02bb6bd82654468b74e29f51068bb0874e708396 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 10:57:11 -0400 Subject: [PATCH 0751/2693] xo-flatstring: bugfix: include path in utest --- utest/flatstring.test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utest/flatstring.test.cpp b/utest/flatstring.test.cpp index 1b5603a7..877a8183 100644 --- a/utest/flatstring.test.cpp +++ b/utest/flatstring.test.cpp @@ -1,6 +1,6 @@ /** @file flatstring.utest.cpp **/ -#include "xo/stringliteral/stringliteral.hpp" +#include "xo/flatstring/flatstring.hpp" #include "xo/indentlog/scope.hpp" #include "xo/indentlog/print/tag.hpp" #include From c9c46c24a6b004ba602b935a1738391a20f14e09 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 10:57:35 -0400 Subject: [PATCH 0752/2693] xo-flatstring: build: targets to install ccov output --- README.md | 11 ++++++++--- utest/CMakeLists.txt | 31 +++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 35b6f731..730c7a18 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,11 @@ Limitations: - (asof April 2024) missing features: `insert`, `erase`, `push_back`, `append`, `replace`, `find`, `compare`, `starts_with`, `ends_with`, `contains`, `substr`. +## Documentation + +xo-flatstring documentation here: https://rconybea.github.io/web/xo-flatstring/html/index.html +unit test coverage here: https://rconybea.github.io/web/xo-flatstring/ccov/html/index.html + ## Getting started ### build + install @@ -39,18 +44,18 @@ When complete, point local browser to `xo-flatstring/.build/docs/sphinx/index.ht ``` $ cd xo-flatstring $ mkdir .build-ccov -$ cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug -B .build-ccov +$ cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -DCMAKE_BUILD_TYPE=coverage -B .build-ccov $ cmake --build .build-ccov ``` run coverage-enabled unit tests ``` -$ (cd .build-ccov && ctest) +$ cmake --build .build-ccov -- test ``` generate html+text coverage report ``` -$ .build-ccov/gen-ccov +$ cmake --build .build-ccov -- ccov ``` browse to `.build-ccov/ccov/html/index.html` diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 11e76f63..142a1a4b 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -11,6 +11,37 @@ xo_include_options2(${SELF_EXE}) add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) #target_code_coverage(${SELF_EXE} AUTO ALL) +# ---------------------------------------------------------------- +# in coverage build, target to build+install coverage report + +set(CCOV_OUTPUT_DIR ${PROJECT_BINARY_DIR}/ccov/html) +set(CCOV_INDEX_FILE ${CCOV_OUTPUT_DIR}/index.html) +set(CCOV_REPORT_EXE ${PROJECT_BINARY_DIR}/gen-ccov) +set(CCOV_INSTALL_DOCDIR ${CMAKE_INSTALL_DOCDIR}/ccov) + +# 'test' target should always be out-of-date +# +# DEPENDS: reminder - can't put 'test' here, requires 'all' target +# +add_custom_command( + OUTPUT ${CCOV_INDEX_FILE} + DEPENDS ${SELF_EXE} + COMMAND ${CCOV_REPORT_EXE} + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + COMMENT "Generating coverage report -> [${CCOV_OUTPUT_DIR}]") + +add_custom_target( + ccov + DEPENDS ${CCOV_INDEX_FILE} ${SELF_EXE}) + +# OPTIONAL: quietly skip this step if ccov report not generated +install( + DIRECTORY ${CCOV_OUTPUT_DIR} + FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CCOV_INSTALL_DOCDIR} + COMPONENT Documentation + OPTIONAL) + # ---------------------------------------------------------------- # deps: logutils, ... From e171c28701d9c3223a644aeb98532fb4bb8a495f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 10:59:53 -0400 Subject: [PATCH 0753/2693] xo-flatstring: README: tidy doc links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 730c7a18..7e90d6b1 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ Limitations: ## Documentation -xo-flatstring documentation here: https://rconybea.github.io/web/xo-flatstring/html/index.html -unit test coverage here: https://rconybea.github.io/web/xo-flatstring/ccov/html/index.html +- xo-flatstring documentation here: [documentation][https://rconybea.github.io/web/xo-flatstring/html/index.html] +- unit test coverage here: [coverage][https://rconybea.github.io/web/xo-flatstring/ccov/html/index.html] ## Getting started From 85fa69c58ccbea410817d2f9401160fccb9b3a50 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 11:02:21 -0400 Subject: [PATCH 0754/2693] xo-flatstring: README: fix links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7e90d6b1..7f136f0e 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ Limitations: ## Documentation -- xo-flatstring documentation here: [documentation][https://rconybea.github.io/web/xo-flatstring/html/index.html] -- unit test coverage here: [coverage][https://rconybea.github.io/web/xo-flatstring/ccov/html/index.html] +- xo-flatstring documentation here: [documentation](https://rconybea.github.io/web/xo-flatstring/html/index.html) +- unit test coverage here: [coverage](https://rconybea.github.io/web/xo-flatstring/ccov/html/index.html) ## Getting started From 0cc317b140d444d88a25edd4af2c7177cb3a0478 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 12:36:44 -0400 Subject: [PATCH 0755/2693] xo-flatstring: build docs --- README.md | 3 +++ utest/CMakeLists.txt | 3 +++ 2 files changed, 6 insertions(+) diff --git a/README.md b/README.md index 7f136f0e..0e7ca89b 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,9 @@ $ cmake --build .build-ccov -- ccov browse to `.build-ccov/ccov/html/index.html` +Running `cmake --install` for a coverage build will install coverage report (if generated) +to `${CMAKE_INSTALL_PREFIX}/share/doc/xo_flatstring` + ### LSP support ``` $ cd xo-flatstring diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 142a1a4b..091e56ef 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -17,6 +17,9 @@ add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) set(CCOV_OUTPUT_DIR ${PROJECT_BINARY_DIR}/ccov/html) set(CCOV_INDEX_FILE ${CCOV_OUTPUT_DIR}/index.html) set(CCOV_REPORT_EXE ${PROJECT_BINARY_DIR}/gen-ccov) +# CMAKE_INSTALL_DOCDIR +# =default=> DATAROOTDIR/doc/PROJECT_NAME +# =default=> CMAKE_INSTALL_PREFIX/share/doc/xo_flatstring set(CCOV_INSTALL_DOCDIR ${CMAKE_INSTALL_DOCDIR}/ccov) # 'test' target should always be out-of-date From ff7afb81762b9c3939eef9ab8a3c610ad2149b96 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 12:36:59 -0400 Subject: [PATCH 0756/2693] xo-flatstring: + img/icon.svg --- img/icon.svg | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 img/icon.svg diff --git a/img/icon.svg b/img/icon.svg new file mode 100644 index 00000000..f86f334f --- /dev/null +++ b/img/icon.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + From e0960d984822c2d297634385d846bb66c02fe1ba Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 12:52:28 -0400 Subject: [PATCH 0757/2693] xo-flatstring: + icons --- img/favicon.ico | Bin 0 -> 303879 bytes img/xo-icon.svg | 72 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 img/favicon.ico create mode 100644 img/xo-icon.svg diff --git a/img/favicon.ico b/img/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..f4e9d76070462c14b4ebab0154ce19516d899be9 GIT binary patch literal 303879 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 + + + + + + + + + + + + + + From 036f412d0d0863d2927d96c50ff19e00c5107a97 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 13:18:10 -0400 Subject: [PATCH 0758/2693] xo-flatstring: icon tidy --- img/favicon.ico | Bin 303879 -> 309803 bytes img/xo-icon.svg | 31 ++++++++++++++++++------------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/img/favicon.ico b/img/favicon.ico index f4e9d76070462c14b4ebab0154ce19516d899be9..15da2145f93eb26e6995c7868ab7883fb2361288 100644 GIT binary patch literal 309803 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 + inkscape:current-layer="layer1" + fit-margin-top="9.8" + fit-margin-left="8" + fit-margin-right="10" + fit-margin-bottom="9.8" /> + id="layer1" + transform="translate(-47.453609,-27.112369)"> From 0ca71b16b966d65e19f4a80bb398fc55e6cfcb2c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 13:51:14 -0400 Subject: [PATCH 0759/2693] xo-flatstring: docs: + favicon (with sphinx) --- {img => docs/_static/img}/favicon.ico | Bin {img => docs/_static/img}/icon.svg | 29 +++++++++++++++----------- {img => docs/_static/img}/xo-icon.svg | 0 docs/conf.py | 1 + 4 files changed, 18 insertions(+), 12 deletions(-) rename {img => docs/_static/img}/favicon.ico (100%) rename {img => docs/_static/img}/icon.svg (73%) rename {img => docs/_static/img}/xo-icon.svg (100%) diff --git a/img/favicon.ico b/docs/_static/img/favicon.ico similarity index 100% rename from img/favicon.ico rename to docs/_static/img/favicon.ico diff --git a/img/icon.svg b/docs/_static/img/icon.svg similarity index 73% rename from img/icon.svg rename to docs/_static/img/icon.svg index f86f334f..e37851a2 100644 --- a/img/icon.svg +++ b/docs/_static/img/icon.svg @@ -2,9 +2,9 @@ + inkscape:current-layer="layer1" + fit-margin-top="9.8" + fit-margin-left="8" + fit-margin-right="10" + fit-margin-bottom="9.8" /> + id="layer1" + transform="translate(-47.453609,-27.112369)"> diff --git a/img/xo-icon.svg b/docs/_static/img/xo-icon.svg similarity index 100% rename from img/xo-icon.svg rename to docs/_static/img/xo-icon.svg diff --git a/docs/conf.py b/docs/conf.py index 26dc0ec0..0860b3d7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -33,3 +33,4 @@ pygments_style = 'sphinx' #html_theme = 'alabaster' html_theme = 'sphinx_rtd_theme' html_static_path = ['_static'] +html_favicon = '_static/img/favicon.ico' From 1d18f11678695e71b5efa4f9256421e3201e3e2e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 18:09:53 -0400 Subject: [PATCH 0760/2693] xo-flatstring: bugfixes + utest expansion --- docs/lessons.rst | 56 ++++- include/xo/flatstring/flatstring.hpp | 124 ++++++++-- utest/flatstring.test.cpp | 333 ++++++++++++++++++++++++--- 3 files changed, 467 insertions(+), 46 deletions(-) diff --git a/docs/lessons.rst b/docs/lessons.rst index 40c39eaf..a5459c6b 100644 --- a/docs/lessons.rst +++ b/docs/lessons.rst @@ -11,7 +11,7 @@ One hurdle we've created for ourselves, is we need both gcc and clang to agree that an expression can be computed at compile-time; otherwise will get false alarms in our IDE (raised by LSP running in the background, which relies on clang). -Must Fully Initialize Memory +Must fully initialize memory ---------------------------- Struggled for a while with the implementation of :ref:xo::flatstring_concat @@ -42,3 +42,57 @@ Correction is to prove to clang that every memory address owned by an empty ``fl flatstring::flatstring() { std::fill_n(value_, N, '\0'); } + +Still need equality comparison alongside spaceship operator +----------------------------------------------------------- + +Had the impression that spaceship operator for :ref:xo::flatstring would be sufficient +to get all six comparison operators: + +.. code-block:: cpp + + template + constexpr auto + operator<=>(const flatstring & s1, + const flatstring & s2) noexcept + { + return (std::string_view(s1) <=> std::string_view(s2)); + } + +We observe this is not the case, at least with gcc 13.1; need to separately define :ref:xo::operator== + +.. code-block:: cpp + + template + constexpr bool + operator==(const flatstring & s1, + const flatstring & s2) noexcept + { + return ((s1 <=> s2) == std::strong_ordering::equal); + } + +Constexpr strict about pointer arithmetic +----------------------------------------- + +Initially attempted to implement :ref:xo::flatstring reverse iterators using char pointers. + +Notice there's an assymetry between reverse iterators and forward iterators. +We can (and do) implement forward iterators using char pointers. +The natural value of ``flatstring::end()`` is a char pointer referring to just past the end of +the string, i.e. to its null terminator. From the compiler's perspective, this is an ordinary +char pointer, just like other iterator values. + +For reverse iterators this isn't the case. The natural value for ``flatstring::rend()`` might +seem to be a char pointer referring to just before the first character in the string. +However this is no longer a valid pointer address -- dereferencing would be undefined behavior. + +In particular, with this implementation, gcc demotes ``flatstring::rend()`` to non-constexpr + +Workaround is to implement a shim iterator class, where representation is pointer to the +character just after the one the iterator position; iterator's ``operator*`` adjusts pointer before +dereferencing. + +This works because gcc can observe that we never dereference a reverse iterator with pointer value +at the beginning of a flatstring. diff --git a/include/xo/flatstring/flatstring.hpp b/include/xo/flatstring/flatstring.hpp index 55d9f1fd..e7233499 100644 --- a/include/xo/flatstring/flatstring.hpp +++ b/include/xo/flatstring/flatstring.hpp @@ -50,10 +50,80 @@ namespace xo { using iterator = char *; /** @brief representation for a readonly iterator **/ using const_iterator = const char *; - /** @brief representation for a read/write reverse iterator **/ - using reverse_iterator = char *; - /** @brief representation for a readonly reverse iterator **/ - using const_reverse_iterator = const char *; + + /** @brief representation for a read/write reverse iterator + * + * constexpr implementation is tricky here, since we can't + * form the address 'just before the beginning of the string' for @p rend() + * without losing constexprness (at least with gcc 13.1) + * + * Instead iterator always refers to the address immediately after its + * real target. This works since @c rbegin() refers to the char just before + * trailing null + **/ + struct reverse_iterator { + public: + constexpr reverse_iterator(char * p) : p_{p} {} + + constexpr bool _has_pointer() const { return p_ != nullptr; } + + constexpr bool operator==(const reverse_iterator & rhs) const noexcept { + return p_ == rhs.p_; + } + + constexpr char & operator* () const { return *(p_ - 1); } + + constexpr reverse_iterator & operator++ () { + --p_; + return *this; + } + + constexpr reverse_iterator operator++ (int) { + reverse_iterator copy = *this; + --p_; + return copy; + } + + private: + char * p_; + }; + + /** @brief representation for a readonly reverse iterator + * + * constexpr implementation is tricky here, since we can't + * form the address 'just before the beginning of the string' for @p rend() + * without losing constexprness (at least with gcc 13.1) + * + * Instead iterator always refers to the address immediately after its + * real target. This works since @c rbegin() refers to the char just before + * trailing null + **/ + struct const_reverse_iterator { + public: + constexpr const_reverse_iterator(const char * p) : p_{p} {} + + constexpr bool _has_pointer() const { return p_ != nullptr; } + + constexpr bool operator==(const const_reverse_iterator & rhs) const noexcept { + return p_ == rhs.p_; + } + + constexpr const char & operator* () const { return *(p_ - 1); } + + constexpr const_reverse_iterator & operator++ () { + --p_; + return *this; + } + + constexpr const_reverse_iterator operator++ (int) { + const_reverse_iterator copy = *this; + --p_; + return copy; + } + + private: + const char * p_; + }; ///@} /** @defgroup flatstring-constants constants **/ @@ -131,22 +201,22 @@ namespace xo { * * @pre 0<=pos<=N-1 **/ - constexpr value_type & operator[](size_type pos) { return value_[pos]; } - constexpr const value_type & operator[](size_type pos) const { return value_[pos]; } + constexpr value_type & operator[](size_type pos) noexcept { return value_[pos]; } + constexpr const value_type & operator[](size_type pos) const noexcept { return value_[pos]; } ///@} /** @defgroup flatstring-iterators iterators **/ ///@{ constexpr iterator begin() { return &value_[0]; } - constexpr iterator end() { return this->last(); } + constexpr iterator end() { return this->last(); } constexpr const_iterator cbegin() const { return &value_[0]; } constexpr const_iterator cend() const { return const_cast(this)->last(); } constexpr const_iterator begin() const { return cbegin(); } constexpr const_iterator end() const { return cend(); } - constexpr reverse_iterator rbegin() { return this->last(); } - constexpr reverse_iterator rend() { return &value_[0]; } + constexpr reverse_iterator rbegin() { return reverse_iterator(this->last()); } + constexpr reverse_iterator rend() { return reverse_iterator(&value_[0]); } constexpr const_reverse_iterator crbegin() const { return const_cast(this)->last(); } constexpr const_reverse_iterator crend() const { return &value_[0]; } constexpr const_reverse_iterator rbegin() const { return crbegin(); } @@ -156,7 +226,7 @@ namespace xo { /** @defgroup flatstring-assign assignment **/ ///@{ /** @brief put string into empty state. fills entire char array with nulls **/ - void clear() { std::fill_n(value_, N, '\0'); } + constexpr void clear() noexcept { std::fill_n(value_, N, '\0'); } /** @brief replace contents with min(count,N-1) copies of character ch **/ constexpr flatstring & assign(size_type count, value_type ch) { @@ -168,7 +238,7 @@ namespace xo { return *this; } - /** @brief replace contents with first N-1 characters of str **/ + /** @brief replace contents with first N-1 characters of @p x **/ constexpr flatstring & assign(const flatstring & x) { for (std::size_t pos = 0; pos < N-1; ++pos) value_[pos] = x.value_[pos]; @@ -176,13 +246,15 @@ namespace xo { return *this; } /** @brief replace contents with substring [pos,pos+count] of str **/ - constexpr flatstring & assign(const flatstring & x, + template + constexpr flatstring & assign(const flatstring & x, size_type pos, size_type count = npos) { std::size_t i = 0; for (; i < std::min(std::min(count, - std::max(x.capacity-1 - pos, - 0)), + (x.fixed_capacity-1 > pos) + ? x.fixed_capacity-1 - pos + : 0ul), N-1); ++i) value_[i] = x.value_[pos+i]; @@ -274,7 +346,7 @@ namespace xo { * strcmp(s, "obey..."); * @endcode **/ - constexpr operator const char * () const { return value_; } + constexpr operator const char * () const noexcept { return value_; } ///@} private: @@ -293,7 +365,7 @@ namespace xo { } template - constexpr Iterator last() { + constexpr Iterator last() noexcept { Iterator p = &value_[N-1]; /* search backward for first padding '\0' */ @@ -426,6 +498,26 @@ namespace xo { { return (std::string_view(s1) <=> std::string_view(s2)); } + + /** @brief equality comparison for two flatstrings. + * + * Example + * @code + * constexpr bool cmp = (flatstring("foo") == flatstring("foo")); + * static_assert(cmp == true); + * @endcode + * + * @note spaceship operator alone isn't sufficient to get this defined, + * at least with gcc 13.1 + **/ + template + constexpr bool + operator==(const flatstring & s1, + const flatstring & s2) noexcept + { + return ((s1 <=> s2) == std::strong_ordering::equal); + } ///@} } /*namespace xo*/ diff --git a/utest/flatstring.test.cpp b/utest/flatstring.test.cpp index 877a8183..197ccfd9 100644 --- a/utest/flatstring.test.cpp +++ b/utest/flatstring.test.cpp @@ -3,13 +3,236 @@ #include "xo/flatstring/flatstring.hpp" #include "xo/indentlog/scope.hpp" #include "xo/indentlog/print/tag.hpp" +#include "xo/indentlog/print/hex.hpp" #include +#include //#include namespace xo { using namespace std; namespace ut { + template + void + flatstring_iter_tests(const String & str, const char * text) { + size_t n = ::strlen(text); + + REQUIRE(str.size() == n); + + /* verify range iteration visits contents in order */ + { + size_t i = 0; + for (char ch : str) { + INFO(XTAG(i)); + + CHECK(ch == text[i]); + + ++i; + } + + REQUIRE(i == n); + } + + String str_copy; + + REQUIRE(str_copy.capacity() == str.capacity()); + REQUIRE(str_copy.empty()); + + /* verify const iteration visits string elements in order */ + { + str_copy = str; + REQUIRE(str_copy == str); + + size_t i = 0; + + for (auto ix = str_copy.cbegin(), end_ix = str_copy.cend(); ix != end_ix; ++ix) { + INFO(XTAG(i)); + + char ch = *ix; + + CHECK(ch == text[i]); + + ++i; + } + + REQUIRE(i == n); + } + + /* verify string overwrite through iterator */ + { + size_t i = 0; + + for (auto ix = str_copy.begin(), end_ix = str_copy.end(); ix != end_ix; ++ix) { + INFO(XTAG(i)); + + *ix = ('a' + i); + + ++i; + } + + REQUIRE(i == n); + + for (i = 0; i < n; ++i) { + CHECK(str_copy[i] == ('a' + i)); + } + } + + /* verify reverse iteration visits string elements in reverse order */ + { + str_copy = str; + REQUIRE(str_copy == str); + + size_t i = 0; + + for (auto ix = str_copy.rbegin(), end_ix = str_copy.rend(); ix != end_ix; ++ix) { + INFO(XTAG(i)); + + char ch = *ix; + + CHECK(ch == text[n-1-i]); + + ++i; + } + + REQUIRE(i == n); + } + + /* verify string overwrite through reverse iterator */ + { + str_copy = str; + REQUIRE(str_copy == str); + + size_t i = 0; + + for (auto ix = str_copy.rbegin(), end_ix = str_copy.rend(); ix != end_ix; ++ix) { + INFO(XTAG(i)); + + *ix = ('a' + i); + + ++i; + } + + REQUIRE(i == n); + + for (i = 0; i< n; ++i) { + CHECK(str_copy[n-1-i] == ('a' + i)); + } + } + + /* verify const reverse iteration visits string elements in reverse order */ + { + str_copy = str; + REQUIRE(str_copy == str); + + size_t i = 0; + + for (auto ix = str_copy.crbegin(), end_ix = str_copy.crend(); ix != end_ix; ++ix) { + INFO(XTAG(i)); + + char ch = *ix; + + CHECK(ch == text[n-1-i]); + + ++i; + } + + REQUIRE(i == n); + } + } + + template + void + flatstring_assign_tests(const String1 & str, const char * text, + const String2 & str2, const char * text2) { + INFO(tostr(XTAG(str), XTAG(text), XTAG(text2))); + + String1 str_copy; + + str_copy.assign(str.c_str()); + REQUIRE(str_copy == str); + + /* verify assignment from C-style string **/ + { + str_copy.assign(text2); + + INFO(tostr(XTAG(str_copy), XTAG(text2))); + + REQUIRE(::strncmp(str_copy.c_str(), text2, + std::min(::strlen(text2)+1, str_copy.capacity())) == 0); + } + + /* verify assignment from prefix of C-style string */ + for (size_t prefix = 0, n_prefix = ::strlen(text2); prefix < n_prefix; ++prefix) + { + str_copy.assign(str); + + REQUIRE(str_copy == str); + + str_copy.assign(text2, prefix); + + INFO(tostr(XTAG(prefix), XTAG(str_copy), XTAG(text2))); + + if (prefix == 0) { + REQUIRE(str_copy.empty()); + } else { + REQUIRE(str_copy.size() == std::min(prefix, str_copy.capacity())); + REQUIRE(::strncmp(str_copy.c_str(), text2, + std::min(prefix, str_copy.capacity())) == 0); + } + } + + /* verify assignment from substring */ + String2 text2_copy; + text2_copy.assign(text2); + + INFO(tostr(XTAG(text2_copy))); + + for (size_t i = 0, n = text2_copy.size(); i < n; ++i) { + /* deliberately letting j extend beyond the end of text2_copy */ + for (size_t j = i; j < n+10; ++j) { + INFO(tostr(XTAG(n), XTAG(i), XTAG(j))); + + str_copy.assign(str); + + REQUIRE(str_copy == str); + + str_copy.assign(text2_copy, i, j-i); + + INFO(tostr(XTAG(str_copy.fixed_capacity), XTAG(str_copy))); + + REQUIRE(str_copy.size() == std::min(j-i, + std::min(text2_copy.size()-i, + str_copy.capacity()))); + REQUIRE(::strncmp(str_copy.c_str(), text2_copy.c_str() + i, + std::min(j-i, str_copy.capacity())) == 0); + } + } + } + + template + void + flatstring_concat_tests(const String1 & str, const char * text, + const String2 & str2, const char * text2) + { + flatstring concat; + + REQUIRE(concat.empty()); + + /* forcing concat to occur at runtime */ + { + concat = flatstring_concat(str, str2); + auto req_str = string(text) + string(text2); + + REQUIRE(::strcmp(concat.c_str(), req_str.c_str()) == 0); + } + { + concat = flatstring_concat(str2, str); + auto req_str = string(text2) + string(text); + + REQUIRE(::strcmp(concat.c_str(), req_str.c_str()) == 0); + } + } + template void flatstring_runtime_tests(const String & str, const char * text) { @@ -22,16 +245,48 @@ namespace xo { REQUIRE(strcmp(str.c_str(), text) == 0); REQUIRE(strcmp(str, text) == 0); - /* verify range iteration visits contents in order */ + String str2 = str; + + { + string str3{str.str()}; + + REQUIRE(::strcmp(str3.c_str(), str.c_str()) == 0); + } + + REQUIRE(string_view(str2) == string_view(str)); + + { + auto cmp = (str2 <=> str); + REQUIRE(cmp == strong_ordering::equal); + } + + { + bool cmp = (str2 == str); + INFO(xtag("cmp", cmp)); + REQUIRE(str2 == str); + + bool cmp2 = (str2 != str); + REQUIRE(cmp2 != cmp); + } + + str2.clear(); + REQUIRE(str2.empty()); + + str2.assign(100, ' '); + REQUIRE(str2.size() == str2.capacity()); + + /* verify entirely ' ' */ { size_t i = 0; - for (char ch : str) { + for (char ch : str2) { INFO(XTAG(i)); - CHECK(ch == text[i]); + CHECK(ch == ' '); ++i; } + + REQUIRE(i == str2.size()); } } @@ -44,8 +299,9 @@ namespace xo { * REQUIRE() calls to do verification that relies on non-constexpr calls such as * strlen(), strcmp() */ -# define LITERAL_TEST_BODY(name, text) \ - constexpr flatstring name{text}; \ +# define LITERAL_TEST_BODY(name, name2, text, text2) \ + constexpr flatstring name{text}; \ + constexpr flatstring name2{text2}; \ static_assert(name[0]==text[0]); \ static_assert(name.at(0)==text[0]); \ static_assert(name.empty() == true || name.empty() == false); \ @@ -54,10 +310,10 @@ namespace xo { static_assert(name.end() != nullptr); \ static_assert(name.cbegin() != nullptr); \ static_assert(name.cend() != nullptr); \ - static_assert(name.rbegin() != nullptr); \ - static_assert(name.rend() != nullptr); \ - static_assert(name.crbegin() != nullptr); \ - static_assert(name.crend() != nullptr); \ + static_assert(name.crbegin()._has_pointer()); \ + static_assert(name.crend()._has_pointer()); \ + /*static_assert(name.rbegin() != nullptr);*/ \ + /*static_assert(!name.rend());*/ \ static_assert(name.size() >= 0); \ static_assert(name.c_str() != nullptr); \ static_assert((name <=> name) == 0); \ @@ -68,14 +324,11 @@ namespace xo { static_assert(!(name > name)); \ static_assert(!(name < name)); \ flatstring_runtime_tests(name, text); \ - REQUIRE(name.fixed_capacity == strlen(text)+1); \ - REQUIRE(name.capacity() == strlen(text)); \ - REQUIRE(name.size() == strlen(text)); \ - REQUIRE(name.length() == strlen(text)); \ - REQUIRE(strcmp(name.c_str(), text) == 0); \ - REQUIRE(strcmp(name, text) == 0); \ + flatstring_iter_tests(name, text); \ + flatstring_assign_tests(name, text, name2, text2); \ + flatstring_concat_tests(name, text, name2, text2); \ static_assert(string_view(name) == string_view(name)); \ - + /* end LITERAL_TEST_BODY */ TEST_CASE("flatstring", "[flatstring][compile-time]") { @@ -92,19 +345,21 @@ namespace xo { /* mostly compile-time tests here */ - LITERAL_TEST_BODY(s1, "h"); - LITERAL_TEST_BODY(s2, "he"); - LITERAL_TEST_BODY(s3, "hel"); - LITERAL_TEST_BODY(s4, "hell"); - LITERAL_TEST_BODY(s5, "hello"); - LITERAL_TEST_BODY(s6, "hello,"); - LITERAL_TEST_BODY(s7, "hello, "); - LITERAL_TEST_BODY(s8, "hello, w"); - LITERAL_TEST_BODY(s9, "hello, wo"); - LITERAL_TEST_BODY(s10, "hello, wor"); - LITERAL_TEST_BODY(s11, "hello, worl"); - LITERAL_TEST_BODY(s12, "hello, world"); - LITERAL_TEST_BODY(s13, "hello, world!"); + LITERAL_TEST_BODY(s1, t1, "h", "abracadabra!"); + LITERAL_TEST_BODY(s2, t2, "he", "bracadabra!"); + LITERAL_TEST_BODY(s3, t3, "hel", "racadabra!"); + LITERAL_TEST_BODY(s4, t4, "hell", "acadabra!"); + LITERAL_TEST_BODY(s5, t5, "hello", "cadabra!"); + LITERAL_TEST_BODY(s6, t6, "hello,", "adabra!"); + LITERAL_TEST_BODY(s7, t7, "hello, ", "dabra!"); + LITERAL_TEST_BODY(s8, t8, "hello, w", "abra!"); + LITERAL_TEST_BODY(s9, t9, "hello, wo", "bra!"); + LITERAL_TEST_BODY(s10, t10, "hello, wor", "ra!"); + LITERAL_TEST_BODY(s11, t11, "hello, worl", "a!"); + LITERAL_TEST_BODY(s12, t12, "hello, world", "!"); + LITERAL_TEST_BODY(s13, t13, "hello, world!", ""); + + static_assert(s1 == s1); static_assert(s1 != s2); static_assert(s2 != s3); @@ -123,6 +378,26 @@ namespace xo { static_assert(s4 > s3); static_assert(s5 > s4); static_assert(s13 > s12); + + /* concat */ + static_assert(flatstring_concat(s1,t1) == flatstring("habracadabra!")); + + /* clear */ + auto s13_copy = s13; + s13_copy.clear(); + + REQUIRE(s13_copy.empty()); + + constexpr auto s13_copy2 = s13; + + static_assert(s13_copy2.size() == s13.size()); + + //cerr << "s13=[" << s13 << "] s13_copy2=[" << s13_copy2 << "]" << endl; + //cerr << xtag("s13", hex_view(s13.c_str(), s13.c_str() + s13.capacity(), true)) << endl; + //cerr << xtag("s13_copy2", hex_view(s13_copy2.c_str(), s13_copy2.c_str() + s13_copy2.capacity(), true)) << endl; + + REQUIRE(s13_copy2 == s13); + } /*TEST_CASE(flatstring)*/ } /*namespace ut*/ From 2b1aa47893da4cffbec755dc3329991c4beb4a10 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 18:10:06 -0400 Subject: [PATCH 0761/2693] xo-flatstring: github: ubuntu build --- .github/workflows/ubuntu-main.yml | 139 ++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 .github/workflows/ubuntu-main.yml diff --git a/.github/workflows/ubuntu-main.yml b/.github/workflows/ubuntu-main.yml new file mode 100644 index 00000000..c587c94a --- /dev/null +++ b/.github/workflows/ubuntu-main.yml @@ -0,0 +1,139 @@ +name: build xo-flatstring + dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install dependencies + run: | + # install catch2, doxygen. see + # [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + + echo "::group::install catch2" + sudo apt-get install -y catch2 + echo "::endgroup" + + echo "::group::install doxygen" + sudo apt-get install -y doxygen + echo "::endgroup" + + #echo "::group::install pybind11" + #sudo apt-get install -y pybind11-dev + #echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: build xo-cmake + run: | + XONAME=xo-cmake + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/xo-indentlog + + - name: build xo-indentlog + run: | + XONAME=xo-indentlog + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: build self (xo-flatstring) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: | + XONAME=xo-unit + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + (cd ${BUILDDIR} && ctest -C ${{env.BUILD_TYPE}}) From 6ce95635b68e5035dac9039203b83cb09a040018 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 18:14:25 -0400 Subject: [PATCH 0762/2693] xo-flatstring: github: + sphinx + sphinx-rtd-theme --- .github/workflows/ubuntu-main.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ubuntu-main.yml b/.github/workflows/ubuntu-main.yml index c587c94a..83e17077 100644 --- a/.github/workflows/ubuntu-main.yml +++ b/.github/workflows/ubuntu-main.yml @@ -34,6 +34,14 @@ jobs: sudo apt-get install -y doxygen echo "::endgroup" + echo "::group::install sphinx + sudo apt-get install -y python3-sphinx + echo "::endgroup" + + echo "::group::install sphinx readthedocs theme" + sudo apt-get install -y python3-sphinx-rtd-theme + echo "::endgroup" + #echo "::group::install pybind11" #sudo apt-get install -y pybind11-dev #echo "::endgroup" From a8dced149c7d2a8b7f729173cf4758d5b2896f36 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 18:15:29 -0400 Subject: [PATCH 0763/2693] xo-flatstring: github: typo in yaml --- .github/workflows/ubuntu-main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ubuntu-main.yml b/.github/workflows/ubuntu-main.yml index 83e17077..ba529a22 100644 --- a/.github/workflows/ubuntu-main.yml +++ b/.github/workflows/ubuntu-main.yml @@ -34,7 +34,7 @@ jobs: sudo apt-get install -y doxygen echo "::endgroup" - echo "::group::install sphinx + echo "::group::install sphinx" sudo apt-get install -y python3-sphinx echo "::endgroup" From 6df49efaa403633181367f350434cfd39fb013a5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 18:16:48 -0400 Subject: [PATCH 0764/2693] xo-flatstring: bugfix: stale #include in example --- example/ex1/ex1.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index e343002e..ad34c3ad 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -2,7 +2,7 @@ #include "xo/flatstring/flatstring.hpp" //#include "xo/stringliteral/stringliteral_iostream.hpp" -#include "xo/flatstring/experiment.hpp" +//#include "xo/flatstring/experiment.hpp" //#include "xo/stringliteral/string_view_concat.hpp" #include From 24f9346652836ec96112213d7fa9bd23941830dc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 18:20:12 -0400 Subject: [PATCH 0765/2693] xo-flatstring: bugfix: prune stale example code --- example/ex1/ex1.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index ad34c3ad..f5843db0 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -14,6 +14,7 @@ main() { using xo::stringliteral_compare; #endif +#ifdef NOT_USING static_assert(foo1().x_ == 1); static_assert(foo1().y_ == 2); @@ -67,6 +68,7 @@ main() { static_assert(z8 == 10); +#endif #ifdef NOT_USING static_assert(count_size("0123") == 5); @@ -83,6 +85,7 @@ main() { static_assert(z10 == 5); #endif +#ifdef NOT_USING //constexpr auto z11 = foofn2("0123"); //static_assert(z9 > 22); @@ -97,11 +100,13 @@ main() { static_assert(s10.size() == 9); cerr << s10.c_str() << endl; +#endif #ifdef NOT_SUCCESSFUL constexpr auto s11 = stringlit_make("0", "1", "23", "456", "78"); #endif +#ifdef NOT_USING constexpr std::size_t z9 = stringlit_capacity(s9, s10); static_assert(z9 == 19); @@ -117,6 +122,7 @@ main() { static_assert(s13.size() == 36); cerr << s13.c_str() << endl; +#endif #ifdef NOT_USING static_assert(stringliteral_compare(s1, s1) == 0); From 8d81f4fb91b91c6debd5120a0ef56caf6484604d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 18:23:22 -0400 Subject: [PATCH 0766/2693] xo-indentlog: build: print GUESSED_CMAKE_CMD --- cmake/xo-bootstrap-macros.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 96592216..3899fe86 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -4,6 +4,7 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr endif() if (NOT XO_SUBMODULE_BUILD) + message("-- GUESSED_CMAKE_CMD=cmake -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -B ${CMAKE_BINARY_DIR}") message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") endif() From bdb39b82263b0bed5ee3c0fb6795e9d789f16bc4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 18:23:39 -0400 Subject: [PATCH 0767/2693] xo-indentlog: + hex printer --- include/xo/indentlog/print/hex.hpp | 144 +++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 include/xo/indentlog/print/hex.hpp diff --git a/include/xo/indentlog/print/hex.hpp b/include/xo/indentlog/print/hex.hpp new file mode 100644 index 00000000..66a34084 --- /dev/null +++ b/include/xo/indentlog/print/hex.hpp @@ -0,0 +1,144 @@ +/** @file hex.hpp **/ + +#pragma once + +#include + +namespace xo { + /** + @class hex indentlog/print/hex.hpp + + @brief Container for a (1-byte) value to be printed in hexadecimal + + Example: + @code + #include "indentlog/print/hex.hpp" + + std::ostream os = ...; + os << hex(16 + 63); // output: 1f + os << hex(16 + 63, true); // output: 1f(O) + @endcode + **/ + struct hex { + /** @brief constructor; create stream-inserter instance */ + hex(std::uint8_t x, bool w_char = false) : x_{x}, with_char_{w_char} {} + + /** + @brief print hexadecimal byte-value on to stream. + @param os print on this stream. + + @tparam Stream typename for character stream. + **/ + template + void print(Stream & os) const { + std::uint8_t lo = x_ & 0xf; + std::uint8_t hi = x_ >> 4; + + char lo_ch = (lo < 10) ? '0' + lo : 'a' + lo - 10; + char hi_ch = (hi < 10) ? '0' + hi : 'a' + hi - 10; + + os << hi_ch << lo_ch; + + if (with_char_) { + os << "("; + if (std::isprint(x_)) + os << static_cast(x_); + else + os << "?"; + os << ")"; + } + } + + private: + /** @brief value to print (in hexadecimal) **/ + std::uint8_t x_; + /** @brief if true, follow with ascii character encoding **/ + bool with_char_; + }; + + /** + @brief stream inserter for an 8-bit quantity to be printed in hexadecimal. + + @param os print on this stream + @param ins package for value to insert + **/ + template + Stream & + operator<< (Stream & os, hex const & ins) { + ins.print(os); + return os; + } + + /** + @class hex_view indentlog/print/hex.hpp + + @brief Container for a range (unowned) of 1-byte values to be printed in hexadecimal + + Print a range of bytes on an arbitrary character stream. + Does not use @c iomanips, so will not alter stream formatting flags if used with @c iostream. + + Example: + @code + #include "indentlog/print/hex.hpp" + + std::ostream os = ...; + os << hex_view("hello", false); // output: [68 65 6c 6c 6f] + os << hex_view("hello", true); // output: [68(h) 65(e) 6c(l) 6c(l) 6f(o)] + @endcode + **/ + struct hex_view { + /** @brief constructor; create stream-inserter instance for a range of bytes **/ + hex_view(std::uint8_t const * lo, std::uint8_t const * hi, bool as_text) + : lo_{lo}, hi_{hi}, as_text_{as_text} {} + /** @brief constructor; create stream-inserter instance for a range of chars **/ + hex_view(char const * lo, char const * hi, bool as_text) + : lo_{reinterpret_cast(lo)}, + hi_{reinterpret_cast(hi)}, + as_text_{as_text} {} + + /** + @brief print hexadecimal byte range on stream. + @param os print on this stream + + @tparam Stream typename for character stream. + **/ + template + void print(Stream & os) const { + os << "["; + std::size_t i = 0; + for (std::uint8_t const * p = lo_; p < hi_; ++p) { + if (i > 0) + os << " "; + os << hex(*p, as_text_); + ++i; + } + os << "]"; + } + + private: + /** @brief print byte range starting at this address **/ + std::uint8_t const * lo_; + /** @brief print byte range up to (but not including) this address **/ + std::uint8_t const * hi_; + /** @brief if true also print ascii encoding (for printable codes), + * \c ? otherwise. @see hex::with_char + **/ + bool as_text_; + }; + + /** + @brief stream inserter for a range of 1-byte values to be printed in hexadecimal + + @param os print on this stream. + @param ins (container for) values to insert. + **/ + template + Stream & + operator<< (Stream & os, hex_view const & ins) { + ins.print(os); + return os; + } + +} /*namespace xo*/ + +/* end hex.hpp */ From d34855e7fb86e66d4bb542df95fc8e74c14ba66c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 18:26:03 -0400 Subject: [PATCH 0768/2693] xo-flatstring: github: xo-unit -> xo-flatstring ! --- .github/workflows/ubuntu-main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ubuntu-main.yml b/.github/workflows/ubuntu-main.yml index ba529a22..6e30f5ed 100644 --- a/.github/workflows/ubuntu-main.yml +++ b/.github/workflows/ubuntu-main.yml @@ -118,7 +118,7 @@ jobs: # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type run: | - XONAME=xo-unit + XONAME=xo-flatstring BUILDDIR=${{github.workspace}}/build_${XONAME} PREFIX=${{github.workspace}}/local From 6b43254b79227d83041c5746604d08e7540608f4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 20:48:38 -0400 Subject: [PATCH 0769/2693] xo-flatstring: github: + run unit tests --- .github/workflows/ubuntu-main.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ubuntu-main.yml b/.github/workflows/ubuntu-main.yml index 6e30f5ed..dc647f52 100644 --- a/.github/workflows/ubuntu-main.yml +++ b/.github/workflows/ubuntu-main.yml @@ -134,6 +134,10 @@ jobs: cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} echo "::endgroup" + echo "::group::run unit tests ${XONAME}" + cmake --build ${BUILDDIR} -- test + echo "::endgroup" + echo "::group::local install ${XONAME}" cmake --install ${BUILDDIR} echo "::endgroup" From b56cd3e39028d99b9013fdd018b3fa49531c6c62 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 20:52:31 -0400 Subject: [PATCH 0770/2693] xo-unit: docs: comment tweaks --- include/xo/unit/dim_util.hpp | 2 +- include/xo/unit/numeric_concept.hpp | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/include/xo/unit/dim_util.hpp b/include/xo/unit/dim_util.hpp index ad2f3496..0f84b956 100644 --- a/include/xo/unit/dim_util.hpp +++ b/include/xo/unit/dim_util.hpp @@ -21,7 +21,7 @@ namespace xo { * (1usd + 1eur) is well-defined, but (1sec + 1m) is not. **/ currency, - /** a screen price. dimensionless **/ + /** a screen price **/ price, }; diff --git a/include/xo/unit/numeric_concept.hpp b/include/xo/unit/numeric_concept.hpp index 720099a3..f1d32f58 100644 --- a/include/xo/unit/numeric_concept.hpp +++ b/include/xo/unit/numeric_concept.hpp @@ -11,13 +11,9 @@ namespace xo { * * Intended to include at least: * - built-in integral and floating-point types - * - boost::rational - * - std::complex + * - xo::raio * - xo::unit::quantity * - * This implies we don't require T to be totally ordered, - * and don't require (<,<=,>=,>) operators. - * * Intend numeric_concept to apply to types suitable for * xo::unit::quantity::repr_type. **/ From 3dd48ee1e754a7cdab298f0bcfdc42bc4bf450bb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 20:53:01 -0400 Subject: [PATCH 0771/2693] xo-unit: bugfix: fix static asserts on operator+= operator-= --- include/xo/unit/quantity.hpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index 138565d1..c49ab4d5 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -406,13 +406,11 @@ namespace xo { * @pre @p y must have the same dimension as @c *this. * * @param y quantity to add - * @retval this quantity after adding y + * @return this quantity after adding y **/ template quantity & operator+=(Quantity2 y) { - static_assert(std::same_as< - typename unit_type::canon_type, - typename Quantity2::unit_type::canon_type >); + static_assert(same_dimension_v); /* relying on assignment that correctly converts-to-lhs-units */ quantity y2 = y; @@ -427,13 +425,11 @@ namespace xo { * @pre @p y must have the same dimensions as @c *this * * @param y quantity to subtract - * @retval this quantity after subtracting y + * @return this quantity after subtracting y **/ template quantity & operator-=(Quantity2 y) { - static_assert(std::same_as< - typename unit_type::canon_type, - typename Quantity2::unit_type::canon_type >); + static_assert(same_dimension_v); quantity y2 = y; From 39ca2aa9f4dc1beb3b319064a198c50a7c9a1777 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 20:53:25 -0400 Subject: [PATCH 0772/2693] xo-unit: quantity: + compare() + operator<=> --- include/xo/unit/quantity.hpp | 55 ++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index c49ab4d5..0df8ec74 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -440,6 +440,61 @@ namespace xo { } ///@} + /** @defgroup quantity-comparisonsupport **/ + ///@{ + /** @brief compare this quantity with another, return 3-way comparison + * + * @pre arguments must be quantities having the same dimension + * + * @param y rhs quantity to compare + * @return signed integer; {-ve, 0, +ve} when @c *this is {less than, equal, greater than} @p y + **/ + template + requires quantity_concept && same_dimension_v + auto compare(Quantity2 y) const { + /* convert y to same {units, repr} as *this */ + quantity y2 = y; + + auto cmp = (this->scale_ <=> y2.scale()); + + return cmp; + } + ///@} + + /** @defgroup quantity-comparison **/ + ///@{ + /** @brief 3-way comparison of two quantities + * + * @pre arguments must be quantities having the same dimension + * + * @param y rhs quantity to compare + * @return std::partial_ordering + **/ + template + requires quantity_concept && same_dimension_v + auto operator<=>(Quantity2 y) const { + return this->compare(y); + } + + /** @brief compare two quantities for equality + * + * Although compiler generates this (due to presence of 3-way comparison operator), + * it flags ambiguous overload when included alongside .h files from std::distribution. + * Look like ambiguity would need to be resolved by a header change + **/ + template + requires quantity_concept && same_dimension_v + bool operator==(Quantity2 y) const { + return std::is_eq(this->compare(y)); + } + + template + requires quantity_concept && same_dimension_v + bool operator!=(Quantity2 y) const { + return std::is_neq(this->compare(y)); + } + + /** @addtogroup quantity-unit-conversion **/ ///@{ /** @brief convert to quantity with same dimension, different {unit_type, repr_type} From 03717ea7e92b73d6476a8d2c33e643230ea81ce4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 20:53:54 -0400 Subject: [PATCH 0773/2693] xo-unit: fix ratio_floor() + check args to Quantity converter --- include/xo/unit/quantity.hpp | 1 + include/xo/unit/ratio_util.hpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index 0df8ec74..588c45ec 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -502,6 +502,7 @@ namespace xo { * @pre @c Quantity2 must have the same dimension as @c *this. **/ template + requires quantity_concept && same_dimension_v constexpr operator Quantity2 () const { /* avoid truncating precision when converting: * use best available representation diff --git a/include/xo/unit/ratio_util.hpp b/include/xo/unit/ratio_util.hpp index 1d7e3199..970068d8 100644 --- a/include/xo/unit/ratio_util.hpp +++ b/include/xo/unit/ratio_util.hpp @@ -13,7 +13,7 @@ namespace xo { template struct ratio_floor { - using type = std::ratio; + using type = std::ratio; }; template From 5f2f9cccaa5bc0eb869ccd299aaeea775bfd4a7b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 20:54:22 -0400 Subject: [PATCH 0774/2693] xo-unit: utest: + quantity unit tests for compare --- utest/quantity.test.cpp | 67 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/utest/quantity.test.cpp b/utest/quantity.test.cpp index 83cf18ce..b2ade7c3 100644 --- a/utest/quantity.test.cpp +++ b/utest/quantity.test.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace xo { using xo::unit::quantity; @@ -787,7 +788,73 @@ namespace xo { log && log(XTAG(q1), XTAG(q2)); } /*TEST_CASE(rescale2)*/ + TEST_CASE("compare1", "[quantity]") { + constexpr bool c_debug_flag = true; + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.compare1")); + //log && log("(A)", xtag("foo", foo)); + + namespace u = xo::unit::unit_qty; + + auto q1 = kilometers(150.0) / u::hour; + auto q2 = kilometers(100.0) / u::hour; + + CHECK(is_gt(q1 <=> q2)); + CHECK(is_eq(q1 <=> q1)); + CHECK(is_lt(q2 <=> q1)); + CHECK(q1 == q1); + CHECK(q1 != q2); + CHECK(q1 >= q1); + CHECK(q1 <= q1); + + CHECK(q1 > q2); + CHECK(q1 >= q2); + + CHECK(q2 < q1); + CHECK(q2 <= q1); + + log && log(XTAG(q1), XTAG(q2), XTAG(is_gt(q1<=>q2))); + } /*TEST_CASE(compare1)*/ + + TEST_CASE("compare2", "[quantity]") { + constexpr bool c_debug_flag = true; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.compare2")); + //log && log("(A)", xtag("foo", foo)); + + namespace u = xo::unit::unit_qty; + + auto q1 = kilometers(150.0) / u::hour; + auto q2 = meters(30.0) / u::second; + + CHECK(is_gt(q1 <=> q2)); + CHECK(is_eq(q1 <=> q1)); + CHECK(is_lt(q2 <=> q1)); + CHECK(q1 == q1); + CHECK(q1 != q2); + CHECK(q1 >= q1); + CHECK(q1 <= q1); + + CHECK(q1 > q2); + CHECK(q1 >= q2); + + CHECK(q2 < q1); + CHECK(q2 <= q1); + + log && log(XTAG(q1), XTAG(q2), XTAG(is_gt(q1<=>q2))); + } /*TEST_CASE(compare2)*/ } /*namespace ut*/ } /*namespace xo*/ From 8ae770610eaeb3ff2c52086026c2eea08d8cd78f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 20:54:42 -0400 Subject: [PATCH 0775/2693] xo-unit: build: + GUESSED_CMAKE_CMD --- cmake/xo-bootstrap-macros.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 96592216..936a1810 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -4,6 +4,7 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr endif() if (NOT XO_SUBMODULE_BUILD) + message("-- GUESSED_CMAKE_CMD=cmake -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -B ${CMAKE_BINARY_DIR}") message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") endif() From 30fd9c6d16fd4cb628b07dd140a4caf651b2946e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 20:55:01 -0400 Subject: [PATCH 0776/2693] xo-cmake: README: minor tweak --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5af84f03..5ef9d025 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ When this completes, point local browser to `xo-unit/.build/docs/sphinx/index.h $ cd xo-unit $ mkdir .build-ccov $ cd .build-ccov -$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. ``` ### LSP support From c9b81d217f0e0a29b296acdeb7641bf520a05a59 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 20:55:19 -0400 Subject: [PATCH 0777/2693] xo-unit: cosmetic doc tweaks --- CMakeLists.txt | 2 +- docs/quantity-class.rst | 12 ++++++++++-- utest/CMakeLists.txt | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fc595582..951644fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,7 +41,7 @@ add_subdirectory(utest) add_subdirectory(docs) # ---------------------------------------------------------------- -# provide find_package() support for reactor customers +# provide find_package() support for projects using this library set(SELF_LIB xo_unit) xo_add_headeronly_library(${SELF_LIB}) diff --git a/docs/quantity-class.rst b/docs/quantity-class.rst index 2945026e..9e1d2c01 100644 --- a/docs/quantity-class.rst +++ b/docs/quantity-class.rst @@ -32,6 +32,12 @@ The simplest way to create a quantity instance is to use either * factory functions in ``xo::unit::qty``, see :doc:`quantity-factoryfunctions` * unit variables in ``xo::unit::units``, see :doc:`quantity-unitvars` +Assignment +---------- + +.. doxygengroup:: quantity-assignment + :content-only: + Access Methods -------------- @@ -63,8 +69,10 @@ Support methods for arithmetic operations .. doxygengroup:: quantity-arithmeticsupport :content-only: -Assignment +Comparison ---------- -.. doxygengroup:: quantity-assignment +Support methods for comparison operators + +.. doxygengroup:: quantity-comparisonsupport :content-only: diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 97d1afa1..d88e7450 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -1,4 +1,4 @@ -# build unittest observable/utest +# xo-unit/utest/CMakeLists.txt set(SELF_EXECUTABLE_NAME utest.unit) set(SELF_SOURCE_FILES From 3cc6ba9a664af32839b7860d87ad13e1ab6317a1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 21:23:20 -0400 Subject: [PATCH 0778/2693] xo-ratio: initial commit --- CMakeLists.txt | 57 +++ LICENSE | 29 ++ README.md | 26 ++ cmake/xo-bootstrap-macros.cmake | 15 + cmake/xo_ratioConfig.cmake.in | 17 + example/CMakeLists.txt | 3 + example/ex1/CMakeLists.txt | 15 + example/ex1/ex1.cpp | 194 ++++++++++ include/xo/ratio/numeric_concept.hpp | 39 ++ include/xo/ratio/ratio.hpp | 547 +++++++++++++++++++++++++++ include/xo/ratio/ratio_concept.hpp | 29 ++ include/xo/ratio/ratio_iostream.hpp | 42 ++ 12 files changed, 1013 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README.md create mode 100644 cmake/xo-bootstrap-macros.cmake create mode 100644 cmake/xo_ratioConfig.cmake.in create mode 100644 example/CMakeLists.txt create mode 100644 example/ex1/CMakeLists.txt create mode 100644 example/ex1/ex1.cpp create mode 100644 include/xo/ratio/numeric_concept.hpp create mode 100644 include/xo/ratio/ratio.hpp create mode 100644 include/xo/ratio/ratio_concept.hpp create mode 100644 include/xo/ratio/ratio_iostream.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..077f1267 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,57 @@ +# xo-ratio/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_ratio VERSION 1.0) +enable_language(CXX) + +# common XO cmake macros (see proj/xo-cmake) +include(cmake/xo-bootstrap-macros.cmake) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() +# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) +add_code_coverage() + +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) + +# ---------------------------------------------------------------- +# c++ settings + +# one-time project-specific c++ flags. usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- + +add_subdirectory(example) +#add_subdirectory(utest) +#add_subdirectory(docs) + +# ---------------------------------------------------------------- +# provide find_package() support for projects using this library + +set(SELF_LIB xo_ratio) +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) + +# ---------------------------------------------------------------- +# dependencies + +#xo_headeronly_dependency(${SELF_LIB} randomgen) +# etc.. + +# end CMakeLists.txt diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..cae3cb5d --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2024 Roland Conybeare , All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Please also refer to the file .github/CONTRIBUTING.md, which clarifies licensing of +external contributions to this project including patches, pull requests, etc. diff --git a/README.md b/README.md new file mode 100644 index 00000000..722ea8bf --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# ratio library + +Header-only, constexpr library providing exact representation for rational numbers. + +Relative to `std::ratio`: +1. Uses `constexpr` instead of creating new types. + This means it can be used seamlessly at runtime. +2. Supports a few more arithmetic operations, + for example exponentiation to integer powers. +3. Provides concept support (with c++20) +4. Requires modern (c++17) support to achieve this + +## Getting Started + +### install dependencies + +- [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) cmake macros + +### build + install +``` +$ cd xo-ratio +$ PREFIX=/usr/local # for example +$ BUILDDIR=.build # for example +$ make ${BUILDDIR} +$ cmake -DCMAKE_PREFIX_PATH=${PREFIX} -B ${BUILDDIR} +``` diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..936a1810 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,15 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +if (NOT XO_SUBMODULE_BUILD) + message("-- GUESSED_CMAKE_CMD=cmake -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -B ${CMAKE_BINARY_DIR}") + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) diff --git a/cmake/xo_ratioConfig.cmake.in b/cmake/xo_ratioConfig.cmake.in new file mode 100644 index 00000000..e5ee1778 --- /dev/null +++ b/cmake/xo_ratioConfig.cmake.in @@ -0,0 +1,17 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# note: changes to find_dependency() calls here +# must coordinate with xo_dependency() calls +# in xo-reactor/src/reactor/CMakeLists.txt +# +#find_dependency(reflect) +#find_dependency(subsys) +#find_dependency(Eigen3) +#find_dependency(webutil) +#find_dependency(printjson) +#find_dependency(callback) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 00000000..36200e18 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,3 @@ +# xo-ratio/example/CMakeLists.txt + +add_subdirectory(ex1) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt new file mode 100644 index 00000000..b7050cfc --- /dev/null +++ b/example/ex1/CMakeLists.txt @@ -0,0 +1,15 @@ +# xo-ratio/example/ex1/CMakeLists.txt + +set(SELF_EXE xo_ratio_ex1) +set(SELF_SRCS ex1.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +# ---------------------------------------------------------------- +# dependencies.. + +xo_self_dependency(${SELF_EXE} xo_ratio) +xo_dependency(${SELF_EXE} reflect) + +# end CMakeLists.txt diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp new file mode 100644 index 00000000..8aa3c82d --- /dev/null +++ b/example/ex1/ex1.cpp @@ -0,0 +1,194 @@ +/** @file ex1.cpp **/ + +#include "xo/ratio/ratio_iostream.hpp" +#include + +int +main() { + using xo::ratio::make_ratio; + using xo::ratio::ratio; + using xo::ratio::ratio_concept; + using namespace std; + + constexpr auto r1 = make_ratio(2, 3); + cerr << "r1=make_ratio(2,3): " << r1 << endl; // output + + static_assert(r1.num() == 2); + static_assert(r1.den() == 3); + + static_assert(r1.compare(r1, r1) == 0); + static_assert(xo::ratio::detail::op_aux_type::compare(r1,r1) == 0); + + static_assert(r1 == r1); + static_assert(!(r1 != r1)); + static_assert(r1 != 0); + static_assert(r1 != 1); + static_assert(r1 != 2); + static_assert(r1 != 3); + + static_assert(r1 >= r1); + static_assert(r1 <= r1); + + static_assert(r1 > 0); + static_assert(r1 >= 0); + static_assert(r1 < 1); + static_assert(r1 <= 1); + + constexpr auto r2 = make_ratio(2, 4); + cerr << "r2=make_ratio(2,4): " << r2 << endl; // output + + static_assert(r2.num() == 1); + static_assert(r2.den() == 2); + + static_assert(r2 == r2); + static_assert(r2 != r1); + static_assert(!(r2 > r1)); + static_assert(!(r2 >= r1)); + static_assert(r2 <= r1); + static_assert(r2 < r1); + static_assert(r1 > r2); + static_assert(r1 >= r2); + static_assert(!(r1 < r2)); + static_assert(!(r1 <= r2)); + + constexpr auto r3 = make_ratio(2, 3) - make_ratio(1, 2); + cerr << "r3=r1-r2: " << r1 - r2 << endl; // output + + static_assert(r3.num() == 1); + static_assert(r3.den() == 6); + + static_assert(r3 == r3); + static_assert(r3 != 0); + static_assert(r3 != 1); + static_assert(r3 < r2); + static_assert(r3 <= r2); + static_assert(r3 < r1); + static_assert(r3 <= r1); + + constexpr auto r4 = r1 + r2; + cerr << "r4=r1+r2: " << r1 + r2 << endl; // output + + static_assert(r4.num() == 7); + static_assert(r4.den() == 6); + static_assert(r4 > 1); + static_assert(r4 < 2); + + constexpr auto r5 = r1 + 3; + cerr << "r5=r1+3: " << r5 << endl; // output + + static_assert(r5.num() == 11); + static_assert(r5.den() == 3); + + constexpr auto r6 = 3 + r1; + cerr << "r5=3+r1: " << r6 << endl; // output + + static_assert(r6.num() == 11); + static_assert(r6.den() == 3); + + static_assert(r5 == r6); + static_assert(r5 > 3); + static_assert(r5 < 4); + static_assert(r5 > r1); + + constexpr auto r7 = r6 - 3; + cerr << "r7=r6-3: " << r7 << endl; // output + + static_assert(r7 == r1); + static_assert(r7 >= r1); + static_assert(r7 <= r1); + + constexpr auto r8 = 3 - r6; + cerr << "r8=3-r6: " << r8 << endl; // output + + static_assert(r8 == r8); + static_assert(r8 > -1); + static_assert(r8 < 0); + static_assert(-1 < r8); + static_assert(-1 <= r8); + static_assert(0 >= r8); + + constexpr auto r9 = r8 * r8; + cerr << "r9=r8*r8: " << r9 << endl; // output + + static_assert(r9 == make_ratio(4, 9)); + + constexpr auto r10 = r9 * 9; + cerr << "r10=r9*9: " << r10 << endl; // output + + static_assert(r10 == 4); + static_assert(r10.to() == 4); + + constexpr auto r11 = r9 * 3; + cerr << "r11=r9*3: " << r11 << endl; // output + + static_assert(r11 == make_ratio(4, 3)); + static_assert(r11.to() == 1); + + constexpr auto r12 = 9 * r9; + cerr << "r12=9*r9: " << r12 << endl; + + static_assert(r12 == r10); + static_assert(r12 == make_ratio(4, 1)); + static_assert(r12.to() == 4); + + constexpr auto r13 = 3 * r9; + cerr << "r13=3*r9: " << r13 << endl; // output + + static_assert(r13 == make_ratio(4, 3)); + static_assert(r13 == make_ratio(-4, -3)); + static_assert(r13 == r11); + static_assert(r13.to() == 1); + + constexpr auto r14 = r9 / r9; + cerr << "r14=r9/r9: " << r14 << endl; // output + + static_assert(r14 == 1); + static_assert(r14.to() == 1); + + constexpr auto r15 = r9 / r8; + cerr << "r15=r9/r8: " << r15 << endl; // (4/9) / (-2/3) = (4/9) * (3/-2) = 12/-18 = -2/3 + + static_assert(r15 == make_ratio(-2, 3)); + static_assert(r15 == make_ratio(2, -3)); + + constexpr auto r16 = r9 / 2; + cerr << "r16=r9/2: " << r16 << endl; + + static_assert(r16 == ratio(2, 9)); + static_assert(!r16.is_integer()); + + constexpr auto r17 = 2 / r9; + cerr << "r17=2/r9: " << r17 << endl; + + static_assert(r17 == ratio(9, 2)); + static_assert(!r17.is_integer()); + + constexpr auto r18 = r12 / r8; + cerr << "r18=r12/r8: " << r12/r8 << endl; + + static_assert(r18.is_integer()); + + constexpr auto r19 = r18.power(2); + cerr << "r19=r18^2: " << r19 << endl; + + static_assert(r19.is_integer()); + static_assert(r19 == ratio(36, 1)); + + constexpr auto r20 = r17.power(-3); + cerr << "r20=r17^-3: " << r20 << endl; + + static_assert(!r20.is_integer()); + static_assert(r20 == ratio(8, 729)); + + /* verify constexpr working */ + static_assert(ratio(2,3).num() == 2); + static_assert(ratio(2,3).den() == 3); + static_assert(make_ratio(-1,2).num() == -1); + static_assert(make_ratio(-1,2).den() == 2); + static_assert(make_ratio(-2,4).num() == -1); + static_assert(make_ratio(-2,4).den() == 2); + static_assert(make_ratio(1,-2).num() == -1); + static_assert(make_ratio(1,-2).den() == 2); + static_assert(make_ratio(-4,-6).num() == 2); + static_assert(make_ratio(-4,-6).den() == 3); +} diff --git a/include/xo/ratio/numeric_concept.hpp b/include/xo/ratio/numeric_concept.hpp new file mode 100644 index 00000000..02ad5894 --- /dev/null +++ b/include/xo/ratio/numeric_concept.hpp @@ -0,0 +1,39 @@ +/** @file numeric_concept.hpp **/ + +#pragma once + +#include + +namespace xo { + namespace ratio { + /** @concept numeric_concept + * @brief Concept for values that participate in arithmetic operations (+,-,*,/) and comparisons + * + * Intended to include at least: + * - built-in integral and floating-point types + * - big_int from ctbignum + * - boost::rational + * - std::complex + * - xo::unit::quantity + * + * Accepting complex numbers --> we don't require T to be totally ordered, + * and don't require (<,<=,>=,>) operators. + * + * Intend numeric_concept to apply to types T suitable for + * xo::ratio::ratio + **/ + template + concept numeric_concept = requires(T x, U y) + { + { -x }; + { x - y }; + { x + y }; + { x * y }; + { x / y }; + { x == y }; + { x != y }; + }; + } /*namespace ratio*/ +} /*namespace xo*/ + +/* end numeric_concept.hpp */ diff --git a/include/xo/ratio/ratio.hpp b/include/xo/ratio/ratio.hpp new file mode 100644 index 00000000..60aad611 --- /dev/null +++ b/include/xo/ratio/ratio.hpp @@ -0,0 +1,547 @@ +/** @file ratio.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "ratio_concept.hpp" +#include +#include +//#include + +namespace xo { + namespace ratio { + namespace detail { + /** @brief converts ratio to lowest terms when feasible + * + * Falls back to identity function for non-totally-ordered Ratio::component_type + */ + template > + struct reducer_type; + + /** @brief promote value to ratio type **/ + template > + struct promoter_type; + } + + /** @brief represent a ratio of two Int values. **/ + template + requires std::totally_ordered + struct ratio + { + public: + using component_type = Int; + + public: + constexpr ratio(Int n, Int d) : num_{n}, den_{d} {} + + /** @brief ratio in lowest commono terms + * + **/ + static constexpr ratio reduce(Int n, Int d) { + return ratio(n, d).reduce(); + } + + /** @brief add two ratios **/ + static constexpr ratio add(const ratio & x, + const ratio & y) { + /* (a/b) + (c/d) + * = a.d / (b.d) + b.c / (b.d) + * = (a.d + b.c) / (b.d) + */ + + auto a = x.num(); + auto b = x.den(); + auto c = y.num(); + auto d = y.den(); + + auto num = a*d + b*c; + auto den = b*d; + + return ratio(num, den).maybe_reduce(); + } + + /** @brief subtract two ratios **/ + static constexpr ratio subtract(const ratio & x, + const ratio & y) { + return add(x, y.negate()); + } + + /** @brief multiply two ratios **/ + static constexpr ratio multiply(const ratio & x, + const ratio & y) { + /* (a/b) * (c/d) = a.c / b.d */ + + /* if x,y normalized, + * opportunity to cancel common factor between (a, d) or (c, b) + * + * want to do this before multiplying to avoid overflow involving intermediate terms + */ + + auto a1 = x.num(); + auto b1 = x.den(); + auto c1 = y.num(); + auto d1 = y.den(); + + auto ad_gcf = std::gcd(a1, d1); + auto bc_gcf = std::gcd(b1, c1); + + auto a = a1 / ad_gcf; + auto b = b1 / bc_gcf; + auto c = c1 / bc_gcf; + auto d = d1 / ad_gcf; + + auto num = a*c; + auto den = b*d; + + return ratio(num, den).maybe_reduce(); + } + + /** @brief divide two ratios **/ + static constexpr ratio divide(const ratio & x, + const ratio & y) { + return multiply(x, y.reciprocal()); + } + + /** @brief compute integer power of a ratio **/ + constexpr ratio power(int p) const { + + constexpr ratio retval = ratio(1, 1); + + if (p == 0) + return ratio(1, 1); + + if (p < 0) + return this->reciprocal().power(-p); + + /* inv: x^p = aj.xj^pj */ + ratio aj = ratio(1, 1); + ratio xj = *this; + int pj = p; + + while (pj > 0) { + if (pj % 2 == 0) { + /* a.x^(2q) = a.(x^2)^q */ + xj = xj * xj; + pj = pj / 2; + } else { + /* a.x^(2q+1) = (a.x).x^(2q) */ + aj = aj * xj; + pj = (pj - 1); + } + } + + /* pj = 0, so: x^p = aj.xj^pj = aj.xj^0 = aj */ + return aj; + } + + /** @brief 3-way compare two ratios **/ + static constexpr auto compare(ratio x, ratio y) { + /* ensure minus signs in numerators only */ + if (x.den() < 0) + return compare_aux(ratio(-x.num(), -x.den()), y); + if (y.den() < 0) + return compare_aux(x, ratio(-y.num(), -y.den())); + + return compare_aux(x, y); + } + + constexpr Int num() const { return num_; } + constexpr Int den() const { return den_; } + + constexpr bool is_integer() const { return den_ == 1 || den_ == -1; } + + constexpr ratio negate() const { return ratio(-num_, den_); } + constexpr ratio reciprocal() const { return ratio(den_, num_); } + + /** @brief requires component_type is totally ordered **/ + constexpr Int floor() const { return (num_ / den_); } + + /** @brief requires component_type is totally ordered **/ + constexpr Int ceil() const { return floor() + 1; } + + /** @brief reduce to lowest terms + * + * @pre @c Int type must be totally ordered + **/ + constexpr ratio reduce() const requires std::totally_ordered { + if (den_ < 0) + return ratio(-num_, -den_).reduce(); + + auto factor = std::gcd(num_, den_); + + return ratio(num_ / factor, + den_ / factor); + } + + /** @brief reduce to lowest terms, if Int representation admits + * + * Otherwise fallback to identity function + **/ + constexpr ratio maybe_reduce() const { + return detail::reducer_type::attempt_reduce(*this); + } + + /** @brief return fractional part of this ratio + * + * @pre @c Int type must be totally ordered + **/ + constexpr ratio frac() const requires std::totally_ordered { + return ratio::subtract(*this, this->floor()); + } + + /** @brief convert to non-ratio representation + * + * For example: to int or double + **/ + template + constexpr Repr to() const { return num_ / static_cast(den_); } + + /** @brief convert to representation using different integer types **/ + template + constexpr operator Ratio2 () const requires ratio_concept { + return Ratio2(num_, den_); + } + + private: + /** @brief 3-way compare auxiliary function. + * + * @pre @p x, @p y have non-negative denominator + **/ + static constexpr auto compare_aux(ratio x, ratio y) { + /* control here: b>=0, d>=0 */ + + /* (a/b) <=> (c/d) + * (a.d/b) <=> c no sign change, since d >= 0 + * (a.d) <=> (b.c) no sign change, since b >= 0 + */ + auto a = x.num(); + auto b = x.den(); + auto c = y.num(); + auto d = y.den(); + + auto lhs = a*d; + auto rhs = b*c; + + return lhs <=> rhs; + } + + private: + /** @brief numerator **/ + Int num_; + /** @brief denominator **/ + Int den_; + }; + + namespace detail { + template + struct reducer_type {}; + + template + struct reducer_type { + static constexpr Ratio attempt_reduce(Ratio x) { return x.reduce(); } + }; + + template + struct reducer_type { + static constexpr Ratio attempt_reduce(Ratio x) { return x; } + }; + } + + namespace detail { + template + struct promoter_type; + + template + struct promoter_type { + /* to 'promote' a ratio, rely on its conversion operator */ + static constexpr Ratio promote(FromType x) { return x; } + }; + + template + struct promoter_type { + /* to 'promote' a non-ratio, use denominator=1 */ + static constexpr Ratio promote(FromType x) { return Ratio(x, 1); } + }; + } + + template + constexpr auto + make_ratio (Int1 n, Int2 d = 1) -> ratio> + { + return ratio>(n, d).maybe_reduce(); + } + + namespace detail { + /** @brief auxiliary function for binary ratio operations + * + * Support binary ratio operations on combinations: + * - (ratio, ratio) + * - (ratio, U) // where U is not a ratio + * - (T, ratio(U)) // where T is not a ratio + * + * Goals: + * + * 1. Support expressions like + * + * @code + * auto x = 1 + make_ratio(2,3); + * @endcode + * + * 2. promote to wider types as needed + * + * @code + * auto x = make_ratio(2,3) + make_ratio(1ul,2ul); + * static_assert(std::same_as); + * @endcode + * + * 3. avoid interfering with other templates that may overload operator+ + * + * @pre at least one of (Left,Right) must be known to be a ratio + **/ + template , + bool RightIsRatio = ratio_concept> + struct op_aux_type; + + /** @brief specialization for two ratio types **/ + template + requires (ratio_concept && ratio_concept) + struct op_aux_type { + using component_type = std::common_type_t; + + using ratio_type = ratio; + + static constexpr ratio_type add (const LeftRatio & x, + const RightRatio & y) + { + return ratio_type::add(x, y); + } + + static constexpr ratio_type subtract (const LeftRatio & x, + const RightRatio & y) + { + return ratio_type::subtract(x, y); + } + + static constexpr ratio_type multiply (const LeftRatio & x, + const RightRatio & y) + { + return ratio_type::multiply(x, y); + } + + static constexpr ratio_type divide (const LeftRatio & x, + const RightRatio & y) + { + return ratio_type::divide(x, y); + } + + static constexpr auto compare (const LeftRatio & x, + const RightRatio & y) + { + return ratio_type::compare(x, y); + } + }; + + /** @brief specialization for left-hand ratio and right-hand integer value **/ + template + requires (ratio_concept && !ratio_concept) + struct op_aux_type { + using component_type = std::common_type_t; + + using ratio_type = ratio; + + static constexpr ratio_type add (const LeftRatio & x, + const Right & y) + { + /* reminder: adding an integer can't introduce reduced terms */ + return ratio_type(x.num() + x.den() * y, x.den()); + } + + static constexpr ratio_type subtract (const LeftRatio & x, + const Right & y) + { + /* reminder: subtracting an integer can't introduce reduced terms */ + return ratio_type(x.num() - x.den() * y, x.den()); + } + + static constexpr ratio_type multiply (const LeftRatio & x, + const Right & yp) + { + auto gcf = std::gcd(x.den(), yp); + + auto a = x.num(); + auto b = x.den() / gcf; + auto y = yp / gcf; + + return ratio_type(a*y, b); + } + + static constexpr ratio_type divide (const LeftRatio & x, + const Right & yp) + { + auto gcf = std::gcd(x.num(), yp); + + auto a = x.num() / gcf; + auto b = x.den(); + auto y = yp / gcf; + + return ratio_type(a*y, b); + } + + static constexpr auto compare (const LeftRatio & x, + const Right & y) + { + /* note: in c++26 std::signof is constexpr, usable here */ + if (x.den() >= 0) + return compare_aux(x, y); + else + return compare_aux(LeftRatio(-x.num(), -x.den()), y); + } + + private: + static constexpr auto compare_aux (const LeftRatio & x, const Right & y) { + return (x.num() <=> x.den() * y); + } + }; + + /** @brief specialization for left-hand integer value and right-hand ratio **/ + template + requires (!ratio_concept && ratio_concept) + struct op_aux_type { + using component_type = std::common_type_t; + + using ratio_type = ratio; + + static constexpr ratio_type add(const Left & x, + const RightRatio & y) + { + /* reminder: adding an integer can't introduce reduced terms */ + return ratio_type(x * y.den() + y.num(), y.den()); + } + + static constexpr ratio_type subtract(const Left & x, + const RightRatio & y) + { + /* reminder: subtracting an integer can't introduce reduced terms */ + return ratio_type(x * y.den() - y.num(), y.den()); + } + + static constexpr ratio_type multiply (const Left & xp, + const RightRatio & y) + { + auto gcf = std::gcd(xp, y.den()); + + auto x = xp / gcf; + auto c = y.num(); + auto d = y.den() / gcf; + + return ratio_type(x*c, d); + } + + static constexpr ratio_type divide (const Left & x, + const RightRatio & y) + { + return multiply(x, y.reciprocal()); + } + + static constexpr auto compare(const Left & x, + const RightRatio & y) + { + if (y.den() >= 0) + return compare_aux(x, y); + else + return compare_aux(x, RightRatio(-y.num(), -y.den())); + } + + private: + static constexpr auto compare_aux (const Left & x, + const RightRatio & y) + { + return (x * y.den() <=> y.num()); + }; + }; + } /*namespace detail*/ + + /** @brief add two ratios. + * + * One argument may be a non-ratio type if it can be promoted to a ratio + **/ + template + inline constexpr auto + operator+ (const Ratio1 & x, const Ratio2 & y) + requires (ratio_concept || ratio_concept) + { + return detail::op_aux_type::add(x, y); + } + + /** @brief subtract two ratios. + * + * One argument may be a non-ratio type if it can be promoted to a ratio + **/ + template + inline constexpr auto + operator- (const Ratio1 & x, const Ratio2 & y) + requires (ratio_concept || ratio_concept) + { + return detail::op_aux_type::subtract(x, y); + } + + /** @brief multiply two ratios + * + * One argument may be a non-ratio type if it can be promoted to a ratio + **/ + template + inline constexpr auto + operator* (const Ratio1 & x, const Ratio2 & y) + requires (ratio_concept || ratio_concept) + { + return detail::op_aux_type::multiply(x, y); + } + + /** @brief divide two ratios + * + * One argument may be a non-ratio type if it can be promoted to a ratio + **/ + template + inline constexpr auto + operator/ (const Ratio1 & x, const Ratio2 & y) + requires (ratio_concept || ratio_concept) + { + return detail::op_aux_type::divide(x, y); + } + + /** @brief compare two ratios for equality + * + * One argument may be a non-ratio type if it can be promoted to a ratio + **/ + template + inline constexpr bool + operator== (const Ratio1 & x, const Ratio2 & y) + requires (ratio_concept || ratio_concept) + { + return (detail::op_aux_type::compare(x, y) == 0); + } + + /** @brief compare two ratios + * + * One argument may be a non-ratio type if it can be promoted to a ratio + **/ + template + inline constexpr auto + operator<=> (const Ratio1 & x, const Ratio2 & y) + requires (ratio_concept || ratio_concept) + { + return detail::op_aux_type::compare(x, y); + } + + } /*namespace ratio*/ +} /*namespace xo*/ + +/** end ratio.hpp **/ diff --git a/include/xo/ratio/ratio_concept.hpp b/include/xo/ratio/ratio_concept.hpp new file mode 100644 index 00000000..8ca1d438 --- /dev/null +++ b/include/xo/ratio/ratio_concept.hpp @@ -0,0 +1,29 @@ +/** @file ratio_concept.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "numeric_concept.hpp" + +namespace xo { + namespace ratio { + /* also expect: + * Ratio::num_type / Ratio::den_type rounds towards -inf + */ + template + concept ratio_concept = requires(Ratio ratio) + { + typename Ratio::component_type; + typename Ratio::component_type; + + { ratio.num() } -> std::same_as; + { ratio.den() } -> std::same_as; + } && numeric_concept; + + } /*namespace ratio*/ +} /*namespace xo*/ + + +/** end ratio_concept.hpp **/ diff --git a/include/xo/ratio/ratio_iostream.hpp b/include/xo/ratio/ratio_iostream.hpp new file mode 100644 index 00000000..fc898b36 --- /dev/null +++ b/include/xo/ratio/ratio_iostream.hpp @@ -0,0 +1,42 @@ +/** @file ratio_iostream.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "ratio.hpp" +#include + +namespace xo { + namespace ratio { + /** @brief print ratio x on stream os. + * + * Example: + * @code + * print_ratio(std::cerr, make_ratio(1,2); // outputs "" + * @endcode + **/ + template + void + print_ratio (std::ostream & os, const Ratio & x) { + os << ""; + } + + /** @brief print ratio x on stream os. + * + * Example: + * @code + * std::cout << make_ratio(2,3); // outputs "" + * @endcode + **/ + template + inline std::ostream & + operator<< (std::ostream & os, const Ratio & x) { + print_ratio(os, x); + return os; + } + } +} + +/** end ratio_iostream.hpp **/ From 978af419b30bf495863aea6563847c9f6f6b4031 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 21:23:47 -0400 Subject: [PATCH 0779/2693] xo-ratio: + .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..13c0afb7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json From 0a03aa949bef07554be809abfdae855e62ffde41 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 13:27:06 -0400 Subject: [PATCH 0780/2693] xo-flatstring: undercap flastring_concat instead of char arrays --- example/ex1/ex1.cpp | 6 +++--- include/xo/flatstring/flatstring.hpp | 13 ++++++++++++- utest/flatstring.test.cpp | 19 +++++++++++++++++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index f5843db0..7f46f432 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -136,12 +136,12 @@ main() { static_assert(sizeof(s14) == 7); constexpr flatstring s15 = flatstring_concat(flatstring("hello"), - ", ", - flatstring("world")); + flatstring(", "), + flatstring("world")); static_assert(s15.fixed_capacity == 13); static_assert(sizeof(s15) == 13); - constexpr auto s16 = xo::flatstring_concat("foo", "bar"); + constexpr auto s16 = xo::flatstring_concat(flatstring("foo"), flatstring("bar")); static_assert(s16.fixed_capacity == 7); diff --git a/include/xo/flatstring/flatstring.hpp b/include/xo/flatstring/flatstring.hpp index e7233499..49db3465 100644 --- a/include/xo/flatstring/flatstring.hpp +++ b/include/xo/flatstring/flatstring.hpp @@ -452,7 +452,18 @@ namespace xo { std::size_t pos = 0; auto detail_concat = [ &pos, &result ](auto && arg) { - constexpr auto count = (sizeof(arg) - sizeof(value_type)) / sizeof(value_type); + /* tradeoff here: + * 1. flatstring::size() is constexpr, so we can concat strings with size() < capacity(). + * (note flatstring::from_int() likely creates such strings) + * 2. ..but no size() method on char arrays. + * 3. std::size() not suitable: size of char array includes null terminator, + * while flatstring.size() excludes it, and flatstring behavior is consistent with + * std::string.size() + * Consequence of using arg.size() here; have to wrap char arrays with + * flatstring() to use them with flatstring_concat() + */ + auto count = arg.size(); + //constexpr auto count = (sizeof(arg) - sizeof(value_type)) / sizeof(value_type); std::copy_n(/*arg.c_str()*/ static_cast(arg), count, result.value_ + pos); pos += count; diff --git a/utest/flatstring.test.cpp b/utest/flatstring.test.cpp index 197ccfd9..8a3761a1 100644 --- a/utest/flatstring.test.cpp +++ b/utest/flatstring.test.cpp @@ -231,6 +231,25 @@ namespace xo { REQUIRE(::strcmp(concat.c_str(), req_str.c_str()) == 0); } + +#ifdef NOT_USING + { + auto concat4 = flatstring_concat(str, + flatstring(text2), + str, + flatstring(text2)); + auto req_str = string(text) + string(text2) + string(text) + string(text2); + + REQUIRE(::strcmp(concat4.c_str(), req_str.c_str()) == 0); + } +#endif + + { + auto concat4 = flatstring_concat(str, str2, str, str2); + auto req_str = string(text) + string(text2) + string(text) + string(text2); + + REQUIRE(::strcmp(concat4.c_str(), req_str.c_str()) == 0); + } } template From ffeb8739079aebd1e3ada7f0156a288eb6f90ce2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 13:28:33 -0400 Subject: [PATCH 0781/2693] xo-flatstring: + from_int() named ctor --- include/xo/flatstring/flatstring.hpp | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/include/xo/flatstring/flatstring.hpp b/include/xo/flatstring/flatstring.hpp index 49db3465..41bfffa3 100644 --- a/include/xo/flatstring/flatstring.hpp +++ b/include/xo/flatstring/flatstring.hpp @@ -168,6 +168,38 @@ namespace xo { } ///@} + /** @brief construct from integer **/ + static constexpr flatstring from_int(int x) { + constexpr size_t buf_z = 20; + + bool negative_flag = (x < 0); + std::size_t i = buf_z; + char buf[buf_z]; + std::fill_n(buf, N, '\0'); + + if (negative_flag) + x = -x; + + buf[--i] = '\0'; + + if (x == 0) + buf[--i] = '0'; + + while ((i > 0) && (x != 0)) { + buf[--i] = ('0' + x % 10); + x = x / 10; + } + + if ((i > 0) && negative_flag) + buf[--i] = '-'; + + char retv[N]; + std::fill_n(retv, N, '\0'); + std::copy_n(buf + i, buf_z - i, retv); + + return retv; + } + /** @defgroup flatstring-properties property-methods **/ ///@{ /** @brief true if (and only if) string is empty **/ From 8a020258c437cb3ca6d7520b51ea99da3de18167 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 13:28:51 -0400 Subject: [PATCH 0782/2693] xo-flatstring: utest: skip ccov target in submodule build --- utest/CMakeLists.txt | 59 ++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 091e56ef..1779b465 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -14,36 +14,41 @@ add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) # ---------------------------------------------------------------- # in coverage build, target to build+install coverage report -set(CCOV_OUTPUT_DIR ${PROJECT_BINARY_DIR}/ccov/html) -set(CCOV_INDEX_FILE ${CCOV_OUTPUT_DIR}/index.html) -set(CCOV_REPORT_EXE ${PROJECT_BINARY_DIR}/gen-ccov) -# CMAKE_INSTALL_DOCDIR -# =default=> DATAROOTDIR/doc/PROJECT_NAME -# =default=> CMAKE_INSTALL_PREFIX/share/doc/xo_flatstring -set(CCOV_INSTALL_DOCDIR ${CMAKE_INSTALL_DOCDIR}/ccov) +if (XO_SUBMODULE_BUILD) + # in submodule build, generate aggregate coverage report + # for all xo libraries +else() + set(CCOV_OUTPUT_DIR ${PROJECT_BINARY_DIR}/ccov/html) + set(CCOV_INDEX_FILE ${CCOV_OUTPUT_DIR}/index.html) + set(CCOV_REPORT_EXE ${PROJECT_BINARY_DIR}/gen-ccov) + # CMAKE_INSTALL_DOCDIR + # =default=> DATAROOTDIR/doc/PROJECT_NAME + # =default=> CMAKE_INSTALL_PREFIX/share/doc/xo_flatstring + set(CCOV_INSTALL_DOCDIR ${CMAKE_INSTALL_DOCDIR}/ccov) -# 'test' target should always be out-of-date -# -# DEPENDS: reminder - can't put 'test' here, requires 'all' target -# -add_custom_command( - OUTPUT ${CCOV_INDEX_FILE} - DEPENDS ${SELF_EXE} - COMMAND ${CCOV_REPORT_EXE} - WORKING_DIRECTORY ${PROJECT_BINARY_DIR} - COMMENT "Generating coverage report -> [${CCOV_OUTPUT_DIR}]") + # 'test' target should always be out-of-date + # + # DEPENDS: reminder - can't put 'test' here, requires 'all' target + # + add_custom_command( + OUTPUT ${CCOV_INDEX_FILE} + DEPENDS ${SELF_EXE} + COMMAND ${CCOV_REPORT_EXE} + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + COMMENT "Generating coverage report -> [${CCOV_OUTPUT_DIR}]") -add_custom_target( - ccov - DEPENDS ${CCOV_INDEX_FILE} ${SELF_EXE}) + add_custom_target( + ccov + DEPENDS ${CCOV_INDEX_FILE} ${SELF_EXE}) -# OPTIONAL: quietly skip this step if ccov report not generated -install( - DIRECTORY ${CCOV_OUTPUT_DIR} - FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ - DESTINATION ${CCOV_INSTALL_DOCDIR} - COMPONENT Documentation - OPTIONAL) + # OPTIONAL: quietly skip this step if ccov report not generated + install( + DIRECTORY ${CCOV_OUTPUT_DIR} + FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CCOV_INSTALL_DOCDIR} + COMPONENT Documentation + OPTIONAL) +endif() # ---------------------------------------------------------------- # deps: logutils, ... From 5cd6f2c71a8f9fdf47acfceca6c4ef570b4910df Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 13:29:19 -0400 Subject: [PATCH 0783/2693] xo-flatstring: minor comment-only --- utest/flatstring_utest_main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utest/flatstring_utest_main.cpp b/utest/flatstring_utest_main.cpp index e7a2c140..e2721d06 100644 --- a/utest/flatstring_utest_main.cpp +++ b/utest/flatstring_utest_main.cpp @@ -1,6 +1,6 @@ -/* @file flatstring_utest_main.cpp */ +/** @file flatstring_utest_main.cpp **/ #define CATCH_CONFIG_MAIN #include "catch2/catch.hpp" -/* end flatstring_utest_main.cpp */ +/** end flatstring_utest_main.cpp **/ From 87c64c59e01ce89eea90256d9119fea36acab0ad Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 13:29:43 -0400 Subject: [PATCH 0784/2693] xo-flatstring: drop editor tmp from repo --- utest/.#flatstring_utest_main.cpp | 1 - 1 file changed, 1 deletion(-) delete mode 120000 utest/.#flatstring_utest_main.cpp diff --git a/utest/.#flatstring_utest_main.cpp b/utest/.#flatstring_utest_main.cpp deleted file mode 120000 index a0c5445f..00000000 --- a/utest/.#flatstring_utest_main.cpp +++ /dev/null @@ -1 +0,0 @@ -roland@roly-desktop-23.717424:1712774400 \ No newline at end of file From 787e19ae409a0bec2ee6e3bd29ac5366b3dfb28a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:05:45 -0400 Subject: [PATCH 0785/2693] xo-ratio: mark several methods noexcept --- include/xo/ratio/ratio.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/xo/ratio/ratio.hpp b/include/xo/ratio/ratio.hpp index 60aad611..a7848518 100644 --- a/include/xo/ratio/ratio.hpp +++ b/include/xo/ratio/ratio.hpp @@ -147,10 +147,10 @@ namespace xo { return compare_aux(x, y); } - constexpr Int num() const { return num_; } - constexpr Int den() const { return den_; } + constexpr Int num() const noexcept { return num_; } + constexpr Int den() const noexcept { return den_; } - constexpr bool is_integer() const { return den_ == 1 || den_ == -1; } + constexpr bool is_integer() const noexcept { return den_ == 1 || den_ == -1; } constexpr ratio negate() const { return ratio(-num_, den_); } constexpr ratio reciprocal() const { return ratio(den_, num_); } @@ -200,7 +200,7 @@ namespace xo { /** @brief convert to representation using different integer types **/ template - constexpr operator Ratio2 () const requires ratio_concept { + constexpr operator Ratio2 () const noexcept requires ratio_concept { return Ratio2(num_, den_); } @@ -209,7 +209,7 @@ namespace xo { * * @pre @p x, @p y have non-negative denominator **/ - static constexpr auto compare_aux(ratio x, ratio y) { + static constexpr auto compare_aux(ratio x, ratio y) noexcept { /* control here: b>=0, d>=0 */ /* (a/b) <=> (c/d) From c0b2e832116ecc09df6fb5ffe4fe14c5b6086128 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:06:29 -0400 Subject: [PATCH 0786/2693] xo-ratio: + .to_str() method using xo-flatstring --- CMakeLists.txt | 1 + cmake/xo_ratioConfig.cmake.in | 2 +- include/xo/ratio/ratio.hpp | 38 ++++++++++++++++++++++++++++++++++- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 077f1267..ca9e8c95 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,7 @@ xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets # ---------------------------------------------------------------- # dependencies +xo_headeronly_dependency(${SELF_LIB} xo_flatstring) #xo_headeronly_dependency(${SELF_LIB} randomgen) # etc.. diff --git a/cmake/xo_ratioConfig.cmake.in b/cmake/xo_ratioConfig.cmake.in index e5ee1778..b7a5a0a2 100644 --- a/cmake/xo_ratioConfig.cmake.in +++ b/cmake/xo_ratioConfig.cmake.in @@ -6,7 +6,7 @@ include(CMakeFindDependencyMacro) # must coordinate with xo_dependency() calls # in xo-reactor/src/reactor/CMakeLists.txt # -#find_dependency(reflect) +find_dependency(xo_flatstring) #find_dependency(subsys) #find_dependency(Eigen3) #find_dependency(webutil) diff --git a/include/xo/ratio/ratio.hpp b/include/xo/ratio/ratio.hpp index a7848518..a8f09e58 100644 --- a/include/xo/ratio/ratio.hpp +++ b/include/xo/ratio/ratio.hpp @@ -6,6 +6,7 @@ #pragma once #include "ratio_concept.hpp" +#include "xo/flatstring/flatstring.hpp" #include #include //#include @@ -196,7 +197,42 @@ namespace xo { * For example: to int or double **/ template - constexpr Repr to() const { return num_ / static_cast(den_); } + constexpr Repr to() const noexcept { return num_ / static_cast(den_); } + + /** @brief convert to short human-friendly flatstring representation + * + * Example: + * @code + * ratio(7,1).to_str<5>(); // "7" + * ratio(1,7).to_str<5>(); // "(1/7)" + * ratio(-1,7).to_str<10>(); // "(-1/7)" + * ratio(-1,7).to_str<5>(); // "(-1/" + * @endcode + **/ + template + constexpr flatstring to_str() const noexcept { + if (this->is_integer()) { + return flatstring::from_int(num_); + } else { + auto num_str = flatstring::from_int(num_); + auto den_str = flatstring::from_int(den_); + + /* tmp capacity will be about 2N+3 */ + auto tmp = flatstring_concat(flatstring("("), + num_str, + flatstring("/"), + den_str, + flatstring(")")); + + flatstring retval; + retval.assign(tmp); + + return retval; + } + } + + /** @brief negate operator **/ + constexpr ratio operator-() const { return ratio(-num_, den_); } /** @brief convert to representation using different integer types **/ template From d907f4eaff9d8cd1095d171b13ac34fde58fcaba Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:07:01 -0400 Subject: [PATCH 0787/2693] xo-ratio: + unit test --- utest/CMakeLists.txt | 57 +++++++++ utest/ratio.test.cpp | 235 +++++++++++++++++++++++++++++++++++++ utest/ratio_utest_main.cpp | 6 + 3 files changed, 298 insertions(+) create mode 100644 utest/CMakeLists.txt create mode 100644 utest/ratio.test.cpp create mode 100644 utest/ratio_utest_main.cpp diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..2da04264 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,57 @@ +# xo-ratio/utest/CMakeLists.txt + +set(SELF_EXE utest.ratio) +set(SELF_SRCS + ratio_utest_main.cpp + ratio.test.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) +add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) + +# ---------------------------------------------------------------- +# in coverage build, target to build+install coverage report + +if (XO_SUBMODULE_BUILD) + # in submodule build, generate aggregate coverage report + # for all xo libraries +else() + set(CCOV_OUTPUT_DIR ${PROJECT_BINARY_DIR}/ccov/html) + set(CCOV_INDEX_FILE ${CCOV_OUTPUT_DIR}/index.html) + set(CCOV_REPORT_EXE ${PROJECT_BINARY_DIR}/gen-ccov) + # CMAKE_INSTALL_DOCDIR + # =default=> DATAROOTDIR/doc/PROJECT_NAME + # =default=> CMAKE_INSTALL_PREFIX/share/doc/xo_flatstring + set(CCOV_INSTALL_DOCDIR ${CMAKE_INSTALL_DOCDIR}/ccov) + + # 'test' target should always be out-of-date + # + # DEPENDS: reminder - can't put 'test' here, requires 'all' target + # + add_custom_command( + OUTPUT ${CCOV_INDEX_FILE} + DEPENDS ${SELF_EXE} + COMMAND ${CCOV_REPORT_EXE} + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + COMMENT "Generating coverage report -> [${CCOV_OUTPUT_DIR}]") + + add_custom_target( + ccov + DEPENDS ${CCOV_INDEX_FILE} ${SELF_EXE}) + + # OPTIONAL: quietly skip this step if ccov report not generated + install( + DIRECTORY ${CCOV_OUTPUT_DIR} + FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CCOV_INSTALL_DOCDIR} + COMPONENT Documentation + OPTIONAL) +endif() + +# ---------------------------------------------------------------- +# dependencies.. + +xo_self_headeronly_dependency(${SELF_EXE} xo_ratio) +xo_dependency(${SELF_EXE} randomgen) +xo_dependency(${SELF_EXE} indentlog) +xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) diff --git a/utest/ratio.test.cpp b/utest/ratio.test.cpp new file mode 100644 index 00000000..918f9b50 --- /dev/null +++ b/utest/ratio.test.cpp @@ -0,0 +1,235 @@ +/** @file ratio.utest.cpp **/ + +#include "xo/ratio/ratio.hpp" +#include "xo/ratio/ratio_iostream.hpp" +#include "xo/randomgen/random_seed.hpp" +#include "xo/randomgen/xoshiro256.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/vector.hpp" +#include "xo/indentlog/print/array.hpp" +#include "xo/indentlog/print/tag.hpp" +//#include "xo/indentlog/print/hex.hpp" +#include +#include +#include + +namespace xo { + + using std::exponential_distribution; + using std::bernoulli_distribution; + + namespace ut { + template + struct ratio_distribution { + ratio_distribution(double sign_prob, double int_lambda) + : sign_dist_{sign_prob}, int_dist_{int_lambda} {} + + template + xo::ratio::ratio + random_ratio(Rng & rng) { + Int num_sign = sign_dist_(rng) ? -1 : +1; + Int num = num_sign * (1 + int_dist_(rng)); + Int den_sign = sign_dist_(rng) ? -1 : +1; + Int den = den_sign * (1 + int_dist_(rng)); + + return xo::ratio::ratio(num, den).reduce(); + } + + template + xo::ratio::ratio operator()(Rng & rng) { + return random_ratio(rng); + } + + /* generate negative numbers some of the time */ + bernoulli_distribution sign_dist_; + /* create ratios involving integers, but don't need integers to be too large */ + exponential_distribution int_dist_; + }; + + template + void + ratio_tests(Rng & rng) + { + constexpr bool debug_flag = true; + + std::size_t n_ratio = 25; + std::size_t n_experiment = n_ratio * n_ratio / 4; + /* want to avoid integer overflow when exponentiating */ + constexpr int max_pwr = 5; + + scope log(XO_DEBUG2(debug_flag, "ratio_tests")); + log && log(xtag("n_ratio", n_ratio)); + + ratio_distribution ratio_dist(0.25 /*sign_prob*/, + 0.05 /*lambda */); + bernoulli_distribution sign_dist(0.5); + exponential_distribution power_dist(0.2 /*lambda*/); + + std::vector> ratio_v; + + /* ensure 0, 1, -1 all present */ + ratio_v.push_back(xo::ratio::ratio(0,1)); + ratio_v.push_back(xo::ratio::ratio(1,1)); + ratio_v.push_back(xo::ratio::ratio(-1,1)); + + for (std::uint32_t i=0, n=n_ratio - ratio_v.size(); i(ratio1.den()); + double ratio2_approx = ratio2.num() / static_cast(ratio2.den()); + + { + auto sum = ratio1 + ratio2; + + double sum_approx = sum.num() / static_cast(sum.den()); + + log && log(XTAG(ratio1), XTAG(ratio2), XTAG(sum)); + + REQUIRE(sum_approx == Approx(ratio1_approx + ratio2_approx).epsilon(1e-6)); + REQUIRE(std::gcd(sum.num(), sum.den()) == 1); + REQUIRE(sum.den() > 0); + + /* comparison tests. piggyback on sum */ + { + auto cmp_approx = (sum_approx <=> ratio1_approx); + REQUIRE(cmp_approx == (sum <=> ratio1)); + } + { + bool eq = (sum == ratio1); + bool eq_approx = (sum_approx == ratio1_approx); + REQUIRE(eq == eq_approx); + } + { + bool ne = (sum != ratio1); + bool ne_approx = (sum_approx != ratio1_approx); + REQUIRE(ne == ne_approx); + } + { + bool gt = (sum > ratio1); + bool gt_approx = (sum_approx > ratio1_approx); + REQUIRE(gt == gt_approx); + } + { + bool ge = (sum >= ratio1); + bool ge_approx = (sum_approx >= ratio1_approx); + REQUIRE(ge == ge_approx); + } + { + bool lt = (sum > ratio1); + bool lt_approx = (sum_approx > ratio1_approx); + REQUIRE(lt == lt_approx); + } + { + bool le = (sum >= ratio1); + bool le_approx = (sum_approx >= ratio1_approx); + REQUIRE(le == le_approx); + } + } + + { + auto neg = -ratio1; + + double neg_approx = neg.num() / static_cast(neg.den()); + + log && log(XTAG(ratio1), XTAG(neg)); + + REQUIRE(neg_approx == Approx(-ratio1_approx).epsilon(1e-06)); + REQUIRE(std::gcd(neg.num(), neg.den()) == 1); + REQUIRE(neg.den() > 0); + } + + { + auto diff = ratio1 - ratio2; + + double diff_approx = diff.num() / static_cast(diff.den()); + + log && log(XTAG(ratio1), XTAG(ratio2), XTAG(diff)); + + REQUIRE(diff_approx == Approx(ratio1_approx - ratio2_approx).epsilon(1e-6)); + REQUIRE(std::gcd(diff.num(), diff.den()) == 1); + REQUIRE(diff.den() > 0); + } + + { + auto prod = ratio1 * ratio2; + + double prod_approx = prod.num() / static_cast(prod.den()); + + log && log(XTAG(ratio1), XTAG(ratio2), XTAG(prod)); + + REQUIRE(prod_approx == Approx(ratio1_approx * ratio2_approx).epsilon(1e-6)); + REQUIRE(std::gcd(prod.num(), prod.den()) == 1); + REQUIRE(prod.den() > 0); + } + + { + auto div = ratio1 * ratio2; + + double div_approx = div.num() / static_cast(div.den()); + + log && log(XTAG(ratio1), XTAG(ratio2), XTAG(div)); + + REQUIRE(div_approx == Approx(ratio1_approx * ratio2_approx).epsilon(1e-6)); + REQUIRE(std::gcd(div.num(), div.den()) == 1); + REQUIRE(div.den() > 0); + } + + { + int exp = (sign_dist(rng) ? -1 : +1) * power_dist(rng); + + if (std::abs(exp) >= max_pwr) { + exp = (std::signbit(exp) ? -1 : +1) * max_pwr; + } + + auto pwr = ratio1.power(exp); + + double pwr_approx = pwr.num() / static_cast(pwr.den()); + + log && log(XTAG(ratio1), XTAG(exp), XTAG(pwr)); + + REQUIRE(pwr_approx == Approx(::pow(ratio1_approx, exp)).epsilon(1e-6)); + REQUIRE(std::gcd(pwr.num(), pwr.den()) == 1); + REQUIRE(pwr.den() >= 0); + } + + { + auto ratio1_str = ratio1.template to_str<20>(); + + log && log(XTAG(ratio1_str)); + } + } + + } + + TEST_CASE("ratio", "[ratio]") { + //constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + uint64_t seed = 5521646833469436535ul; + //rng::Seed seed; + + //std::cerr << "ratio: seed=" << seed << std::endl; + + auto rng = rng::xoshiro256ss(seed); + + ratio_tests(rng); + } /*TEST_CASE(ratio)*/ + + } /*namespace ut*/ + +} /*namespace xo*/ + + +/** end ratio.utest.cpp **/ diff --git a/utest/ratio_utest_main.cpp b/utest/ratio_utest_main.cpp new file mode 100644 index 00000000..1bf5bace --- /dev/null +++ b/utest/ratio_utest_main.cpp @@ -0,0 +1,6 @@ +/** @file ratio_utest_main.cpp **/ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/** end ratio_utest_main.cpp **/ From 0c7cb8c37e18b395bf2e5aa609d01cca5b5cf19f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:07:50 -0400 Subject: [PATCH 0788/2693] xo-ratio: expand example ex1.cpp --- example/ex1/ex1.cpp | 86 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 8aa3c82d..144d3ed9 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -1,13 +1,75 @@ /** @file ex1.cpp **/ #include "xo/ratio/ratio_iostream.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/hex.hpp" #include +namespace { + using xo::xtag; + using xo::hex_view; + +#ifdef NOT_USING + template + xo::flatstring + flatstring_from_int(int x) + { + XO_SCOPE(log, always); + + constexpr size_t buf_z = 20; + + bool negative_flag = (x < 0); + std::size_t i = buf_z; + char buf[buf_z]; + std::fill_n(buf, N, '\0'); + + if (negative_flag) + x = -x; + + buf[--i] = '\0'; + + while ((i > 0) && (x != 0)) { + buf[--i] = ('0' + x % 10); + x = x / 10; + } + + if ((i > 0) && negative_flag) + buf[--i] = '-'; + + char retv[N]; + std::fill_n(retv, N, '\0'); + std::copy_n(buf + i, buf_z - i, retv); + + log && log(xtag("i",i), xtag("buf[i..]", hex_view(buf+i, buf+buf_z, true))); + + return retv; + } +#endif + + template + constexpr xo::flatstring + ratio_to_str(Ratio x) noexcept + { + if (x.is_integer()) { + return xo::flatstring::from_int(x.num()); + } else { + constexpr auto num_str = xo::flatstring::from_int(x.num()); + constexpr auto den_str = xo::flatstring::from_int(x.den()); + + constexpr auto tmp = flatstring_concat("(", num_str, "/", den_str, ")"); + + return tmp; + } + } + +} + int main() { using xo::ratio::make_ratio; using xo::ratio::ratio; using xo::ratio::ratio_concept; + using xo::flatstring; using namespace std; constexpr auto r1 = make_ratio(2, 3); @@ -158,11 +220,24 @@ main() { static_assert(!r16.is_integer()); constexpr auto r17 = 2 / r9; - cerr << "r17=2/r9: " << r17 << endl; + cerr << "r17=2/r9: " << r17 << endl; // 9/2 static_assert(r17 == ratio(9, 2)); static_assert(!r17.is_integer()); + constexpr auto s17_num_str = flatstring<20>::from_int(r17.num()); + static_assert(s17_num_str == flatstring("9")); + constexpr auto s17_den_str = flatstring<20>::from_int(r17.den()); + static_assert(s17_den_str == flatstring("2")); + constexpr auto s17_str = flatstring_concat(flatstring_concat(flatstring("("), + s17_num_str), + flatstring_concat(flatstring("/"), + s17_den_str), + flatstring(")")); + cerr << "s17_str=" << s17_str << endl; + + //constexpr auto s17 = ratio_to_str(r17); + constexpr auto r18 = r12 / r8; cerr << "r18=r12/r8: " << r12/r8 << endl; @@ -180,6 +255,15 @@ main() { static_assert(!r20.is_integer()); static_assert(r20 == ratio(8, 729)); + //cerr << flatstring_from_int<10>(1) << endl; + + constexpr auto s20 = flatstring<10>::from_int(-123); + cerr << "s20=" << s20 << endl; + + static_assert(s20.size() > 0); + static_assert(s20 == flatstring("-123")); + + /* verify constexpr working */ static_assert(ratio(2,3).num() == 2); static_assert(ratio(2,3).den() == 3); From ec73e10c56c5bd1ae0a097aafd2f708f98c1001b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:08:04 -0400 Subject: [PATCH 0789/2693] xo-ratio: build: + utest dir (!) --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ca9e8c95..44a7c278 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ xo_toplevel_compile_options() # ---------------------------------------------------------------- add_subdirectory(example) -#add_subdirectory(utest) +add_subdirectory(utest) #add_subdirectory(docs) # ---------------------------------------------------------------- From 413c84d65e27fe1b17a39e4393d8bcf0d102aed4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:12:22 -0400 Subject: [PATCH 0790/2693] xo-ratio: README: ++ elaborate --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 722ea8bf..cf8b2de2 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Relative to `std::ratio`: This means it can be used seamlessly at runtime. 2. Supports a few more arithmetic operations, for example exponentiation to integer powers. +3. Provides constexpr conversion to fixed-capacity strings (using xo-flatstring) 3. Provides concept support (with c++20) 4. Requires modern (c++17) support to achieve this @@ -15,6 +16,7 @@ Relative to `std::ratio`: ### install dependencies - [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) cmake macros +- [github/Rconybea/xo-flatstring](https://github.com/Rconybea/xo-flatstring) fixed-capacity strings ### build + install ``` From 96e50d0a6b5d93b4b58e6607349b8e91b8e399fc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:19:29 -0400 Subject: [PATCH 0791/2693] xo-ratio: README elaboration --- README.md | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cf8b2de2..2ed3e4e5 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,9 @@ Relative to `std::ratio`: 3. Provides concept support (with c++20) 4. Requires modern (c++17) support to achieve this +Relative to `boost::ratio`: +1. Streamlined, assumes modern compiler support + ## Getting Started ### install dependencies @@ -18,11 +21,45 @@ Relative to `std::ratio`: - [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) cmake macros - [github/Rconybea/xo-flatstring](https://github.com/Rconybea/xo-flatstring) fixed-capacity strings +### clone xo-ratio + +``` +$ cd ~/proj # for example +$ git clone https://github.com/Rconybea/xo-ratio +``` + ### build + install ``` $ cd xo-ratio $ PREFIX=/usr/local # for example $ BUILDDIR=.build # for example $ make ${BUILDDIR} -$ cmake -DCMAKE_PREFIX_PATH=${PREFIX} -B ${BUILDDIR} +$ cmake --build .build +$ cmake --install .build +``` + +### build with unit test coverage +``` +$ cd xo-ratio +$ mkdir .build-ccov +$ cmake -DCMAKE_BUILD_TYPE=coverage -B .build-ccov +$ cmake --build .build-ccov +``` + +run coverage-enabled unit tests +``` +$ cmake --build .build-ccov -- test +``` + +generate html+text coverage report +``` +$ cmake --build .build-ccov -- ccov +``` + +browse to `.build-ccov/ccov/html/index.html` + +### LSP support +``` +$ cd xo-ratio +$ ln -s .build/compile_commands.json ``` From 66998590dc7fc7b4c9b1b9e1418e9696506c2f80 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:24:09 -0400 Subject: [PATCH 0792/2693] xo-flatstring: ++ README --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 0e7ca89b..fa3dcc93 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,18 @@ Limitations: ## Getting started +### Install dependencies + +- [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) cmake macros +- [github/Rconybea/xo-indentlog](https://github.com/Rconybea/indentlog) logging (used by unit tests) + +### Clone xo-flatstring + +``` +$ cd ~/proj # for example +$ git clone https://github.com/rconybea/xo-flatstring +``` + ### build + install ``` $ cd xo-flatstring From d5747dde17fab6d0c100d558c32a8bd0532eafc3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:24:22 -0400 Subject: [PATCH 0793/2693] xo-flatstring: + from_flatstring() + from_chars() --- include/xo/flatstring/flatstring.hpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/include/xo/flatstring/flatstring.hpp b/include/xo/flatstring/flatstring.hpp index 41bfffa3..da80014b 100644 --- a/include/xo/flatstring/flatstring.hpp +++ b/include/xo/flatstring/flatstring.hpp @@ -168,6 +168,26 @@ namespace xo { } ///@} + /** @brief construct from another flatstring **/ + template + static constexpr flatstring from_flatstring(const flatstring & str) noexcept { + flatstring retval; + + retval.assign(str); + + return retval; + } + + /** @brief construct from char array **/ + template + static constexpr flatstring from_chars(const char (&str)[N2]) noexcept { + flatstring retval; + + retval.assign(str); + + return retval; + } + /** @brief construct from integer **/ static constexpr flatstring from_int(int x) { constexpr size_t buf_z = 20; From b1b8d89f7c2fe41261e5bd98897c6119a0979942 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:24:35 -0400 Subject: [PATCH 0794/2693] xo-flatstring: README: drop install detail for coverage build --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index fa3dcc93..344cead4 100644 --- a/README.md +++ b/README.md @@ -72,9 +72,6 @@ $ cmake --build .build-ccov -- ccov browse to `.build-ccov/ccov/html/index.html` -Running `cmake --install` for a coverage build will install coverage report (if generated) -to `${CMAKE_INSTALL_PREFIX}/share/doc/xo_flatstring` - ### LSP support ``` $ cd xo-flatstring From a848e5ea67ebe8713cd339b6a7cf8a6b0ab40b6a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:25:54 -0400 Subject: [PATCH 0795/2693] xo-ratio: ++ README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2ed3e4e5..5b6dda3a 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Relative to `boost::ratio`: ### install dependencies - [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) cmake macros +- [github/rconybea/xo-indentlog](https://github.com/Rconybea/xo-indentlog) logging (used by unit tests) - [github/Rconybea/xo-flatstring](https://github.com/Rconybea/xo-flatstring) fixed-capacity strings ### clone xo-ratio From fd305ffe8f928cd4736a6870cdeb28738a6ac511 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:26:02 -0400 Subject: [PATCH 0796/2693] xo-ratio: github: + ubuntu build --- .github/workflows/ubuntu-main.yml | 186 ++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 .github/workflows/ubuntu-main.yml diff --git a/.github/workflows/ubuntu-main.yml b/.github/workflows/ubuntu-main.yml new file mode 100644 index 00000000..260a8310 --- /dev/null +++ b/.github/workflows/ubuntu-main.yml @@ -0,0 +1,186 @@ +name: build xo-ratio + dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install dependencies + run: | + # install catch2, doxygen. see + # [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + + echo "::group::install catch2" + sudo apt-get install -y catch2 + echo "::endgroup" + + echo "::group::install doxygen" + sudo apt-get install -y doxygen + echo "::endgroup" + + echo "::group::install sphinx" + sudo apt-get install -y python3-sphinx + echo "::endgroup" + + echo "::group::install sphinx readthedocs theme" + sudo apt-get install -y python3-sphinx-rtd-theme + echo "::endgroup" + + #echo "::group::install pybind11" + #sudo apt-get install -y pybind11-dev + #echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: build xo-cmake + run: | + XONAME=xo-cmake + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/xo-indentlog + + - name: build xo-indentlog + run: | + XONAME=xo-indentlog + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-flatstring + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-flatstring + path: repo/xo-flatstring + + - name: build xo-flatstring + run: | + XONAME=xo-flatstring + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: build self (xo-ratio) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: | + XONAME=xo-ratio + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::run unit tests ${XONAME}" + cmake --build ${BUILDDIR} -- test + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + (cd ${BUILDDIR} && ctest -C ${{env.BUILD_TYPE}}) From cef1f81f5f32a2bf01e52308ce4463e90fa26428 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:28:18 -0400 Subject: [PATCH 0797/2693] xo-ratio: build: drop unused xo-reflect dep --- example/ex1/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt index b7050cfc..224e5690 100644 --- a/example/ex1/CMakeLists.txt +++ b/example/ex1/CMakeLists.txt @@ -10,6 +10,6 @@ xo_include_options2(${SELF_EXE}) # dependencies.. xo_self_dependency(${SELF_EXE} xo_ratio) -xo_dependency(${SELF_EXE} reflect) +#xo_dependency(${SELF_EXE} reflect) # end CMakeLists.txt From 24df33c505435a17caae2f1ee0b0b784ca29ecc8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:30:47 -0400 Subject: [PATCH 0798/2693] xo-ratio: github: + randomgen dep --- .github/workflows/ubuntu-main.yml | 35 +++++++++++++++++++++++++++++++ README.md | 3 ++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ubuntu-main.yml b/.github/workflows/ubuntu-main.yml index 260a8310..4b5f22cd 100644 --- a/.github/workflows/ubuntu-main.yml +++ b/.github/workflows/ubuntu-main.yml @@ -114,6 +114,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-randomgen + uses: actions/checkout@v3 + with: + repository: Rconybea/randomgen + path: repo/xo-randomgen + + - name: build xo-randomgen + run: | + XONAME=xo-randomgen + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-flatstring uses: actions/checkout@v3 with: diff --git a/README.md b/README.md index 5b6dda3a..a5047565 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,9 @@ Relative to `boost::ratio`: ### install dependencies - [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) cmake macros -- [github/rconybea/xo-indentlog](https://github.com/Rconybea/xo-indentlog) logging (used by unit tests) - [github/Rconybea/xo-flatstring](https://github.com/Rconybea/xo-flatstring) fixed-capacity strings +- [github/rconybea/xo-indentlog](https://github.com/Rconybea/xo-indentlog) logging (used by unit tests) +- [github/rconybea/xo-randomgen](https://github.com/Rconybea/xo-randomgen) rng (used by unit tests) ### clone xo-ratio From 54cb306903e3662774d3df62d0c21ef9c292fa80 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:35:53 -0400 Subject: [PATCH 0799/2693] xo-ratio: bugfix: narrow operator<< template --- include/xo/ratio/ratio_iostream.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/xo/ratio/ratio_iostream.hpp b/include/xo/ratio/ratio_iostream.hpp index fc898b36..9d1c9939 100644 --- a/include/xo/ratio/ratio_iostream.hpp +++ b/include/xo/ratio/ratio_iostream.hpp @@ -17,9 +17,9 @@ namespace xo { * print_ratio(std::cerr, make_ratio(1,2); // outputs "" * @endcode **/ - template + template void - print_ratio (std::ostream & os, const Ratio & x) { + print_ratio (std::ostream & os, const ratio & x) { os << ""; } @@ -30,9 +30,9 @@ namespace xo { * std::cout << make_ratio(2,3); // outputs "" * @endcode **/ - template + template inline std::ostream & - operator<< (std::ostream & os, const Ratio & x) { + operator<< (std::ostream & os, const ratio & x) { print_ratio(os, x); return os; } From 75f78b45d3a8a89c46223033be75420816969119 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:48:40 -0400 Subject: [PATCH 0800/2693] xo-ratio: ccov: renovate coverage generation -> 2nd gen scheme --- CMakeLists.txt | 38 +++++++++++---- README.md | 5 ++ cmake/gen-ccov.in | 20 ++++++++ cmake/lcov-harness | 114 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 168 insertions(+), 9 deletions(-) create mode 100644 cmake/gen-ccov.in create mode 100755 cmake/lcov-harness diff --git a/CMakeLists.txt b/CMakeLists.txt index 44a7c278..df36a04d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,17 +12,37 @@ include(cmake/xo-bootstrap-macros.cmake) # unit test setup enable_testing() -# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) -add_code_coverage() -# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. -# we're not interested in code coverage for these sources. -# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; -# rather, want coverage on the code that the unit tests exercise. +# ---------------------------------------------------------------- +# cmake -DCMAKE_BUILD_TYPE=coverage + +if (NOT DEFINED PROJECT_CXX_FLAGS_COVERAGE) + # note: for clang would use -fprofile-instr-generate -fcoverage-mapping here instead and also at link time + set(PROJECT_CXX_FLAGS_COVERAGE ${PROJECT_CXX_FLAGS} -ggdb -Og -fprofile-arcs -ftest-coverage + CACHE STRING "coverage c++ compiler flags") +endif() +message("-- PROJECT_CXX_FLAGS_COVERAGE: coverage c++ flags are [${PROJECT_CXX_FLAGS_COVERAGE}]") + +add_compile_options("$<$:${PROJECT_CXX_FLAGS_COVERAGE}>") +# when -DCMAKE_BUILD_TYPE=coverage, link executables with gcov +link_libraries("$<$:gcov>") + +find_program(LCOV_EXECUTABLE NAMES lcov) +find_program(GENHTML_EXECUTABLE NAMES genhtml) + +# with coverage build: +# 1. invoke instrumented executables for which you want coverage: +# (cd path/to/build && ctest) +# 2. post-process low-level coverage data +# (path/to/build/gen-ccov) +# 3. point browser to generated html data +# file:///path/to/build/ccov/html/index.html # -# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target -# -add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) +configure_file( + ${PROJECT_SOURCE_DIR}/cmake/gen-ccov.in + ${PROJECT_BINARY_DIR}/gen-ccov) + +file(CHMOD ${PROJECT_BINARY_DIR}/gen-ccov PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) # ---------------------------------------------------------------- # c++ settings diff --git a/README.md b/README.md index a5047565..09c36590 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,11 @@ Relative to `std::ratio`: Relative to `boost::ratio`: 1. Streamlined, assumes modern compiler support +## Documentation + +- xo-ratio documentation under construction +- unit test coverage here: [coverage](https://rconybea.github.io/web/xo-ratio/ccov/html/index.html) + ## Getting Started ### install dependencies diff --git a/cmake/gen-ccov.in b/cmake/gen-ccov.in new file mode 100644 index 00000000..e335aed4 --- /dev/null +++ b/cmake/gen-ccov.in @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +srcdir=@PROJECT_SOURCE_DIR@ +builddir=@PROJECT_BINARY_DIR@ +lcov=@LCOV_EXECUTABLE@ +genhtml=@GENHTML_EXECUTABLE@ + +if [[ $lcov == "LCOV_EXECUTABLE-NOTFOUND" ]]; then + echo "gen-ccov: lcov executable not found" + exit 1 +fi + +if [[ $genhtml == "GENHTML_EXECUTABLE-NOTFOUND" ]]; then + echo "gen-ccov: genhtml executable not found" + exit 1 +fi + +mkdir $builddir/ccov + +$srcdir/cmake/lcov-harness $srcdir $builddir $builddir/ccov/out $lcov $genhtml diff --git a/cmake/lcov-harness b/cmake/lcov-harness new file mode 100755 index 00000000..27ac8be9 --- /dev/null +++ b/cmake/lcov-harness @@ -0,0 +1,114 @@ +#!/usr/bin/env bash + +srcdir=$1 +builddir=$2 +outputstem=$3 +lcov=$4 +genhtml=$5 + +if [[ -z "${srcdir}" ]]; then + echo "lcov-harness: expected non-empty srcdir" + exit 1 +fi + +if [[ -z ${builddir} ]]; then + echo "lcov-harness: expected non-empty builddir" + exit 1 +fi + +if [[ -z ${outputstem} ]]; then + echo "lcov-harness: expected non-empty outputstem" + exit 1 +fi + +if [[ -z ${lcov} ]]; then + echo "lcov-harness: exepcted non-empty lcov" + exit 1 +fi + +if [[ -z ${genhtml} ]]; then + echo "lcov-harness: expected non-empty genhtml" + exit 1 +fi + +# directory stems for location of {.gcda, gcno} coverage information, +# +# if we have source tree: +# +# ${srcdir} +# +- foo +# | \- foo.cpp +# \- bar +# \- quux +# +- quux.cpp +# \- quux_main.cpp +# +# then we expect build tree: +# +# ${builddir} +# +- foo +# | \- CMakeFiles +# | \- foo_target.dir +# | +- foo.cpp.gcda +# | \- foo.cpp.gcno +# +- bar +# \- quux +# \- CMakeFiles +# \- target4quux.dir +# +- quux.cpp.gcda +# +- quux.cpp.gcno +# +- quux_main.cpp.gcda +# \- quux_main.cpp.gcno +# +# in which case will have cmd_body: +# +# ${primarydirs} +# ./foo/CMakeFiles/foo_target.dir +# ./bar/quux/CMakeFiles/target4quux.dir +# +# here foo_target, quux_target are whatever build is using for corresponding cmake target names. +# +# We want to invoke lcov like: +# +# lcov --capture \ +# --output ${builddir}/ccov \ +# --exclude /utest/ \ +# --base-directory ${srcdir}/foo --directory ${builddir}/foo/CMakeFiles/foo_target.dir \ +# --base-directory ${srcdir}/bar/quux --directory ${builddir}/bar/quux/CMakeFiles/target4quux.dir +# +primarydirs=$(cd ${builddir} && find -name '*.gcno' \ + | xargs --replace=xx dirname xx \ + | uniq \ + | sed -e 's:^\./::') + +#echo "primarydirs=${primarydirs}" + +cmd="${lcov} --output ${outputstem}.info --capture --ignore-errors source" + +for bdir in ${primarydirs}; do + sdir=$(dirname $(dirname ${bdir})) + + cmd="${cmd} --base-directory ${srcdir}/${sdir} --directory ${builddir}/${bdir}" +done + +#echo cmd=${cmd} + +set -x + +# capture +${cmd} + +# keep only files with paths under source tree +# (don't want coverage for external libraries such as libstdc++ etc) +${lcov} --extract ${outputstem}.info "${srcdir}/*" --output ${outputstem}2.info + +# remove unit test dirs +# (we're interested in coverage of our installed code, not of the unit tests that exercise it) +${lcov} --remove ${outputstem}2.info '*/utest/*' --output ${outputstem}3.info + +# generate .html tree +mkdir -p ${builddir}/ccov/html +${genhtml} --ignore-errors source --show-details --prefix ${srcdir} --output-directory ${builddir}/ccov/html ${outputstem}3.info + +# also send report to stdout +${lcov} --list ${outputstem}3.info From 06e230ebfddc606893d8342a30873ddec61e6d8c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 18:03:11 -0400 Subject: [PATCH 0801/2693] xo-ratio: docs: initial doxygen+sphinx scaffold --- CMakeLists.txt | 2 +- docs/CMakeLists.txt | 118 ++ docs/Doxyfile.in | 2816 ++++++++++++++++++++++++++++++++++ docs/_static/img/favicon.ico | Bin 0 -> 309803 bytes docs/_static/img/icon.svg | 77 + docs/_static/img/xo-icon.svg | 77 + docs/conf.py | 36 + docs/index.rst | 34 + docs/install.rst | 88 ++ 9 files changed, 3247 insertions(+), 1 deletion(-) create mode 100644 docs/CMakeLists.txt create mode 100644 docs/Doxyfile.in create mode 100644 docs/_static/img/favicon.ico create mode 100644 docs/_static/img/icon.svg create mode 100644 docs/_static/img/xo-icon.svg create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/install.rst diff --git a/CMakeLists.txt b/CMakeLists.txt index df36a04d..882e9914 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,7 +58,7 @@ xo_toplevel_compile_options() add_subdirectory(example) add_subdirectory(utest) -#add_subdirectory(docs) +add_subdirectory(docs) # ---------------------------------------------------------------- # provide find_package() support for projects using this library diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt new file mode 100644 index 00000000..57a99c20 --- /dev/null +++ b/docs/CMakeLists.txt @@ -0,0 +1,118 @@ +# xo-ratio/docs/CMakeLists.txt + +if (XO_SUBMODULE_BUILD) + # in submodule build, rely on toplevel docs/CMakeLists.txt file instead +else() + # build docs starting from here only in standalone build. + # otherwise use top-level doxygen setup instead. + + set(ALL_LIBRARY_TARGETS xo_ratio) # todo: automate this from xo-cmake macros + set(ALL_UTEST_TARGETS utest.ratio xo_ratio_ex1 ) # todo: automate this from xo-cmake macros + + # look for doxygen executable + find_program(DOXYGEN_EXECUTABLE NAMES doxygen REQUIRED) + message("-- DOXYGEN_EXECUTABLE=${DOXYGEN_EXECUTABLE}") + + # look for sphinx-build executable + find_program(SPHINX_EXECUTABLE NAMES sphinx-build REQUIRED) + message("-- SPHINX_EXECUTABLE=${SPHINX_EXECUTABLE}") + + set(DOX_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) + + set(DOX_INPUT_DIR ${PROJECT_SOURCE_DIR}) + set(DOX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/dox) + + set(DOX_INDEX_FILE ${DOX_OUTPUT_DIR}/html/index.html) + + # .hpp files reachable from xo-ratio/include + # + # REMINDER: for reliability will need to re-run cmake when the set of .hpp files changes + # + file(GLOB_RECURSE DOX_HPP_FILES_GLOB ${PROJECT_SOURCE_DIR}/include *.hpp) + + set(SPHINX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/sphinx/html) + set(SPHINX_INDEX_FILE ${SPHINX_OUTPUT_DIR}/index.html) + # + # sphinx .rst files reachable from cmake-examples/docs + # + # REMINDER: for reliability will need to re-run cmake when the set of .rst files changes + # + file(GLOB_RECURSE SPHINX_RST_FILES_GLOB ${CMAKE_CURRENT_SOURCE_DIR} *.rst) + + set(SPHINX_RST_FILES index.rst + #install.rst + #lessons.rst + #flatstring-reference.rst + #flatstring-class.rst + ) + + # TODO: + # 1. move Doxyfile.in to xo-cmake project + # 2. replace this command section with xo-cmake macro + # + configure_file( + Doxyfile.in ${DOX_CONFIG_FILE} + FILE_PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE + @ONLY) + + set(DOX_DEPS ${ALL_LIBRARY_TARGETS} ${ALL_UTEST_TARGETS} ${DOX_HPP_FILES_GLOB}) + + file(MAKE_DIRECTORY ${DOX_OUTPUT_DIR}) + add_custom_command( + OUTPUT ${DOX_INDEX_FILE} + DEPENDS ${DOX_DEPS} + COMMAND "${DOXYGEN_EXECUTABLE}" ${DOX_CONFIG_FILE} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + MAIN_DEPENDENCY ${DOX_CONFIG_FILE} + COMMENT "Generating docs (doxygen)") + + # To build this target + # $ cmake --build .build -j -- doxygen + # or + # $ cd .build + # $ make doxygen + # + add_custom_target( + doxygen + DEPENDS ${DOX_INDEX_FILE} ${DOX_DEPS} + ) + + # root of sphinx doc tree + set(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}) + set(SPHINX_DEPS doxygen conf.py ${SPHINX_RST_FILES} ${SPHINX_RST_FILES_GLOB} ${DOX_DEPS}) + + add_custom_command( + OUTPUT ${SPHINX_INDEX_FILE} + DEPENDS ${SPHINX_DEPS} + COMMAND ${SPHINX_EXECUTABLE} + -b html -Dbreathe_projects.xodoxxml=${CMAKE_CURRENT_BINARY_DIR}/dox/xml + ${SPHINX_SOURCE} ${SPHINX_OUTPUT_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating docs (sphinx) -> [${SPHINX_OUTPUT_DIR}]") + + # make sphinx --> generate sphinx documentation + # + add_custom_target( + sphinx + DEPENDS ${SPHINX_INDEX_FILE}) + + # - html docs generated in build/docs/sphinx + # - copy the doc tree to share/doc/xo_unit/html + # + # DESTINATION: CMAKE_INSTALL_DOCDIR + # => DATAROOTDIR/doc/PROJECT_NAME + # => CMAKE_INSTALL_PREFIX/share/doc/xo_flatstring + # OPTIONAL: install directory tree if it exists, + # but don't complain if it's missing + install( + DIRECTORY ${SPHINX_OUTPUT_DIR} + FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_DOCDIR} + COMPONENT Documentation + OPTIONAL) + + # make docs --> generate sphinx documentation + add_custom_target( + docs + DEPENDS sphinx) +endif() diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in new file mode 100644 index 00000000..13dfb0fa --- /dev/null +++ b/docs/Doxyfile.in @@ -0,0 +1,2816 @@ +# If filename is Doxyfile.in: +# template (to be expanded by cmake) for real doxyfile +# @SOMEVAR@ expands to value of cmake variable SOMEVAR +# +# expressions to be expanded include: +# @DOX_INPUT_DIR@ +# @DOX_OUTPUT_DIR@ +# +# if filename is Doxyfile: +# expanded template in build directory, to configure doxygen + +# Doxyfile 1.9.7 +# + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "Cmake Examples" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = @DOX_OUTPUT_DIR@ + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = @DOX_INPUT_DIR@ + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN Use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0. and GITHUB Use the lower case version of title +# with any whitespace replaced by '-' and punctations characters removed.. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = YES + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = YES + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = YES + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. + +CASE_SENSE_NAMES = SYSTEM + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about +# undocumented enumeration values. If set to NO, doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = @DOX_INPUT_DIR@ + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding +# "INPUT_ENCODING" for further information on supported encodings. + +INPUT_FILE_ENCODING = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, +# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C +# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, +# *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.l \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = */utest/* + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# ANamespace::AClass, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generate light mode output, DARK always +# generate dark mode output, AUTO_LIGHT automatically set the mode according to +# the user preference, use light mode if no preference is set (the default), +# AUTO_DARK automatically set the mode according to the user preference, use +# dark mode if no preference is set and TOGGLE allow to user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /