Browse Source

Util: Add format string parsing

master
Riyyi 2 years ago
parent
commit
a118f3da3c
  1. 14
      src/util/format/builder.h
  2. 5
      src/util/format/format.h
  3. 68
      src/util/format/formatter.h
  4. 140
      src/util/format/parser.cpp
  5. 13
      src/util/format/parser.h

14
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)
{

5
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<typename T>
void formatParameterValue(Builder& builder, const void* value)
void formatParameterValue(Builder& builder, Parser& parser, const void* value)
{
Formatter<T> formatter;
formatter.parse(parser);
formatter.format(builder, *static_cast<const T*>(value));
}

68
src/util/format/formatter.h

@ -7,9 +7,12 @@
#pragma once
#include <cstddef> // size_t
#include <cstdint> // int32_t, uint8_t, uint32_t, int64_t,
#include <cstdint> // int8_t, int32_t, int64_t, uint8_t, uint32_t
#include <map>
#include <string>
#include <string_view>
#include <type_traits> // is_integral_v, is_same
#include <unordered_map>
#include <vector>
#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<typename T>
struct Formatter {
Specifier specifier;
constexpr void parse(Parser& parser)
{
if (std::is_integral_v<T>) {
parser.parseSpecifier(specifier, Parser::SpecifierType::Integral);
}
else if (std::is_floating_point_v<T>) {
parser.parseSpecifier(specifier, Parser::SpecifierType::FloatingPoint);
}
else if (std::is_same_v<T, char>) {
parser.parseSpecifier(specifier, Parser::SpecifierType::Char);
}
else if (std::is_same_v<T, std::string_view>) {
parser.parseSpecifier(specifier, Parser::SpecifierType::String);
}
}
void format(Builder& builder, T value) const { (void)builder, (void)value; }
};
@ -80,6 +139,13 @@ struct Formatter<char[N]> : Formatter<const char*> {
template<typename T>
struct Formatter<T*> {
Specifier specifier;
constexpr void parse(Parser& parser)
{
parser.parseSpecifier(specifier, Parser::SpecifierType::Pointer);
}
void format(Builder& builder, T* value) const
{
value == nullptr

140
src/util/format/parser.cpp

@ -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)));
}
}

13
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<size_t> consumeIndex();
size_t stringToNumber(std::string_view value);
void parseSpecifier(Specifier& specifier, SpecifierType type);
private:
ArgumentIndexingMode m_mode { ArgumentIndexingMode::Automatic };

Loading…
Cancel
Save