From e5b0e242e807389dc314e9f7f64914eecf84d2c6 Mon Sep 17 00:00:00 2001 From: Myles Busig Date: Tue, 26 Mar 2024 23:06:58 -0600 Subject: [PATCH] Add string_streamx string_streamx is a string_stream with additional formatting options. Currently the only extra option is the ability to expand strings to a length using a fill character, along with left/right justification. More options similar to std::stringstream may be added in the future. These extra options do come at a performance cost, and string_stream should be preferred unless the extra options are absolutely needed. --- include/mtl/string_streamx.hpp | 234 +++++++++++++++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 include/mtl/string_streamx.hpp diff --git a/include/mtl/string_streamx.hpp b/include/mtl/string_streamx.hpp new file mode 100644 index 0000000..a257268 --- /dev/null +++ b/include/mtl/string_streamx.hpp @@ -0,0 +1,234 @@ +#pragma once + +#include "mtl/stream.hpp" +#include "mtl/string.hpp" + +namespace mtl { + +/** + * \brief Extended string builder + * + * Provides the same functionality as mtl::string_stream along with additional + * formatting options. Allows 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. + * + * The extra functionality provided by this class does come at a performance + * cost. mtl::string_stream should be preferred unless these options are + * absolutely needed. + * + * 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_streamx { + istring& m_str; + struct { + size_t width = 0; + bool hex = false; + bool left = 0; + char fill_char = ' '; + } m_format; + +public: + string_streamx(istring& str) : m_str(str) {} + + /** + * \brief Flushes the buffer to the underlying output sequence and + * clears the buffer + * + * By default simply clears the buffer, but can be overriden to + * provide more functionality. This function is called internally when + * mtl::flush and mtl::endl are piped to the stream. The derived + * class must be sure to clear the buffer when finished. + */ + virtual void flush() { + m_str.clear(); + } + + void clear() { + m_str.clear(); + } + + string_streamx& operator<<(const string_view& str) { + if (str.length() < m_format.width && !m_format.left) { + m_str.append(m_format.width - str.length(), m_format.fill_char); + } + + m_str.append(str); + + if (str.length() < m_format.width && m_format.left) { + m_str.append(m_format.width - str.length(), m_format.fill_char); + } + + m_format.width = 0; + return *this; + } + + string_streamx& operator<<(char ch) { + if (1 < m_format.width && !m_format.left) { + m_str.append(m_format.width - 1, m_format.fill_char); + } + + m_str += ch; + + if (1 < m_format.width && m_format.left) { + m_str.append(m_format.width - 1, m_format.fill_char); + } + + m_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> + string_streamx& operator<<(T x) { + string<16> str; + to_string(x, str, m_format.hex); + + if (str.length() < m_format.width && !m_format.left) { + m_str.append(m_format.width - str.length(), m_format.fill_char); + } + + m_str.append(str); + + if (str.length() < m_format.width && m_format.left) { + m_str.append(m_format.width - str.length(), m_format.fill_char); + } + + m_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> + string_streamx& operator<<(T x) { + string<16> str; + to_string(x, str, m_format.hex); + + if (str.length() < m_format.width && !m_format.left) { + m_str.append(m_format.width - str.length(), m_format.fill_char); + } + + m_str.append(str); + + if (str.length() < m_format.width && m_format.left) { + m_str.append(m_format.width - str.length(), m_format.fill_char); + } + + m_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> + string_streamx& operator<<(T x) { + string<5> str = x ? "true" : "false"; + + if (str.length() < m_format.width && !m_format.left) { + m_str.append(m_format.width - str.length(), m_format.fill_char); + } + + m_str.append(str); + + if (str.length() < m_format.width && m_format.left) { + m_str.append(m_format.width - str.length(), m_format.fill_char); + } + + m_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> + string_streamx& operator<<(T addr) { + string<16> str; + to_string(addr, str); + + if (str.length() < m_format.width && !m_format.left) { + m_str.append(m_format.width - str.length(), m_format.fill_char); + } + + m_str.append(str); + + if (str.length() < m_format.width && m_format.left) { + m_str.append(m_format.width - str.length(), m_format.fill_char); + } + + m_format.width = 0; + + return *this; + } + + string_streamx& operator<<(_flush) { + flush(); + return *this; + } + string_streamx& operator<<(_endl) { + m_str += '\n'; + flush(); + return *this; + } + + string_streamx& operator<<(_dec) { + m_format.hex = false; + return *this; + } + string_streamx& operator<<(_hex) { + m_format.hex = true; + return *this; + } + + string_streamx& operator<<(_left) { + m_format.left = true; + return *this; + } + string_streamx& operator<<(_right) { + m_format.left = false; + return *this; + } + string_streamx& operator<<(setw w) { + m_format.width = w.m_w; + return *this; + } +}; + +} // namespace mtl