Util: Switch from format() to struct Formatter
This commit is contained in:
@@ -28,7 +28,7 @@ void Builder::putLiteral(std::string_view literal)
|
||||
|
||||
void Builder::putF32(float number, size_t precision) const
|
||||
{
|
||||
precision = (precision > std::numeric_limits<float>::digits10) ? m_precision : precision;
|
||||
precision = (precision > std::numeric_limits<float>::digits10) ? 6 : precision;
|
||||
|
||||
std::stringstream stream;
|
||||
stream
|
||||
@@ -41,7 +41,7 @@ void Builder::putF32(float number, size_t precision) const
|
||||
|
||||
void Builder::putF64(double number, size_t precision) const
|
||||
{
|
||||
precision = (precision > std::numeric_limits<double>::digits10) ? m_precision : precision;
|
||||
precision = (precision > std::numeric_limits<double>::digits10) ? 6 : precision;
|
||||
|
||||
std::stringstream stream;
|
||||
stream
|
||||
@@ -52,9 +52,4 @@ void Builder::putF64(double number, size_t precision) const
|
||||
m_builder << string;
|
||||
}
|
||||
|
||||
void Builder::resetSpecifiers()
|
||||
{
|
||||
setPrecision();
|
||||
}
|
||||
|
||||
} // namespace Util::Format
|
||||
|
||||
@@ -16,8 +16,7 @@ namespace Util::Format {
|
||||
class Builder {
|
||||
public:
|
||||
explicit Builder(std::stringstream& builder)
|
||||
: m_precision(6)
|
||||
, m_builder(builder)
|
||||
: m_builder(builder)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -27,20 +26,16 @@ 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 = -1) const;
|
||||
void putF64(double number, size_t precision = -1) const;
|
||||
void putF32(float number, size_t precision = 6) const;
|
||||
void putF64(double number, size_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; }
|
||||
|
||||
void resetSpecifiers();
|
||||
void setPrecision(size_t precision = 6) { m_precision = precision; };
|
||||
|
||||
const std::stringstream& builder() const { return m_builder; }
|
||||
std::stringstream& builder() { return m_builder; }
|
||||
|
||||
private:
|
||||
size_t m_precision { 6 };
|
||||
std::stringstream& m_builder;
|
||||
};
|
||||
|
||||
|
||||
@@ -11,27 +11,36 @@
|
||||
#include "util/format/builder.h"
|
||||
#include "util/format/format.h"
|
||||
#include "util/format/parser.h"
|
||||
#include "util/meta/assert.h"
|
||||
|
||||
namespace Util::Format {
|
||||
|
||||
void variadicFormatImpl(Builder& builder, Parser& parser, TypeErasedParameters& parameters)
|
||||
{
|
||||
// Consume everything until '{' or EOF
|
||||
const auto literal = parser.consumeLiteral();
|
||||
builder.putLiteral(literal);
|
||||
|
||||
if (!parameters.isEOF()) {
|
||||
std::string_view specifier;
|
||||
if (parser.consumeSpecifier(specifier)) {
|
||||
parser.applySpecifier(builder, specifier);
|
||||
// Reached end of format string
|
||||
if (parser.isEOF()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& parameter = parameters.parameter(parameters.tell());
|
||||
parameter.format(builder, parameter.value);
|
||||
// Consume index + ':'
|
||||
auto indexMaybe = parser.consumeIndex();
|
||||
|
||||
// Get parameter at index, or next
|
||||
size_t index = indexMaybe.has_value() ? indexMaybe.value() : parameters.tell();
|
||||
VERIFY(index < parameters.size(), "argument not found at index '%zu':'%zu'", index, parameters.size());
|
||||
auto& parameter = parameters.parameter(index);
|
||||
|
||||
// Format the parameter
|
||||
parameter.format(builder, parser, parameter.value);
|
||||
|
||||
// Go to next parameter
|
||||
parameters.ignore();
|
||||
|
||||
builder.resetSpecifiers();
|
||||
}
|
||||
|
||||
// Recurse
|
||||
if (!parser.isEOF()) {
|
||||
variadicFormatImpl(builder, parser, parameters);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ struct Parameter {
|
||||
template<typename T>
|
||||
void formatParameterValue(Builder& builder, const void* value)
|
||||
{
|
||||
_format(builder, *static_cast<const T*>(value));
|
||||
Formatter<T> formatter;
|
||||
formatter.format(builder, *static_cast<const T*>(value));
|
||||
}
|
||||
|
||||
// Type erasure improves both compile time and binary size significantly
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // int32_t, uint32_t, int64_t,
|
||||
#include <cstring> // strlen
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "util/format/builder.h"
|
||||
#include "util/format/formatter.h"
|
||||
#include "util/format/parser.h"
|
||||
|
||||
namespace Util::Format {
|
||||
|
||||
// Integral
|
||||
|
||||
template<>
|
||||
void Formatter<int32_t>::format(Builder& builder, int32_t value) const
|
||||
{
|
||||
builder.putI32(value);
|
||||
}
|
||||
|
||||
template<>
|
||||
void Formatter<uint32_t>::format(Builder& builder, uint32_t value) const
|
||||
{
|
||||
builder.putU32(value);
|
||||
}
|
||||
|
||||
template<>
|
||||
void Formatter<int64_t>::format(Builder& builder, int64_t value) const
|
||||
{
|
||||
builder.putI64(value);
|
||||
}
|
||||
|
||||
template<>
|
||||
void Formatter<size_t>::format(Builder& builder, size_t value) const
|
||||
{
|
||||
builder.putU64(value);
|
||||
}
|
||||
|
||||
// Floating point
|
||||
|
||||
template<>
|
||||
void Formatter<float>::format(Builder& builder, float value) const
|
||||
{
|
||||
builder.putF32(value);
|
||||
}
|
||||
|
||||
template<>
|
||||
void Formatter<double>::format(Builder& builder, double value) const
|
||||
{
|
||||
builder.putF64(value);
|
||||
}
|
||||
|
||||
// Char
|
||||
|
||||
template<>
|
||||
void Formatter<char>::format(Builder& builder, char value) const
|
||||
{
|
||||
builder.putCharacter(value);
|
||||
}
|
||||
|
||||
template<>
|
||||
void Formatter<bool>::format(Builder& builder, bool value) const
|
||||
{
|
||||
builder.putString(value ? "true" : "false");
|
||||
}
|
||||
|
||||
// String
|
||||
|
||||
void Formatter<const char*>::format(Builder& builder, const char* value) const
|
||||
{
|
||||
builder.putString(value != nullptr ? std::string_view { value, strlen(value) } : "(nil)");
|
||||
}
|
||||
|
||||
template<>
|
||||
void Formatter<std::string_view>::format(Builder& builder, std::string_view value) const
|
||||
{
|
||||
builder.putString(value);
|
||||
}
|
||||
|
||||
// Pointer
|
||||
|
||||
void Formatter<std::nullptr_t>::format(Builder& builder, std::nullptr_t) const
|
||||
{
|
||||
Formatter<const void*>::format(builder, 0);
|
||||
}
|
||||
|
||||
// Containers
|
||||
|
||||
} // namespace Util::Format
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // int32_t, uint8_t, uint32_t, int64_t,
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "util/format/builder.h"
|
||||
#include "util/format/parser.h"
|
||||
|
||||
namespace Util::Format {
|
||||
|
||||
|
||||
template<typename T>
|
||||
struct Formatter {
|
||||
|
||||
void format(Builder& builder, T value) const { (void)builder, (void)value; }
|
||||
};
|
||||
|
||||
// Integral
|
||||
|
||||
template<>
|
||||
void Formatter<int32_t>::format(Builder& builder, int32_t value) const;
|
||||
|
||||
template<>
|
||||
void Formatter<uint32_t>::format(Builder& builder, uint32_t value) const;
|
||||
|
||||
template<>
|
||||
void Formatter<int64_t>::format(Builder& builder, int64_t value) const;
|
||||
|
||||
template<>
|
||||
void Formatter<size_t>::format(Builder& builder, size_t value) const; // uint64_t
|
||||
|
||||
// Floating point
|
||||
|
||||
template<>
|
||||
void Formatter<float>::format(Builder& builder, float value) const;
|
||||
|
||||
template<>
|
||||
void Formatter<double>::format(Builder& builder, double value) const;
|
||||
|
||||
// Char
|
||||
|
||||
template<>
|
||||
void Formatter<char>::format(Builder& builder, char value) const;
|
||||
|
||||
template<>
|
||||
void Formatter<bool>::format(Builder& builder, bool value) const;
|
||||
|
||||
// String
|
||||
|
||||
template<>
|
||||
void Formatter<std::string_view>::format(Builder& builder, std::string_view value) const;
|
||||
|
||||
template<>
|
||||
struct Formatter<std::string> : Formatter<std::string_view> {
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Formatter<const char*> : Formatter<std::string_view> {
|
||||
void format(Builder& builder, const char* value) const;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Formatter<char*> : Formatter<const char*> {
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
struct Formatter<char[N]> : Formatter<const char*> {
|
||||
};
|
||||
|
||||
// Pointer
|
||||
|
||||
template<typename T>
|
||||
struct Formatter<T*> {
|
||||
void format(Builder& builder, T* value) const
|
||||
{
|
||||
value == nullptr
|
||||
? builder.putString("0x0")
|
||||
: builder.putPointer(static_cast<const void*>(value));
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Formatter<std::nullptr_t> : Formatter<const void*> {
|
||||
void format(Builder& builder, std::nullptr_t) const;
|
||||
};
|
||||
|
||||
// Container
|
||||
|
||||
template<typename T>
|
||||
struct Formatter<std::vector<T>> : Formatter<T> {
|
||||
void format(Builder& builder, const std::vector<T>& value) const
|
||||
{
|
||||
builder.putString("{\n");
|
||||
for (auto it = value.cbegin(); it != value.cend(); ++it) {
|
||||
builder.putString(" ");
|
||||
Formatter<T>::format(builder, *it);
|
||||
|
||||
// Add comma, except after the last element
|
||||
if (it != std::prev(value.end(), 1)) {
|
||||
builder.putCharacter(',');
|
||||
}
|
||||
builder.putCharacter('\n');
|
||||
}
|
||||
builder.putCharacter('}');
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Util::Format
|
||||
+47
-25
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <algorithm> // replace
|
||||
#include <cstddef> // size_t
|
||||
#include <limits> // numeric_limits
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
@@ -53,7 +54,9 @@ void Parser::checkFormatParameterConsistency()
|
||||
if (peek0 == '{') {
|
||||
braceOpen++;
|
||||
|
||||
VERIFY(peek1 == '}' || peek1 == ':', "invalid parameter reference");
|
||||
if (peek1 >= '0' && peek1 <= '9') {
|
||||
m_mode = ArgumentIndexingMode::Manual;
|
||||
}
|
||||
}
|
||||
if (peek0 == '}') {
|
||||
braceClose++;
|
||||
@@ -63,13 +66,13 @@ void Parser::checkFormatParameterConsistency()
|
||||
}
|
||||
m_index = 0;
|
||||
|
||||
VERIFY(!(braceOpen < braceClose), "extra open braces in format string");
|
||||
// VERIFY(!(braceOpen < braceClose), "extra open braces in format string");
|
||||
|
||||
VERIFY(!(braceOpen > braceClose), "extra closing braces in format string");
|
||||
// VERIFY(!(braceOpen > braceClose), "extra closing braces in format string");
|
||||
|
||||
VERIFY(!(braceOpen < m_parameterCount), "format string does not reference all passed parameters");
|
||||
// VERIFY(!(braceOpen < m_parameterCount), "format string does not reference all passed parameters");
|
||||
|
||||
VERIFY(!(braceOpen > m_parameterCount), "format string references nonexistent parameter");
|
||||
// VERIFY(!(braceOpen > m_parameterCount), "format string references nonexistent parameter");
|
||||
}
|
||||
|
||||
std::string_view Parser::consumeLiteral()
|
||||
@@ -101,44 +104,63 @@ std::string_view Parser::consumeLiteral()
|
||||
return m_input.substr(begin);
|
||||
}
|
||||
|
||||
bool Parser::consumeSpecifier(std::string_view& specifier)
|
||||
std::optional<size_t> Parser::consumeIndex()
|
||||
{
|
||||
if (!consumeSpecific('{')) {
|
||||
return false;
|
||||
VERIFY_NOT_REACHED();
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!consumeSpecific(':')) {
|
||||
VERIFY(consumeSpecific('}'), "invalid parameter reference");
|
||||
specifier = "";
|
||||
switch (m_mode) {
|
||||
case ArgumentIndexingMode::Automatic: {
|
||||
VERIFY(consumeSpecific('}') || consumeSpecific(':'), "expecting '}' or ':', not '%c'", peek());
|
||||
return {};
|
||||
}
|
||||
else {
|
||||
case ArgumentIndexingMode::Manual: {
|
||||
const auto begin = tell();
|
||||
|
||||
// Go until AFTER the closing brace
|
||||
while (peek(-1) != '}') {
|
||||
consume();
|
||||
while (!isEOF()) {
|
||||
char peek0 = peek();
|
||||
if (peek0 == '}' || peek0 == ':') {
|
||||
break;
|
||||
}
|
||||
|
||||
specifier = m_input.substr(begin, tell() - begin - 1);
|
||||
VERIFY(peek0 >= '0' && peek0 <= '9', "expecting number, not '%c'", peek0);
|
||||
|
||||
ignore();
|
||||
}
|
||||
|
||||
return true;
|
||||
size_t result = stringToNumber(m_input.substr(begin, tell() - begin));
|
||||
|
||||
if (peek() == ':') {
|
||||
ignore();
|
||||
}
|
||||
|
||||
void Parser::applySpecifier(Builder& builder, std::string_view specifier)
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
size_t Parser::stringToNumber(std::string_view value)
|
||||
{
|
||||
if (specifier[0] == '.') {
|
||||
size_t value = 0;
|
||||
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
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
for (size_t i = 1; i < specifier.length(); ++i) {
|
||||
if (specifier[i] < '0' || specifier[i] > '9') {
|
||||
return;
|
||||
}
|
||||
value *= 10;
|
||||
value += specifier[i] - '0'; // Subtract ASCII 48 to get the number
|
||||
}
|
||||
|
||||
builder.setPrecision(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
#include "util/genericlexer.h"
|
||||
@@ -17,17 +18,24 @@ class Builder;
|
||||
|
||||
class Parser final : public GenericLexer {
|
||||
public:
|
||||
enum class ArgumentIndexingMode {
|
||||
Automatic, // {} ,{}
|
||||
Manual, // {0},{1}
|
||||
};
|
||||
|
||||
Parser(std::string_view format, size_t parameterCount);
|
||||
virtual ~Parser();
|
||||
|
||||
void checkFormatParameterConsistency();
|
||||
|
||||
std::string_view consumeLiteral();
|
||||
bool consumeSpecifier(std::string_view& specifier);
|
||||
std::optional<size_t> consumeIndex();
|
||||
|
||||
size_t stringToNumber(std::string_view value);
|
||||
|
||||
void applySpecifier(Builder& builder, std::string_view specifier);
|
||||
|
||||
private:
|
||||
ArgumentIndexingMode m_mode { ArgumentIndexingMode::Automatic };
|
||||
size_t m_parameterCount { 0 };
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user