Compare commits
4
Commits
bbb8691c90
...
6209251ca6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6209251ca6 | ||
|
|
a118f3da3c | ||
|
|
8049477795 | ||
|
|
f08225136f |
@@ -4,6 +4,7 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <algorithm> // min
|
||||
#include <cstddef> // size_t
|
||||
#include <iomanip> // setprecision
|
||||
#include <ios> // defaultfloat, fixed
|
||||
@@ -26,9 +27,9 @@ void Builder::putLiteral(std::string_view literal)
|
||||
}
|
||||
}
|
||||
|
||||
void Builder::putF32(float number, size_t precision) const
|
||||
void Builder::putF32(float number, uint8_t precision) const
|
||||
{
|
||||
precision = (precision > std::numeric_limits<float>::digits10) ? 6 : precision;
|
||||
precision = std::min(precision, static_cast<uint8_t>(std::numeric_limits<float>::digits10));
|
||||
|
||||
std::stringstream stream;
|
||||
stream
|
||||
@@ -39,9 +40,9 @@ void Builder::putF32(float number, size_t precision) const
|
||||
m_builder << string;
|
||||
}
|
||||
|
||||
void Builder::putF64(double number, size_t precision) const
|
||||
void Builder::putF64(double number, uint8_t precision) const
|
||||
{
|
||||
precision = (precision > std::numeric_limits<double>::digits10) ? 6 : precision;
|
||||
precision = std::min(precision, static_cast<uint8_t>(std::numeric_limits<double>::digits10));
|
||||
|
||||
std::stringstream stream;
|
||||
stream
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // int32_t, uint32_t, int64_t
|
||||
#include <cstdint> // int32_t, int64_t, uint8_t, uint32_t
|
||||
#include <sstream> // stringstream
|
||||
#include <string_view>
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
@@ -26,8 +40,8 @@ public:
|
||||
void putU32(uint32_t number) const { m_builder << number; } // unsigned int
|
||||
void putI64(int64_t number) const { m_builder << number; } // long int
|
||||
void putU64(size_t number) const { m_builder << number; } // long unsigned int
|
||||
void putF32(float number, size_t precision = 6) const;
|
||||
void putF64(double number, size_t precision = 6) const;
|
||||
void putF32(float number, uint8_t precision = 6) const;
|
||||
void putF64(double number, uint8_t precision = 6) const;
|
||||
void putCharacter(char character) const { m_builder.write(&character, 1); }
|
||||
void putString(const std::string_view string) const { m_builder.write(string.data(), string.length()); }
|
||||
void putPointer(const void* pointer) const { m_builder << pointer; }
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -47,14 +47,24 @@ void Formatter<size_t>::format(Builder& builder, size_t value) const
|
||||
template<>
|
||||
void Formatter<float>::format(Builder& builder, float value) const
|
||||
{
|
||||
if (specifier.precision < 0) {
|
||||
builder.putF32(value);
|
||||
}
|
||||
else {
|
||||
builder.putF32(value, specifier.precision);
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void Formatter<double>::format(Builder& builder, double value) const
|
||||
{
|
||||
if (specifier.precision < 0) {
|
||||
builder.putF64(value);
|
||||
}
|
||||
else {
|
||||
builder.putF64(value, specifier.precision);
|
||||
}
|
||||
}
|
||||
|
||||
// Char
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+132
-10
@@ -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,24 +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 = 1; i < value.length(); ++i) {
|
||||
if (value[i] < '0' || value[i] > '9') {
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user