diff --git a/src/util/format/builder.h b/src/util/format/builder.h index 9469e9d..f5a8a5f 100644 --- a/src/util/format/builder.h +++ b/src/util/format/builder.h @@ -15,6 +15,20 @@ namespace Util::Format { class Builder { public: + enum class Align : uint8_t { + None, + Left = 60, // '<' + Right = 62, // '>' + Center = 94, // '^' + }; + + enum class Sign : uint8_t { + None, + Negative = 45, // '-' + Both = 43, // '+' + Space = 32, // ' ' + }; + explicit Builder(std::stringstream& builder) : m_builder(builder) { diff --git a/src/util/format/format.h b/src/util/format/format.h index 1e54787..7f216d9 100644 --- a/src/util/format/format.h +++ b/src/util/format/format.h @@ -21,13 +21,14 @@ class Parser; struct Parameter { const void* value; - void (*format)(Builder& builder, const void* value); + void (*format)(Builder& builder, Parser& parser, const void* value); }; template -void formatParameterValue(Builder& builder, const void* value) +void formatParameterValue(Builder& builder, Parser& parser, const void* value) { Formatter formatter; + formatter.parse(parser); formatter.format(builder, *static_cast(value)); } diff --git a/src/util/format/formatter.h b/src/util/format/formatter.h index c0d0d8d..d906469 100644 --- a/src/util/format/formatter.h +++ b/src/util/format/formatter.h @@ -7,9 +7,12 @@ #pragma once #include // size_t -#include // int32_t, uint8_t, uint32_t, int64_t, +#include // int8_t, int32_t, int64_t, uint8_t, uint32_t +#include #include #include +#include // is_integral_v, is_same +#include #include #include "util/format/builder.h" @@ -17,9 +20,65 @@ namespace Util::Format { +enum class PresentationType : uint8_t { + None, // Defaults are any of: 'dgcsp', depending on the type + // Interger + Binary = 98, // 'b' + BinaryUppercase = 66, // 'B' + Decimal = 100, // 'd' + Octal = 111, // 'o' + Hex = 120, // 'x' + HexUppercase = 88, // 'X' + // Floating-point + Hexfloat = 97, // 'a' + HexfloatUppercase = 65, // 'A' + Exponent = 101, // 'e' + ExponentUppercase = 69, // 'E' + FixedPoint = 102, // 'f' + FixedPointUppercase = 70, // 'F' + General = 103, // 'g' + GeneralUppercase = 71, // 'G' + // Character + Character = 99, // 'c' + // String + String = 115, // 's' + // Pointer + Pointer = 112, // 'p' +}; + +struct Specifier { + char fill = ' '; + Builder::Align align = Builder::Align::None; + + Builder::Sign sign = Builder::Sign::None; + + bool alternativeForm = false; + bool zeroPadding = false; + int width = 0; + int8_t precision = -1; + + PresentationType type = PresentationType::None; +}; template struct Formatter { + Specifier specifier; + + constexpr void parse(Parser& parser) + { + if (std::is_integral_v) { + parser.parseSpecifier(specifier, Parser::SpecifierType::Integral); + } + else if (std::is_floating_point_v) { + parser.parseSpecifier(specifier, Parser::SpecifierType::FloatingPoint); + } + else if (std::is_same_v) { + parser.parseSpecifier(specifier, Parser::SpecifierType::Char); + } + else if (std::is_same_v) { + parser.parseSpecifier(specifier, Parser::SpecifierType::String); + } + } void format(Builder& builder, T value) const { (void)builder, (void)value; } }; @@ -80,6 +139,13 @@ struct Formatter : Formatter { template struct Formatter { + Specifier specifier; + + constexpr void parse(Parser& parser) + { + parser.parseSpecifier(specifier, Parser::SpecifierType::Pointer); + } + void format(Builder& builder, T* value) const { value == nullptr diff --git a/src/util/format/parser.cpp b/src/util/format/parser.cpp index 943664f..5c46148 100644 --- a/src/util/format/parser.cpp +++ b/src/util/format/parser.cpp @@ -6,11 +6,13 @@ #include // replace #include // size_t +#include // int8_t #include // numeric_limits #include #include #include "util/format/builder.h" +#include "util/format/formatter.h" #include "util/format/parser.h" #include "util/meta/assert.h" @@ -75,6 +77,19 @@ void Parser::checkFormatParameterConsistency() // VERIFY(!(braceOpen > m_parameterCount), "format string references nonexistent parameter"); } +size_t Parser::stringToNumber(std::string_view value) +{ + size_t result = 0; + + for (size_t i = 0; i < value.length(); ++i) { + VERIFY(value[i] >= '0' && value[i] <= '9', "unexpected '%c'", value[i]); + result *= 10; + result += value[i] - '0'; // Subtract ASCII 48 to get the number + } + + return result; +} + std::string_view Parser::consumeLiteral() { const auto begin = tell(); @@ -143,22 +158,131 @@ std::optional Parser::consumeIndex() VERIFY_NOT_REACHED(); } -size_t Parser::stringToNumber(std::string_view value) +void Parser::parseSpecifier(Specifier& specifier, SpecifierType type) { - size_t result = 0; + if (consumeSpecific('}') || isEOF()) { + return; + } - for (size_t i = 0; i < value.length(); ++i) { - VERIFY(value[i] >= '0' && value[i] <= '9', "unexpected '%c'", value[i]); - result *= 10; - result += value[i] - '0'; // Subtract ASCII 48 to get the number + // Alignment + char peek0 = peek(); + char peek1 = peek(1); + if (peek1 == '<' || peek1 == '>' || peek1 == '^') { + specifier.fill = peek0; + specifier.align = static_cast(peek1); + ignore(2); } - return result; -} + enum State { + AfterAlign, + AfterSign, + AfterAlternativeForm, + AfterZeroPadding, + AfterWidth, + AfterDot, + AfterPrecision, + AfterType, + } state { State::AfterAlign }; + + size_t widthBegin = std::numeric_limits::max(); + size_t precisionBegin = std::numeric_limits::max(); + size_t widthEnd = 0; + size_t precisionEnd = 0; + std::string_view acceptedTypes = "bdoxaefgscpBXAFG"; + + while (true) { + char peek0 = peek(); + + if (peek0 == '}') { + ignore(); + break; + } + if (isEOF()) { + break; + } + + // Sign is only valid for numeric types + if (peek0 == '+' || peek0 == '-' || peek0 == ' ') { + VERIFY(state < State::AfterSign, "unexpected '%c' at this position", peek0); + VERIFY(type == SpecifierType::Integral || type == SpecifierType::FloatingPoint, + "sign option is only valid for numeric types"); + state = State::AfterSign; + specifier.sign = static_cast(peek0); + } + + // Alternative form is only valid for numeric types + if (peek0 == '#') { + VERIFY(state < State::AfterAlternativeForm, "unexpected '#' at this position"); + VERIFY(type == SpecifierType::Integral || type == SpecifierType::FloatingPoint, + "'#' option is only valid for numeric types"); + state = State::AfterAlternativeForm; + specifier.alternativeForm = true; + } + + // Sign aware zero padding is only valid for numeric types + if (peek0 == '0') { + if (state < State::AfterWidth) { + VERIFY(state < State::AfterZeroPadding, "unexpected '0' at this position"); + VERIFY(type == SpecifierType::Integral || type == SpecifierType::FloatingPoint, + "zero padding option is only valid for numeric types"); + state = State::AfterZeroPadding; + specifier.zeroPadding = true; + } + } + + if (peek0 >= '0' && peek0 <= '9') { + if (widthBegin == std::numeric_limits::max() && state < State::AfterDot) { + VERIFY(state < State::AfterWidth, "unexpected '%c' at this position", peek0); + state = State::AfterWidth; + widthBegin = tell(); + } + if (precisionBegin == std::numeric_limits::max() && state == State::AfterDot) { + state = State::AfterPrecision; + precisionBegin = tell(); + } + } + if (peek0 == '.') { + if (state == State::AfterWidth) { + widthEnd = tell(); } + + VERIFY(state < State::AfterDot, "unexpected '.' at this position"); + state = State::AfterDot; } + if ((peek0 >= 'a' && peek0 <= 'z') + || (peek0 >= 'A' && peek0 <= 'Z')) { + if (state == State::AfterWidth) { + widthEnd = tell(); + } + if (state == State::AfterPrecision) { + precisionEnd = tell(); + } + + VERIFY(state < State::AfterType, "unexpected '%c' at this position", peek0); + state = State::AfterType; + VERIFY(acceptedTypes.find(peek0) != std::string_view::npos, "unexpected '%c' at this position", peek0); + specifier.type = static_cast(peek0); + } + + ignore(); + } + + if (widthBegin != std::numeric_limits::max()) { + if (widthEnd == 0) { + // We parse until after the closing '}', so take this into account + widthEnd = tell() - 1; + } + specifier.width = static_cast(stringToNumber(m_input.substr(widthBegin, widthEnd - widthBegin))); + } + + if (precisionBegin != std::numeric_limits::max()) { + if (precisionEnd == 0) { + // We parse until after the closing '}', so take this into account + precisionEnd = tell() - 1; + } + specifier.precision = static_cast(stringToNumber(m_input.substr(precisionBegin, precisionEnd - precisionBegin))); } } diff --git a/src/util/format/parser.h b/src/util/format/parser.h index ecf45c7..ba2321e 100644 --- a/src/util/format/parser.h +++ b/src/util/format/parser.h @@ -15,6 +15,7 @@ namespace Util::Format { class Builder; +struct Specifier; class Parser final : public GenericLexer { public: @@ -23,16 +24,24 @@ public: Manual, // {0},{1} }; + enum class SpecifierType { + Integral, + FloatingPoint, + Char, + String, + Pointer, + }; + Parser(std::string_view format, size_t parameterCount); virtual ~Parser(); void checkFormatParameterConsistency(); + size_t stringToNumber(std::string_view value); std::string_view consumeLiteral(); std::optional consumeIndex(); - size_t stringToNumber(std::string_view value); - + void parseSpecifier(Specifier& specifier, SpecifierType type); private: ArgumentIndexingMode m_mode { ArgumentIndexingMode::Automatic };