Browse Source

Util: Add formatting and type printing capability

Extending the type compatibility is done with an easy to use API.
master
Riyyi 2 years ago
parent
commit
835481f4a5
  1. 6
      src/util/format/.clang-tidy
  2. 62
      src/util/format/builder.cpp
  3. 50
      src/util/format/builder.h
  4. 216
      src/util/format/format.cpp
  5. 198
      src/util/format/format.h
  6. 151
      src/util/format/parser.cpp
  7. 37
      src/util/format/parser.h
  8. 168
      src/util/format/toformat.h

6
src/util/format/.clang-tidy

@ -0,0 +1,6 @@
# -*- yaml -*-
---
# FIXME: Figure out why NOLINTBEGIN/NOLINTEND doesnt work
Checks: -misc-unused-using-decls
...

62
src/util/format/builder.cpp

@ -0,0 +1,62 @@
/*
* Copyright (C) 2022 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#include <cstddef> // size_t
#include <iomanip> // setprecision
#include <ios> // fixed
#include <limits> // numeric_limits
#include <sstream> // stringstream
#include <string>
#include <string_view>
#include "util/format/builder.h"
namespace Util::Format {
void Builder::putLiteral(std::string_view literal)
{
for (size_t i = 0; i < literal.length(); ++i) {
putCharacter(literal[i]);
if (literal[i] == '{' || literal[i] == '}') {
++i;
}
}
}
void Builder::putF32(float number, size_t precision) const
{
precision = (precision > std::numeric_limits<float>::digits10) ? m_precision : precision;
std::stringstream stream;
stream
<< std::fixed
<< std::setprecision(precision)
<< number;
std::string string = stream.str();
string = string.substr(0, string.find_first_of('0', string.find('.')));
m_builder << string;
}
void Builder::putF64(double number, size_t precision) const
{
precision = (precision > std::numeric_limits<double>::digits10) ? m_precision : precision;
std::stringstream stream;
stream
<< std::fixed
<< std::setprecision(precision)
<< number;
std::string string = stream.str();
string = string.substr(0, string.find_first_of('0', string.find('.')));
m_builder << string;
}
void Builder::resetSpecifiers()
{
setPrecision();
}
} // namespace Util::Format

50
src/util/format/builder.h

@ -0,0 +1,50 @@
/*
* Copyright (C) 2022 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#ifndef UTIL_FORMAT_BUILDER_H
#define UTIL_FORMAT_BUILDER_H
#include <cstddef> // size_t
#include <cstdint> // int32_t, uint32_t, int64_t
#include <sstream> // stringstream
#include <string_view>
namespace Util::Format {
class Builder {
public:
explicit Builder(std::stringstream& builder)
: m_precision(6)
, m_builder(builder)
{
}
void putLiteral(std::string_view literal);
void putI32(int32_t number) const { m_builder << number; } // int
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 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;
};
} // namespace Util::Format
#endif // UTIL_FORMAT_BUILDER_H

216
src/util/format/format.cpp

@ -0,0 +1,216 @@
/*
* Copyright (C) 2022 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#include <sstream> // stringstream
#include <string>
#include <string_view>
#include "util/format/builder.h"
#include "util/format/format.h"
#include "util/format/parser.h"
namespace Util::Format {
void variadicFormatImpl(Builder& builder, Parser& parser, TypeErasedParameters& parameters)
{
const auto literal = parser.consumeLiteral();
builder.putLiteral(literal);
if (!parameters.isEOF()) {
std::string_view specifier;
if (parser.consumeSpecifier(specifier)) {
parser.applySpecifier(builder, specifier);
}
auto& parameter = parameters.parameter(parameters.tell());
parameter.format(builder, parameter.value);
parameters.ignore();
builder.resetSpecifiers();
}
if (!parser.isEOF()) {
variadicFormatImpl(builder, parser, parameters);
}
}
void variadicFormat(std::stringstream& stream, std::string_view format, TypeErasedParameters& parameters)
{
Builder builder { stream };
Parser parser { format, parameters.size() };
variadicFormatImpl(builder, parser, parameters);
}
// -----------------------------------------
void prettyVariadicFormat(Type type, bool bold, std::string_view format, TypeErasedParameters& parameters)
{
std::stringstream stream;
if (type != Type::None || bold) {
stream << "\033[";
switch (type) {
case Type::Info:
stream << "34";
break;
case Type::Warn:
stream << "33";
break;
case Type::Danger:
stream << "31";
break;
case Type::Success:
stream << "32";
break;
case Type::Comment:
stream << "37";
break;
default:
break;
};
if (bold) {
stream << ";1";
}
stream << "m";
}
variadicFormat(stream, format, parameters);
if (type != Type::None || bold) {
stream << "\033[0m";
}
printf("%s\n", stream.str().c_str());
}
// -----------------------------------------
Dbg::Dbg(Type type, bool bold)
: m_type(type)
, m_bold(bold)
, m_stream()
, m_builder(m_stream)
{
if (type != Type::None || m_bold) {
m_stream << "\033[";
switch (type) {
case Type::Info:
m_stream << "34";
break;
case Type::Warn:
m_stream << "33";
break;
case Type::Danger:
m_stream << "31";
break;
case Type::Success:
m_stream << "32";
break;
case Type::Comment:
m_stream << "37";
break;
default:
break;
};
if (m_bold) {
m_stream << ";1";
}
m_stream << "m";
}
}
Dbg::~Dbg()
{
if (m_type != Type::None || m_bold) {
m_stream << "\033[0m";
}
printf("%s", m_stream.str().c_str());
}
Dbg dbg()
{
return Dbg(Type::None, false);
}
Dbg dbgb()
{
return Dbg(Type::None, true);
}
Dbg info()
{
return Dbg(Type::Info, false);
}
Dbg infob()
{
return Dbg(Type::Info, true);
}
Dbg warn()
{
return Dbg(Type::Warn, false);
}
Dbg warnb()
{
return Dbg(Type::Warn, true);
}
Dbg danger()
{
return Dbg(Type::Danger, false);
}
Dbg dangerb()
{
return Dbg(Type::Danger, true);
}
Dbg success()
{
return Dbg(Type::Success, false);
}
Dbg successb()
{
return Dbg(Type::Success, true);
}
Dbg comment()
{
return Dbg(Type::Comment, false);
}
Dbg commentb()
{
return Dbg(Type::Comment, true);
}
// -----------------------------------------
Str::Str(std::string& fill)
: m_fill(fill)
, m_stream()
, m_builder(m_stream)
{
}
Str::~Str()
{
m_fill = m_stream.str();
}
Str str(std::string& fill)
{
return Str(fill);
}
} // namespace Util::Format

198
src/util/format/format.h

@ -0,0 +1,198 @@
/*
* Copyright (C) 2022 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#ifndef UTIL_FORMAT_FORMAT_H
#define UTIL_FORMAT_FORMAT_H
#include <cstddef> // size_t
#include <sstream> // stringstream
#include <string>
#include <string_view>
#include <vector>
#include "util/format/builder.h"
#include "util/format/parser.h"
#include "util/format/toformat.h"
namespace Util::Format {
struct Parameter {
const void* value;
void (*format)(Builder& builder, const void* value);
};
template<typename T>
void formatParameterValue(Builder& builder, const void* value)
{
format(builder, *static_cast<const T*>(value));
}
class TypeErasedParameters {
public:
const Parameter parameter(size_t index) { return m_parameters[index]; }
size_t tell() const { return m_index; }
size_t size() const { return m_parameters.size(); }
bool isEOF() const { return m_index >= m_parameters.size(); }
void ignore() { m_index++; }
protected:
size_t m_index { 0 };
std::vector<Parameter> m_parameters;
};
template<typename... Parameters>
class VariadicParameters final : public TypeErasedParameters {
public:
VariadicParameters(const Parameters&... parameters)
{
m_parameters = { { &parameters, formatParameterValue<Parameters> }... };
}
};
// -----------------------------------------
void variadicFormatImpl(Builder& builder, Parser& parser, TypeErasedParameters& parameters);
void variadicFormat(std::stringstream& stream, std::string_view format, TypeErasedParameters& parameters);
// -----------------------------------------
enum class Type {
None, // Foreground
Info, // Blue
Warn, // Yellow
Danger, // Red
Success, // Green
Comment, // White
};
void prettyVariadicFormat(Type type, bool bold, std::string_view format, TypeErasedParameters& parameters);
#define FORMAT_FUNCTION(name, type, bold) \
template<size_t N, typename... Parameters> \
void name(const char(&format)[N] = "", const Parameters&... parameters) \
{ \
VariadicParameters variadicParameters { parameters... }; \
prettyVariadicFormat(Type::type, bold, { format, N - 1 }, variadicParameters); \
}
FORMAT_FUNCTION(dbgln, None, false);
FORMAT_FUNCTION(dbgbln, None, true);
FORMAT_FUNCTION(infoln, Info, false);
FORMAT_FUNCTION(infobln, Info, true);
FORMAT_FUNCTION(warnln, Warn, false);
FORMAT_FUNCTION(warnbln, Warn, true);
FORMAT_FUNCTION(dangerln, Danger, false);
FORMAT_FUNCTION(dangerbln, Danger, true);
FORMAT_FUNCTION(successln, Success, false);
FORMAT_FUNCTION(successbln, Success, true);
FORMAT_FUNCTION(commentln, Comment, false);
FORMAT_FUNCTION(commentbln, Comment, true);
// -----------------------------------------
class Dbg {
public:
Dbg(Type type, bool bold);
virtual ~Dbg();
Builder& builder() { return m_builder; }
private:
Type m_type;
bool m_bold;
std::stringstream m_stream;
Builder m_builder;
};
template<typename T>
const Dbg& operator<<(const Dbg& debug, const T& value)
{
format(const_cast<Dbg&>(debug).builder(), value);
return debug;
}
Dbg dbg();
Dbg dbgb();
Dbg info();
Dbg infob();
Dbg warn();
Dbg warnb();
Dbg danger();
Dbg dangerb();
Dbg success();
Dbg successb();
Dbg comment();
Dbg commentb();
// -----------------------------------------
template<typename... Parameters>
void strln(std::string& fill, std::string_view format, const Parameters&... parameters)
{
std::stringstream stream;
VariadicParameters variadicParameters { parameters... };
variadicFormat(stream, format, variadicParameters);
fill = stream.str();
}
class Str {
public:
Str(std::string& fill);
virtual ~Str();
Builder& builder() { return m_builder; }
private:
std::string& m_fill;
std::stringstream m_stream;
Builder m_builder;
};
template<typename T>
const Str& operator<<(const Str& string, const T& value)
{
format(const_cast<Str&>(string).builder(), value);
return string;
}
Str str(std::string& fill);
} // namespace Util::Format
using Util::Format::commentbln;
using Util::Format::commentln;
using Util::Format::dangerbln;
using Util::Format::dangerln;
using Util::Format::dbgbln;
using Util::Format::dbgln;
using Util::Format::infobln;
using Util::Format::infoln;
using Util::Format::successbln;
using Util::Format::successln;
using Util::Format::warnbln;
using Util::Format::warnln;
using Util::Format::comment;
using Util::Format::commentb;
using Util::Format::danger;
using Util::Format::dangerb;
using Util::Format::dbg;
using Util::Format::dbgb;
using Util::Format::info;
using Util::Format::infob;
using Util::Format::success;
using Util::Format::successb;
using Util::Format::warn;
using Util::Format::warnb;
using Util::Format::str;
using Util::Format::strln;
using FormatBuilder = Util::Format::Builder;
#endif // UTIL_FORMAT_FORMAT_H

151
src/util/format/parser.cpp

@ -0,0 +1,151 @@
/*
* Copyright (C) 2022 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#include <algorithm> // replace
#include <cassert> // assert
#include <cstddef> // size_t
#include <string>
#include <string_view>
#include "util/format/builder.h"
#include "util/format/parser.h"
namespace Util::Format {
Parser::Parser(std::string_view format, size_t parameterCount)
: GenericLexer(format)
, m_parameterCount(parameterCount)
{
checkFormatParameterConsistency();
}
Parser::~Parser()
{
}
// -----------------------------------------
void Parser::checkFormatParameterConsistency()
{
size_t length = m_input.length();
// Format string does not reference all passed parameters
assert(length >= m_parameterCount * 2);
size_t braceOpen = 0;
size_t braceClose = 0;
while (!isEOF()) {
char peek0 = peek();
char peek1 = peek(1);
if (peek0 == '{' && peek1 == '{') {
ignore(2);
continue;
}
if (peek0 == '}' && peek1 == '}') {
ignore(2);
continue;
}
if (peek0 == '{') {
braceOpen++;
// Only empty references {} or specified references {:} are valid
assert(peek1 == '}' || peek1 == ':');
}
if (peek0 == '}') {
braceClose++;
}
ignore();
}
m_index = 0;
// Extra open braces in format string
assert(!(braceOpen < braceClose));
// Extra closing braces in format string
assert(!(braceOpen > braceClose));
// Format string does not reference all passed parameters
assert(!(braceOpen < m_parameterCount));
// Format string references nonexistent parameter
assert(!(braceOpen > m_parameterCount));
}
std::string_view Parser::consumeLiteral()
{
const auto begin = tell();
while (!isEOF()) {
char peek0 = peek();
char peek1 = peek(1);
if (peek0 == '{' && peek1 == '{') {
ignore(2);
continue;
}
if (peek0 == '}' && peek1 == '}') {
ignore(2);
continue;
}
// Get literal before the specifier {}
if (peek0 == '{' || peek0 == '}') {
return m_input.substr(begin, tell() - begin);
}
ignore();
}
return m_input.substr(begin);
}
bool Parser::consumeSpecifier(std::string_view& specifier)
{
if (!consumeSpecific('{')) {
return false;
}
if (!consumeSpecific(':')) {
assert(consumeSpecific('}'));
specifier = "";
}
else {
const auto begin = tell();
// Go until AFTER the closing brace
while (peek(-1) != '}') {
consume();
}
specifier = m_input.substr(begin, tell() - begin - 1);
}
return true;
}
void Parser::applySpecifier(Builder& builder, std::string_view specifier)
{
if (specifier[0] == '.') {
size_t value = 0;
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);
}
}
} // namespace Util::Format

37
src/util/format/parser.h

@ -0,0 +1,37 @@
/*
* Copyright (C) 2022 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#ifndef UTIL_FORMAT_PARSER_H
#define UTIL_FORMAT_PARSER_H
#include <cstddef> // size_t
#include <string_view>
#include "util/genericlexer.h"
namespace Util::Format {
class Builder;
class Parser final : public GenericLexer {
public:
Parser(std::string_view format, size_t parameterCount);
virtual ~Parser();
void checkFormatParameterConsistency();
std::string_view consumeLiteral();
bool consumeSpecifier(std::string_view& specifier);
void applySpecifier(Builder& builder, std::string_view specifier);
private:
size_t m_parameterCount { 0 };
};
} // namespace Util::Format
#endif // UTIL_FORMAT_PARSER_H

168
src/util/format/toformat.h

@ -0,0 +1,168 @@
/*
* Copyright (C) 2022 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#ifndef UTIL_TO_FORMAT_H
#define UTIL_TO_FORMAT_H
#include <cstddef> // nullptr_t
#include <cstdint> // 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
#endif // UTIL_TO_FORMAT_H
Loading…
Cancel
Save