Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cff165f08 | ||
|
|
7825e87c19 | ||
|
|
04250a8605 | ||
|
|
5e419f501d | ||
|
|
1b7a736be2 |
@@ -14,6 +14,7 @@
|
|||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
#include "util/format/builder.h"
|
#include "util/format/builder.h"
|
||||||
|
#include "util/meta/assert.h"
|
||||||
|
|
||||||
namespace Util::Format {
|
namespace Util::Format {
|
||||||
|
|
||||||
@@ -53,4 +54,34 @@ void Builder::putF64(double number, uint8_t precision) const
|
|||||||
m_builder << string;
|
m_builder << string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Builder::putString(std::string_view string, size_t width, Align align, char fill) const
|
||||||
|
{
|
||||||
|
size_t length = string.length();
|
||||||
|
if (width < length) {
|
||||||
|
m_builder.write(string.data(), length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (align) {
|
||||||
|
case Align::None:
|
||||||
|
case Align::Left:
|
||||||
|
m_builder.write(string.data(), length);
|
||||||
|
m_builder << std::string(width - length, fill);
|
||||||
|
break;
|
||||||
|
case Align::Center: {
|
||||||
|
size_t half = (width - length) / 2;
|
||||||
|
m_builder << std::string(half, fill);
|
||||||
|
m_builder.write(string.data(), length);
|
||||||
|
m_builder << std::string(width - half - length, fill);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Align::Right:
|
||||||
|
m_builder << std::string(width - length, fill);
|
||||||
|
m_builder.write(string.data(), length);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Util::Format
|
} // namespace Util::Format
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public:
|
|||||||
void putF32(float number, uint8_t precision = 6) const;
|
void putF32(float number, uint8_t precision = 6) const;
|
||||||
void putF64(double 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 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(std::string_view string, size_t width = 0, Align align = Align::Left, char fill = ' ') const;
|
||||||
void putPointer(const void* pointer) const { m_builder << pointer; }
|
void putPointer(const void* pointer) const { m_builder << pointer; }
|
||||||
|
|
||||||
const std::stringstream& builder() const { return m_builder; }
|
const std::stringstream& builder() const { return m_builder; }
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ void variadicFormatImpl(Builder& builder, Parser& parser, TypeErasedParameters&
|
|||||||
|
|
||||||
// Get parameter at index, or next
|
// Get parameter at index, or next
|
||||||
size_t index = indexMaybe.has_value() ? indexMaybe.value() : parameters.tell();
|
size_t index = indexMaybe.has_value() ? indexMaybe.value() : parameters.tell();
|
||||||
VERIFY(index < parameters.size(), "argument not found at index '%zu':'%zu'", index, parameters.size());
|
VERIFY(index < parameters.size(), "argument not found at index '%zu'", index);
|
||||||
auto& parameter = parameters.parameter(index);
|
auto& parameter = parameters.parameter(index);
|
||||||
|
|
||||||
// Format the parameter
|
// Format the parameter
|
||||||
|
|||||||
@@ -60,11 +60,11 @@ void Formatter<double>::format(Builder& builder, double value) const
|
|||||||
{
|
{
|
||||||
if (specifier.precision < 0) {
|
if (specifier.precision < 0) {
|
||||||
builder.putF64(value);
|
builder.putF64(value);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
builder.putF64(value, specifier.precision);
|
builder.putF64(value, specifier.precision);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Char
|
// Char
|
||||||
|
|
||||||
@@ -82,15 +82,26 @@ void Formatter<bool>::format(Builder& builder, bool value) const
|
|||||||
|
|
||||||
// String
|
// String
|
||||||
|
|
||||||
void Formatter<const char*>::format(Builder& builder, const char* value) const
|
|
||||||
{
|
|
||||||
builder.putString(value != nullptr ? std::string_view { value, strlen(value) } : "(nil)");
|
|
||||||
}
|
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
void Formatter<std::string_view>::format(Builder& builder, std::string_view value) const
|
void Formatter<std::string_view>::format(Builder& builder, std::string_view value) const
|
||||||
{
|
{
|
||||||
builder.putString(value);
|
builder.putString(value, specifier.width, specifier.align, specifier.fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Formatter<const char*>::parse(Parser& parser)
|
||||||
|
{
|
||||||
|
parser.parseSpecifier(specifier, Parser::ParameterType::CString);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Formatter<const char*>::format(Builder& builder, const char* value) const
|
||||||
|
{
|
||||||
|
if (specifier.type == PresentationType::Pointer) {
|
||||||
|
Formatter<const void*> formatter { specifier };
|
||||||
|
formatter.format(builder, static_cast<const void*>(value));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.putString(value != nullptr ? std::string_view { value, strlen(value) } : "nullptr");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pointer
|
// Pointer
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ struct Specifier {
|
|||||||
|
|
||||||
bool alternativeForm = false;
|
bool alternativeForm = false;
|
||||||
bool zeroPadding = false;
|
bool zeroPadding = false;
|
||||||
int width = 0;
|
size_t width = 0;
|
||||||
int8_t precision = -1;
|
int8_t precision = -1;
|
||||||
|
|
||||||
PresentationType type = PresentationType::None;
|
PresentationType type = PresentationType::None;
|
||||||
@@ -67,16 +67,16 @@ struct Formatter {
|
|||||||
constexpr void parse(Parser& parser)
|
constexpr void parse(Parser& parser)
|
||||||
{
|
{
|
||||||
if (std::is_integral_v<T>) {
|
if (std::is_integral_v<T>) {
|
||||||
parser.parseSpecifier(specifier, Parser::SpecifierType::Integral);
|
parser.parseSpecifier(specifier, Parser::ParameterType::Integral);
|
||||||
}
|
}
|
||||||
else if (std::is_floating_point_v<T>) {
|
else if (std::is_floating_point_v<T>) {
|
||||||
parser.parseSpecifier(specifier, Parser::SpecifierType::FloatingPoint);
|
parser.parseSpecifier(specifier, Parser::ParameterType::FloatingPoint);
|
||||||
}
|
}
|
||||||
else if (std::is_same_v<T, char>) {
|
else if (std::is_same_v<T, char>) {
|
||||||
parser.parseSpecifier(specifier, Parser::SpecifierType::Char);
|
parser.parseSpecifier(specifier, Parser::ParameterType::Char);
|
||||||
}
|
}
|
||||||
else if (std::is_same_v<T, std::string_view>) {
|
else if (std::is_same_v<T, std::string_view>) {
|
||||||
parser.parseSpecifier(specifier, Parser::SpecifierType::String);
|
parser.parseSpecifier(specifier, Parser::ParameterType::String);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,6 +124,7 @@ struct Formatter<std::string> : Formatter<std::string_view> {
|
|||||||
|
|
||||||
template<>
|
template<>
|
||||||
struct Formatter<const char*> : Formatter<std::string_view> {
|
struct Formatter<const char*> : Formatter<std::string_view> {
|
||||||
|
void parse(Parser& parser);
|
||||||
void format(Builder& builder, const char* value) const;
|
void format(Builder& builder, const char* value) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -143,13 +144,13 @@ struct Formatter<T*> {
|
|||||||
|
|
||||||
constexpr void parse(Parser& parser)
|
constexpr void parse(Parser& parser)
|
||||||
{
|
{
|
||||||
parser.parseSpecifier(specifier, Parser::SpecifierType::Pointer);
|
parser.parseSpecifier(specifier, Parser::ParameterType::Pointer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void format(Builder& builder, T* value) const
|
void format(Builder& builder, T* value) const
|
||||||
{
|
{
|
||||||
value == nullptr
|
value == nullptr
|
||||||
? builder.putString("0x0")
|
? builder.putString("nullptr")
|
||||||
: builder.putPointer(static_cast<const void*>(value));
|
: builder.putPointer(static_cast<const void*>(value));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ std::optional<size_t> Parser::consumeIndex()
|
|||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Parser::parseSpecifier(Specifier& specifier, SpecifierType type)
|
void Parser::parseSpecifier(Specifier& specifier, ParameterType type)
|
||||||
{
|
{
|
||||||
if (consumeSpecific('}') || isEOF()) {
|
if (consumeSpecific('}') || isEOF()) {
|
||||||
return;
|
return;
|
||||||
@@ -204,7 +204,7 @@ void Parser::parseSpecifier(Specifier& specifier, SpecifierType type)
|
|||||||
// Sign is only valid for numeric types
|
// Sign is only valid for numeric types
|
||||||
if (peek0 == '+' || peek0 == '-' || peek0 == ' ') {
|
if (peek0 == '+' || peek0 == '-' || peek0 == ' ') {
|
||||||
VERIFY(state < State::AfterSign, "unexpected '%c' at this position", peek0);
|
VERIFY(state < State::AfterSign, "unexpected '%c' at this position", peek0);
|
||||||
VERIFY(type == SpecifierType::Integral || type == SpecifierType::FloatingPoint,
|
VERIFY(type == ParameterType::Integral || type == ParameterType::FloatingPoint,
|
||||||
"sign option is only valid for numeric types");
|
"sign option is only valid for numeric types");
|
||||||
state = State::AfterSign;
|
state = State::AfterSign;
|
||||||
specifier.sign = static_cast<Builder::Sign>(peek0);
|
specifier.sign = static_cast<Builder::Sign>(peek0);
|
||||||
@@ -213,7 +213,7 @@ void Parser::parseSpecifier(Specifier& specifier, SpecifierType type)
|
|||||||
// Alternative form is only valid for numeric types
|
// Alternative form is only valid for numeric types
|
||||||
if (peek0 == '#') {
|
if (peek0 == '#') {
|
||||||
VERIFY(state < State::AfterAlternativeForm, "unexpected '#' at this position");
|
VERIFY(state < State::AfterAlternativeForm, "unexpected '#' at this position");
|
||||||
VERIFY(type == SpecifierType::Integral || type == SpecifierType::FloatingPoint,
|
VERIFY(type == ParameterType::Integral || type == ParameterType::FloatingPoint,
|
||||||
"'#' option is only valid for numeric types");
|
"'#' option is only valid for numeric types");
|
||||||
state = State::AfterAlternativeForm;
|
state = State::AfterAlternativeForm;
|
||||||
specifier.alternativeForm = true;
|
specifier.alternativeForm = true;
|
||||||
@@ -223,7 +223,7 @@ void Parser::parseSpecifier(Specifier& specifier, SpecifierType type)
|
|||||||
if (peek0 == '0') {
|
if (peek0 == '0') {
|
||||||
if (state < State::AfterWidth) {
|
if (state < State::AfterWidth) {
|
||||||
VERIFY(state < State::AfterZeroPadding, "unexpected '0' at this position");
|
VERIFY(state < State::AfterZeroPadding, "unexpected '0' at this position");
|
||||||
VERIFY(type == SpecifierType::Integral || type == SpecifierType::FloatingPoint,
|
VERIFY(type == ParameterType::Integral || type == ParameterType::FloatingPoint,
|
||||||
"zero padding option is only valid for numeric types");
|
"zero padding option is only valid for numeric types");
|
||||||
state = State::AfterZeroPadding;
|
state = State::AfterZeroPadding;
|
||||||
specifier.zeroPadding = true;
|
specifier.zeroPadding = true;
|
||||||
@@ -274,7 +274,7 @@ void Parser::parseSpecifier(Specifier& specifier, SpecifierType type)
|
|||||||
// We parse until after the closing '}', so take this into account
|
// We parse until after the closing '}', so take this into account
|
||||||
widthEnd = tell() - 1;
|
widthEnd = tell() - 1;
|
||||||
}
|
}
|
||||||
specifier.width = static_cast<int>(stringToNumber(m_input.substr(widthBegin, widthEnd - widthBegin)));
|
specifier.width = stringToNumber(m_input.substr(widthBegin, widthEnd - widthBegin));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (precisionBegin != std::numeric_limits<size_t>::max()) {
|
if (precisionBegin != std::numeric_limits<size_t>::max()) {
|
||||||
@@ -284,6 +284,73 @@ void Parser::parseSpecifier(Specifier& specifier, SpecifierType type)
|
|||||||
}
|
}
|
||||||
specifier.precision = static_cast<int8_t>(stringToNumber(m_input.substr(precisionBegin, precisionEnd - precisionBegin)));
|
specifier.precision = static_cast<int8_t>(stringToNumber(m_input.substr(precisionBegin, precisionEnd - precisionBegin)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkSpecifierType(specifier, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void Parser::checkSpecifierIntegralType(const Specifier& specifier)
|
||||||
|
{
|
||||||
|
switch (specifier.type) {
|
||||||
|
case PresentationType::None:
|
||||||
|
case PresentationType::Binary:
|
||||||
|
case PresentationType::BinaryUppercase:
|
||||||
|
case PresentationType::Decimal:
|
||||||
|
case PresentationType::Octal:
|
||||||
|
case PresentationType::Hex:
|
||||||
|
case PresentationType::HexUppercase:
|
||||||
|
case PresentationType::Character:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
VERIFY("invalid type spcifier");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void Parser::checkSpecifierType(const Specifier& specifier, ParameterType type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case ParameterType::Integral:
|
||||||
|
checkSpecifierIntegralType(specifier);
|
||||||
|
break;
|
||||||
|
case ParameterType::FloatingPoint:
|
||||||
|
switch (specifier.type) {
|
||||||
|
case PresentationType::Hexfloat:
|
||||||
|
case PresentationType::HexfloatUppercase:
|
||||||
|
case PresentationType::Exponent:
|
||||||
|
case PresentationType::ExponentUppercase:
|
||||||
|
case PresentationType::FixedPoint:
|
||||||
|
case PresentationType::FixedPointUppercase:
|
||||||
|
case PresentationType::General:
|
||||||
|
case PresentationType::GeneralUppercase:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
VERIFY("invalid type spcifier");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ParameterType::Char:
|
||||||
|
if (specifier.type != PresentationType::None
|
||||||
|
&& specifier.type != PresentationType::Character) {
|
||||||
|
checkSpecifierIntegralType(specifier);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ParameterType::CString:
|
||||||
|
VERIFY(specifier.type == PresentationType::None
|
||||||
|
|| specifier.type == PresentationType::String
|
||||||
|
|| specifier.type == PresentationType::Pointer,
|
||||||
|
"invalid type specifier");
|
||||||
|
break;
|
||||||
|
case ParameterType::String:
|
||||||
|
VERIFY(specifier.type == PresentationType::None
|
||||||
|
|| specifier.type == PresentationType::String,
|
||||||
|
"invalid type specifier");
|
||||||
|
break;
|
||||||
|
case ParameterType::Pointer:
|
||||||
|
VERIFY(specifier.type == PresentationType::None
|
||||||
|
|| specifier.type == PresentationType::Pointer,
|
||||||
|
"invalid type specifier");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Util::Format
|
} // namespace Util::Format
|
||||||
|
|||||||
@@ -24,10 +24,11 @@ public:
|
|||||||
Manual, // {0},{1}
|
Manual, // {0},{1}
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class SpecifierType {
|
enum class ParameterType {
|
||||||
Integral,
|
Integral,
|
||||||
FloatingPoint,
|
FloatingPoint,
|
||||||
Char,
|
Char,
|
||||||
|
CString,
|
||||||
String,
|
String,
|
||||||
Pointer,
|
Pointer,
|
||||||
};
|
};
|
||||||
@@ -41,7 +42,9 @@ public:
|
|||||||
std::string_view consumeLiteral();
|
std::string_view consumeLiteral();
|
||||||
std::optional<size_t> consumeIndex();
|
std::optional<size_t> consumeIndex();
|
||||||
|
|
||||||
void parseSpecifier(Specifier& specifier, SpecifierType type);
|
void parseSpecifier(Specifier& specifier, ParameterType type);
|
||||||
|
constexpr void checkSpecifierIntegralType(const Specifier& specifier);
|
||||||
|
constexpr void checkSpecifierType(const Specifier& specifier, ParameterType type);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ArgumentIndexingMode m_mode { ArgumentIndexingMode::Automatic };
|
ArgumentIndexingMode m_mode { ArgumentIndexingMode::Automatic };
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ TEST_CASE(FormatBasicTypes)
|
|||||||
EXPECT_EQ(result, "");
|
EXPECT_EQ(result, "");
|
||||||
|
|
||||||
result = Util::format("{}", nullptr);
|
result = Util::format("{}", nullptr);
|
||||||
EXPECT_EQ(result, "(nil)");
|
EXPECT_EQ(result, "nullptr");
|
||||||
|
|
||||||
int* number = new int(3);
|
int* number = new int(3);
|
||||||
result = Util::format("{}", number);
|
result = Util::format("{}", number);
|
||||||
|
|||||||
Reference in New Issue
Block a user