initial implementation + example + cmake "build"

This commit is contained in:
Roland Conybeare 2023-09-11 16:09:26 -04:00
commit 8a1f29a44b
15 changed files with 1130 additions and 0 deletions

7
CMakeLists.txt Normal file
View file

@ -0,0 +1,7 @@
cmake_minimum_required(VERSION 3.10)
project(nestlog VERSION 0.1)
enable_language(CXX)
enable_testing()
add_subdirectory(example)

21
example/CMakeLists.txt Normal file
View file

@ -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()

View file

@ -0,0 +1,3 @@
add_executable(ex1 ex1.cpp)
target_include_directories(ex1 PUBLIC ${PROJECT_SOURCE_DIR}/include)

14
example/ex1/ex1.cpp Normal file
View file

@ -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);
}

25
include/nestlog/array.hpp Normal file
View file

@ -0,0 +1,25 @@
/* @file array.hpp */
#pragma once
#include <iostream>
#include <array>
namespace std {
template<typename T, size_t N>
inline std::ostream &
operator<<(std::ostream & os,
std::array<T, N> 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 */

45
include/nestlog/fixed.hpp Normal file
View file

@ -0,0 +1,45 @@
/* @file fixed.hpp */
#pragma once
#include <iostream>
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 */

View file

@ -0,0 +1,227 @@
/* @file log_state.hpp */
#pragma once
#include "log_streambuf.hpp"
#include <ostream>
#include <memory> // for std::unique_ptr
namespace xo {
// track per-thread state associated with nesting logger
//
template <typename CharT, typename Traits>
class state_impl {
public:
using log_streambuf_type = log_streambuf<char, std::char_traits<char>>;
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<log_streambuf_type> 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<log_streambuf_type> 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 <typename CharT, typename Traits>
state_impl<CharT, Traits>::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 <typename CharT, typename Traits>
void
state_impl<CharT, Traits>::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_; i<n; ++i) {
this->ss_ << pad_char;
}
} /*indent*/
template <typename CharT, typename Traits>
void
state_impl<CharT, Traits>::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 <typename CharT, typename Traits>
void
state_impl<CharT, Traits>::preamble(std::string_view name1,
std::string_view name2)
{
this->entryexit_aux(name1, name2, '+' /*label_char*/);
} /*preamble*/
template <typename CharT, typename Traits>
void
state_impl<CharT, Traits>::postamble(std::string_view name1,
std::string_view name2)
{
this->entryexit_aux(name1, name2, '-' /*label_char*/);
} /*postamble*/
template <typename CharT, typename Traits>
void
state_impl<CharT, Traits>::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 */

View file

@ -0,0 +1,122 @@
/* @file log_streambuf.hpp */
#pragma once
#include <iostream>
#include <vector>
#include <cstring> // e.g. for std::memcpy()
#include <cassert> // e.g. for std::memcpy()
namespace xo {
/* recycling buffer for logging.
* write to self-extending storage array;
*/
template <typename CharT, typename Traits>
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<std::streamsize>(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<char> buf_v_;
}; /*log_streambuf*/
} /*namespace xo*/
/* end log_streambuf.hpp */

41
include/nestlog/pad.hpp Normal file
View file

@ -0,0 +1,41 @@
/* @file pad.hpp */
#pragma once
#include <iostream>
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<pad.n_pad(); ++i)
s << " ";
return s;
} /*operator<<*/
} /*namespace xo*/
/* end pad.hpp */

View file

@ -0,0 +1,28 @@
/* @file printer.hpp */
#pragma once
#include <utility>
namespace xo {
namespace print {
/* print an event to a logfile
* intended to be usable as EventSink argument
* to RealizationSimSource<T, EventSink>
*/
template<typename T, typename Stream>
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 */

147
include/nestlog/quoted.hpp Normal file
View file

@ -0,0 +1,147 @@
/* file quoted.hpp
*
* author: Roland Conybeare, Sep 2022
*/
#pragma once
#include "nestlog/tostr.hpp"
#include <sstream>
#include <string_view>
#include <utility>
#include <type_traits>
namespace xo {
namespace print {
/* use this to avoid template conversion hassles
* since literal strings get treated as arrays
*/
template<typename T>
char const * ccs(T x) { return x; }
/* Printing cases:
* 1. T&&:
* move into quoted_impl<T>. T must be moveable!
* 2. T&:
* copy reference into quoted_impl<T&>.
* similarly for T const &, copy reference into quoted_impl<T const &>
*/
template<typename T>
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<typename T>
std::ostream &
operator<<(std::ostream & os, quoted_impl<T> const & x) {
x.print(os);
return os;
} /*operator*/
/* writing out std::forward<T> behavior for completeness' sake:
*
* 1. call quoted(x) with rvalue std::string x, then:
* - T will be deduced to [std::string]
* (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<typename T>
auto quoted(T && x) {
return quoted_impl(false /*unq_flag*/, std::forward<T>(x));
}
inline auto qcstr(char const * x) {
return quoted(x);
} /*qcstr*/
template<typename T>
auto unq(T && x) {
return quoted_impl(true /*unq_flag*/, std::forward<T>(x));
}
} /*namespace print*/
} /*namespace xo*/
/* end quoted.hpp */

236
include/nestlog/scope.hpp Normal file
View file

