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.
This commit is contained in:
Madeline Busig 2024-04-10 08:39:30 -06:00
parent 554c88f7f2
commit 82f60f4767
4 changed files with 137 additions and 26 deletions

View File

@ -8,6 +8,12 @@ namespace mtl {
namespace log { namespace log {
#ifdef __GBA__
constexpr char endl_char = 0;
#else
constexpr char endl_char = '\n';
#endif
/** /**
* \brief Log stream * \brief Log stream
* *
@ -15,9 +21,16 @@ namespace log {
* output depending on the platform. Each flush is treated as a separate message. * output depending on the platform. Each flush is treated as a separate message.
* The capacity of the stream depends on the platform. * 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<false, endl_char> {
private: private:
uint32_t m_log_level; uint32_t m_log_level;
@ -25,8 +38,8 @@ public:
stream(istring& str, uint32_t log_level); stream(istring& str, uint32_t log_level);
~stream(); ~stream();
using string_stream::operator=; using basic_string_stream::operator=;
using string_stream::operator<<; using basic_string_stream::operator<<;
void flush() override; void flush() override;
}; };

View File

@ -8,19 +8,42 @@ namespace mtl {
/** /**
* \brief Basic string builder * \brief Basic string builder
* *
* Mimics std::stringstream basic functionality. Does not provide thorough * Mimics std::stringstream basic functionality. Should not be used directly,
* formatting options, only decimal/hexadecimal switching. If more extensive * instead mtl::string_stream or mtl::string_streamx should be used depending
* formatting is needed, use mtl::string_streamx instead. * 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 * 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. * until flush is called, mtl::flush is piped, or mtl::endl is piped.
*/ */
class string_stream { template <bool EXT = false, char ENDL = '\n'>
class basic_string_stream {
protected:
istring& m_str; istring& m_str;
bool m_hex = false; bool m_hex = false;
struct ext_format {
size_t width = 0;
bool left = 0;
char fill_char = ' ';
} m_ext_format;
public: 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 * \brief Flushes the buffer to the underlying output sequence and
@ -39,13 +62,35 @@ public:
m_str.clear(); 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); 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; 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; 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; return *this;
} }
@ -59,9 +104,21 @@ public:
* because booleans are not signed. * because booleans are not signed.
*/ */
template <typename T, std::enable_if_t<std::is_signed_v<T>, bool> = true> template <typename T, std::enable_if_t<std::is_signed_v<T>, bool> = true>
string_stream& operator<<(T x) { basic_string_stream& operator<<(T x) {
string<16> str; 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; return *this;
} }
@ -76,9 +133,21 @@ public:
* std::is_unsigned counts booleans as unsigned integers. * std::is_unsigned counts booleans as unsigned integers.
*/ */
template <typename T, std::enable_if_t<std::is_unsigned_v<T> && !std::is_same_v<T, bool>, bool> = true> template <typename T, std::enable_if_t<std::is_unsigned_v<T> && !std::is_same_v<T, bool>, bool> = true>
string_stream& operator<<(T x) { basic_string_stream& operator<<(T x) {
string<16> str; 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; return *this;
} }
@ -93,8 +162,20 @@ public:
* implicitly converted to boolean instead of mtl::string_view. * implicitly converted to boolean instead of mtl::string_view.
*/ */
template <typename T, std::enable_if_t<std::is_same_v<T, bool>, bool> = true> template <typename T, std::enable_if_t<std::is_same_v<T, bool>, bool> = true>
string_stream& operator<<(T x) { basic_string_stream& operator<<(T x) {
m_str.append(x ? "true" : "false"); 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; return *this;
} }
@ -112,31 +193,48 @@ public:
template <typename T, std::enable_if_t<std::is_pointer_v<T> template <typename T, std::enable_if_t<std::is_pointer_v<T>
&& !std::is_same_v<T, char*> && !std::is_same_v<T, char*>
&& !std::is_same_v<T, const char*>, bool> = true> && !std::is_same_v<T, const char*>, bool> = true>
string_stream& operator<<(T addr) { basic_string_stream& operator<<(T addr) {
string<16> str; 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; return *this;
} }
string_stream& operator<<(_flush) { basic_string_stream& operator<<(_flush) {
flush(); flush();
return *this; return *this;
} }
string_stream& operator<<(_endl) { basic_string_stream& operator<<(_endl) {
m_str += '\n'; if constexpr (ENDL) {
m_str += ENDL;
}
flush(); flush();
return *this; return *this;
} }
string_stream& operator<<(_dec) { basic_string_stream& operator<<(_dec) {
m_hex = false; m_hex = false;
return *this; return *this;
} }
string_stream& operator<<(_hex) { basic_string_stream& operator<<(_hex) {
m_hex = true; m_hex = true;
return *this; return *this;
} }
}; };
using string_stream = basic_string_stream<>;
using string_streamx = basic_string_stream<true>;
} // namespace mtl } // namespace mtl

View File

@ -40,7 +40,7 @@ static string_ext string(reinterpret_cast<char*>(0x4FFF600), 256);
}; // namespace reg }; // namespace reg
stream::stream(istring& buf, uint32_t log_level) 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; reg::enable = 0xC0DE;
} }
stream::~stream() { stream::~stream() {

View File

@ -10,7 +10,7 @@ namespace log {
static string<512> buffer; static string<512> buffer;
stream::stream(istring& buf, uint32_t log_level) 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() { } stream::~stream() { }
void stream::flush() { void stream::flush() {