Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75034a2c5a | ||
|
|
835481f4a5 | ||
|
|
9b3489676e | ||
|
|
dd209a2609 | ||
|
|
2408be335d | ||
|
|
42143fed71 | ||
|
|
8dd3f11d7b | ||
|
|
e3041bfdc7 | ||
|
|
4ce026ec78 | ||
|
|
4e6c5ca341 | ||
|
|
b4100113fe |
@@ -0,0 +1,6 @@
|
|||||||
|
# -*- yaml -*-
|
||||||
|
|
||||||
|
---
|
||||||
|
# FIXME: Figure out why NOLINTBEGIN/NOLINTEND doesnt work
|
||||||
|
Checks: -misc-unused-using-decls
|
||||||
|
...
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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 = { { ¶meters, 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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <algorithm> // max, min
|
||||||
|
|
||||||
|
#include "util/genericlexer.h"
|
||||||
|
|
||||||
|
namespace Util {
|
||||||
|
|
||||||
|
GenericLexer::GenericLexer(std::string_view input)
|
||||||
|
: m_input(input)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
GenericLexer::~GenericLexer()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
size_t GenericLexer::tell() const
|
||||||
|
{
|
||||||
|
return m_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GenericLexer::tellRemaining() const
|
||||||
|
{
|
||||||
|
return m_input.length() - m_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GenericLexer::isEOF() const
|
||||||
|
{
|
||||||
|
return m_index >= m_input.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
char GenericLexer::peek(size_t offset) const
|
||||||
|
{
|
||||||
|
return (m_index + offset >= 0 && m_index + offset < m_input.length())
|
||||||
|
? m_input[m_index + offset]
|
||||||
|
: '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericLexer::ignore(size_t count)
|
||||||
|
{
|
||||||
|
m_index += std::min(count, m_input.length() - m_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericLexer::retreat(size_t count)
|
||||||
|
{
|
||||||
|
m_index -= std::min(count, m_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
char GenericLexer::consume()
|
||||||
|
{
|
||||||
|
assert(!isEOF());
|
||||||
|
return m_input[m_index++];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GenericLexer::consumeSpecific(const char& character)
|
||||||
|
{
|
||||||
|
if (peek() != character) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ignore();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Util
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef UTIL_GENERIC_LEXER_H
|
||||||
|
#define UTIL_GENERIC_LEXER_H
|
||||||
|
|
||||||
|
#include <cassert> // assert
|
||||||
|
#include <cstddef> // size_t
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace Util {
|
||||||
|
|
||||||
|
class GenericLexer {
|
||||||
|
public:
|
||||||
|
GenericLexer(std::string_view input);
|
||||||
|
virtual ~GenericLexer();
|
||||||
|
|
||||||
|
// Position
|
||||||
|
|
||||||
|
size_t tell() const;
|
||||||
|
size_t tellRemaining() const;
|
||||||
|
bool isEOF() const;
|
||||||
|
|
||||||
|
// Access
|
||||||
|
|
||||||
|
char peek(size_t offset = 0) const;
|
||||||
|
|
||||||
|
// Modifiers
|
||||||
|
|
||||||
|
void ignore(size_t count = 1);
|
||||||
|
void retreat(size_t count = 1);
|
||||||
|
char consume();
|
||||||
|
bool consumeSpecific(const char& character);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
size_t m_index { 0 };
|
||||||
|
std::string_view m_input;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Util
|
||||||
|
|
||||||
|
#endif // UTIL_GENERIC_LEXER_H
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef JSON_CONVERSION_H
|
|
||||||
#define JSON_CONVERSION_H
|
|
||||||
|
|
||||||
namespace Json {
|
|
||||||
|
|
||||||
namespace Detail {
|
|
||||||
|
|
||||||
// Avoid ODR (One Definition Rule) violations
|
|
||||||
template<typename T>
|
|
||||||
constexpr T staticConst {};
|
|
||||||
|
|
||||||
} // namespace Detail
|
|
||||||
|
|
||||||
} // namespace Json
|
|
||||||
|
|
||||||
#endif // JSON_CONVERSION_H
|
|
||||||
@@ -17,8 +17,8 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "util/json/array.h"
|
#include "util/json/array.h"
|
||||||
#include "util/json/conversion.h"
|
|
||||||
#include "util/json/object.h"
|
#include "util/json/object.h"
|
||||||
|
#include "util/meta/odr.h"
|
||||||
|
|
||||||
namespace Json {
|
namespace Json {
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ struct fromJsonFunction {
|
|||||||
// Anonymous namespace prevents multiple definition of the reference
|
// Anonymous namespace prevents multiple definition of the reference
|
||||||
namespace {
|
namespace {
|
||||||
// Function object
|
// Function object
|
||||||
constexpr const auto& fromJson = Detail::staticConst<Detail::fromJsonFunction>; // NOLINT (misc-definitions-in-headers)
|
constexpr const auto& fromJson = Util::Detail::staticConst<Detail::fromJsonFunction>; // NOLINT(misc-definitions-in-headers,clang-diagnostic-unused-variable)
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
} // namespace Json
|
} // namespace Json
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
namespace Json {
|
namespace Json {
|
||||||
|
|
||||||
Job::Job(const std::string& input)
|
Job::Job(std::string_view input)
|
||||||
: m_input(input)
|
: m_input(input)
|
||||||
{
|
{
|
||||||
// FIXME: Make this work for all newline types: \n, \r, \r\n
|
// FIXME: Make this work for all newline types: \n, \r, \r\n
|
||||||
@@ -68,7 +68,7 @@ void Job::printErrorLine(Token token, const char* message)
|
|||||||
message);
|
message);
|
||||||
|
|
||||||
// Get the JSON line that caused the error
|
// Get the JSON line that caused the error
|
||||||
std::istringstream input(m_input);
|
std::istringstream input(m_input.data());
|
||||||
std::string line;
|
std::string line;
|
||||||
for (size_t i = 0; std::getline(input, line); ++i) {
|
for (size_t i = 0; std::getline(input, line); ++i) {
|
||||||
if (i == token.line) {
|
if (i == token.line) {
|
||||||
|
|||||||
+4
-4
@@ -8,7 +8,7 @@
|
|||||||
#define JSON_JOB_H
|
#define JSON_JOB_H
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <string>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "util/json/lexer.h"
|
#include "util/json/lexer.h"
|
||||||
@@ -19,7 +19,7 @@ class Value;
|
|||||||
|
|
||||||
class Job {
|
class Job {
|
||||||
public:
|
public:
|
||||||
Job(const std::string& input);
|
Job(std::string_view input);
|
||||||
virtual ~Job();
|
virtual ~Job();
|
||||||
|
|
||||||
enum class Color {
|
enum class Color {
|
||||||
@@ -36,13 +36,13 @@ public:
|
|||||||
void printErrorLine(Token token, const char* message);
|
void printErrorLine(Token token, const char* message);
|
||||||
|
|
||||||
bool success() const { return m_success; }
|
bool success() const { return m_success; }
|
||||||
const std::string& input() const { return m_input; }
|
std::string_view input() const { return m_input; }
|
||||||
std::vector<Token>* tokens() { return &m_tokens; }
|
std::vector<Token>* tokens() { return &m_tokens; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_success { true };
|
bool m_success { true };
|
||||||
|
|
||||||
std::string m_input;
|
std::string_view m_input;
|
||||||
size_t m_lineNumbersWidth { 0 };
|
size_t m_lineNumbersWidth { 0 };
|
||||||
|
|
||||||
std::vector<Token> m_tokens;
|
std::vector<Token> m_tokens;
|
||||||
|
|||||||
+26
-54
@@ -13,7 +13,8 @@
|
|||||||
namespace Json {
|
namespace Json {
|
||||||
|
|
||||||
Lexer::Lexer(Job* job)
|
Lexer::Lexer(Job* job)
|
||||||
: m_job(job)
|
: GenericLexer(job->input())
|
||||||
|
, m_job(job)
|
||||||
, m_tokens(job->tokens())
|
, m_tokens(job->tokens())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -26,7 +27,7 @@ Lexer::~Lexer()
|
|||||||
|
|
||||||
void Lexer::analyze()
|
void Lexer::analyze()
|
||||||
{
|
{
|
||||||
while (m_index < m_job->input().length()) {
|
while (m_index < m_input.length()) {
|
||||||
switch (peek()) {
|
switch (peek()) {
|
||||||
case '{':
|
case '{':
|
||||||
m_tokens->push_back({ Token::Type::BraceOpen, m_line, m_column, "{" });
|
m_tokens->push_back({ Token::Type::BraceOpen, m_line, m_column, "{" });
|
||||||
@@ -47,7 +48,7 @@ void Lexer::analyze()
|
|||||||
m_tokens->push_back({ Token::Type::Comma, m_line, m_column, "," });
|
m_tokens->push_back({ Token::Type::Comma, m_line, m_column, "," });
|
||||||
break;
|
break;
|
||||||
case '"':
|
case '"':
|
||||||
if (!getString()) {
|
if (!consumeString()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -62,7 +63,7 @@ void Lexer::analyze()
|
|||||||
case '7':
|
case '7':
|
||||||
case '8':
|
case '8':
|
||||||
case '9':
|
case '9':
|
||||||
if (!getNumber()) {
|
if (!consumeNumber()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -92,7 +93,7 @@ void Lexer::analyze()
|
|||||||
case 'x':
|
case 'x':
|
||||||
case 'y':
|
case 'y':
|
||||||
case 'z':
|
case 'z':
|
||||||
if (!getLiteral()) {
|
if (!consumeLiteral()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -100,7 +101,7 @@ void Lexer::analyze()
|
|||||||
case '\t':
|
case '\t':
|
||||||
break;
|
break;
|
||||||
case '\r':
|
case '\r':
|
||||||
if (peekNext() == '\n') { // CRLF \r\n
|
if (peek(1) == '\n') { // CRLF \r\n
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
m_column = -1;
|
m_column = -1;
|
||||||
@@ -119,42 +120,14 @@ void Lexer::analyze()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
increment();
|
ignore();
|
||||||
|
m_column++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
|
|
||||||
char Lexer::peek()
|
bool Lexer::consumeString()
|
||||||
{
|
|
||||||
return m_job->input()[m_index];
|
|
||||||
}
|
|
||||||
|
|
||||||
char Lexer::peekNext()
|
|
||||||
{
|
|
||||||
return m_job->input()[m_index + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
void Lexer::increment()
|
|
||||||
{
|
|
||||||
m_index++;
|
|
||||||
m_column++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Lexer::decrement()
|
|
||||||
{
|
|
||||||
m_index--;
|
|
||||||
m_column--;
|
|
||||||
}
|
|
||||||
|
|
||||||
char Lexer::consume()
|
|
||||||
{
|
|
||||||
char character = peek();
|
|
||||||
increment();
|
|
||||||
return character;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Lexer::getString()
|
|
||||||
{
|
{
|
||||||
size_t column = m_column;
|
size_t column = m_column;
|
||||||
std::string symbol = "";
|
std::string symbol = "";
|
||||||
@@ -166,21 +139,21 @@ bool Lexer::getString()
|
|||||||
|
|
||||||
if (!escape && character == '\\') {
|
if (!escape && character == '\\') {
|
||||||
symbol += '\\';
|
symbol += '\\';
|
||||||
increment();
|
ignore();
|
||||||
escape = true;
|
escape = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!escape &&
|
if (!escape
|
||||||
(character == '"'
|
&& (character == '"'
|
||||||
|| character == '\r'
|
|| character == '\r'
|
||||||
|| character == '\n'
|
|| character == '\n'
|
||||||
|| character == '\0')) {
|
|| character == '\0')) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
symbol += character;
|
symbol += character;
|
||||||
increment();
|
ignore();
|
||||||
|
|
||||||
if (escape) {
|
if (escape) {
|
||||||
escape = false;
|
escape = false;
|
||||||
@@ -197,7 +170,7 @@ bool Lexer::getString()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Lexer::getNumberOrLiteral(Token::Type type)
|
bool Lexer::consumeNumberOrLiteral(Token::Type type)
|
||||||
{
|
{
|
||||||
size_t index = m_index;
|
size_t index = m_index;
|
||||||
size_t column = m_column;
|
size_t column = m_column;
|
||||||
@@ -216,30 +189,29 @@ bool Lexer::getNumberOrLiteral(Token::Type type)
|
|||||||
|| character == '\t'
|
|| character == '\t'
|
||||||
|| character == '\r'
|
|| character == '\r'
|
||||||
|| character == '\n'
|
|| character == '\n'
|
||||||
|| character == '\0'
|
|| character == '\0') {
|
||||||
) {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
increment();
|
ignore();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_tokens->push_back({ type, m_line, column,
|
m_tokens->push_back({ type, m_line, column,
|
||||||
m_job->input().substr(index, m_index - index) });
|
std::string(m_input.substr(index, m_index - index)) });
|
||||||
|
|
||||||
decrement();
|
retreat();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Lexer::getNumber()
|
bool Lexer::consumeNumber()
|
||||||
{
|
{
|
||||||
return getNumberOrLiteral(Token::Type::Number);
|
return consumeNumberOrLiteral(Token::Type::Number);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Lexer::getLiteral()
|
bool Lexer::consumeLiteral()
|
||||||
{
|
{
|
||||||
return getNumberOrLiteral(Token::Type::Literal);
|
return consumeNumberOrLiteral(Token::Type::Literal);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Json
|
} // namespace Json
|
||||||
|
|||||||
+7
-14
@@ -11,10 +11,11 @@
|
|||||||
// https://www.rfc-editor.org/rfc/pdfrfc/rfc8259.txt.pdf
|
// https://www.rfc-editor.org/rfc/pdfrfc/rfc8259.txt.pdf
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <memory> // shared_ptr
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "util/genericlexer.h"
|
||||||
|
|
||||||
namespace Json {
|
namespace Json {
|
||||||
|
|
||||||
class Job;
|
class Job;
|
||||||
@@ -40,7 +41,7 @@ struct Token {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Lexical analyzer
|
// Lexical analyzer
|
||||||
class Lexer {
|
class Lexer final : public Util::GenericLexer {
|
||||||
public:
|
public:
|
||||||
Lexer(Job* job);
|
Lexer(Job* job);
|
||||||
virtual ~Lexer();
|
virtual ~Lexer();
|
||||||
@@ -48,21 +49,13 @@ public:
|
|||||||
void analyze();
|
void analyze();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
char peek();
|
bool consumeString();
|
||||||
char peekNext();
|
bool consumeNumberOrLiteral(Token::Type type);
|
||||||
|
bool consumeNumber();
|
||||||
void increment();
|
bool consumeLiteral();
|
||||||
void decrement();
|
|
||||||
char consume();
|
|
||||||
|
|
||||||
bool getString();
|
|
||||||
bool getNumberOrLiteral(Token::Type type);
|
|
||||||
bool getNumber();
|
|
||||||
bool getLiteral();
|
|
||||||
|
|
||||||
Job* m_job { nullptr };
|
Job* m_job { nullptr };
|
||||||
|
|
||||||
size_t m_index { 0 };
|
|
||||||
size_t m_column { 0 };
|
size_t m_column { 0 };
|
||||||
size_t m_line { 0 };
|
size_t m_line { 0 };
|
||||||
|
|
||||||
|
|||||||
+46
-48
@@ -44,19 +44,19 @@ Value Parser::parse()
|
|||||||
Token token = peek();
|
Token token = peek();
|
||||||
switch (token.type) {
|
switch (token.type) {
|
||||||
case Token::Type::Literal:
|
case Token::Type::Literal:
|
||||||
result = getLiteral();
|
result = consumeLiteral();
|
||||||
break;
|
break;
|
||||||
case Token::Type::Number:
|
case Token::Type::Number:
|
||||||
result = getNumber();
|
result = consumeNumber();
|
||||||
break;
|
break;
|
||||||
case Token::Type::String:
|
case Token::Type::String:
|
||||||
result = getString();
|
result = consumeString();
|
||||||
break;
|
break;
|
||||||
case Token::Type::BracketOpen:
|
case Token::Type::BracketOpen:
|
||||||
result = getArray();
|
result = consumeArray();
|
||||||
break;
|
break;
|
||||||
case Token::Type::BraceOpen:
|
case Token::Type::BraceOpen:
|
||||||
result = getObject();
|
result = consumeObject();
|
||||||
break;
|
break;
|
||||||
case Token::Type::BracketClose:
|
case Token::Type::BracketClose:
|
||||||
m_job->printErrorLine(token, "expecting value, not ']'");
|
m_job->printErrorLine(token, "expecting value, not ']'");
|
||||||
@@ -72,7 +72,7 @@ Value Parser::parse()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!reachedEnd()) {
|
if (!isEOF()) {
|
||||||
m_job->printErrorLine(peek(), "multiple root elements");
|
m_job->printErrorLine(peek(), "multiple root elements");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,36 +81,31 @@ Value Parser::parse()
|
|||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
|
|
||||||
bool Parser::reachedEnd()
|
bool Parser::isEOF()
|
||||||
{
|
{
|
||||||
return m_index >= m_tokens->size();
|
return m_index >= m_tokens->size();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Parser::seekForward(Token::Type type)
|
|
||||||
{
|
|
||||||
for (; !reachedEnd(); ++m_index) {
|
|
||||||
if (peek().type == type) {
|
|
||||||
m_index++;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Token Parser::peek()
|
Token Parser::peek()
|
||||||
{
|
{
|
||||||
return m_tokens->at(m_index);
|
assert(!isEOF());
|
||||||
|
return (*m_tokens)[m_index];
|
||||||
}
|
}
|
||||||
|
|
||||||
Token Parser::consume()
|
Token Parser::consume()
|
||||||
{
|
{
|
||||||
Token token = peek();
|
assert(!isEOF());
|
||||||
m_index++;
|
return (*m_tokens)[m_index++];
|
||||||
return token;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Value Parser::getLiteral()
|
void Parser::ignoreUntil(Token::Type type)
|
||||||
|
{
|
||||||
|
while (!isEOF() && peek().type != type) {
|
||||||
|
++m_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Parser::consumeLiteral()
|
||||||
{
|
{
|
||||||
Token token = consume();
|
Token token = consume();
|
||||||
|
|
||||||
@@ -128,7 +123,7 @@ Value Parser::getLiteral()
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Value Parser::getNumber()
|
Value Parser::consumeNumber()
|
||||||
{
|
{
|
||||||
Token token = consume();
|
Token token = consume();
|
||||||
|
|
||||||
@@ -231,7 +226,7 @@ Value Parser::getNumber()
|
|||||||
return std::stod(token.symbol);
|
return std::stod(token.symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
Value Parser::getString()
|
Value Parser::consumeString()
|
||||||
{
|
{
|
||||||
Token token = consume();
|
Token token = consume();
|
||||||
|
|
||||||
@@ -305,7 +300,7 @@ Value Parser::getString()
|
|||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
Value Parser::getArray()
|
Value Parser::consumeArray()
|
||||||
{
|
{
|
||||||
m_index++;
|
m_index++;
|
||||||
|
|
||||||
@@ -313,33 +308,34 @@ Value Parser::getArray()
|
|||||||
m_job->printErrorLine(token, message.c_str());
|
m_job->printErrorLine(token, message.c_str());
|
||||||
|
|
||||||
// After an error, try to find the closing bracket
|
// After an error, try to find the closing bracket
|
||||||
seekForward(Token::Type::BracketClose);
|
ignoreUntil(Token::Type::BracketClose);
|
||||||
|
m_index++;
|
||||||
};
|
};
|
||||||
|
|
||||||
Value array = Value::Type::Array;
|
Value array = Value::Type::Array;
|
||||||
Token token;
|
Token token;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
// EOF
|
// EOF
|
||||||
if (reachedEnd()) {
|
if (isEOF()) {
|
||||||
reportError(m_tokens->at(m_index - 1), "expecting closing ']' at end");
|
reportError(m_tokens->at(m_index - 1), "expecting closing ']' at end");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
token = peek();
|
token = peek();
|
||||||
if (token.type == Token::Type::Literal) {
|
if (token.type == Token::Type::Literal) {
|
||||||
array.emplace_back(getLiteral());
|
array.emplace_back(consumeLiteral());
|
||||||
}
|
}
|
||||||
else if (token.type == Token::Type::Number) {
|
else if (token.type == Token::Type::Number) {
|
||||||
array.emplace_back(getNumber());
|
array.emplace_back(consumeNumber());
|
||||||
}
|
}
|
||||||
else if (token.type == Token::Type::String) {
|
else if (token.type == Token::Type::String) {
|
||||||
array.emplace_back(getString());
|
array.emplace_back(consumeString());
|
||||||
}
|
}
|
||||||
else if (token.type == Token::Type::BracketOpen) {
|
else if (token.type == Token::Type::BracketOpen) {
|
||||||
array.emplace_back(getArray());
|
array.emplace_back(consumeArray());
|
||||||
}
|
}
|
||||||
else if (token.type == Token::Type::BraceOpen) {
|
else if (token.type == Token::Type::BraceOpen) {
|
||||||
array.emplace_back(getObject());
|
array.emplace_back(consumeObject());
|
||||||
}
|
}
|
||||||
else if (token.type == Token::Type::BracketClose) {
|
else if (token.type == Token::Type::BracketClose) {
|
||||||
// Trailing comma
|
// Trailing comma
|
||||||
@@ -354,7 +350,7 @@ Value Parser::getArray()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EOF
|
// EOF
|
||||||
if (reachedEnd()) {
|
if (isEOF()) {
|
||||||
reportError(token, "expecting closing ']' at end");
|
reportError(token, "expecting closing ']' at end");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -376,7 +372,7 @@ Value Parser::getArray()
|
|||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
Value Parser::getObject()
|
Value Parser::consumeObject()
|
||||||
{
|
{
|
||||||
m_index++;
|
m_index++;
|
||||||
|
|
||||||
@@ -384,7 +380,8 @@ Value Parser::getObject()
|
|||||||
m_job->printErrorLine(token, message.c_str());
|
m_job->printErrorLine(token, message.c_str());
|
||||||
|
|
||||||
// After an error, try to find the closing brace
|
// After an error, try to find the closing brace
|
||||||
seekForward(Token::Type::BraceClose);
|
ignoreUntil(Token::Type::BraceClose);
|
||||||
|
m_index++;
|
||||||
};
|
};
|
||||||
|
|
||||||
Value object = Value::Type::Object;
|
Value object = Value::Type::Object;
|
||||||
@@ -393,7 +390,7 @@ Value Parser::getObject()
|
|||||||
std::map<std::string, uint8_t> unique;
|
std::map<std::string, uint8_t> unique;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
// EOF
|
// EOF
|
||||||
if (reachedEnd()) {
|
if (isEOF()) {
|
||||||
reportError(m_tokens->at(m_index - 1), "expecting closing '}' at end");
|
reportError(m_tokens->at(m_index - 1), "expecting closing '}' at end");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -414,9 +411,10 @@ Value Parser::getObject()
|
|||||||
|
|
||||||
// Find member name
|
// Find member name
|
||||||
m_index--;
|
m_index--;
|
||||||
Value tmpName = getString();
|
Value tmpName = consumeString();
|
||||||
if (tmpName.m_type != Value::Type::String) {
|
if (tmpName.m_type != Value::Type::String) {
|
||||||
seekForward(Token::Type::BraceClose);
|
ignoreUntil(Token::Type::BraceClose);
|
||||||
|
m_index++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,7 +428,7 @@ Value Parser::getObject()
|
|||||||
unique.insert({ name, 0 });
|
unique.insert({ name, 0 });
|
||||||
|
|
||||||
// EOF
|
// EOF
|
||||||
if (reachedEnd()) {
|
if (isEOF()) {
|
||||||
reportError(token, "expecting colon, not 'EOF'");
|
reportError(token, "expecting colon, not 'EOF'");
|
||||||
reportError(token, "expecting closing '}' at end");
|
reportError(token, "expecting closing '}' at end");
|
||||||
break;
|
break;
|
||||||
@@ -444,7 +442,7 @@ Value Parser::getObject()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EOF
|
// EOF
|
||||||
if (reachedEnd()) {
|
if (isEOF()) {
|
||||||
reportError(token, "expecting value, not 'EOF'");
|
reportError(token, "expecting value, not 'EOF'");
|
||||||
reportError(token, "expecting closing '}' at end");
|
reportError(token, "expecting closing '}' at end");
|
||||||
break;
|
break;
|
||||||
@@ -453,19 +451,19 @@ Value Parser::getObject()
|
|||||||
// Add member (name:value pair) to object
|
// Add member (name:value pair) to object
|
||||||
token = peek();
|
token = peek();
|
||||||
if (token.type == Token::Type::Literal) {
|
if (token.type == Token::Type::Literal) {
|
||||||
object.emplace(name, getLiteral());
|
object.emplace(name, consumeLiteral());
|
||||||
}
|
}
|
||||||
else if (token.type == Token::Type::Number) {
|
else if (token.type == Token::Type::Number) {
|
||||||
object.emplace(name, getNumber());
|
object.emplace(name, consumeNumber());
|
||||||
}
|
}
|
||||||
else if (token.type == Token::Type::String) {
|
else if (token.type == Token::Type::String) {
|
||||||
object.emplace(name, getString());
|
object.emplace(name, consumeString());
|
||||||
}
|
}
|
||||||
else if (token.type == Token::Type::BracketOpen) {
|
else if (token.type == Token::Type::BracketOpen) {
|
||||||
object.emplace(name, getArray());
|
object.emplace(name, consumeArray());
|
||||||
}
|
}
|
||||||
else if (token.type == Token::Type::BraceOpen) {
|
else if (token.type == Token::Type::BraceOpen) {
|
||||||
object.emplace(name, getObject());
|
object.emplace(name, consumeObject());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
reportError(token, "expecting value, not '" + token.symbol + "'");
|
reportError(token, "expecting value, not '" + token.symbol + "'");
|
||||||
@@ -473,7 +471,7 @@ Value Parser::getObject()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EOF
|
// EOF
|
||||||
if (reachedEnd()) {
|
if (isEOF()) {
|
||||||
reportError(token, "expecting closing '}' at end");
|
reportError(token, "expecting closing '}' at end");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,17 +25,16 @@ public:
|
|||||||
Value parse();
|
Value parse();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool reachedEnd();
|
bool isEOF();
|
||||||
bool seekForward(Token::Type type);
|
|
||||||
|
|
||||||
Token peek();
|
Token peek();
|
||||||
Token consume();
|
Token consume();
|
||||||
|
void ignoreUntil(Token::Type type);
|
||||||
|
|
||||||
Value getLiteral();
|
Value consumeLiteral();
|
||||||
Value getNumber();
|
Value consumeNumber();
|
||||||
Value getString();
|
Value consumeString();
|
||||||
Value getArray();
|
Value consumeArray();
|
||||||
Value getObject();
|
Value consumeObject();
|
||||||
|
|
||||||
Job* m_job { nullptr };
|
Job* m_job { nullptr };
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,8 @@
|
|||||||
#include <utility> // forward
|
#include <utility> // forward
|
||||||
|
|
||||||
#include "util/json/array.h"
|
#include "util/json/array.h"
|
||||||
#include "util/json/conversion.h"
|
|
||||||
#include "util/json/object.h"
|
#include "util/json/object.h"
|
||||||
|
#include "util/meta/odr.h"
|
||||||
|
|
||||||
namespace Json {
|
namespace Json {
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ struct toJsonFunction {
|
|||||||
// Anonymous namespace prevents multiple definition of the reference
|
// Anonymous namespace prevents multiple definition of the reference
|
||||||
namespace {
|
namespace {
|
||||||
// Function object
|
// Function object
|
||||||
constexpr const auto& toJson = Detail::staticConst<Detail::toJsonFunction>; // NOLINT (misc-definitions-in-headers)
|
constexpr const auto& toJson = Util::Detail::staticConst<Detail::toJsonFunction>; // NOLINT(misc-definitions-in-headers,clang-diagnostic-unused-variable)
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
} // namespace Json
|
} // namespace Json
|
||||||
|
|||||||
@@ -7,10 +7,12 @@
|
|||||||
#include <algorithm> // all_of
|
#include <algorithm> // all_of
|
||||||
#include <cassert> // assert
|
#include <cassert> // assert
|
||||||
#include <cstdint> // uint32_t
|
#include <cstdint> // uint32_t
|
||||||
|
#include <fstream> // >>
|
||||||
#include <iostream> // istream, ostream
|
#include <iostream> // istream, ostream
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility> // move, swap
|
#include <utility> // move, swap
|
||||||
|
|
||||||
|
#include "util/format/format.h"
|
||||||
#include "util/json/array.h"
|
#include "util/json/array.h"
|
||||||
#include "util/json/job.h"
|
#include "util/json/job.h"
|
||||||
#include "util/json/object.h"
|
#include "util/json/object.h"
|
||||||
@@ -152,7 +154,7 @@ void Value::clear()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Value Value::parse(const std::string& input)
|
Value Value::parse(std::string_view input)
|
||||||
{
|
{
|
||||||
return Job(input).fire();
|
return Job(input).fire();
|
||||||
}
|
}
|
||||||
@@ -330,4 +332,9 @@ std::ostream& operator<<(std::ostream& output, const Value& value)
|
|||||||
return output << value.dump(4);
|
return output << value.dump(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void format(FormatBuilder& builder, const Value& value)
|
||||||
|
{
|
||||||
|
builder.putString(value.dump(4));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Json
|
} // namespace Json
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <utility> // forward
|
#include <utility> // forward
|
||||||
|
|
||||||
|
#include "util/format/format.h"
|
||||||
#include "util/json/fromjson.h"
|
#include "util/json/fromjson.h"
|
||||||
#include "util/json/tojson.h"
|
#include "util/json/tojson.h"
|
||||||
|
|
||||||
@@ -65,7 +66,7 @@ public:
|
|||||||
|
|
||||||
// --------------------------------------
|
// --------------------------------------
|
||||||
|
|
||||||
static Value parse(const std::string& input);
|
static Value parse(std::string_view input);
|
||||||
static Value parse(std::ifstream& file);
|
static Value parse(std::ifstream& file);
|
||||||
std::string dump(const uint32_t indent = 0, const char indentCharacter = ' ') const;
|
std::string dump(const uint32_t indent = 0, const char indentCharacter = ' ') const;
|
||||||
|
|
||||||
@@ -134,6 +135,8 @@ private:
|
|||||||
std::istream& operator>>(std::istream& input, Value& value);
|
std::istream& operator>>(std::istream& input, Value& value);
|
||||||
std::ostream& operator<<(std::ostream& output, const Value& value);
|
std::ostream& operator<<(std::ostream& output, const Value& value);
|
||||||
|
|
||||||
|
void format(FormatBuilder& builder, const Value& value);
|
||||||
|
|
||||||
} // namespace Json
|
} // namespace Json
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef META_ODR_H
|
||||||
|
#define META_ODR_H
|
||||||
|
|
||||||
|
namespace Util {
|
||||||
|
|
||||||
|
namespace Detail {
|
||||||
|
|
||||||
|
// Avoid ODR (One Definition Rule) violations,
|
||||||
|
// variable templates are required to have external linkage
|
||||||
|
template<typename T>
|
||||||
|
constexpr T staticConst {};
|
||||||
|
|
||||||
|
} // namespace Detail
|
||||||
|
|
||||||
|
} // namespace Util
|
||||||
|
|
||||||
|
#endif // META_ODR_H
|
||||||
Reference in New Issue
Block a user