From dff93169879c5b1ea2d831d88e4e0ee3d01231ca Mon Sep 17 00:00:00 2001 From: Myles Busig Date: Wed, 10 Apr 2024 08:39:30 -0600 Subject: [PATCH] Refactor string_stream and string_streamx, and provide ENDL selection option This commit refactors string_stream and string_streamx into a common basic_string_stream template class. When the EXT template == true, the string_streamx formatting options are enabled, they default to disabled. These options are enabled at compile time, and do not affect performance when they are disabled. By implementing the two streams in this manner, duplicated code is removed. This commit also adds the ENDL template paramter. When ENDL is set to zero, no endline character is printed when piping mtl::endl. Otherwise, the character is printed. Defaults to '\n'. This allows logging on the GBA to handle mtl::endl correctly and not print two newlines on the MGBA emulator. --- include/mtl/log.hpp | 21 +++++- include/mtl/string_stream.hpp | 138 +++++++++++++++++++++++++++++----- src/gba/log.cpp | 2 +- src/log.cpp | 2 +- 4 files changed, 137 insertions(+), 26 deletions(-) diff --git a/include/mtl/log.hpp b/include/mtl/log.hpp index e5f74ad..cffa01f 100644 --- a/include/mtl/log.hpp +++ b/include/mtl/log.hpp @@ -8,6 +8,12 @@ namespace mtl { namespace log { +#ifdef __GBA__ +constexpr char endl_char = 0; +#else +constexpr char endl_char = '\n'; +#endif + /** * \brief Log stream * @@ -15,9 +21,16 @@ namespace log { * output depending on the platform. Each flush is treated as a separate message. * The capacity of the stream depends on the platform. * - * See mtl::string_stream for information on when the stream is flushed. + * See mtl::basic_string_stream for information on when the stream is flushed. + * + * Platform specific info: + * + * GBA: Flushing cannot be done without printing to a new line. Trying to + * insert a newline will instead just flush the stream onto a new line. On MGBA + * this also starts the line with "GBA Debug: ", so the line is not completely + * blank. */ -class stream : public string_stream { +class stream : public basic_string_stream { private: uint32_t m_log_level; @@ -25,8 +38,8 @@ public: stream(istring& str, uint32_t log_level); ~stream(); - using string_stream::operator=; - using string_stream::operator<<; + using basic_string_stream::operator=; + using basic_string_stream::operator<<; void flush() override; }; diff --git a/include/mtl/string_stream.hpp b/include/mtl/string_stream.hpp index 0b29b3d..732d8aa 100644 --- a/include/mtl/string_stream.hpp +++ b/include/mtl/string_stream.hpp @@ -8,19 +8,42 @@ namespace mtl { /** * \brief Basic string builder * - * Mimics std::stringstream basic functionality. Does not provide thorough - * formatting options, only decimal/hexadecimal switching. If more extensive - * formatting is needed, use mtl::string_streamx instead. + * Mimics std::stringstream basic functionality. Should not be used directly, + * instead mtl::string_stream or mtl::string_streamx should be used depending + * on whether extended formatting options are needed. + * + * If EXT == false, only decimal/hexadecimal formatting options are enabled. + * Pipe mtl::dec or mtl::hex to print variables in decimal or hexadecimal + * format, respectively. Defaults to decimal format. + * + * If EXT == true, additionally enables expanding strings to a given length + * using a fill character. Width is set by piping mtl::setw() and applies to + * the next value only. Justification is set by piping mtl::left or mtl::right. + * These extra formatting options do not affect performance when they are + * enabled, due to being enabled at compile time. They do affect performance + * when they are enabled though, and should only be used when absolutely needed. + * + * ENDL is the character that is printed when mtl::endl is piped. Can be set + * to zero if no newline character is needed, and the stream will only be + * flushed instead. * * Variables are piped to the stream using operator<<. The string is buffered * until flush is called, mtl::flush is piped, or mtl::endl is piped. */ -class string_stream { +template +class basic_string_stream { +protected: istring& m_str; bool m_hex = false; + struct ext_format { + size_t width = 0; + bool left = 0; + char fill_char = ' '; + } m_ext_format; + public: - string_stream(istring& str) : m_str(str) {} + basic_string_stream(istring& str) : m_str(str) {} /** * \brief Flushes the buffer to the underlying output sequence and @@ -39,13 +62,35 @@ public: m_str.clear(); } - string_stream& operator<<(const string_view& str) { + basic_string_stream& operator<<(const string_view& str) { + if (EXT && str.length() < m_ext_format.width && !m_ext_format.left) { + m_str.append(m_ext_format.width - str.length(), m_ext_format.fill_char); + } + m_str.append(str); + + if (EXT && str.length() < m_ext_format.width && m_ext_format.left) { + m_str.append(m_ext_format.width - str.length(), m_ext_format.fill_char); + } + + if (EXT) { m_ext_format.width = 0; } + return *this; } - string_stream& operator<<(char ch) { + basic_string_stream& operator<<(char ch) { + if (EXT && 1 < m_ext_format.width && !m_ext_format.left) { + m_str.append(m_ext_format.width - 1, m_ext_format.fill_char); + } + m_str += ch; + + if (EXT && 1 < m_ext_format.width && m_ext_format.left) { + m_str.append(m_ext_format.width - 1, m_ext_format.fill_char); + } + + if (EXT) { m_ext_format.width = 0; } + return *this; } @@ -59,9 +104,21 @@ public: * because booleans are not signed. */ template , bool> = true> - string_stream& operator<<(T x) { + basic_string_stream& operator<<(T x) { string<16> str; - m_str.append(to_string(x, str, m_hex)); + to_string(x, str, m_hex); + + if (EXT && str.length() < m_ext_format.width && !m_ext_format.left) { + m_str.append(m_ext_format.width - str.length(), m_ext_format.fill_char); + } + + m_str.append(str); + + if (EXT && str.length() < m_ext_format.width && m_ext_format.left) { + m_str.append(m_ext_format.width - str.length(), m_ext_format.fill_char); + } + + if (EXT) { m_ext_format.width = 0; } return *this; } @@ -76,9 +133,21 @@ public: * std::is_unsigned counts booleans as unsigned integers. */ template && !std::is_same_v, bool> = true> - string_stream& operator<<(T x) { + basic_string_stream& operator<<(T x) { string<16> str; - m_str.append(to_string(x, str, m_hex)); + to_string(x, str, m_hex); + + if (EXT && str.length() < m_ext_format.width && !m_ext_format.left) { + m_str.append(m_ext_format.width - str.length(), m_ext_format.fill_char); + } + + m_str.append(str); + + if (EXT && str.length() < m_ext_format.width && m_ext_format.left) { + m_str.append(m_ext_format.width - str.length(), m_ext_format.fill_char); + } + + if (EXT) { m_ext_format.width = 0; } return *this; } @@ -93,8 +162,20 @@ public: * implicitly converted to boolean instead of mtl::string_view. */ template , bool> = true> - string_stream& operator<<(T x) { - m_str.append(x ? "true" : "false"); + basic_string_stream& operator<<(T x) { + string_view str = x ? "true" : "false"; + + if (EXT && str.length() < m_ext_format.width && !m_ext_format.left) { + m_str.append(m_ext_format.width - str.length(), m_ext_format.fill_char); + } + + m_str.append(str); + + if (EXT && str.length() < m_ext_format.width && m_ext_format.left) { + m_str.append(m_ext_format.width - str.length(), m_ext_format.fill_char); + } + + if (EXT) { m_ext_format.width = 0; } return *this; } @@ -112,31 +193,48 @@ public: template && !std::is_same_v && !std::is_same_v, bool> = true> - string_stream& operator<<(T addr) { + basic_string_stream& operator<<(T addr) { string<16> str; - m_str.append(to_string(addr, str)); + to_string(addr, str); + + if (EXT && str.length() < m_ext_format.width && !m_ext_format.left) { + m_str.append(m_ext_format.width - str.length(), m_ext_format.fill_char); + } + + m_str.append(str); + + if (EXT && str.length() < m_ext_format.width && m_ext_format.left) { + m_str.append(m_ext_format.width - str.length(), m_ext_format.fill_char); + } + + if (EXT) { m_ext_format.width = 0; } return *this; } - string_stream& operator<<(_flush) { + basic_string_stream& operator<<(_flush) { flush(); return *this; } - string_stream& operator<<(_endl) { - m_str += '\n'; + basic_string_stream& operator<<(_endl) { + if constexpr (ENDL) { + m_str += ENDL; + } flush(); return *this; } - string_stream& operator<<(_dec) { + basic_string_stream& operator<<(_dec) { m_hex = false; return *this; } - string_stream& operator<<(_hex) { + basic_string_stream& operator<<(_hex) { m_hex = true; return *this; } }; +using string_stream = basic_string_stream<>; +using string_streamx = basic_string_stream; + } // namespace mtl diff --git a/src/gba/log.cpp b/src/gba/log.cpp index 8f7fa14..5e3678c 100644 --- a/src/gba/log.cpp +++ b/src/gba/log.cpp @@ -40,7 +40,7 @@ static string_ext string(reinterpret_cast(0x4FFF600), 256); }; // namespace reg stream::stream(istring& buf, uint32_t log_level) - : string_stream(buf), m_log_level(log_level) { + : basic_string_stream(buf), m_log_level(log_level) { reg::enable = 0xC0DE; } stream::~stream() { diff --git a/src/log.cpp b/src/log.cpp index ee6de85..05b01eb 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -10,7 +10,7 @@ namespace log { static string<512> buffer; stream::stream(istring& buf, uint32_t log_level) - : string_stream(buf), m_log_level(log_level) { } + : basic_string_stream(buf), m_log_level(log_level) { } stream::~stream() { } void stream::flush() {