@ -0,0 +1,236 @@
/* @file scopẹhpp */
#pragma once
#include "log_state.hpp"
#include "tostr.hpp"
#include "tag.hpp"
#include <stdexcept>
#include <cstdint>
#include <memory> // for std::unique_ptr
namespace xo {
template <typename ChartT, typename Traits>
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 <typename CharT, typename Traits = std::char_traits<CharT>>
class basic_scope {
public:
using state_impl_type = state_impl<CharT, Traits>;
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<typename... Tn>
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<typename... Tn>
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<state_impl_type> s_threadlocal_state;
/* name of this scope (part 1) */
std::string_view name1_ = "<name1>";
/* name of this scope (part 2) */
std::string_view name2_ = "::<name2>";
/* set once per scope .finalized=true <-> logging disabled */
bool finalized_ = false;
}; /*basic_scope*/
template <typename CharT, typename Traits>
basic_scope<CharT, Traits>::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 <typename CharT, typename Traits>
basic_scope<CharT, Traits>::basic_scope(std::string_view fn1, bool enabled_flag)
: basic_scope(fn1, "", enabled_flag)
{}
template <typename CharT, typename Traits>
basic_scope<CharT, Traits>::basic_scope(std::string_view fn)
: basic_scope(fn, true /*enabled_flag*/)
{}
template <typename CharT, typename Traits>
basic_scope<CharT, Traits>::~basic_scope() {
if(!this->finalized_)
this->end_scope();
} /*dtor*/
template <typename CharT, typename Traits>
thread_local std::unique_ptr<state_impl<CharT, Traits>>
basic_scope<CharT, Traits>::s_threadlocal_state;
template <typename CharT, typename Traits>
std::uint32_t
basic_scope<CharT, Traits>::nesting_level() const {
return require_thread_local_state()->nesting_level();
} /*nesting_level*/
template <typename CharT, typename Traits>
basic_scope<CharT, Traits>::state_impl_type *
basic_scope<CharT, Traits>::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 <typename CharT, typename Traits>
basic_scope<CharT, Traits>::state_impl_type *
basic_scope<CharT, Traits>::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 <typename CharT, typename Traits>
std::ostream &
basic_scope<CharT, Traits>::logstate2stream(state_impl_type * logstate)
{
return logstate->ss();
} /*logstate2stream*/
template <typename CharT, typename Traits>
void
basic_scope<CharT, Traits>::flush2clog(state_impl_type * logstate,
std::streambuf * p_sbuf)
{
logstate->flush2sbuf(p_sbuf);
} /*flush2clog*/
template <typename CharT, typename Traits>
void
basic_scope<CharT, Traits>::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<char>;
} /*namespace xo*/
/* end scope.hpp */

87
include/nestlog/tag.hpp Normal file
View file

@ -0,0 +1,87 @@
/* @file tag.hpp */
#pragma once
#include "nestlog/quoted.hpp"
#include <iostream>
// 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 <bool PrefixSpace, typename Name, typename Value>
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<typename Name, typename Value>
tag_impl<false, Name, Value>
make_tag(Name && n, Value && v)
{
return tag_impl<false, Name, Value>(n, v);
} /*make_tag*/
template<typename Value>
tag_impl<false, char const *, Value>
make_tag(char const * n, Value && v) {
return tag_impl<false, char const *, Value>(n, v);
} /*make_tag*/
template<typename Name, typename Value>
tag_impl<true, Name, Value>
xtag(Name && n, Value && v)
{
return tag_impl<true, Name, Value>(n, v);
} /*xtag*/
template<typename Value>
tag_impl<true, char const *, Value>
xtag(char const * n, Value && v) {
return tag_impl<true, char const *, Value>(n, v);
} /*xtag*/
inline
tag_impl<true, char const *, char const *>
xtag_pre(char const * n) {
return tag_impl<true, char const *, char const *>(n, "");
} /*xtag_pre*/
template <bool PrefixSpace, typename Name, typename Value>
inline std::ostream &
operator<<(std::ostream &s,
tag_impl<PrefixSpace, Name, Value> const & tag)
{
using xo::print::unq;
if(PrefixSpace)
s << " ";
s << ":" << tag.name()
<< " " << unq(tag.value());
return s;
} /*operator<<*/
} /*namespace xo*/
/* end tag.hpp */

99
include/nestlog/tostr.hpp Normal file
View file

@ -0,0 +1,99 @@
/* file tostr.hpp
*
* author: Roland Conybeare, Sep 2022
*/
#pragma once
#include <iostream>
#include <sstream>
#include <string>
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<class Stream, typename T>
Stream & tos(Stream & s, T && x) {
s << x;
return s;
} /*tos*/
template <class Stream, typename T, typename... Tn>
Stream &tos(Stream &s, T &&x, Tn &&...rest) {
s << x;
return tos(s, rest...);
} /*tos*/
// like tos(..), but append newline
//
template <class Stream, typename... Tn>
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 <typename... Tn> std::string tostr(Tn &&...args) {
std::stringstream ss;
tos(ss, args...);
//ss << std::ends;
return ss.str();
} /*tostr*/
} /*namespace xo*/
/* end tostr.hpp */

View file

@ -0,0 +1,28 @@
/* file vector.hpp
*
* author: Roland Conybeare, Sep 2022
*/
#pragma once
#include <iostream>
#include <vector>
namespace std {
template<typename T>
inline std::ostream &
operator<<(std::ostream & os,
std::vector<T> const & v)
{
os << "[";
for(size_t i=0, z=v.size(); i<z; ++i) {
if(i > 0)
os << " ";
os << v[i];
}
os << "]";
return os;
} /*operator<<*/
} /*namespace std*/
/* end vector.hpp */