diff --git a/src/util/format/builder.cpp b/src/util/format/builder.cpp index 991938f..7883670 100644 --- a/src/util/format/builder.cpp +++ b/src/util/format/builder.cpp @@ -28,7 +28,7 @@ void Builder::putLiteral(std::string_view literal) void Builder::putF32(float number, size_t precision) const { - precision = (precision > std::numeric_limits::digits10) ? m_precision : precision; + precision = (precision > std::numeric_limits::digits10) ? 6 : precision; std::stringstream stream; stream @@ -41,7 +41,7 @@ void Builder::putF32(float number, size_t precision) const void Builder::putF64(double number, size_t precision) const { - precision = (precision > std::numeric_limits::digits10) ? m_precision : precision; + precision = (precision > std::numeric_limits::digits10) ? 6 : precision; std::stringstream stream; stream @@ -52,9 +52,4 @@ void Builder::putF64(double number, size_t precision) const m_builder << string; } -void Builder::resetSpecifiers() -{ - setPrecision(); -} - } // namespace Util::Format diff --git a/src/util/format/builder.h b/src/util/format/builder.h index 01043cb..9944937 100644 --- a/src/util/format/builder.h +++ b/src/util/format/builder.h @@ -16,8 +16,7 @@ namespace Util::Format { class Builder { public: explicit Builder(std::stringstream& builder) - : m_precision(6) - , m_builder(builder) + : m_builder(builder) { } @@ -27,20 +26,16 @@ public: 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 putF32(float number, size_t precision = 6) const; + void putF64(double number, size_t precision = 6) 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; }; diff --git a/src/util/format/format.cpp b/src/util/format/format.cpp index f859605..965d2e5 100644 --- a/src/util/format/format.cpp +++ b/src/util/format/format.cpp @@ -11,27 +11,36 @@ #include "util/format/builder.h" #include "util/format/format.h" #include "util/format/parser.h" +#include "util/meta/assert.h" namespace Util::Format { void variadicFormatImpl(Builder& builder, Parser& parser, TypeErasedParameters& parameters) { + // Consume everything until '{' or EOF const auto literal = parser.consumeLiteral(); builder.putLiteral(literal); - if (!parameters.isEOF()) { - std::string_view specifier; - if (parser.consumeSpecifier(specifier)) { - parser.applySpecifier(builder, specifier); - } + // Reached end of format string + if (parser.isEOF()) { + return; + } - auto& parameter = parameters.parameter(parameters.tell()); - parameter.format(builder, parameter.value); - parameters.ignore(); + // Consume index + ':' + auto indexMaybe = parser.consumeIndex(); - builder.resetSpecifiers(); - } + // Get parameter at index, or next + size_t index = indexMaybe.has_value() ? indexMaybe.value() : parameters.tell(); + VERIFY(index < parameters.size(), "argument not found at index '%zu':'%zu'", index, parameters.size()); + auto& parameter = parameters.parameter(index); + + // Format the parameter + parameter.format(builder, parser, parameter.value); + + // Go to next parameter + parameters.ignore(); + // Recurse if (!parser.isEOF()) { variadicFormatImpl(builder, parser, parameters); } diff --git a/src/util/format/format.h b/src/util/format/format.h index a26ea03..5046fa0 100644 --- a/src/util/format/format.h +++ b/src/util/format/format.h @@ -28,7 +28,8 @@ struct Parameter { template void formatParameterValue(Builder& builder, const void* value) { - _format(builder, *static_cast(value)); + Formatter formatter; + formatter.format(builder, *static_cast(value)); } // Type erasure improves both compile time and binary size significantly diff --git a/src/util/format/formatter.cpp b/src/util/format/formatter.cpp new file mode 100644 index 0000000..ff07a24 --- /dev/null +++ b/src/util/format/formatter.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2022 Riyyi + * + * SPDX-License-Identifier: MIT + */ + +#include // size_t +#include // int32_t, uint32_t, int64_t, +#include // strlen +#include +#include + +#include "util/format/builder.h" +#include "util/format/formatter.h" +#include "util/format/parser.h" + +namespace Util::Format { + +// Integral + +template<> +void Formatter::format(Builder& builder, int32_t value) const +{ + builder.putI32(value); +} + +template<> +void Formatter::format(Builder& builder, uint32_t value) const +{ + builder.putU32(value); +} + +template<> +void Formatter::format(Builder& builder, int64_t value) const +{ + builder.putI64(value); +} + +template<> +void Formatter::format(Builder& builder, size_t value) const +{ + builder.putU64(value); +} + +// Floating point + +template<> +void Formatter::format(Builder& builder, float value) const +{ + builder.putF32(value); +} + +template<> +void Formatter::format(Builder& builder, double value) const +{ + builder.putF64(value); +} + +// Char + +template<> +void Formatter::format(Builder& builder, char value) const +{ + builder.putCharacter(value); +} + +template<> +void Formatter::format(Builder& builder, bool value) const +{ + builder.putString(value ? "true" : "false"); +} + +// String + +void Formatter::format(Builder& builder, const char* value) const +{ + builder.putString(value != nullptr ? std::string_view { value, strlen(value) } : "(nil)"); +} + +template<> +void Formatter::format(Builder& builder, std::string_view value) const +{ + builder.putString(value); +} + +// Pointer + +void Formatter::format(Builder& builder, std::nullptr_t) const +{ + Formatter::format(builder, 0); +} + +// Containers + +} // namespace Util::Format diff --git a/src/util/format/formatter.h b/src/util/format/formatter.h new file mode 100644 index 0000000..c80c232 --- /dev/null +++ b/src/util/format/formatter.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2022 Riyyi + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include // size_t +#include // int32_t, uint8_t, uint32_t, int64_t, +#include +#include +#include + +#include "util/format/builder.h" +#include "util/format/parser.h" + +namespace Util::Format { + + +template +struct Formatter { + + void format(Builder& builder, T value) const { (void)builder, (void)value; } +}; + +// Integral + +template<> +void Formatter::format(Builder& builder, int32_t value) const; + +template<> +void Formatter::format(Builder& builder, uint32_t value) const; + +template<> +void Formatter::format(Builder& builder, int64_t value) const; + +template<> +void Formatter::format(Builder& builder, size_t value) const; // uint64_t + +// Floating point + +template<> +void Formatter::format(Builder& builder, float value) const; + +template<> +void Formatter::format(Builder& builder, double value) const; + +// Char + +template<> +void Formatter::format(Builder& builder, char value) const; + +template<> +void Formatter::format(Builder& builder, bool value) const; + +// String + +template<> +void Formatter::format(Builder& builder, std::string_view value) const; + +template<> +struct Formatter : Formatter { +}; + +template<> +struct Formatter : Formatter { + void format(Builder& builder, const char* value) const; +}; + +template<> +struct Formatter : Formatter { +}; + +template +struct Formatter : Formatter { +}; + +// Pointer + +template +struct Formatter { + void format(Builder& builder, T* value) const + { + value == nullptr + ? builder.putString("0x0") + : builder.putPointer(static_cast(value)); + } +}; + +template<> +struct Formatter : Formatter { + void format(Builder& builder, std::nullptr_t) const; +}; + +// Container + +template +struct Formatter> : Formatter { + void format(Builder& builder, const std::vector& value) const + { + builder.putString("{\n"); + for (auto it = value.cbegin(); it != value.cend(); ++it) { + builder.putString(" "); + Formatter::format(builder, *it); + + // Add comma, except after the last element + if (it != std::prev(value.end(), 1)) { + builder.putCharacter(','); + } + builder.putCharacter('\n'); + } + builder.putCharacter('}'); + } +}; + +} // namespace Util::Format diff --git a/src/util/format/parser.cpp b/src/util/format/parser.cpp index 5ed2f28..3ea7532 100644 --- a/src/util/format/parser.cpp +++ b/src/util/format/parser.cpp @@ -6,6 +6,7 @@ #include // replace #include // size_t +#include // numeric_limits #include #include @@ -53,7 +54,9 @@ void Parser::checkFormatParameterConsistency() if (peek0 == '{') { braceOpen++; - VERIFY(peek1 == '}' || peek1 == ':', "invalid parameter reference"); + if (peek1 >= '0' && peek1 <= '9') { + m_mode = ArgumentIndexingMode::Manual; + } } if (peek0 == '}') { braceClose++; @@ -63,13 +66,13 @@ void Parser::checkFormatParameterConsistency() } m_index = 0; - VERIFY(!(braceOpen < braceClose), "extra open braces in format string"); + // VERIFY(!(braceOpen < braceClose), "extra open braces in format string"); - VERIFY(!(braceOpen > braceClose), "extra closing braces in format string"); + // VERIFY(!(braceOpen > braceClose), "extra closing braces in format string"); - VERIFY(!(braceOpen < m_parameterCount), "format string does not reference all passed parameters"); + // VERIFY(!(braceOpen < m_parameterCount), "format string does not reference all passed parameters"); - VERIFY(!(braceOpen > m_parameterCount), "format string references nonexistent parameter"); + // VERIFY(!(braceOpen > m_parameterCount), "format string references nonexistent parameter"); } std::string_view Parser::consumeLiteral() @@ -101,44 +104,63 @@ std::string_view Parser::consumeLiteral() return m_input.substr(begin); } -bool Parser::consumeSpecifier(std::string_view& specifier) +std::optional Parser::consumeIndex() { if (!consumeSpecific('{')) { - return false; + VERIFY_NOT_REACHED(); + return {}; } - if (!consumeSpecific(':')) { - VERIFY(consumeSpecific('}'), "invalid parameter reference"); - specifier = ""; + switch (m_mode) { + case ArgumentIndexingMode::Automatic: { + VERIFY(consumeSpecific('}') || consumeSpecific(':'), "expecting '}' or ':', not '%c'", peek()); + return {}; } - else { + case ArgumentIndexingMode::Manual: { const auto begin = tell(); - // Go until AFTER the closing brace - while (peek(-1) != '}') { - consume(); + while (!isEOF()) { + char peek0 = peek(); + if (peek0 == '}' || peek0 == ':') { + break; + } + + VERIFY(peek0 >= '0' && peek0 <= '9', "expecting number, not '%c'", peek0); + + ignore(); + } + + size_t result = stringToNumber(m_input.substr(begin, tell() - begin)); + + if (peek() == ':') { + ignore(); } - specifier = m_input.substr(begin, tell() - begin - 1); + return result; } + }; - return true; + VERIFY_NOT_REACHED(); } -void Parser::applySpecifier(Builder& builder, std::string_view specifier) +size_t Parser::stringToNumber(std::string_view value) { - if (specifier[0] == '.') { - size_t value = 0; + size_t result = 0; + + for (size_t i = 1; i < value.length(); ++i) { + if (value[i] < '0' || value[i] > '9') { + VERIFY_NOT_REACHED(); + } + result *= 10; + result += value[i] - '0'; // Subtract ASCII 48 to get the number + } + + return result; +} - 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); } } diff --git a/src/util/format/parser.h b/src/util/format/parser.h index 6715480..ecf45c7 100644 --- a/src/util/format/parser.h +++ b/src/util/format/parser.h @@ -6,7 +6,8 @@ #pragma once -#include // size_t +#include // size_t +#include #include #include "util/genericlexer.h" @@ -17,17 +18,24 @@ class Builder; class Parser final : public GenericLexer { public: + enum class ArgumentIndexingMode { + Automatic, // {} ,{} + Manual, // {0},{1} + }; + Parser(std::string_view format, size_t parameterCount); virtual ~Parser(); void checkFormatParameterConsistency(); std::string_view consumeLiteral(); - bool consumeSpecifier(std::string_view& specifier); + std::optional consumeIndex(); + + size_t stringToNumber(std::string_view value); - void applySpecifier(Builder& builder, std::string_view specifier); private: + ArgumentIndexingMode m_mode { ArgumentIndexingMode::Automatic }; size_t m_parameterCount { 0 }; };