Util: Add format string parsing
This commit is contained in:
+133
-9
@@ -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;
|
||||
|
||||
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
|
||||
if (consumeSpecific('}') || isEOF()) {
|
||||
return;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
// Alignment
|
||||
char peek0 = peek();
|
||||
char peek1 = peek(1);
|
||||
if (peek1 == '<' || peek1 == '>' || peek1 == '^') {
|
||||
specifier.fill = peek0;
|
||||
specifier.align = static_cast<Builder::Align>(peek1);
|
||||
ignore(2);
|
||||
}
|
||||
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user