From 835481f4a59460d11eaa653db1975c6ff0ff2076 Mon Sep 17 00:00:00 2001 From: Riyyi Date: Fri, 22 Jul 2022 01:59:19 +0200 Subject: [PATCH] Util: Add formatting and type printing capability Extending the type compatibility is done with an easy to use API. --- src/util/format/.clang-tidy | 6 + src/util/format/builder.cpp | 62 +++++++++++ src/util/format/builder.h | 50 +++++++++ src/util/format/format.cpp | 216 ++++++++++++++++++++++++++++++++++++ src/util/format/format.h | 198 +++++++++++++++++++++++++++++++++ src/util/format/parser.cpp | 151 +++++++++++++++++++++++++ src/util/format/parser.h | 37 ++++++ src/util/format/toformat.h | 168 ++++++++++++++++++++++++++++ 8 files changed, 888 insertions(+) create mode 100644 src/util/format/.clang-tidy create mode 100644 src/util/format/builder.cpp create mode 100644 src/util/format/builder.h create mode 100644 src/util/format/format.cpp create mode 100644 src/util/format/format.h create mode 100644 src/util/format/parser.cpp create mode 100644 src/util/format/parser.h create mode 100644 src/util/format/toformat.h diff --git a/src/util/format/.clang-tidy b/src/util/format/.clang-tidy new file mode 100644 index 0000000..84a57de --- /dev/null +++ b/src/util/format/.clang-tidy @@ -0,0 +1,6 @@ +# -*- yaml -*- + +--- +# FIXME: Figure out why NOLINTBEGIN/NOLINTEND doesnt work +Checks: -misc-unused-using-decls +... diff --git a/src/util/format/builder.cpp b/src/util/format/builder.cpp new file mode 100644 index 0000000..ea15036 --- /dev/null +++ b/src/util/format/builder.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2022 Riyyi + * + * SPDX-License-Identifier: MIT + */ + +#include // size_t +#include // setprecision +#include // fixed +#include // numeric_limits +#include // stringstream +#include +#include + +#include "util/format/builder.h" + +namespace Util::Format { + +void Builder::putLiteral(std::string_view literal) +{ + for (size_t i = 0; i < literal.length(); ++i) { + putCharacter(literal[i]); + if (literal[i] == '{' || literal[i] == '}') { + ++i; + } + } +} + +void Builder::putF32(float number, size_t precision) const +{ + precision = (precision > std::numeric_limits::digits10) ? m_precision : precision; + + std::stringstream stream; + stream + << std::fixed + << std::setprecision(precision) + << number; + std::string string = stream.str(); + string = string.substr(0, string.find_first_of('0', string.find('.'))); + m_builder << string; +} + +void Builder::putF64(double number, size_t precision) const +{ + precision = (precision > std::numeric_limits::digits10) ? m_precision : precision; + + std::stringstream stream; + stream + << std::fixed + << std::setprecision(precision) + << number; + std::string string = stream.str(); + string = string.substr(0, string.find_first_of('0', string.find('.'))); + m_builder << string; +} + +void Builder::resetSpecifiers() +{ + setPrecision(); +} + +} // namespace Util::Format diff --git a/src/util/format/builder.h b/src/util/format/builder.h new file mode 100644 index 0000000..0755b75 --- /dev/null +++ b/src/util/format/builder.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2022 Riyyi + * + * SPDX-License-Identifier: MIT + */ + +#ifndef UTIL_FORMAT_BUILDER_H +#define UTIL_FORMAT_BUILDER_H + +#include // size_t +#include // int32_t, uint32_t, int64_t +#include // stringstream +#include + +namespace Util::Format { + +class Builder { +public: + explicit Builder(std::stringstream& builder) + : m_precision(6) + , m_builder(builder) + { + } + + void putLiteral(std::string_view literal); + + void putI32(int32_t number) const { m_builder << number; } // int + void putU32(uint32_t number) const { m_builder << number; } // unsigned int + void putI64(int64_t number) const { m_builder << number; } // long int + void putU64(size_t number) const { m_builder << number; } // long unsigned int + void putF32(float number, size_t precision = -1) const; + void putF64(double number, size_t precision = -1) const; + void putCharacter(char character) const { m_builder.write(&character, 1); } + void putString(const std::string_view string) const { m_builder.write(string.data(), string.length()); } + void putPointer(const void* pointer) const { m_builder << pointer; } + + void resetSpecifiers(); + void setPrecision(size_t precision = 6) { m_precision = precision; }; + + const std::stringstream& builder() const { return m_builder; } + std::stringstream& builder() { return m_builder; } + +private: + size_t m_precision { 6 }; + std::stringstream& m_builder; +}; + +} // namespace Util::Format + +#endif // UTIL_FORMAT_BUILDER_H diff --git a/src/util/format/format.cpp b/src/util/format/format.cpp new file mode 100644 index 0000000..694932c --- /dev/null +++ b/src/util/format/format.cpp @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2022 Riyyi + * + * SPDX-License-Identifier: MIT + */ + +#include // stringstream +#include +#include + +#include "util/format/builder.h" +#include "util/format/format.h" +#include "util/format/parser.h" + +namespace Util::Format { + +void variadicFormatImpl(Builder& builder, Parser& parser, TypeErasedParameters& parameters) +{ + const auto literal = parser.consumeLiteral(); + builder.putLiteral(literal); + + if (!parameters.isEOF()) { + std::string_view specifier; + if (parser.consumeSpecifier(specifier)) { + parser.applySpecifier(builder, specifier); + } + + auto& parameter = parameters.parameter(parameters.tell()); + parameter.format(builder, parameter.value); + parameters.ignore(); + + builder.resetSpecifiers(); + } + + if (!parser.isEOF()) { + variadicFormatImpl(builder, parser, parameters); + } +} + +void variadicFormat(std::stringstream& stream, std::string_view format, TypeErasedParameters& parameters) +{ + Builder builder { stream }; + Parser parser { format, parameters.size() }; + + variadicFormatImpl(builder, parser, parameters); +} + +// ----------------------------------------- + +void prettyVariadicFormat(Type type, bool bold, std::string_view format, TypeErasedParameters& parameters) +{ + std::stringstream stream; + + if (type != Type::None || bold) { + stream << "\033["; + switch (type) { + case Type::Info: + stream << "34"; + break; + case Type::Warn: + stream << "33"; + break; + case Type::Danger: + stream << "31"; + break; + case Type::Success: + stream << "32"; + break; + case Type::Comment: + stream << "37"; + break; + default: + break; + }; + + if (bold) { + stream << ";1"; + } + stream << "m"; + } + + variadicFormat(stream, format, parameters); + + if (type != Type::None || bold) { + stream << "\033[0m"; + } + + printf("%s\n", stream.str().c_str()); +} + +// ----------------------------------------- + +Dbg::Dbg(Type type, bool bold) + : m_type(type) + , m_bold(bold) + , m_stream() + , m_builder(m_stream) +{ + if (type != Type::None || m_bold) { + m_stream << "\033["; + switch (type) { + case Type::Info: + m_stream << "34"; + break; + case Type::Warn: + m_stream << "33"; + break; + case Type::Danger: + m_stream << "31"; + break; + case Type::Success: + m_stream << "32"; + break; + case Type::Comment: + m_stream << "37"; + break; + default: + break; + }; + + if (m_bold) { + m_stream << ";1"; + } + m_stream << "m"; + } +} + +Dbg::~Dbg() +{ + if (m_type != Type::None || m_bold) { + m_stream << "\033[0m"; + } + + printf("%s", m_stream.str().c_str()); +} + +Dbg dbg() +{ + return Dbg(Type::None, false); +} + +Dbg dbgb() +{ + return Dbg(Type::None, true); +} + +Dbg info() +{ + return Dbg(Type::Info, false); +} + +Dbg infob() +{ + return Dbg(Type::Info, true); +} + +Dbg warn() +{ + return Dbg(Type::Warn, false); +} + +Dbg warnb() +{ + return Dbg(Type::Warn, true); +} + +Dbg danger() +{ + return Dbg(Type::Danger, false); +} + +Dbg dangerb() +{ + return Dbg(Type::Danger, true); +} + +Dbg success() +{ + return Dbg(Type::Success, false); +} + +Dbg successb() +{ + return Dbg(Type::Success, true); +} + +Dbg comment() +{ + return Dbg(Type::Comment, false); +} + +Dbg commentb() +{ + return Dbg(Type::Comment, true); +} + +// ----------------------------------------- + +Str::Str(std::string& fill) + : m_fill(fill) + , m_stream() + , m_builder(m_stream) +{ +} + +Str::~Str() +{ + m_fill = m_stream.str(); +} + +Str str(std::string& fill) +{ + return Str(fill); +} + +} // namespace Util::Format diff --git a/src/util/format/format.h b/src/util/format/format.h new file mode 100644 index 0000000..1a60b1c --- /dev/null +++ b/src/util/format/format.h @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2022 Riyyi + * + * SPDX-License-Identifier: MIT + */ + +#ifndef UTIL_FORMAT_FORMAT_H +#define UTIL_FORMAT_FORMAT_H + +#include // size_t +#include // stringstream +#include +#include +#include + +#include "util/format/builder.h" +#include "util/format/parser.h" +#include "util/format/toformat.h" + +namespace Util::Format { + +struct Parameter { + const void* value; + void (*format)(Builder& builder, const void* value); +}; + +template +void formatParameterValue(Builder& builder, const void* value) +{ + format(builder, *static_cast(value)); +} + +class TypeErasedParameters { +public: + const Parameter parameter(size_t index) { return m_parameters[index]; } + + size_t tell() const { return m_index; } + size_t size() const { return m_parameters.size(); } + bool isEOF() const { return m_index >= m_parameters.size(); } + void ignore() { m_index++; } + +protected: + size_t m_index { 0 }; + std::vector m_parameters; +}; + +template +class VariadicParameters final : public TypeErasedParameters { +public: + VariadicParameters(const Parameters&... parameters) + { + m_parameters = { { ¶meters, formatParameterValue }... }; + } +}; + +// ----------------------------------------- + +void variadicFormatImpl(Builder& builder, Parser& parser, TypeErasedParameters& parameters); +void variadicFormat(std::stringstream& stream, std::string_view format, TypeErasedParameters& parameters); + +// ----------------------------------------- + +enum class Type { + None, // Foreground + Info, // Blue + Warn, // Yellow + Danger, // Red + Success, // Green + Comment, // White +}; + +void prettyVariadicFormat(Type type, bool bold, std::string_view format, TypeErasedParameters& parameters); + +#define FORMAT_FUNCTION(name, type, bold) \ + template \ + void name(const char(&format)[N] = "", const Parameters&... parameters) \ + { \ + VariadicParameters variadicParameters { parameters... }; \ + prettyVariadicFormat(Type::type, bold, { format, N - 1 }, variadicParameters); \ + } + +FORMAT_FUNCTION(dbgln, None, false); +FORMAT_FUNCTION(dbgbln, None, true); +FORMAT_FUNCTION(infoln, Info, false); +FORMAT_FUNCTION(infobln, Info, true); +FORMAT_FUNCTION(warnln, Warn, false); +FORMAT_FUNCTION(warnbln, Warn, true); +FORMAT_FUNCTION(dangerln, Danger, false); +FORMAT_FUNCTION(dangerbln, Danger, true); +FORMAT_FUNCTION(successln, Success, false); +FORMAT_FUNCTION(successbln, Success, true); +FORMAT_FUNCTION(commentln, Comment, false); +FORMAT_FUNCTION(commentbln, Comment, true); + +// ----------------------------------------- + +class Dbg { +public: + Dbg(Type type, bool bold); + virtual ~Dbg(); + + Builder& builder() { return m_builder; } + +private: + Type m_type; + bool m_bold; + + std::stringstream m_stream; + Builder m_builder; +}; + +template +const Dbg& operator<<(const Dbg& debug, const T& value) +{ + format(const_cast(debug).builder(), value); + return debug; +} + +Dbg dbg(); +Dbg dbgb(); +Dbg info(); +Dbg infob(); +Dbg warn(); +Dbg warnb(); +Dbg danger(); +Dbg dangerb(); +Dbg success(); +Dbg successb(); +Dbg comment(); +Dbg commentb(); + +// ----------------------------------------- + +template +void strln(std::string& fill, std::string_view format, const Parameters&... parameters) +{ + std::stringstream stream; + VariadicParameters variadicParameters { parameters... }; + variadicFormat(stream, format, variadicParameters); + fill = stream.str(); +} + +class Str { +public: + Str(std::string& fill); + virtual ~Str(); + + Builder& builder() { return m_builder; } + +private: + std::string& m_fill; + std::stringstream m_stream; + Builder m_builder; +}; + +template +const Str& operator<<(const Str& string, const T& value) +{ + format(const_cast(string).builder(), value); + return string; +} + +Str str(std::string& fill); + +} // namespace Util::Format + +using Util::Format::commentbln; +using Util::Format::commentln; +using Util::Format::dangerbln; +using Util::Format::dangerln; +using Util::Format::dbgbln; +using Util::Format::dbgln; +using Util::Format::infobln; +using Util::Format::infoln; +using Util::Format::successbln; +using Util::Format::successln; +using Util::Format::warnbln; +using Util::Format::warnln; + +using Util::Format::comment; +using Util::Format::commentb; +using Util::Format::danger; +using Util::Format::dangerb; +using Util::Format::dbg; +using Util::Format::dbgb; +using Util::Format::info; +using Util::Format::infob; +using Util::Format::success; +using Util::Format::successb; +using Util::Format::warn; +using Util::Format::warnb; + +using Util::Format::str; +using Util::Format::strln; + +using FormatBuilder = Util::Format::Builder; + +#endif // UTIL_FORMAT_FORMAT_H diff --git a/src/util/format/parser.cpp b/src/util/format/parser.cpp new file mode 100644 index 0000000..ed4db1c --- /dev/null +++ b/src/util/format/parser.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2022 Riyyi + * + * SPDX-License-Identifier: MIT + */ + +#include // replace +#include // assert +#include // size_t +#include +#include + +#include "util/format/builder.h" +#include "util/format/parser.h" + +namespace Util::Format { + +Parser::Parser(std::string_view format, size_t parameterCount) + : GenericLexer(format) + , m_parameterCount(parameterCount) +{ + checkFormatParameterConsistency(); +} + +Parser::~Parser() +{ +} + +// ----------------------------------------- + +void Parser::checkFormatParameterConsistency() +{ + size_t length = m_input.length(); + + // Format string does not reference all passed parameters + assert(length >= m_parameterCount * 2); + + size_t braceOpen = 0; + size_t braceClose = 0; + while (!isEOF()) { + char peek0 = peek(); + char peek1 = peek(1); + + if (peek0 == '{' && peek1 == '{') { + ignore(2); + continue; + } + + if (peek0 == '}' && peek1 == '}') { + ignore(2); + continue; + } + + if (peek0 == '{') { + braceOpen++; + + // Only empty references {} or specified references {:} are valid + assert(peek1 == '}' || peek1 == ':'); + } + if (peek0 == '}') { + braceClose++; + } + + ignore(); + } + m_index = 0; + + // Extra open braces in format string + assert(!(braceOpen < braceClose)); + + // Extra closing braces in format string + assert(!(braceOpen > braceClose)); + + // Format string does not reference all passed parameters + assert(!(braceOpen < m_parameterCount)); + + // Format string references nonexistent parameter + assert(!(braceOpen > m_parameterCount)); +} + +std::string_view Parser::consumeLiteral() +{ + const auto begin = tell(); + + while (!isEOF()) { + char peek0 = peek(); + char peek1 = peek(1); + + if (peek0 == '{' && peek1 == '{') { + ignore(2); + continue; + } + + if (peek0 == '}' && peek1 == '}') { + ignore(2); + continue; + } + + // Get literal before the specifier {} + if (peek0 == '{' || peek0 == '}') { + return m_input.substr(begin, tell() - begin); + } + + ignore(); + } + + return m_input.substr(begin); +} + +bool Parser::consumeSpecifier(std::string_view& specifier) +{ + if (!consumeSpecific('{')) { + return false; + } + + if (!consumeSpecific(':')) { + assert(consumeSpecific('}')); + specifier = ""; + } + else { + const auto begin = tell(); + + // Go until AFTER the closing brace + while (peek(-1) != '}') { + consume(); + } + + specifier = m_input.substr(begin, tell() - begin - 1); + } + + return true; +} + +void Parser::applySpecifier(Builder& builder, std::string_view specifier) +{ + if (specifier[0] == '.') { + size_t value = 0; + + for (size_t i = 1; i < specifier.length(); ++i) { + if (specifier[i] < '0' || specifier[i] > '9') { + return; + } + value *= 10; + value += specifier[i] - '0'; // Subtract ASCII 48 to get the number + } + + builder.setPrecision(value); + } +} + +} // namespace Util::Format diff --git a/src/util/format/parser.h b/src/util/format/parser.h new file mode 100644 index 0000000..e81d24c --- /dev/null +++ b/src/util/format/parser.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 Riyyi + * + * SPDX-License-Identifier: MIT + */ + +#ifndef UTIL_FORMAT_PARSER_H +#define UTIL_FORMAT_PARSER_H + +#include // size_t +#include + +#include "util/genericlexer.h" + +namespace Util::Format { + +class Builder; + +class Parser final : public GenericLexer { +public: + Parser(std::string_view format, size_t parameterCount); + virtual ~Parser(); + + void checkFormatParameterConsistency(); + + std::string_view consumeLiteral(); + bool consumeSpecifier(std::string_view& specifier); + + void applySpecifier(Builder& builder, std::string_view specifier); + +private: + size_t m_parameterCount { 0 }; +}; + +} // namespace Util::Format + +#endif // UTIL_FORMAT_PARSER_H diff --git a/src/util/format/toformat.h b/src/util/format/toformat.h new file mode 100644 index 0000000..159a26b --- /dev/null +++ b/src/util/format/toformat.h @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2022 Riyyi + * + * SPDX-License-Identifier: MIT + */ + +#ifndef UTIL_TO_FORMAT_H +#define UTIL_TO_FORMAT_H + +#include // nullptr_t +#include // int64_t +#include // strlen +#include // next +#include +#include +#include +#include +#include // forward +#include + +#include "util/meta/odr.h" + +namespace Util::Format { + +namespace Detail { + +template +void format(FormatBuilder& builder, std::nullptr_t) +{ + builder.putString("(nil)"); +} + +template +void format(FormatBuilder& builder, T* pointer) +{ + builder.putPointer(static_cast(pointer)); +} + +template +void format(FormatBuilder& builder, bool boolean) +{ + builder.putString(boolean ? "true" : "false"); +} + +template +void format(FormatBuilder& builder, int32_t number) // int +{ + builder.putI32(number); +} + +template +void format(FormatBuilder& builder, uint32_t number) // unsigned int +{ + builder.putU32(number); +} + +template +void format(FormatBuilder& builder, int64_t number) // long int +{ + builder.putI64(number); +} + +template // uint64_t +void format(FormatBuilder& builder, size_t number) // long unsigned int +{ + builder.putU64(number); +} + +template +void format(FormatBuilder& builder, float number) +{ + builder.putF32(number); +} + +template +void format(FormatBuilder& builder, double number) +{ + builder.putF64(number); +} + +template +void format(FormatBuilder& builder, char character) +{ + builder.putCharacter(character); +} + +template +void format(FormatBuilder& builder, const char* string) +{ + builder.putString({ string, strlen(string) }); +} + +template +void format(FormatBuilder& builder, const std::string& string) +{ + builder.putString(string); +} + +template +void format(FormatBuilder& builder, std::string_view string) +{ + builder.putString(string); +} + +template +void format(FormatBuilder& builder, const std::vector& array) +{ + builder.putString("{\n"); + for (auto it = array.cbegin(); it != array.cend(); ++it) { + builder.putString(" "); + format(builder, *it); + + // Add comma, except after the last element + if (it != std::prev(array.end(), 1)) { + builder.putCharacter(','); + } + builder.putCharacter('\n'); + } + builder.putCharacter('}'); +} + +#define FORMAT_MAP \ + builder.putString("{\n"); \ + auto last = map.end(); \ + for (auto it = map.begin(); it != last; ++it) { \ + builder.putString(R"( ")"); \ + format(builder, it->first); \ + builder.putString(R"(": )"); \ + format(builder, it->second); \ + \ + /* Add comma, except after the last element */ \ + if (std::next(it) != last) { \ + builder.putCharacter(','); \ + } \ + \ + builder.putCharacter('\n'); \ + } \ + builder.putCharacter('}'); + +template +void format(FormatBuilder& builder, const std::map& map) +{ + FORMAT_MAP; +} + +template +void format(FormatBuilder& builder, const std::unordered_map& map) +{ + FORMAT_MAP; +} + +struct formatFunction { + template + auto operator()(FormatBuilder& builder, T&& value) const + { + return format(builder, std::forward(value)); + } +}; + +} // namespace Detail + +namespace { +constexpr const auto& format = Util::Detail::staticConst; // NOLINT(misc-definitions-in-headers,clang-diagnostic-unused-variable) +} // namespace + +} // namespace Util::Format + +#endif // UTIL_TO_FORMAT_H