Util: Add format string parsing

This commit is contained in:
Riyyi
2022-08-03 12:51:33 +02:00
parent 8049477795
commit a118f3da3c
5 changed files with 228 additions and 14 deletions
+133 -9
View File
@@ -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)));
}
}