|
|
|
@ -6,11 +6,13 @@
|
|
|
|
|
|
|
|
|
|
#include <algorithm> // replace |
|
|
|
|
#include <cstddef> // size_t |
|
|
|
|
#include <cstdint> // int8_t |
|
|
|
|
#include <limits> // numeric_limits |
|
|
|
|
#include <string> |
|
|
|
|
#include <string_view> |
|
|
|
|
|
|
|
|
|
#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<size_t> 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<Builder::Align>(peek1); |
|
|
|
|
ignore(2); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
enum State { |
|
|
|
|
AfterAlign, |
|
|
|
|
AfterSign, |
|
|
|
|
AfterAlternativeForm, |
|
|
|
|
AfterZeroPadding, |
|
|
|
|
AfterWidth, |
|
|
|
|
AfterDot, |
|
|
|
|
AfterPrecision, |
|
|
|
|
AfterType, |
|
|
|
|
} state { State::AfterAlign }; |
|
|
|
|
|
|
|
|
|
size_t widthBegin = std::numeric_limits<size_t>::max(); |
|
|
|
|
size_t precisionBegin = std::numeric_limits<size_t>::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<Builder::Sign>(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<size_t>::max() && state < State::AfterDot) { |
|
|
|
|
VERIFY(state < State::AfterWidth, "unexpected '%c' at this position", peek0); |
|
|
|
|
state = State::AfterWidth; |
|
|
|
|
widthBegin = tell(); |
|
|
|
|
} |
|
|
|
|
if (precisionBegin == std::numeric_limits<size_t>::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<PresentationType>(peek0); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ignore(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (widthBegin != std::numeric_limits<size_t>::max()) { |
|
|
|
|
if (widthEnd == 0) { |
|
|
|
|
// We parse until after the closing '}', so take this into account
|
|
|
|
|
widthEnd = tell() - 1; |
|
|
|
|
} |
|
|
|
|
specifier.width = static_cast<int>(stringToNumber(m_input.substr(widthBegin, widthEnd - widthBegin))); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (precisionBegin != std::numeric_limits<size_t>::max()) { |
|
|
|
|
if (precisionEnd == 0) { |
|
|
|
|
// We parse until after the closing '}', so take this into account
|
|
|
|
|
precisionEnd = tell() - 1; |
|
|
|
|
} |
|
|
|
|
specifier.precision = static_cast<int8_t>(stringToNumber(m_input.substr(precisionBegin, precisionEnd - precisionBegin))); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|