mtl/include/mtl/string_stream.hpp
Madeline Busig 4dd979ef54 Change basic_string_stream to not clear the buffer on flush
If the buffer is cleared when flushed, the class does not function
correctly as a string builder. For example, if a string is built with a
newline inside, everything before the newline will be cleared and the
string will be incomplete. Clearing the buffer on flush only makes sense
for applications such as logging or writting to a file.
2024-04-10 08:46:21 -06:00

238 lines
6.7 KiB
C++

#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 <bool EXT = false, char ENDL = '\n'>
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 <typename T, std::enable_if_t<std::is_signed_v<T>, 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 <typename T, std::enable_if_t<std::is_unsigned_v<T> && !std::is_same_v<T, bool>, 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 <typename T, std::enable_if_t<std::is_same_v<T, bool>, 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 <typename T, std::enable_if_t<std::is_pointer_v<T>
&& !std::is_same_v<T, char*>
&& !std::is_same_v<T, const char*>, 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<true>;
} // namespace mtl