#pragma once #include "mtl/stream.hpp" #include "mtl/string.hpp" namespace mtl { /** * \brief Basic string builder * * 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. */ 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: basic_string_stream(istring& str) : m_str(str) {} /** * \brief Flushes the buffer to the underlying output sequence and * clears the buffer * * By default is a NOP, but can be overriden to provide more * functionality. This function is called internally when * mtl::flush and mtl::endl are piped to the stream. */ virtual void flush() { } void clear() { m_str.clear(); } 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; } 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; } /** * \brief Appends the signed integer x * * Writes the integer in decimal if the stream was piped mtl::dec, * hexidecimal if the stream was piped mtl::hex. Defaults to mtl::dec. * * The template does not need to check that the integer is a boolean * because booleans are not signed. */ template , bool> = true> basic_string_stream& operator<<(T x) { string<16> str; 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; } /** * \brief Appends the unsigned integer x * * Writes the integer in decimal if the stream was piped mtl::dec, * hexidecimal if the stream was piped mtl::hex. Defaults to mtl::dec. * * The template must check that the integer is not a boolean because * std::is_unsigned counts booleans as unsigned integers. */ template && !std::is_same_v, bool> = true> basic_string_stream& operator<<(T x) { string<16> str; 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; } /** * \brief Appends the boolean x * * Writes the boolean as either "true" or "false". * * This function needs to be templated otherwise non-boolean arguments * might be implicitly converted to boolean. Ex. const char* would be * implicitly converted to boolean instead of mtl::string_view. */ template , bool> = true> 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; } /** * \brief Appends the address of the pointer addr * * Always writes the address is hexidecimal format. Does not pad the * value to the address width. * * The template must check that the pointer is not a character pointer * otherwise C strings will not be converted to a mtl::string_view and * the address of the string will be printed instead. */ template && !std::is_same_v && !std::is_same_v, bool> = true> basic_string_stream& operator<<(T addr) { string<16> 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; } basic_string_stream& operator<<(_flush) { flush(); return *this; } basic_string_stream& operator<<(_endl) { if constexpr (ENDL) { m_str += ENDL; } flush(); return *this; } basic_string_stream& operator<<(_dec) { m_hex = false; return *this; } basic_string_stream& operator<<(_hex) { m_hex = true; return *this; } }; using string_stream = basic_string_stream<>; using string_streamx = basic_string_stream; } // namespace mtl