Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6209251ca6 | ||
|
|
a118f3da3c | ||
|
|
8049477795 | ||
|
|
f08225136f |
@@ -4,6 +4,7 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <algorithm> // min
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <iomanip> // setprecision
|
#include <iomanip> // setprecision
|
||||||
#include <ios> // defaultfloat, fixed
|
#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;
|
std::stringstream stream;
|
||||||
stream
|
stream
|
||||||
@@ -39,9 +40,9 @@ void Builder::putF32(float number, size_t precision) const
|
|||||||
m_builder << string;
|
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;
|
std::stringstream stream;
|
||||||
stream
|
stream
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
#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 <sstream> // stringstream
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
@@ -15,6 +15,20 @@ namespace Util::Format {
|
|||||||
|
|
||||||
class Builder {
|
class Builder {
|
||||||
public:
|
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)
|
explicit Builder(std::stringstream& builder)
|
||||||
: m_builder(builder)
|
: m_builder(builder)
|
||||||
{
|
{
|
||||||
@@ -26,8 +40,8 @@ public:
|
|||||||
void putU32(uint32_t number) const { m_builder << number; } // unsigned int
|
void putU32(uint32_t number) const { m_builder << number; } // unsigned int
|
||||||
void putI64(int64_t number) const { m_builder << number; } // long 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 putU64(size_t number) const { m_builder << number; } // long unsigned int
|
||||||
void putF32(float number, size_t precision = 6) const;
|
void putF32(float number, uint8_t precision = 6) const;
|
||||||
void putF64(double number, size_t precision = 6) const;
|
void putF64(double number, uint8_t precision = 6) const;
|
||||||
void putCharacter(char character) const { m_builder.write(&character, 1); }
|
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 putString(const std::string_view string) const { m_builder.write(string.data(), string.length()); }
|
||||||
void putPointer(const void* pointer) const { m_builder << pointer; }
|
void putPointer(const void* pointer) const { m_builder << pointer; }
|
||||||
|
|||||||
@@ -21,13 +21,14 @@ class Parser;
|
|||||||
|
|
||||||
struct Parameter {
|
struct Parameter {
|
||||||
const void* value;
|
const void* value;
|
||||||
void (*format)(Builder& builder, const void* value);
|
void (*format)(Builder& builder, Parser& parser, const void* value);
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void formatParameterValue(Builder& builder, const void* value)
|
void formatParameterValue(Builder& builder, Parser& parser, const void* value)
|
||||||
{
|
{
|
||||||
Formatter<T> formatter;
|
Formatter<T> formatter;
|
||||||
|
formatter.parse(parser);
|
||||||
formatter.format(builder, *static_cast<const T*>(value));
|
formatter.format(builder, *static_cast<const T*>(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,13 +47,23 @@ void Formatter<size_t>::format(Builder& builder, size_t value) const
|
|||||||
template<>
|
template<>
|
||||||
void Formatter<float>::format(Builder& builder, float value) const
|
void Formatter<float>::format(Builder& builder, float value) const
|
||||||
{
|
{
|
||||||
|
if (specifier.precision < 0) {
|
||||||
builder.putF32(value);
|
builder.putF32(value);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
builder.putF32(value, specifier.precision);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
void Formatter<double>::format(Builder& builder, double value) const
|
void Formatter<double>::format(Builder& builder, double value) const
|
||||||
{
|
{
|
||||||
|
if (specifier.precision < 0) {
|
||||||
builder.putF64(value);
|
builder.putF64(value);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
builder.putF64(value, specifier.precision);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Char
|
// Char
|
||||||
|
|||||||
@@ -7,9 +7,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
#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>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <type_traits> // is_integral_v, is_same
|
||||||
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "util/format/builder.h"
|
#include "util/format/builder.h"
|
||||||
@@ -17,9 +20,65 @@
|
|||||||
|
|
||||||
namespace Util::Format {
|
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>
|
template<typename T>
|
||||||
struct Formatter {
|
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; }
|
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>
|
template<typename T>
|
||||||
struct Formatter<T*> {
|
struct Formatter<T*> {
|
||||||
|
Specifier specifier;
|
||||||
|
|
||||||
|
constexpr void parse(Parser& parser)
|
||||||
|
{
|
||||||
|
parser.parseSpecifier(specifier, Parser::SpecifierType::Pointer);
|
||||||
|
}
|
||||||
|
|
||||||
void format(Builder& builder, T* value) const
|
void format(Builder& builder, T* value) const
|
||||||
{
|
{
|
||||||
value == nullptr
|
value == nullptr
|
||||||
|
|||||||
+133
-11
@@ -6,11 +6,13 @@
|
|||||||
|
|
||||||
#include <algorithm> // replace
|
#include <algorithm> // replace
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
|
#include <cstdint> // int8_t
|
||||||
#include <limits> // numeric_limits
|
#include <limits> // numeric_limits
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
#include "util/format/builder.h"
|
#include "util/format/builder.h"
|
||||||
|
#include "util/format/formatter.h"
|
||||||
#include "util/format/parser.h"
|
#include "util/format/parser.h"
|
||||||
#include "util/meta/assert.h"
|
#include "util/meta/assert.h"
|
||||||
|
|
||||||
@@ -75,6 +77,19 @@ void Parser::checkFormatParameterConsistency()
|
|||||||
// VERIFY(!(braceOpen > m_parameterCount), "format string references nonexistent parameter");
|
// 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()
|
std::string_view Parser::consumeLiteral()
|
||||||
{
|
{
|
||||||
const auto begin = tell();
|
const auto begin = tell();
|
||||||
@@ -143,24 +158,131 @@ std::optional<size_t> Parser::consumeIndex()
|
|||||||
VERIFY_NOT_REACHED();
|
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 = 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
namespace Util::Format {
|
||||||
|
|
||||||
class Builder;
|
class Builder;
|
||||||
|
struct Specifier;
|
||||||
|
|
||||||
class Parser final : public GenericLexer {
|
class Parser final : public GenericLexer {
|
||||||
public:
|
public:
|
||||||
@@ -23,16 +24,24 @@ public:
|
|||||||
Manual, // {0},{1}
|
Manual, // {0},{1}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class SpecifierType {
|
||||||
|
Integral,
|
||||||
|
FloatingPoint,
|
||||||
|
Char,
|
||||||
|
String,
|
||||||
|
Pointer,
|
||||||
|
};
|
||||||
|
|
||||||
Parser(std::string_view format, size_t parameterCount);
|
Parser(std::string_view format, size_t parameterCount);
|
||||||
virtual ~Parser();
|
virtual ~Parser();
|
||||||
|
|
||||||
void checkFormatParameterConsistency();
|
void checkFormatParameterConsistency();
|
||||||
|
size_t stringToNumber(std::string_view value);
|
||||||
|
|
||||||
std::string_view consumeLiteral();
|
std::string_view consumeLiteral();
|
||||||
std::optional<size_t> consumeIndex();
|
std::optional<size_t> consumeIndex();
|
||||||
|
|
||||||
size_t stringToNumber(std::string_view value);
|
void parseSpecifier(Specifier& specifier, SpecifierType type);
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ArgumentIndexingMode m_mode { ArgumentIndexingMode::Automatic };
|
ArgumentIndexingMode m_mode { ArgumentIndexingMode::Automatic };
|
||||||
|
|||||||
Reference in New Issue
Block a user