From 955cfd2ada474db098840d8e680f0b849dae7854 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 9 Nov 2020 02:18:51 -0500 Subject: [PATCH] split to assert.cpp comments --- src/utility.cpp | 23 -------- src/utility.h | 5 +- src/utility/algo.h | 13 ++++- src/utility/assert.cpp | 29 ++++++++++ src/utility/fs.h | 61 ++++++++++++++++++++ src/utility/io.cpp | 116 +++++++++++++++++++++++++------------- src/utility/io.h | 80 ++++++++++++++++---------- src/utility/string.cpp | 17 +++++- src/utility/string.h | 64 +++++++++++++++++++-- src/utility/threading.cpp | 30 +++++----- src/utility/threading.h | 16 +++++- vs/mob.vcxproj | 1 + vs/mob.vcxproj.filters | 3 + 13 files changed, 337 insertions(+), 121 deletions(-) create mode 100644 src/utility/assert.cpp diff --git a/src/utility.cpp b/src/utility.cpp index f9bb6b0..8334411 100644 --- a/src/utility.cpp +++ b/src/utility.cpp @@ -9,29 +9,6 @@ namespace mob { - - -void mob_assertion_failed( - const char* message, - const char* exp, const wchar_t* file, int line, const char* func) -{ - if (message) - { - gcx().error(context::generic, - "assertion failed: {}:{} {}: {} ({})", - std::wstring(file), line, func, message, exp); - } - else - { - gcx().error(context::generic, - "assertion failed: {}:{} {}: '{}'", - std::wstring(file), line, func, exp); - } - - if (IsDebuggerPresent()) - DebugBreak(); -} - url make_prebuilt_url(const std::string& filename) { return diff --git a/src/utility.h b/src/utility.h index a4422d8..89afd3a 100644 --- a/src/utility.h +++ b/src/utility.h @@ -28,9 +28,6 @@ bool is_any_set(E e, E v) } -class context; -class url; - class bailed { public: @@ -146,6 +143,8 @@ enum class arch }; +class url; + url make_prebuilt_url(const std::string& filename); url make_appveyor_artifact_url( arch a, const std::string& project, const std::string& filename); diff --git a/src/utility/algo.h b/src/utility/algo.h index 0092638..5e9e978 100644 --- a/src/utility/algo.h +++ b/src/utility/algo.h @@ -98,6 +98,9 @@ struct repeat_converter }; +// a range that infinitely return `s`, can be used with zip() to create a pair +// with a repeating value +// template auto repeat(const T& s) { @@ -105,12 +108,15 @@ auto repeat(const T& s) } +// returns a container of pairs from both ranges; if the ranges are not the +// same size, truncates to the smallest one +// template < class Range1, class Range2, class Container=std::vector>> + typename Range1::value_type, + typename Range2::value_type>>> Container zip(const Range1& range1, const Range2& range2) { Container out; @@ -134,7 +140,8 @@ Container zip(const Range1& range1, const Range2& range2) return out; } - +// returns a vector containing the result of `f(e)` for each element `e` of `v` +// template auto map(const std::vector& v, F&& f) { diff --git a/src/utility/assert.cpp b/src/utility/assert.cpp new file mode 100644 index 0000000..8434ace --- /dev/null +++ b/src/utility/assert.cpp @@ -0,0 +1,29 @@ +#include "pch.h" +#include "assert.h" +#include "../core/context.h" + +namespace mob +{ + +void mob_assertion_failed( + const char* message, + const char* exp, const wchar_t* file, int line, const char* func) +{ + if (message) + { + gcx().error(context::generic, + "assertion failed: {}:{} {}: {} ({})", + std::wstring(file), line, func, message, exp); + } + else + { + gcx().error(context::generic, + "assertion failed: {}:{} {}: '{}'", + std::wstring(file), line, func, exp); + } + + if (IsDebuggerPresent()) + DebugBreak(); +} + +} // namespace diff --git a/src/utility/fs.h b/src/utility/fs.h index df41ceb..17f058f 100644 --- a/src/utility/fs.h +++ b/src/utility/fs.h @@ -5,6 +5,7 @@ namespace mob class context; + struct handle_closer { using pointer = HANDLE; @@ -31,6 +32,8 @@ struct file_closer using file_ptr = std::unique_ptr; +// deletes the given file in the destructor unless cancel() is called +// class file_deleter { public: @@ -49,6 +52,8 @@ private: }; +// deletes the given directory in the destructor unless cancel is called +// class directory_deleter { public: @@ -67,15 +72,47 @@ private: }; +// creates a temporary file in the given directory that is used to detect +// crashes or interruptions; some prefix is added to the filename to try and +// make it unlikely to clash +// +// for example: +// +// interruption_file ifile("some/dir", "some action"); +// +// if (ifile.exists()) +// { +// // action was previously interrupted, do something about it +// } +// +// // create interruption file +// ifile.create(); +// +// // do stuff that might fail and throw or return early +// +// // success, remove +// ifile.remove(); +// class interruption_file { public: interruption_file(const context& cx, fs::path dir, std::string name); + // path to the interruption file + // fs::path file() const; + + // whether the file exists + // bool exists() const; + + // creates the interruption file + // void create(); + + // removes the interruption file + // void remove(); private: @@ -85,12 +122,36 @@ private: }; +// creates a file in the given directory that is used to bypass an operation +// in the future; some prefix is added to the filename to try and make it +// unlikely to clash +// +// for example: +// +// bypass_file built(cx, "some/dir/", "built"); +// +// if (built.exists()) +// { +// // already built, bypass +// return; +// } +// +// // do the build process +// +// // bypass next time +// built.create(); +// class bypass_file { public: bypass_file(const context& cx, fs::path dir, std::string name); + // whether the bypass file exists + // bool exists() const; + + // creates the bypass file + // void create(); private: diff --git a/src/utility/io.cpp b/src/utility/io.cpp index 728f518..abf719a 100644 --- a/src/utility/io.cpp +++ b/src/utility/io.cpp @@ -5,41 +5,70 @@ namespace mob { -static std::mutex g_output_mutex; +enum class color_methods +{ + none = 0, + ansi, + console +}; -extern u8stream u8cout(false); -extern u8stream u8cerr(true); +// returns whether the given standard handle is for a console or is redirected +// somewhere else +// +bool is_handle_console(int handle) +{ + DWORD d = 0; + if (GetConsoleMode(GetStdHandle(handle), &d)) + { + // this is a console + return true; + } -static bool stdout_console = [] + return false; +} + +// figures out if the terminal supports ansi color codes; the old conhost +// doesn't, but the new terminal does +// +// returns color_methods::none if the output is not a console +// +color_methods get_color_method() { DWORD d = 0; if (GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &d)) { - // this is a console - return true; + if ((d & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0) + return color_methods::console; + else + return color_methods::ansi; } - return false; -}(); + // not a console + return color_methods::none; +} -static bool stderr_console = [] -{ - DWORD d = 0; - if (GetConsoleMode(GetStdHandle(STD_ERROR_HANDLE), &d)) - { - // this is a console - return true; - } +// global output mutex, avoids interleaving +static std::mutex g_output_mutex; - return false; -}(); +// streams +extern u8stream u8cout(false); +extern u8stream u8cerr(true); + +// whether stdout and stderr are for console, only check once +static bool stdout_console = is_handle_console(STD_OUTPUT_HANDLE); +static bool stderr_console = is_handle_console(STD_ERROR_HANDLE); + +// color method supported by terminal, only check once +static color_methods g_color_method = get_color_method(); void set_std_streams() { + // only set to utf16 when the output is the console + if (stdout_console) _setmode(_fileno(stdout), _O_U16TEXT); @@ -93,26 +122,6 @@ void u8stream::write_ln(std::string_view utf8) } } -enum class color_methods -{ - none = 0, - ansi, - console -}; - -static color_methods g_color_method = [] -{ - DWORD d = 0; - if (GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &d)) - { - if ((d & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0) - return color_methods::console; - else - return color_methods::ansi; - } - - return color_methods::none; -}(); console_color::console_color() : reset_(false), old_atts_(0) @@ -194,4 +203,35 @@ console_color::~console_color() } } + +font_restorer::font_restorer() + : restore_(false) +{ + std::memset(&old_, 0, sizeof(old_)); + old_.cbSize = sizeof(old_); + + if (GetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &old_)) + restore_ = true; +} + +font_restorer::~font_restorer() +{ + if (!restore_) + return; + + CONSOLE_FONT_INFOEX now = {}; + now.cbSize = sizeof(now); + + if (!GetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &now)) + return; + + if (std::wcsncmp(old_.FaceName, now.FaceName, LF_FACESIZE) != 0) + restore(); +} + +void font_restorer::restore() +{ + ::SetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &old_); +} + } // namespace diff --git a/src/utility/io.h b/src/utility/io.h index 555e1fe..feb01df 100644 --- a/src/utility/io.h +++ b/src/utility/io.h @@ -3,6 +3,9 @@ namespace mob { +// sets the current console color in the constructor, restores it in the +// destructor +// class console_color { public: @@ -14,24 +17,50 @@ public: red }; + // no-op + // console_color(); + + // sets the given color in the console + // console_color(colors c); + + // resets the color + // ~console_color(); + // non-copyable + console_color(const console_color&) = delete; + console_color& operator=(const console_color&) = delete; + private: + // true when the color was changed and must be reset in the destructor bool reset_; + + // old color WORD old_atts_; }; +// a stream that accepts utf8 strings and writes them to stdout/stderr +// +// if stdout/stderr is a console, converts to utf16 and outputs; if it's +// redirected, outputs utf8 directly +// +// thread-safe, no interleaving +// class u8stream { public: + // use stderr when err is true, stdout otherwise + // u8stream(bool err) : err_(err) { } + // outputs arguments without a newline + // template u8stream& operator<<(Args&&... args) { @@ -43,58 +72,49 @@ public: return *this; } + // outputs given string followed by a newline + // void write_ln(std::string_view utf8); private: + // whether this is stdout or stderr bool err_; - void do_output(const std::string& s); + + // outputs the given utf8 string + // + void do_output(const std::string& utf8); }; +// stdout extern u8stream u8cout; + +// stderr extern u8stream u8cerr; + +// called from main(), changes the standard output to utf-16 +// void set_std_streams(); + +// the global mutex used to avoid interleaving +// std::mutex& global_output_mutex(); // see https://github.com/isanae/mob/issues/4 // -// this restores the original console font if it changed +// this saves the current console font in the constructor and restores it in +// the destructor if it changed // class font_restorer { public: - font_restorer() - : restore_(false) - { - std::memset(&old_, 0, sizeof(old_)); - old_.cbSize = sizeof(old_); + font_restorer(); + ~font_restorer(); - if (GetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &old_)) - restore_ = true; - } - - ~font_restorer() - { - if (!restore_) - return; - - CONSOLE_FONT_INFOEX now = {}; - now.cbSize = sizeof(now); - - if (!GetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &now)) - return; - - if (std::wcsncmp(old_.FaceName, now.FaceName, LF_FACESIZE) != 0) - restore(); - } - - void restore() - { - ::SetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &old_); - } + void restore(); private: CONSOLE_FONT_INFOEX old_; diff --git a/src/utility/string.cpp b/src/utility/string.cpp index f41e4f0..f5e7228 100644 --- a/src/utility/string.cpp +++ b/src/utility/string.cpp @@ -73,46 +73,59 @@ std::vector split(const std::string& s, const std::string& seps) std::vector split_quoted(const std::string& s, const std::string& seps) { std::vector v; - bool q = false; std::string token; + // currently between double quotes + bool q = false; + for (std::size_t i=0; i T join(const std::vector& v, const Sep& sep) { @@ -41,33 +45,77 @@ T join(const std::vector& v, const Sep& sep) return s; } +// splits the given string on any character in `seps` +// std::vector split(const std::string& s, const std::string& seps); -std::vector split_quoted(const std::string& s, const std::string& seps); +// splits the given string on any character in `seps` that are not between +// double quotes +// +std::vector split_quoted( + const std::string& s, const std::string& seps); + + +// adds enough of character `c` to the end of string `s` so its length is `n` +// character +// std::string pad_right(std::string s, std::size_t n, char c=' '); + +// adds enough of character `c` to the start of string `s` so its length is `n` +// character +// std::string pad_left(std::string s, std::size_t n, char c=' '); + +// removes any character found in `what` from both start and end of string; +// in-place +// void trim(std::string& s, std::string_view what=" \t\r\n"); void trim(std::wstring& s, std::wstring_view what=L" \t\r\n"); + +// removes any character found in `what` from both start and end of string; +// returns a copy +// +std::string trim_copy(std::string_view s, std::string_view what=" \t\r\n"); +std::wstring trim_copy(std::wstring_view s, std::wstring_view what=L" \t\r\n"); + +// formats a vector of pairs into two columns, putting `indent` spaces at the +// start of each line and `spacing` spaces between the columns +// std::string table( const std::vector>& v, std::size_t indent, std::size_t spacing); -std::string trim_copy(std::string_view s, std::string_view what=" \t\r\n"); -std::wstring trim_copy(std::wstring_view s, std::wstring_view what=L" \t\r\n"); - +// converts a utf8 string to utf16 +// std::wstring utf8_to_utf16(std::string_view s); + +// converts a utf16 string to utf8 +// std::string utf16_to_utf8(std::wstring_view ws); + +// converts bytes of the given encoding to utf8 +// std::string bytes_to_utf8(encodings e, std::string_view bytes); + +// converts a utf8 string to bytes of the given encoding +// std::string utf8_to_bytes(encodings e, std::string_view utf8); + +// don't allow this for anything else than an fs::path template std::string path_to_utf8(T&&) = delete; +// convert a path to utf8; p.u8string() would work, but it returns a +// std::u8string instead of an std::string +// std::string path_to_utf8(fs::path p); +// calls f() for each line in the given string, skipping empty lines +// template void for_each_line(std::string_view s, F&& f) { @@ -87,8 +135,11 @@ void for_each_line(std::string_view s, F&& f) if (p == end || *p == '\n' || *p == '\r') { + // end of line or string + if (p != start) { + // line was not empty MOB_ASSERT(p >= start); const auto n = static_cast(p - start); @@ -97,6 +148,7 @@ void for_each_line(std::string_view s, F&& f) f(std::string_view(start, n)); } + // skip to start of next line while (p != end && (*p == '\n' || *p == '\r')) ++p; diff --git a/src/utility/threading.cpp b/src/utility/threading.cpp index a58a628..dd1d15e 100644 --- a/src/utility/threading.cpp +++ b/src/utility/threading.cpp @@ -215,22 +215,24 @@ bool thread_pool::try_add(fun thread_fun) { for (auto& t : threads_) { - if (!t->running) + if (t->running) + continue; + + // found one + + if (t->thread.joinable()) + t->thread.join(); + + t->running = true; + t->thread_fun = thread_fun; + + t->thread = std::thread([&] { - if (t->thread.joinable()) - t->thread.join(); + t->thread_fun(); + t->running = false; + }); - t->running = true; - t->thread_fun = thread_fun; - - t->thread = std::thread([&] - { - t->thread_fun(); - t->running = false; - }); - - return true; - } + return true; } return false; diff --git a/src/utility/threading.h b/src/utility/threading.h index 91ce733..ec19cf7 100644 --- a/src/utility/threading.h +++ b/src/utility/threading.h @@ -3,8 +3,13 @@ namespace mob { +// sets unhandled exception and std::terminate handlers for the current thread +// void set_thread_exception_handlers(); + +// starts a thread with exception handlers set up +// template std::thread start_thread(F&& f) { @@ -16,6 +21,8 @@ std::thread start_thread(F&& f) } +// executes a function in a thread, blocks if there are too many +// class thread_pool { public: @@ -28,7 +35,13 @@ public: thread_pool(const thread_pool&) = delete; thread_pool& operator=(const thread_pool&) = delete; + // runs the given function in a thread; if there no threads available, + // blocks until another thread finishes + // void add(fun f); + + // blocks until all threads are finished + // void join(); private: @@ -39,10 +52,11 @@ private: std::thread thread; }; - const std::size_t count_; std::vector> threads_; + // tries to find an available thread, returns false if none are found + // bool try_add(fun thread_fun); }; diff --git a/vs/mob.vcxproj b/vs/mob.vcxproj index 75ac69f..c1452f8 100644 --- a/vs/mob.vcxproj +++ b/vs/mob.vcxproj @@ -106,6 +106,7 @@ + diff --git a/vs/mob.vcxproj.filters b/vs/mob.vcxproj.filters index d43e16d..a1f3a05 100644 --- a/vs/mob.vcxproj.filters +++ b/vs/mob.vcxproj.filters @@ -165,6 +165,9 @@ src\utility + + src\utility +