// file : butl/diagnostics -*- C++ -*- // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file #ifndef BUTL_DIAGNOSTICS #define BUTL_DIAGNOSTICS #include <cassert> #include <ostream> #include <sstream> #include <utility> // move(), forward() #include <butl/ft/exception> // uncaught_exceptions #include <exception> // uncaught_exception(s)() #include <butl/export> #include <butl/utility> namespace butl { // Diagnostic facility base infrastructure. // // Diagnostics destination stream (std::cerr by default). Note that its // modification is not MT-safe. Also note that concurrent writing to the // stream from multiple threads can result in interleaved characters. To // prevent this an object of diag_lock type (see below) must be created prior // to write operation. // LIBBUTL_EXPORT extern std::ostream* diag_stream; // Acquire the diagnostics exclusive access mutex in ctor, release in dtor. // An object of the type must be created prior to writing to diag_stream (see // above). // struct LIBBUTL_EXPORT diag_lock { diag_lock (); ~diag_lock (); }; struct diag_record; template <typename> struct diag_prologue; template <typename> struct diag_mark; using diag_epilogue = void (const diag_record&); struct LIBBUTL_EXPORT diag_record { template <typename T> friend const diag_record& operator<< (const diag_record& r, const T& x) { r.os << x; return r; } diag_record () : #ifdef __cpp_lib_uncaught_exceptions uncaught_ (std::uncaught_exceptions ()), #endif empty_ (true), epilogue_ (nullptr) {} template <typename B> explicit diag_record (const diag_prologue<B>& p): diag_record () { *this << p;} template <typename B> explicit diag_record (const diag_mark<B>& m): diag_record () { *this << m;} ~diag_record () noexcept (false); bool empty () const {return empty_;} bool full () const {return !empty_;} void flush () const; void append (const char* indent, diag_epilogue* e) const { // Ignore subsequent epilogues (e.g., from nested marks, etc). // if (empty_) { epilogue_ = e; empty_ = false; } else if (indent != nullptr) os << indent; } // Move constructible-only type. // // Older versions of libstdc++ don't have the ostringstream move support // and accuratly detecting its version is non-trivial. So we always use // the pessimized implementation with libstdc++. Luckily, GCC doesn't seem // to be needing move due to copy/move elision. // #ifdef __GLIBCXX__ diag_record (diag_record&&); #else diag_record (diag_record&& r) : #ifdef __cpp_lib_uncaught_exceptions uncaught_ (r.uncaught_), #endif empty_ (r.empty_), epilogue_ (r.epilogue_), os (std::move (r.os)) { if (!empty_) { r.empty_ = true; r.epilogue_ = nullptr; } } #endif diag_record& operator= (diag_record&&) = delete; diag_record (const diag_record&) = delete; diag_record& operator= (const diag_record&) = delete; protected: #ifdef __cpp_lib_uncaught_exceptions const int uncaught_; #endif mutable bool empty_; mutable diag_epilogue* epilogue_; public: mutable std::ostringstream os; }; template <typename B> struct diag_prologue: B { diag_prologue (const char* i = "\n ", diag_epilogue* e = nullptr) : B (), indent_ (i), epilogue_ (e) {} template <typename... A> diag_prologue (A&&... a) : B (std::forward<A> (a)...), indent_ ("\n "), epilogue_ (nullptr) {} template <typename... A> diag_prologue (diag_epilogue* e, A&&... a) : B (std::forward<A> (a)...), indent_ ("\n "), epilogue_ (e) {} template <typename... A> diag_prologue (const char* i, diag_epilogue* e, A&&... a) : B (std::forward<A> (a)...), indent_ (i), epilogue_ (e) {} template <typename T> diag_record operator<< (const T& x) const { diag_record r; r.append (indent_, epilogue_); B::operator() (r); r << x; return r; } friend const diag_record& operator<< (const diag_record& r, const diag_prologue& p) { r.append (p.indent_, p.epilogue_); p (r); return r; } private: const char* indent_; diag_epilogue* epilogue_; }; template <typename B> struct diag_mark: B { diag_mark (): B () {} template <typename... A> diag_mark (A&&... a): B (std::forward<A> (a)...) {} template <typename T> diag_record operator<< (const T& x) const { return B::operator() () << x; } friend const diag_record& operator<< (const diag_record& r, const diag_mark& m) { return r << m (); } }; template <typename B> struct diag_noreturn_end: B { diag_noreturn_end (): B () {} template <typename... A> diag_noreturn_end (A&&... a): B (std::forward<A> (a)...) {} [[noreturn]] friend void operator<< (const diag_record& r, const diag_noreturn_end& e) { // We said that we never return which means this end mark cannot be used // to "maybe not return". And not returning without any diagnostics is // probably a mistake. // assert (r.full ()); e.B::operator() (r); } }; } #endif // BUTL_DIAGNOSTICS