initial implementation + example + cmake "build"
This commit is contained in:
parent
9c6722f99a
commit
8a1f29a44b
15 changed files with 1130 additions and 0 deletions
7
CMakeLists.txt
Normal file
7
CMakeLists.txt
Normal 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
21
example/CMakeLists.txt
Normal 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()
|
||||
3
example/ex1/CMakeLists.txt
Normal file
3
example/ex1/CMakeLists.txt
Normal 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
14
example/ex1/ex1.cpp
Normal 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
25
include/nestlog/array.hpp
Normal 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
45
include/nestlog/fixed.hpp
Normal 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 */
|
||||
227
include/nestlog/log_state.hpp
Normal file
227
include/nestlog/log_state.hpp
Normal 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 */
|
||||
122
include/nestlog/log_streambuf.hpp
Normal file
122
include/nestlog/log_streambuf.hpp
Normal 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
41
include/nestlog/pad.hpp
Normal 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 */
|
||||
28
include/nestlog/printer.hpp
Normal file
28
include/nestlog/printer.hpp
Normal 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
147
include/nestlog/quoted.hpp
Normal 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
236
include/nestlog/scope.hpp
Normal 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
87
include/nestlog/tag.hpp
Normal 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
99
include/nestlog/tostr.hpp
Normal 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 */
|
||||
28
include/nestlog/vector.hpp
Normal file
28
include/nestlog/vector.hpp
Normal 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 */
|
||||
Loading…
Add table
Add a link
Reference in a new issue