diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b2a41b6 --- /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 0000000..ac2655a --- /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 0000000..ce8d9a5 --- /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 0000000..6092260 --- /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 0000000..03b265e --- /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 0000000..bf1a552 --- /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 0000000..41556b8 --- /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 0000000..5d06dab --- /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 0000000..123c8d5 --- /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 0000000..5e4cc8b --- /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 0000000..29f5791 --- /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 0000000..16a3e21 --- /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 0000000..b2bc927 --- /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 0000000..be5f449 --- /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 */