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() {