// file : libbutl/diagnostics.mxx -*- C++ -*- // license : MIT; see accompanying LICENSE file #ifndef __cpp_modules_ts #pragma once #endif #include #ifndef __cpp_lib_modules_ts #include #include #include // move(), forward() #include // uncaught_exception[s]() #endif #include // uncaught_exceptions #ifdef __cpp_modules_ts export module butl.diagnostics; #ifdef __cpp_lib_modules_ts import std.core; import std.io; #endif #endif #include LIBBUTL_MODEXPORT 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_stream_lock type (see below) must be // created prior to write operation. // LIBBUTL_SYMEXPORT 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_SYMEXPORT diag_stream_lock { diag_stream_lock (); ~diag_stream_lock (); // Support for one-liners, for example: // // diag_stream_lock () << "Hello, World!" << endl; // template std::ostream& operator<< (const T& x) const { return *diag_stream << x; } }; // Progress line facility. // // The idea is to keep a progress line at the bottom of the terminal with // other output scrolling above it. For a non-terminal STDERR the progress // line is printed as a regular one terminated with the newline character. // The printing of the progress line is integrated into diag_stream_lock and // diag_progress_lock. To print or update the progress acquire // diag_progress_lock and update the diag_progress string. To remove the // progress line, set this string to empty. For better readability start the // progress line with a space (which is where the cursor will be parked). // Should only be used if diag_stream points to std::cerr. // // Note that child processes writing to the same stream may not completely // overwrite the progress line so in this case it makes sense to keep the // line as short as possible. // // To restore the progress line being overwritten by an independent writer // (such as a child process), create and destroy the diag_progress_lock. // LIBBUTL_SYMEXPORT extern std::string diag_progress; struct LIBBUTL_SYMEXPORT diag_progress_lock { diag_progress_lock (); ~diag_progress_lock (); }; // // struct diag_record; template struct diag_prologue; template struct diag_mark; using diag_epilogue = void (const diag_record&); struct LIBBUTL_SYMEXPORT diag_record { template const diag_record& operator<< (const T& x) const { os << x; return *this; } diag_record () : #ifdef __cpp_lib_uncaught_exceptions uncaught_ (std::uncaught_exceptions ()), #endif empty_ (true), epilogue_ (nullptr) {} template explicit diag_record (const diag_prologue& p): diag_record () { *this << p;} template explicit diag_record (const diag_mark& 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; // Diagnostics writer. The default implementation writes the record text // to diag_stream. If it is NULL, then the record text is ignored. // static void (*writer) (const diag_record&); protected: #ifdef __cpp_lib_uncaught_exceptions const int uncaught_; #endif mutable bool empty_; mutable diag_epilogue* epilogue_; public: mutable std::ostringstream os; }; template struct diag_prologue: B { const char* indent; diag_epilogue* epilogue; diag_prologue (const char* i = "\n ", diag_epilogue* e = nullptr) : B (), indent (i), epilogue (e) {} template diag_prologue (A&&... a) : B (std::forward (a)...), indent ("\n "), epilogue (nullptr) {} template diag_prologue (diag_epilogue* e, A&&... a) : B (std::forward (a)...), indent ("\n "), epilogue (e) {} template diag_prologue (const char* i, diag_epilogue* e, A&&... a) : B (std::forward (a)...), indent (i), epilogue (e) {} template 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; } }; template struct diag_mark: B { diag_mark (): B () {} template diag_mark (A&&... a): B (std::forward (a)...) {} template 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 struct diag_noreturn_end: B { diag_noreturn_end (): B () {} template diag_noreturn_end (A&&... a): B (std::forward (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); } }; }