Compare commits

...
5 Commits
12 changed files with 357 additions and 253 deletions
+2 -7
View File
@@ -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
+3 -8
View File
@@ -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;
};
+18 -9
View File
@@ -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);
}
+6 -4
View File
@@ -12,12 +12,13 @@
#include <string>
#include <string_view>
#include "util/format/builder.h"
#include "util/format/parser.h"
#include "util/format/toformat.h"
#include "util/format/formatter.h"
namespace Util::Format {
class Builder;
class Parser;
struct Parameter {
const void* value;
void (*format)(Builder& builder, const void* value);
@@ -26,7 +27,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
+93
View File
@@ -0,0 +1,93 @@
/*
* 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);
}
} // namespace Util::Format
+145
View File
@@ -0,0 +1,145 @@
/*
* 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('}');
}
};
#define UTIL_FORMAT_FORMAT_AS_MAP(type) \
template<typename K, typename V> \
struct Formatter<type<K, V>> \
: Formatter<K> \
, Formatter<V> { \
void format(Builder& builder, const type<K, V>& value) const \
{ \
builder.putString("{\n"); \
auto last = value.end(); \
for (auto it = value.begin(); it != last; ++it) { \
builder.putString(R"( ")"); \
Formatter<K>::format(builder, it->first); \
builder.putString(R"(": )"); \
Formatter<V>::format(builder, it->second); \
\
/* Add comma, except after the last element */ \
if (std::next(it) != last) { \
builder.putCharacter(','); \
} \
\
builder.putCharacter('\n'); \
} \
builder.putCharacter('}'); \
} \
}
UTIL_FORMAT_FORMAT_AS_MAP(std::map);
UTIL_FORMAT_FORMAT_AS_MAP(std::unordered_map);
} // namespace Util::Format
+16 -16
View File
@@ -17,31 +17,31 @@ std::string formatTimeElapsed()
return format("{:.3}s ", s_timer.elapsedNanoseconds() / 1000000000.0);
}
std::string formatType(Type type)
std::string formatType(LogType type)
{
std::string output;
formatTo(output, "[");
switch (type) {
case Type::Trace:
case LogType::Trace:
formatTo(output, "trace");
break;
case Type::Debug:
case LogType::Debug:
formatTo(output, fg(TerminalColor::Magenta), "debug");
break;
case Type::Success:
case LogType::Success:
formatTo(output, fg(TerminalColor::Green), "success");
break;
case Type::Info:
case LogType::Info:
formatTo(output, fg(TerminalColor::Blue), "info");
break;
case Type::Warn:
case LogType::Warn:
formatTo(output, Emphasis::Bold | fg(TerminalColor::Yellow), "warn");
break;
case Type::Error:
case LogType::Error:
formatTo(output, Emphasis::Bold | fg(TerminalColor::Red), "error");
break;
case Type::Critical:
case LogType::Critical:
formatTo(output, Emphasis::Bold | fg(TerminalColor::White) | bg(TerminalColor::Red), "critical");
break;
default:
@@ -54,7 +54,7 @@ std::string formatType(Type type)
// -----------------------------------------
LogOperatorStyle::LogOperatorStyle(FILE* file, Type type)
LogOperatorStyle::LogOperatorStyle(FILE* file, LogType type)
: m_file(file)
, m_type(type)
, m_stream()
@@ -73,37 +73,37 @@ LogOperatorStyle::~LogOperatorStyle()
LogOperatorStyle trace()
{
return LogOperatorStyle(stdout, Type::Trace);
return LogOperatorStyle(stdout, LogType::Trace);
}
LogOperatorStyle debug()
{
return LogOperatorStyle(stdout, Type::Debug);
return LogOperatorStyle(stdout, LogType::Debug);
}
LogOperatorStyle success()
{
return LogOperatorStyle(stdout, Type::Success);
return LogOperatorStyle(stdout, LogType::Success);
}
LogOperatorStyle info()
{
return LogOperatorStyle(stdout, Type::Info);
return LogOperatorStyle(stdout, LogType::Info);
}
LogOperatorStyle warn()
{
return LogOperatorStyle(stderr, Type::Warn);
return LogOperatorStyle(stderr, LogType::Warn);
}
LogOperatorStyle error()
{
return LogOperatorStyle(stderr, Type::Error);
return LogOperatorStyle(stderr, LogType::Error);
}
LogOperatorStyle critical()
{
return LogOperatorStyle(stderr, Type::Critical);
return LogOperatorStyle(stderr, LogType::Critical);
}
} // namespace Util::Format
+11 -11
View File
@@ -18,7 +18,7 @@ namespace Util::Format {
static Util::Timer s_timer;
enum class Type : uint8_t {
enum class LogType : uint8_t {
Trace, // White
Debug, // Purple
Success, // Green
@@ -29,7 +29,7 @@ enum class Type : uint8_t {
};
std::string formatTimeElapsed();
std::string formatType(Type type);
std::string formatType(LogType type);
#define LOG_FUNCTION(name, file, type) \
template<size_t N, typename... Parameters> \
@@ -42,26 +42,26 @@ std::string formatType(Type type);
print(file, "\n"); \
}
LOG_FUNCTION(trace, stdout, Type::Trace);
LOG_FUNCTION(debug, stdout, Type::Debug);
LOG_FUNCTION(success, stdout, Type::Success);
LOG_FUNCTION(info, stdout, Type::Info);
LOG_FUNCTION(warn, stderr, Type::Warn);
LOG_FUNCTION(error, stderr, Type::Error);
LOG_FUNCTION(critical, stderr, Type::Critical);
LOG_FUNCTION(trace, stdout, LogType::Trace);
LOG_FUNCTION(debug, stdout, LogType::Debug);
LOG_FUNCTION(success, stdout, LogType::Success);
LOG_FUNCTION(info, stdout, LogType::Info);
LOG_FUNCTION(warn, stderr, LogType::Warn);
LOG_FUNCTION(error, stderr, LogType::Error);
LOG_FUNCTION(critical, stderr, LogType::Critical);
// -----------------------------------------
class LogOperatorStyle {
public:
LogOperatorStyle(FILE* file, Type type);
LogOperatorStyle(FILE* file, LogType type);
virtual ~LogOperatorStyle();
Builder& builder() { return m_builder; }
private:
FILE* m_file;
Type m_type;
LogType m_type;
std::stringstream m_stream;
Builder m_builder;
+47 -25
View File
@@ -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);
}
}
+10 -2
View File
@@ -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 };
};
-165
View File
@@ -1,165 +0,0 @@
/*
* Copyright (C) 2022 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cstddef> // nullptr_t, size_t
#include <cstdint> // int32_t, uint32_t, int64_t
#include <cstring> // strlen
#include <iterator> // next
#include <map>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility> // forward
#include <vector>
#include "util/meta/odr.h"
namespace Util::Format {
namespace Detail {
template<typename FormatBuilder>
void format(FormatBuilder& builder, std::nullptr_t)
{
builder.putString("(nil)");
}
template<typename FormatBuilder, typename T>
void format(FormatBuilder& builder, T* pointer)
{
builder.putPointer(static_cast<const void*>(pointer));
}
template<typename FormatBuilder>
void format(FormatBuilder& builder, bool boolean)
{
builder.putString(boolean ? "true" : "false");
}
template<typename FormatBuilder>
void format(FormatBuilder& builder, int32_t number) // int
{
builder.putI32(number);
}
template<typename FormatBuilder>
void format(FormatBuilder& builder, uint32_t number) // unsigned int
{
builder.putU32(number);
}
template<typename FormatBuilder>
void format(FormatBuilder& builder, int64_t number) // long int
{
builder.putI64(number);
}
template<typename FormatBuilder> // uint64_t
void format(FormatBuilder& builder, size_t number) // long unsigned int
{
builder.putU64(number);
}
template<typename FormatBuilder>
void format(FormatBuilder& builder, float number)
{
builder.putF32(number);
}
template<typename FormatBuilder>
void format(FormatBuilder& builder, double number)
{
builder.putF64(number);
}
template<typename FormatBuilder>
void format(FormatBuilder& builder, char character)
{
builder.putCharacter(character);
}
template<typename FormatBuilder>
void format(FormatBuilder& builder, const char* string)
{
builder.putString({ string, strlen(string) });
}
template<typename FormatBuilder>
void format(FormatBuilder& builder, const std::string& string)
{
builder.putString(string);
}
template<typename FormatBuilder>
void format(FormatBuilder& builder, std::string_view string)
{
builder.putString(string);
}
template<typename FormatBuilder, typename T>
void format(FormatBuilder& builder, const std::vector<T>& array)
{
builder.putString("{\n");
for (auto it = array.cbegin(); it != array.cend(); ++it) {
builder.putString(" ");
format(builder, *it);
// Add comma, except after the last element
if (it != std::prev(array.end(), 1)) {
builder.putCharacter(',');
}
builder.putCharacter('\n');
}
builder.putCharacter('}');
}
#define FORMAT_MAP \
builder.putString("{\n"); \
auto last = map.end(); \
for (auto it = map.begin(); it != last; ++it) { \
builder.putString(R"( ")"); \
format(builder, it->first); \
builder.putString(R"(": )"); \
format(builder, it->second); \
\
/* Add comma, except after the last element */ \
if (std::next(it) != last) { \
builder.putCharacter(','); \
} \
\
builder.putCharacter('\n'); \
} \
builder.putCharacter('}');
template<typename FormatBuilder, typename K, typename V>
void format(FormatBuilder& builder, const std::map<K, V>& map)
{
FORMAT_MAP;
}
template<typename FormatBuilder, typename K, typename V>
void format(FormatBuilder& builder, const std::unordered_map<K, V>& map)
{
FORMAT_MAP;
}
struct formatFunction {
template<typename FormatBuilder, typename T>
auto operator()(FormatBuilder& builder, T&& value) const
{
return format(builder, std::forward<T>(value));
}
};
} // namespace Detail
namespace {
constexpr const auto& _format = Util::Detail::staticConst<Detail::formatFunction>; // NOLINT(misc-definitions-in-headers,clang-diagnostic-unused-variable)
} // namespace
} // namespace Util::Format
+2 -2
View File
@@ -66,8 +66,8 @@ inline void __assertion_failed(const char* assertion, const char* file, uint32_t
fprintf(stderr, ": ");
// Cant use the formatting library to print asserts caused by the formatting library
std::string_view functionString = function;
if (!functionString.starts_with("Util::Format::")
&& !functionString.starts_with("Util::GenericLexer::")) {
if (functionString.find("Util::Format::") != std::string_view::npos
&& functionString.find("Util::GenericLexer::") != std::string_view::npos) {
std::string message;
formatTo(message, parameters...);
fprintf(stderr, "%s", message.c_str());