This commit is contained in:
Mars 2024-06-05 19:04:53 -04:00
parent 693fa17d10
commit 500138ce67
Signed by: pupbrained
GPG key ID: 874E22DF2F9DFCB5
331 changed files with 12348 additions and 60593 deletions

View file

@ -1,2 +1,2 @@
CompileFlags: CompileFlags:
Add: [-std=c++2b, -Wunused-function] Add: [-std=c++20, -Wunused-function]

View file

@ -2,11 +2,11 @@
"nodes": { "nodes": {
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1717112898, "lastModified": 1717430266,
"narHash": "sha256-7R2ZvOnvd9h8fDd65p0JnB7wXfUvreox3xFdYWd1BnY=", "narHash": "sha256-EWy2Qbkl/HUwmO8KDBzgDQf+4rl+fLiPFvp3nUSWcxc=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "6132b0f6e344ce2fe34fc051b72fb46e34f668e0", "rev": "d125f0e4d85f1517b639d4a8f848175da46fcd3e",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -46,7 +46,12 @@
]; ];
}; };
stdenv = pkgs.stdenvAdapters.useMoldLinker pkgs.llvmPackages_18.stdenv; stdenv = pkgs.llvmPackages_18.stdenv;
darwinPkgs = nixpkgs.lib.optionals pkgs.stdenv.isDarwin (with pkgs.darwin; [
apple_sdk.frameworks.CoreFoundation
apple_sdk.frameworks.MediaPlayer
]);
in in
with pkgs; { with pkgs; {
packages = rec { packages = rec {
@ -72,7 +77,7 @@
]); ]);
buildInputs = [ buildInputs = [
boost185 coost
fmt fmt
]; ];
@ -105,19 +110,22 @@
ninja ninja
pkg-config pkg-config
boost185 coost
fmt fmt
glib glib
libcpr libcpr
tomlplusplus tomlplusplus
] ]
++ (lib.optionals pkgs.hostPlatform.isLinux [playerctl]); ++ (lib.optionals pkgs.hostPlatform.isLinux [playerctl])
++ darwinPkgs;
buildInputs = [ buildInputs =
boost185 [
libcpr coost
tomlplusplus libcpr
]; tomlplusplus
]
++ darwinPkgs;
name = "C++"; name = "C++";
}; };

13
include/rfl/bson.hpp Normal file
View file

@ -0,0 +1,13 @@
#ifndef RFL_BSON_HPP_
#define RFL_BSON_HPP_
#include "../rfl.hpp"
#include "bson/Parser.hpp"
#include "bson/Reader.hpp"
#include "bson/Writer.hpp"
#include "bson/load.hpp"
#include "bson/read.hpp"
#include "bson/save.hpp"
#include "bson/write.hpp"
#endif

View file

@ -0,0 +1,50 @@
#ifndef RFL_BSON_PARSER_HPP_
#define RFL_BSON_PARSER_HPP_
#include <bson/bson.h>
#include "../parsing/Parser.hpp"
#include "Reader.hpp"
#include "Writer.hpp"
namespace rfl::parsing {
/// bson_oid_t needs to be treated as a special case, otherwise it will be read
/// as a struct.
template <class R, class W, class ProcessorsType>
requires AreReaderAndWriter<R, W, bson_oid_t>
struct Parser<R, W, ProcessorsType, bson_oid_t> {
using InputVarType = typename R::InputVarType;
using OutputVarType = typename W::OutputVarType;
using ParentType = Parent<W>;
static Result<bson_oid_t> read(const R& _r,
const InputVarType& _var) noexcept {
return _r.template to_basic_type<bson_oid_t>(_var);
}
template <class P>
static void write(const W& _w, const bson_oid_t& _oid,
const P& _parent) noexcept {
ParentType::add_value(_w, _oid, _parent);
}
static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
static_assert(rfl::always_false_v<R>,
"bson_oid_t cannot be expressed inside a JSON schema.");
return schema::Type{schema::Type::String{}};
}
};
} // namespace rfl::parsing
namespace rfl::bson {
template <class T, class ProcessorsType>
using Parser = parsing::Parser<Reader, Writer, T, ProcessorsType>;
} // namespace rfl::bson
#endif

217
include/rfl/bson/Reader.hpp Normal file
View file

@ -0,0 +1,217 @@
#ifndef RFL_BSON_READER_HPP_
#define RFL_BSON_READER_HPP_
#include <bson/bson.h>
#include <array>
#include <concepts>
#include <exception>
#include <map>
#include <memory>
#include <optional>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <vector>
#include "../Box.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl {
namespace bson {
/// Please refer to https://mongoc.org/libbson/current/api.html
struct Reader {
struct BSONValue {
bson_value_t val_;
};
struct BSONInputArray {
BSONValue* val_;
};
struct BSONInputObject {
BSONValue* val_;
};
struct BSONInputVar {
BSONValue* val_;
};
using InputArrayType = BSONInputArray;
using InputObjectType = BSONInputObject;
using InputVarType = BSONInputVar;
template <class T>
static constexpr bool has_custom_constructor = (requires(InputVarType var) {
T::from_bson_obj(var);
});
rfl::Result<InputVarType> get_field(
const std::string& _name, const InputObjectType& _obj) const noexcept {
bson_t b;
bson_iter_t iter;
const auto doc = _obj.val_->val_.value.v_doc;
if (bson_init_static(&b, doc.data, doc.data_len)) {
if (bson_iter_init(&iter, &b)) {
while (bson_iter_next(&iter)) {
auto key = std::string(bson_iter_key(&iter));
if (key == _name) {
return to_input_var(&iter);
}
}
}
}
return Error("No field named '" + _name + "' was found.");
}
bool is_empty(const InputVarType& _var) const noexcept {
return _var.val_->val_.value_type == BSON_TYPE_NULL;
}
template <class T>
rfl::Result<T> to_basic_type(const InputVarType& _var) const noexcept {
const auto btype = _var.val_->val_.value_type;
const auto value = _var.val_->val_.value;
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
switch (btype) {
case BSON_TYPE_UTF8:
return std::string(value.v_utf8.str, value.v_utf8.len);
case BSON_TYPE_SYMBOL:
return std::string(value.v_symbol.symbol, value.v_symbol.len);
default:
return rfl::Error(
"Could not cast to string. The type must be UTF8 or symbol.");
}
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
if (btype != BSON_TYPE_BOOL) {
return rfl::Error("Could not cast to boolean.");
}
return value.v_bool;
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>() ||
std::is_integral<std::remove_cvref_t<T>>()) {
switch (btype) {
case BSON_TYPE_DOUBLE:
return static_cast<T>(value.v_double);
case BSON_TYPE_INT32:
return static_cast<T>(value.v_int32);
case BSON_TYPE_INT64:
return static_cast<T>(value.v_int64);
case BSON_TYPE_DATE_TIME:
return static_cast<T>(value.v_datetime);
default:
return rfl::Error(
"Could not cast to numeric value. The type must be double, "
"int32, int64 or date_time.");
}
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bson_oid_t>()) {
if (btype != BSON_TYPE_OID) {
return rfl::Error("Could not cast to OID.");
}
return value.v_oid;
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
}
rfl::Result<InputArrayType> to_array(
const InputVarType& _var) const noexcept {
const auto btype = _var.val_->val_.value_type;
if (btype != BSON_TYPE_ARRAY && btype != BSON_TYPE_DOCUMENT) {
return Error("Could not cast to an array.");
}
return InputArrayType{_var.val_};
}
template <class ArrayReader>
std::optional<Error> read_array(const ArrayReader& _array_reader,
const InputArrayType& _arr) const noexcept {
bson_t b;
bson_iter_t iter;
const auto doc = _arr.val_->val_.value.v_doc;
if (bson_init_static(&b, doc.data, doc.data_len)) {
if (bson_iter_init(&iter, &b)) {
while (bson_iter_next(&iter)) {
const auto err = _array_reader.read(to_input_var(&iter));
if (err) {
return err;
}
}
}
}
return std::nullopt;
}
template <class ObjectReader>
std::optional<Error> read_object(const ObjectReader& _object_reader,
const InputObjectType& _obj) const noexcept {
bson_t b;
bson_iter_t iter;
const auto doc = _obj.val_->val_.value.v_doc;
if (bson_init_static(&b, doc.data, doc.data_len)) {
if (bson_iter_init(&iter, &b)) {
while (bson_iter_next(&iter)) {
const char* k = bson_iter_key(&iter);
_object_reader.read(std::string_view(k), to_input_var(&iter));
}
}
}
return std::nullopt;
}
rfl::Result<InputObjectType> to_object(
const InputVarType& _var) const noexcept {
const auto btype = _var.val_->val_.value_type;
if (btype != BSON_TYPE_DOCUMENT) {
return Error("Could not cast to a document.");
}
return InputObjectType{_var.val_};
}
template <class T>
rfl::Result<T> use_custom_constructor(
const InputVarType& _var) const noexcept {
try {
return T::from_bson_obj(_var);
} catch (std::exception& e) {
return rfl::Error(e.what());
}
}
private:
struct BSONValues {
std::vector<rfl::Box<BSONValue>> vec_;
~BSONValues() {
for (auto& v : vec_) {
bson_value_destroy(&(v->val_));
}
}
};
private:
InputVarType to_input_var(bson_iter_t* _iter) const noexcept {
values_->vec_.emplace_back(rfl::Box<BSONValue>::make());
auto* last_value = values_->vec_.back().get();
bson_value_copy(bson_iter_value(_iter), &last_value->val_);
return InputVarType{last_value};
}
private:
/// Contains the values inside the object.
rfl::Ref<BSONValues> values_;
};
} // namespace bson
} // namespace rfl
#endif // JSON_PARSER_HPP_

227
include/rfl/bson/Writer.hpp Normal file
View file

@ -0,0 +1,227 @@
#ifndef RFL_BSON_WRITER_HPP_
#define RFL_BSON_WRITER_HPP_
#include <bson/bson.h>
#include <exception>
#include <map>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <variant>
#include <vector>
#include "../Box.hpp"
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl {
namespace bson {
/// Please refer to https://mongoc.org/libbson/current/api.html
class Writer {
struct BSONType {
bson_t val_;
};
struct IsArray {
bson_array_builder_t* ptr_;
};
struct IsObject {
bson_t* ptr_;
};
struct IsRoot {};
using ParentType = std::variant<IsArray, IsObject, IsRoot>;
public:
struct BSONOutputArray {
BSONOutputArray(bson_array_builder_t* _val, ParentType _parent)
: parent_(_parent), val_(_val) {}
ParentType parent_;
bson_array_builder_t* val_;
};
struct BSONOutputObject {
BSONOutputObject(bson_t* _val, ParentType _parent)
: parent_(_parent), val_(_val) {}
ParentType parent_;
bson_t* val_;
};
struct BSONOutputVar {};
using OutputArrayType = BSONOutputArray;
using OutputObjectType = BSONOutputObject;
using OutputVarType = BSONOutputVar;
Writer(bson_t* _doc) : doc_(_doc) {}
~Writer() = default;
OutputArrayType array_as_root(const size_t _size) const noexcept {
bson_array_builder_t* val = bson_array_builder_new();
return OutputArrayType(val, IsRoot{});
}
OutputObjectType object_as_root(const size_t _size) const noexcept {
return OutputObjectType(doc_, IsRoot{});
}
OutputVarType null_as_root() const noexcept {
// Appears to be unsupported by the BSON C API.
return OutputVarType{};
}
template <class T>
OutputVarType value_as_root(const T& _var) const noexcept {
static_assert(rfl::always_false_v<T>,
"BSON only allows arrays or objects as its root.");
return OutputVarType{};
}
OutputArrayType add_array_to_array(const size_t _size,
OutputArrayType* _parent) const noexcept {
bson_array_builder_t* val;
bson_array_builder_append_array_builder_begin(_parent->val_, &val);
return OutputArrayType(val, IsArray{_parent->val_});
}
OutputArrayType add_array_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
bson_array_builder_t* val;
bson_append_array_builder_begin(_parent->val_, _name.data(),
static_cast<int>(_name.size()), &val);
return OutputArrayType(val, IsObject{_parent->val_});
}
OutputObjectType add_object_to_array(
const size_t _size, OutputArrayType* _parent) const noexcept {
subdocs_->emplace_back(rfl::Box<BSONType>());
bson_array_builder_append_document_begin(_parent->val_,
&(subdocs_->back()->val_));
return OutputObjectType(&subdocs_->back()->val_, IsArray{_parent->val_});
}
OutputObjectType add_object_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
subdocs_->emplace_back(rfl::Box<BSONType>());
bson_append_document_begin(_parent->val_, _name.data(),
static_cast<int>(_name.size()),
&(subdocs_->back()->val_));
return OutputObjectType(&subdocs_->back()->val_, IsObject{_parent->val_});
}
template <class T>
OutputVarType add_value_to_array(const T& _var,
OutputArrayType* _parent) const noexcept {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
bson_array_builder_append_utf8(_parent->val_, _var.c_str(),
static_cast<int>(_var.size()));
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
bson_array_builder_append_bool(_parent->val_, _var);
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
bson_array_builder_append_double(_parent->val_,
static_cast<double>(_var));
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
bson_array_builder_append_int64(_parent->val_,
static_cast<std::int64_t>(_var));
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bson_oid_t>()) {
bson_array_builder_append_oid(_parent->val_, &_var);
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
return OutputVarType{};
}
template <class T>
OutputVarType add_value_to_object(const std::string_view& _name,
const T& _var,
OutputObjectType* _parent) const noexcept {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
bson_append_utf8(_parent->val_, _name.data(),
static_cast<int>(_name.size()), _var.c_str(),
static_cast<int>(_var.size()));
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
bson_append_bool(_parent->val_, _name.data(),
static_cast<int>(_name.size()), _var);
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
bson_append_double(_parent->val_, _name.data(),
static_cast<int>(_name.size()),
static_cast<double>(_var));
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
bson_append_int64(_parent->val_, _name.data(),
static_cast<int>(_name.size()),
static_cast<std::int64_t>(_var));
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bson_oid_t>()) {
bson_append_oid(_parent->val_, _name.data(),
static_cast<int>(_name.size()), &_var);
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
return OutputVarType{};
}
OutputVarType add_null_to_array(OutputArrayType* _parent) const noexcept {
bson_array_builder_append_null(_parent->val_);
return OutputVarType{};
}
OutputVarType add_null_to_object(const std::string_view& _name,
OutputObjectType* _parent) const noexcept {
bson_append_null(_parent->val_, _name.data(),
static_cast<int>(_name.size()));
return OutputVarType{};
}
void end_array(OutputArrayType* _arr) const noexcept {
const auto handle = [&](const auto _parent) {
using Type = std::remove_cvref_t<decltype(_parent)>;
if constexpr (std::is_same<Type, IsArray>()) {
bson_array_builder_append_array_builder_end(_parent.ptr_, _arr->val_);
} else if constexpr (std::is_same<Type, IsObject>()) {
bson_append_array_builder_end(_parent.ptr_, _arr->val_);
} else if constexpr (std::is_same<Type, IsRoot>()) {
bson_array_builder_build(_arr->val_, doc_);
} else {
static_assert(rfl::always_false_v<Type>, "Unsupported type.");
}
};
std::visit(handle, _arr->parent_);
}
void end_object(OutputObjectType* _obj) const noexcept {
const auto handle = [&](const auto _parent) {
using Type = std::remove_cvref_t<decltype(_parent)>;
if constexpr (std::is_same<Type, IsArray>()) {
bson_array_builder_append_document_end(_parent.ptr_, _obj->val_);
} else if constexpr (std::is_same<Type, IsObject>()) {
bson_append_document_end(_parent.ptr_, _obj->val_);
} else if constexpr (std::is_same<Type, IsRoot>()) {
} else {
static_assert(rfl::always_false_v<Type>, "Unsupported type.");
}
};
std::visit(handle, _obj->parent_);
}
private:
/// Pointer to the main document. In BSON, documents are what are usually
/// called objects.
bson_t* const doc_;
/// Contain all of the subdocuments.
const rfl::Ref<std::vector<rfl::Box<BSONType>>> subdocs_;
};
} // namespace bson
} // namespace rfl
#endif // BSON_PARSER_HPP_

22
include/rfl/bson/load.hpp Normal file
View file

@ -0,0 +1,22 @@
#ifndef RFL_BSON_LOAD_HPP_
#define RFL_BSON_LOAD_HPP_
#include "../Result.hpp"
#include "../io/load_bytes.hpp"
#include "read.hpp"
namespace rfl {
namespace bson {
template <class T, class... Ps>
Result<T> load(const std::string& _fname) {
const auto read_bytes = [](const auto& _bytes) {
return read<T, Ps...>(_bytes);
};
return rfl::io::load_bytes(_fname).and_then(read_bytes);
}
} // namespace bson
} // namespace rfl
#endif

61
include/rfl/bson/read.hpp Normal file
View file

@ -0,0 +1,61 @@
#ifndef RFL_BSON_READ_HPP_
#define RFL_BSON_READ_HPP_
#include <bson/bson.h>
#include <istream>
#include <string>
#include "../Processors.hpp"
#include "../internal/wrap_in_rfl_array_t.hpp"
#include "Parser.hpp"
#include "Reader.hpp"
namespace rfl {
namespace bson {
using InputObjectType = typename Reader::InputObjectType;
using InputVarType = typename Reader::InputVarType;
/// Parses an object from a BSON var.
template <class T, class... Ps>
Result<internal::wrap_in_rfl_array_t<T>> read(const InputVarType& _obj) {
const auto r = Reader();
return Parser<T, Processors<Ps...>>::read(r, _obj);
}
/// Parses an BSON object using reflection.
template <class T, class... Ps>
auto read(const uint8_t* _bytes, const size_t _size) {
Reader::BSONValue value;
value.val_.value.v_doc.data_len = static_cast<uint32_t>(_size);
value.val_.value.v_doc.data = const_cast<uint8_t*>(_bytes);
value.val_.value_type = BSON_TYPE_DOCUMENT;
auto doc = InputVarType{&value};
return read<T, Ps...>(doc);
}
/// Parses an BSON object using reflection.
template <class T, class... Ps>
auto read(const char* _bytes, const size_t _size) {
return read<T, Ps...>(reinterpret_cast<const uint8_t*>(_bytes), _size);
}
/// Parses an object from BSON using reflection.
template <class T, class... Ps>
auto read(const std::vector<char>& _bytes) {
return read<T, Ps...>(_bytes.data(), _bytes.size());
}
/// Parses an object from a stream.
template <class T, class... Ps>
auto read(std::istream& _stream) {
std::istreambuf_iterator<char> begin(_stream), end;
auto bytes = std::vector<char>(begin, end);
return read<T, Ps...>(bytes.data(), bytes.size());
}
} // namespace bson
} // namespace rfl
#endif

26
include/rfl/bson/save.hpp Normal file
View file

@ -0,0 +1,26 @@
#ifndef RFL_BSON_SAVE_HPP_
#define RFL_BSON_SAVE_HPP_
#include <fstream>
#include <iostream>
#include <string>
#include "../Result.hpp"
#include "../io/save_bytes.hpp"
#include "write.hpp"
namespace rfl {
namespace bson {
template <class... Ps>
Result<Nothing> save(const std::string& _fname, const auto& _obj) {
const auto write_func = [](const auto& _obj, auto& _stream) -> auto& {
return write<Ps...>(_obj, _stream);
};
return rfl::io::save_bytes(_fname, _obj, write_func);
}
} // namespace bson
} // namespace rfl
#endif

View file

@ -0,0 +1,61 @@
#ifndef RFL_BSON_WRITE_HPP_
#define RFL_BSON_WRITE_HPP_
#include <bson/bson.h>
#include <ostream>
#include <sstream>
#include <string>
#include <utility>
#include "../Processors.hpp"
#include "../parsing/Parent.hpp"
#include "Parser.hpp"
namespace rfl {
namespace bson {
/// Returns BSON bytes. Careful: It is the responsibility of the caller to call
/// bson_free on the returned pointer.
template <class... Ps>
std::pair<uint8_t*, size_t> to_buffer(const auto& _obj) noexcept {
using T = std::remove_cvref_t<decltype(_obj)>;
using ParentType = parsing::Parent<Writer>;
bson_t* doc = nullptr;
uint8_t* buf = nullptr;
size_t buflen = 0;
bson_writer_t* bson_writer =
bson_writer_new(&buf, &buflen, 0, bson_realloc_ctx, NULL);
bson_writer_begin(bson_writer, &doc);
const auto rfl_writer = Writer(doc);
Parser<T, Processors<Ps...>>::write(rfl_writer, _obj,
typename ParentType::Root{});
bson_writer_end(bson_writer);
const auto len = bson_writer_get_length(bson_writer);
bson_writer_destroy(bson_writer);
return std::make_pair(buf, len);
}
/// Returns BSON bytes.
template <class... Ps>
std::vector<char> write(const auto& _obj) noexcept {
auto [buf, len] = to_buffer<Ps...>(_obj);
const auto result = std::vector<char>(reinterpret_cast<char*>(buf),
reinterpret_cast<char*>(buf) + len);
bson_free(buf);
return result;
}
/// Writes a BSON into an ostream.
template <class... Ps>
std::ostream& write(const auto& _obj, std::ostream& _stream) noexcept {
auto [buf, len] = to_buffer<Ps...>(_obj);
_stream.write(reinterpret_cast<const char*>(buf), len);
bson_free(buf);
return _stream;
}
} // namespace bson
} // namespace rfl
#endif // BSON_PARSER_HPP_

13
include/rfl/cbor.hpp Normal file
View file

@ -0,0 +1,13 @@
#ifndef RFL_CBOR_HPP_
#define RFL_CBOR_HPP_
#include "../rfl.hpp"
#include "cbor/Parser.hpp"
#include "cbor/Reader.hpp"
#include "cbor/Writer.hpp"
#include "cbor/load.hpp"
#include "cbor/read.hpp"
#include "cbor/save.hpp"
#include "cbor/write.hpp"
#endif

View file

@ -0,0 +1,45 @@
#ifndef RFL_CBOR_PARSER_HPP_
#define RFL_CBOR_PARSER_HPP_
#include "../parsing/Parser.hpp"
#include "Reader.hpp"
#include "Writer.hpp"
namespace rfl {
namespace parsing {
/// CBOR requires us to explicitly set the number of fields in advance. Because
/// of that, we require all of the fields and then set them to nullptr, if
/// necessary.
template <class ProcessorsType, class... FieldTypes>
requires AreReaderAndWriter<cbor::Reader, cbor::Writer,
NamedTuple<FieldTypes...>>
struct Parser<cbor::Reader, cbor::Writer, NamedTuple<FieldTypes...>,
ProcessorsType>
: public NamedTupleParser<cbor::Reader, cbor::Writer,
/*_ignore_empty_containers=*/false,
/*_all_required=*/true, ProcessorsType,
FieldTypes...> {
};
template <class ProcessorsType, class... Ts>
requires AreReaderAndWriter<cbor::Reader, cbor::Writer, std::tuple<Ts...>>
struct Parser<cbor::Reader, cbor::Writer, std::tuple<Ts...>, ProcessorsType>
: public TupleParser<cbor::Reader, cbor::Writer,
/*_ignore_empty_containers=*/false,
/*_all_required=*/true, ProcessorsType, Ts...> {
};
} // namespace parsing
} // namespace rfl
namespace rfl {
namespace cbor {
template <class T, class ProcessorsType>
using Parser = parsing::Parser<Reader, Writer, T, ProcessorsType>;
}
} // namespace rfl
#endif

259
include/rfl/cbor/Reader.hpp Normal file
View file

@ -0,0 +1,259 @@
#ifndef RFL_CBOR_READER_HPP_
#define RFL_CBOR_READER_HPP_
#include <cbor.h>
#include <array>
#include <concepts>
#include <exception>
#include <map>
#include <memory>
#include <source_location>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <vector>
#include "../Box.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl {
namespace cbor {
/// Please refer to https://intel.github.io/tinycbor/current/index.html
struct Reader {
struct CBORInputArray {
CborValue* val_;
};
struct CBORInputObject {
CborValue* val_;
};
struct CBORInputVar {
CborValue* val_;
};
using InputArrayType = CBORInputArray;
using InputObjectType = CBORInputObject;
using InputVarType = CBORInputVar;
template <class T>
static constexpr bool has_custom_constructor = (requires(InputVarType var) {
T::from_cbor_obj(var);
});
rfl::Result<InputVarType> get_field(
const std::string& _name, const InputObjectType& _obj) const noexcept {
CborValue val;
auto buffer = std::vector<char>();
auto err = cbor_value_enter_container(_obj.val_, &val);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
size_t length = 0;
err = cbor_value_get_map_length(_obj.val_, &length);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
for (size_t i = 0; i < length; ++i) {
if (!cbor_value_is_text_string(&val)) {
return Error("Expected the key to be a string value.");
}
err = get_string(&val, &buffer);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
err = cbor_value_advance(&val);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
if (_name == buffer.data()) {
return to_input_var(&val);
}
err = cbor_value_advance(&val);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
}
return Error("No field named '" + _name + "' was found.");
}
bool is_empty(const InputVarType& _var) const noexcept {
return cbor_value_is_null(_var.val_);
}
template <class T>
rfl::Result<T> to_basic_type(const InputVarType& _var) const noexcept {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
if (!cbor_value_is_text_string(_var.val_)) {
return Error("Could not cast to string.");
}
std::vector<char> buffer;
const auto err = get_string(_var.val_, &buffer);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
return std::string(buffer.data());
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
if (!cbor_value_is_boolean(_var.val_)) {
return rfl::Error("Could not cast to boolean.");
}
bool result = false;
const auto err = cbor_value_get_boolean(_var.val_, &result);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
return result;
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>() ||
std::is_integral<std::remove_cvref_t<T>>()) {
if (cbor_value_is_integer(_var.val_)) {
std::int64_t result = 0;
const auto err = cbor_value_get_int64(_var.val_, &result);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
return static_cast<T>(result);
} else if (cbor_value_is_float(_var.val_)) {
float result = 0.0;
const auto err = cbor_value_get_float(_var.val_, &result);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
return static_cast<T>(result);
} else if (cbor_value_is_double(_var.val_)) {
double result = 0.0;
const auto err = cbor_value_get_double(_var.val_, &result);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
return static_cast<T>(result);
}
return rfl::Error(
"Could not cast to numeric value. The type must be integral, float "
"or double.");
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
}
rfl::Result<InputArrayType> to_array(
const InputVarType& _var) const noexcept {
if (!cbor_value_is_array(_var.val_)) {
return Error("Could not cast to an array.");
}
return InputArrayType{_var.val_};
}
rfl::Result<InputObjectType> to_object(
const InputVarType& _var) const noexcept {
if (!cbor_value_is_map(_var.val_)) {
return Error("Could not cast to an object.");
}
return InputObjectType{_var.val_};
}
template <class ArrayReader>
std::optional<Error> read_array(const ArrayReader& _array_reader,
const InputArrayType& _arr) const noexcept {
CborValue val;
auto buffer = std::vector<char>();
auto err = cbor_value_enter_container(_arr.val_, &val);
if (err != CborNoError && err != CborErrorOutOfMemory) {
return Error(cbor_error_string(err));
}
size_t length = 0;
err = cbor_value_get_array_length(_arr.val_, &length);
if (err != CborNoError && err != CborErrorOutOfMemory) {
return Error(cbor_error_string(err));
}
for (size_t i = 0; i < length; ++i) {
const auto err2 = _array_reader.read(to_input_var(&val));
if (err2) {
return err2;
}
err = cbor_value_advance(&val);
if (err != CborNoError && err != CborErrorOutOfMemory) {
return Error(cbor_error_string(err));
}
}
return std::nullopt;
}
template <class ObjectReader>
std::optional<Error> read_object(const ObjectReader& _object_reader,
const InputObjectType& _obj) const noexcept {
size_t length = 0;
auto err = cbor_value_get_map_length(_obj.val_, &length);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
CborValue val;
err = cbor_value_enter_container(_obj.val_, &val);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
auto buffer = std::vector<char>();
for (size_t i = 0; i < length; ++i) {
err = get_string(&val, &buffer);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
err = cbor_value_advance(&val);
if (err != CborNoError) {
return Error(cbor_error_string(err));
}
const auto name = std::string_view(buffer.data(), buffer.size() - 1);
_object_reader.read(name, InputVarType{&val});
cbor_value_advance(&val);
}
return std::nullopt;
}
template <class T>
rfl::Result<T> use_custom_constructor(
const InputVarType& _var) const noexcept {
try {
return T::from_cbor_obj(_var);
} catch (std::exception& e) {
return rfl::Error(e.what());
}
}
private:
CborError get_string(const CborValue* _ptr,
std::vector<char>* _buffer) const noexcept {
size_t length = 0;
auto err = cbor_value_get_string_length(_ptr, &length);
if (err != CborNoError && err != CborErrorOutOfMemory) {
return err;
}
_buffer->resize(length + 1);
(*_buffer)[length] = '\0';
return cbor_value_copy_text_string(_ptr, _buffer->data(), &length, NULL);
}
InputVarType to_input_var(CborValue* _ptr) const noexcept {
values_->emplace_back(rfl::Box<CborValue>::make(*_ptr));
auto* last_value = values_->back().get();
return InputVarType{last_value};
}
private:
/// Contains the values inside the object.
rfl::Box<std::vector<rfl::Box<CborValue>>> values_;
};
} // namespace cbor
} // namespace rfl
#endif // JSON_PARSER_HPP_

164
include/rfl/cbor/Writer.hpp Normal file
View file

@ -0,0 +1,164 @@
#ifndef RFL_CBOR_WRITER_HPP_
#define RFL_CBOR_WRITER_HPP_
#include <cbor.h>
#include <exception>
#include <map>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <variant>
#include <vector>
#include "../Box.hpp"
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl {
namespace cbor {
class Writer {
public:
struct CBOROutputArray {
CborEncoder* encoder_;
CborEncoder* parent_;
};
struct CBOROutputObject {
CborEncoder* encoder_;
CborEncoder* parent_;
};
struct CBOROutputVar {};
using OutputArrayType = CBOROutputArray;
using OutputObjectType = CBOROutputObject;
using OutputVarType = CBOROutputVar;
Writer(CborEncoder* _encoder) : encoder_(_encoder) {}
~Writer() = default;
OutputArrayType array_as_root(const size_t _size) const noexcept {
return new_array(_size, encoder_);
}
OutputObjectType object_as_root(const size_t _size) const noexcept {
return new_object(_size, encoder_);
}
OutputVarType null_as_root() const noexcept {
cbor_encode_null(encoder_);
return OutputVarType{};
}
template <class T>
OutputVarType value_as_root(const T& _var) const noexcept {
return new_value(_var, encoder_);
}
OutputArrayType add_array_to_array(const size_t _size,
OutputArrayType* _parent) const noexcept {
return new_array(_size, _parent->encoder_);
}
OutputArrayType add_array_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
cbor_encode_text_string(_parent->encoder_, _name.data(), _name.size());
return new_array(_size, _parent->encoder_);
}
OutputObjectType add_object_to_array(
const size_t _size, OutputArrayType* _parent) const noexcept {
return new_object(_size, _parent->encoder_);
}
OutputObjectType add_object_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
cbor_encode_text_string(_parent->encoder_, _name.data(), _name.size());
return new_object(_size, _parent->encoder_);
}
template <class T>
OutputVarType add_value_to_array(const T& _var,
OutputArrayType* _parent) const noexcept {
return new_value(_var, _parent->encoder_);
}
template <class T>
OutputVarType add_value_to_object(const std::string_view& _name,
const T& _var,
OutputObjectType* _parent) const noexcept {
cbor_encode_text_string(_parent->encoder_, _name.data(), _name.size());
return new_value(_var, _parent->encoder_);
}
OutputVarType add_null_to_array(OutputArrayType* _parent) const noexcept {
cbor_encode_null(_parent->encoder_);
return OutputVarType{};
}
OutputVarType add_null_to_object(const std::string_view& _name,
OutputObjectType* _parent) const noexcept {
cbor_encode_text_string(_parent->encoder_, _name.data(), _name.size());
cbor_encode_null(_parent->encoder_);
return OutputVarType{};
}
void end_array(OutputArrayType* _arr) const noexcept {
cbor_encoder_close_container(_arr->parent_, _arr->encoder_);
}
void end_object(OutputObjectType* _obj) const noexcept {
cbor_encoder_close_container(_obj->parent_, _obj->encoder_);
}
private:
OutputArrayType new_array(const size_t _size,
CborEncoder* _parent) const noexcept {
subencoders_->emplace_back(rfl::Box<CborEncoder>::make());
cbor_encoder_create_array(_parent, subencoders_->back().get(), _size);
return OutputArrayType{subencoders_->back().get(), _parent};
}
OutputObjectType new_object(const size_t _size,
CborEncoder* _parent) const noexcept {
subencoders_->emplace_back(rfl::Box<CborEncoder>::make());
cbor_encoder_create_map(_parent, subencoders_->back().get(), _size);
return OutputObjectType{subencoders_->back().get(), _parent};
}
template <class T>
OutputVarType new_value(const T& _var, CborEncoder* _parent) const noexcept {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
cbor_encode_text_string(_parent, _var.c_str(), _var.size());
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
cbor_encode_boolean(_parent, _var);
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
cbor_encode_double(_parent, static_cast<double>(_var));
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
cbor_encode_int(_parent, static_cast<std::int64_t>(_var));
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
return OutputVarType{};
}
private:
/// The underlying TinyCBOR encoder.
CborEncoder* const encoder_;
/// Contain all of the subobjects and subarrays.
const rfl::Box<std::vector<rfl::Box<CborEncoder>>> subencoders_;
};
} // namespace cbor
} // namespace rfl
#endif // CBOR_PARSER_HPP_

23
include/rfl/cbor/load.hpp Normal file
View file

@ -0,0 +1,23 @@
#ifndef RFL_CBOR_LOAD_HPP_
#define RFL_CBOR_LOAD_HPP_
#include "../Processors.hpp"
#include "../Result.hpp"
#include "../io/load_bytes.hpp"
#include "read.hpp"
namespace rfl {
namespace cbor {
template <class T, class... Ps>
Result<T> load(const std::string& _fname) {
const auto read_bytes = [](const auto& _bytes) {
return read<T, Ps...>(_bytes);
};
return rfl::io::load_bytes(_fname).and_then(read_bytes);
}
} // namespace cbor
} // namespace rfl
#endif

57
include/rfl/cbor/read.hpp Normal file
View file

@ -0,0 +1,57 @@
#ifndef RFL_CBOR_READ_HPP_
#define RFL_CBOR_READ_HPP_
#include <cbor.h>
#include <istream>
#include <string>
#include "../Processors.hpp"
#include "../internal/wrap_in_rfl_array_t.hpp"
#include "Parser.hpp"
#include "Reader.hpp"
namespace rfl {
namespace cbor {
using InputObjectType = typename Reader::InputObjectType;
using InputVarType = typename Reader::InputVarType;
/// Parses an object from a CBOR var.
template <class T, class... Ps>
auto read(const InputVarType& _obj) {
const auto r = Reader();
return Parser<T, Processors<Ps...>>::read(r, _obj);
}
/// Parses an object from CBOR using reflection.
template <class T, class... Ps>
Result<internal::wrap_in_rfl_array_t<T>> read(const char* _bytes,
const size_t _size) {
CborParser parser;
CborValue value;
cbor_parser_init(reinterpret_cast<const uint8_t*>(_bytes), _size, 0, &parser,
&value);
auto doc = InputVarType{&value};
auto result = read<T, Ps...>(doc);
return result;
}
/// Parses an object from CBOR using reflection.
template <class T, class... Ps>
auto read(const std::vector<char>& _bytes) {
return read<T, Ps...>(_bytes.data(), _bytes.size());
}
/// Parses an object from a stream.
template <class T, class... Ps>
auto read(std::istream& _stream) {
std::istreambuf_iterator<char> begin(_stream), end;
auto bytes = std::vector<char>(begin, end);
return read<T, Ps...>(bytes.data(), bytes.size());
}
} // namespace cbor
} // namespace rfl
#endif

26
include/rfl/cbor/save.hpp Normal file
View file

@ -0,0 +1,26 @@
#ifndef RFL_CBOR_SAVE_HPP_
#define RFL_CBOR_SAVE_HPP_
#include <fstream>
#include <iostream>
#include <string>
#include "../Result.hpp"
#include "../io/save_bytes.hpp"
#include "write.hpp"
namespace rfl {
namespace cbor {
template <class... Ps>
Result<Nothing> save(const std::string& _fname, const auto& _obj) {
const auto write_func = [](const auto& _obj, auto& _stream) -> auto& {
return write<Ps...>(_obj, _stream);
};
return rfl::io::save_bytes(_fname, _obj, write_func);
}
} // namespace cbor
} // namespace rfl
#endif

View file

@ -0,0 +1,59 @@
#ifndef RFL_CBOR_WRITE_HPP_
#define RFL_CBOR_WRITE_HPP_
#include <cbor.h>
#include <cstdint>
#include <ostream>
#include <sstream>
#include <string>
#include <utility>
#include "../parsing/Parent.hpp"
#include "Parser.hpp"
namespace rfl {
namespace cbor {
template <class... Ps>
void write_into_buffer(const auto& _obj, CborEncoder* _encoder,
std::vector<char>* _buffer) noexcept {
using T = std::remove_cvref_t<decltype(_obj)>;
using ParentType = parsing::Parent<Writer>;
cbor_encoder_init(_encoder, reinterpret_cast<uint8_t*>(_buffer->data()),
_buffer->size(), 0);
const auto writer = Writer(_encoder);
Parser<T, Processors<Ps...>>::write(writer, _obj,
typename ParentType::Root{});
}
/// Returns CBOR bytes.
template <class... Ps>
std::vector<char> write(const auto& _obj) noexcept {
std::vector<char> buffer(4096);
CborEncoder encoder;
write_into_buffer<Ps...>(_obj, &encoder, &buffer);
const auto total_bytes_needed =
buffer.size() + cbor_encoder_get_extra_bytes_needed(&encoder);
if (total_bytes_needed != buffer.size()) {
buffer.resize(total_bytes_needed);
write_into_buffer<Ps...>(_obj, &encoder, &buffer);
}
const auto length = cbor_encoder_get_buffer_size(
&encoder, reinterpret_cast<uint8_t*>(buffer.data()));
buffer.resize(length);
return buffer;
}
/// Writes a CBOR into an ostream.
template <class... Ps>
std::ostream& write(const auto& _obj, std::ostream& _stream) noexcept {
auto buffer = write<Ps...>(_obj);
_stream.write(buffer.data(), buffer.size());
return _stream;
}
} // namespace cbor
} // namespace rfl
#endif

13
include/rfl/flexbuf.hpp Normal file
View file

@ -0,0 +1,13 @@
#ifndef RFL_FLEXBUF_HPP_
#define RFL_FLEXBUF_HPP_
#include "../rfl.hpp"
#include "flexbuf/Parser.hpp"
#include "flexbuf/Reader.hpp"
#include "flexbuf/Writer.hpp"
#include "flexbuf/load.hpp"
#include "flexbuf/read.hpp"
#include "flexbuf/save.hpp"
#include "flexbuf/write.hpp"
#endif

View file

@ -0,0 +1,17 @@
#ifndef FLEXBUF_PARSER_HPP_
#define FLEXBUF_PARSER_HPP_
#include "../parsing/Parser.hpp"
#include "Reader.hpp"
#include "Writer.hpp"
namespace rfl {
namespace flexbuf {
template <class T, class ProcessorsType>
using Parser = parsing::Parser<Reader, Writer, T, ProcessorsType>;
}
} // namespace rfl
#endif

View file

@ -0,0 +1,145 @@
#ifndef FLEXBUF_READER_HPP_
#define FLEXBUF_READER_HPP_
#include <flatbuffers/flexbuffers.h>
#include <exception>
#include <map>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl {
namespace flexbuf {
struct Reader {
using InputArrayType = flexbuffers::Vector;
using InputObjectType = flexbuffers::Map;
using InputVarType = flexbuffers::Reference;
template <class T, class = void>
struct has_from_flexbuf : std::false_type {};
template <class T>
struct has_from_flexbuf<
T, std::enable_if_t<std::is_invocable_r<T, decltype(T::from_flexbuf),
InputVarType>::value>>
: std::true_type {};
template <class T>
struct has_from_flexbuf<
T, std::enable_if_t<std::is_invocable_r<
rfl::Result<T>, decltype(T::from_flexbuf), InputVarType>::value>>
: std::true_type {};
template <class T>
static constexpr bool has_custom_constructor = has_from_flexbuf<T>::value;
rfl::Result<InputVarType> get_field(
const std::string& _name, const InputObjectType& _obj) const noexcept {
const auto keys = _obj.Keys();
for (size_t i = 0; i < keys.size(); ++i) {
if (_name == keys[i].AsString().c_str()) {
return _obj.Values()[i];
}
}
return rfl::Error("Map does not contain any element called '" + _name +
"'.");
}
bool is_empty(const InputVarType& _var) const noexcept {
return _var.IsNull();
}
template <class T>
rfl::Result<T> to_basic_type(const InputVarType& _var) const noexcept {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
if (!_var.IsString()) {
return rfl::Error("Could not cast to string.");
}
return std::string(_var.AsString().c_str());
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
if (!_var.IsBool()) {
return rfl::Error("Could not cast to boolean.");
}
return _var.AsBool();
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
if (!_var.IsNumeric()) {
return rfl::Error("Could not cast to double.");
}
return static_cast<T>(_var.AsDouble());
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
if (!_var.IsNumeric()) {
return rfl::Error("Could not cast to int.");
}
return static_cast<T>(_var.AsInt64());
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
}
template <class ArrayReader>
std::optional<Error> read_array(const ArrayReader& _array_reader,
const InputArrayType& _arr) const noexcept {
const auto size = _arr.size();
for (size_t i = 0; i < size; ++i) {
const auto err = _array_reader.read(InputVarType(_arr[i]));
if (err) {
return err;
}
}
return std::nullopt;
}
template <class ObjectReader>
std::optional<Error> read_object(const ObjectReader& _object_reader,
const InputObjectType& _obj) const noexcept {
const auto keys = _obj.Keys();
const auto values = _obj.Values();
const auto num_values = std::min(keys.size(), values.size());
for (size_t i = 0; i < num_values; ++i) {
_object_reader.read(std::string_view(keys[i].AsString().c_str()),
values[i]);
}
return std::nullopt;
}
rfl::Result<InputArrayType> to_array(
const InputVarType& _var) const noexcept {
if (!_var.IsVector()) {
return rfl::Error("Could not cast to Vector.");
}
return _var.AsVector();
}
rfl::Result<InputObjectType> to_object(
const InputVarType& _var) const noexcept {
if (!_var.IsMap()) {
return rfl::Error("Could not cast to Map!");
}
return _var.AsMap();
}
template <class T>
rfl::Result<T> use_custom_constructor(
const InputVarType& _var) const noexcept {
try {
return T::from_flexbuf(_var);
} catch (std::exception& e) {
return rfl::Error(e.what());
}
}
};
} // namespace flexbuf
} // namespace rfl
#endif

View file

@ -0,0 +1,176 @@
#ifndef FLEXBUF_WRITER_HPP_
#define FLEXBUF_WRITER_HPP_
#include <flatbuffers/flexbuffers.h>
#include <exception>
#include <functional>
#include <map>
#include <optional>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl {
namespace flexbuf {
struct Writer {
struct OutputArray {
size_t start_;
};
struct OutputObject {
size_t start_;
};
struct OutputVar {};
using OutputArrayType = OutputArray;
using OutputObjectType = OutputObject;
using OutputVarType = OutputVar;
Writer(const Ref<flexbuffers::Builder>& _fbb) : fbb_(_fbb) {}
~Writer() = default;
OutputArrayType array_as_root(const size_t _size) const noexcept {
return new_array();
}
OutputObjectType object_as_root(const size_t _size) const noexcept {
return new_object();
}
OutputVarType null_as_root() const noexcept {
fbb_->Null();
return OutputVarType{};
}
template <class T>
OutputVarType value_as_root(const T& _var) const noexcept {
return insert_value(_var);
}
OutputArrayType add_array_to_array(const size_t _size,
OutputArrayType* _parent) const noexcept {
return new_array();
}
OutputArrayType add_array_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
return new_array(_name);
}
OutputObjectType add_object_to_array(
const size_t _size, OutputArrayType* _parent) const noexcept {
return new_object();
}
OutputObjectType add_object_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
return new_object(_name);
}
template <class T>
OutputVarType add_value_to_array(const T& _var,
OutputArrayType* _parent) const noexcept {
return insert_value(_var);
}
template <class T>
OutputVarType add_value_to_object(const std::string_view& _name,
const T& _var,
OutputObjectType* _parent) const noexcept {
return insert_value(_name, _var);
}
OutputVarType add_null_to_array(OutputArrayType* _parent) const noexcept {
fbb_->Null();
return OutputVarType{};
}
OutputVarType add_null_to_object(const std::string_view& _name,
OutputObjectType* _parent) const noexcept {
fbb_->Null(_name.data());
return OutputVarType{};
}
void end_array(OutputArrayType* _arr) const noexcept {
fbb_->EndVector(_arr->start_, false, false);
}
void end_object(OutputObjectType* _obj) const noexcept {
fbb_->EndMap(_obj->start_);
}
private:
template <class T>
OutputVarType insert_value(const std::string_view& _name,
const T& _var) const noexcept {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
fbb_->String(_name.data(), _var);
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
fbb_->Bool(_name.data(), _var);
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
fbb_->Double(_name.data(), _var);
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
fbb_->Int(_name.data(), _var);
} else {
static_assert(always_false_v<T>, "Unsupported type");
}
return OutputVarType{};
}
template <class T>
OutputVarType insert_value(const T& _var) const noexcept {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
fbb_->String(_var);
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
fbb_->Bool(_var);
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
fbb_->Double(_var);
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
fbb_->Int(_var);
} else {
static_assert(always_false_v<T>, "Unsupported type");
}
return OutputVarType{};
}
OutputArrayType new_array(const std::string_view& _name) const noexcept {
const auto start = fbb_->StartVector(_name.data());
return OutputArrayType{start};
}
OutputArrayType new_array() const noexcept {
const auto start = fbb_->StartVector();
return OutputArrayType{start};
}
OutputObjectType new_object(const std::string_view& _name) const noexcept {
const auto start = fbb_->StartMap(_name.data());
return OutputObjectType{start};
}
OutputObjectType new_object() const noexcept {
const auto start = fbb_->StartMap();
return OutputObjectType{start};
}
private:
Ref<flexbuffers::Builder> fbb_;
};
} // namespace flexbuf
} // namespace rfl
#endif

View file

@ -0,0 +1,22 @@
#ifndef RFL_FLEXBUF_LOAD_HPP_
#define RFL_FLEXBUF_LOAD_HPP_
#include "../Result.hpp"
#include "../io/load_bytes.hpp"
#include "read.hpp"
namespace rfl {
namespace flexbuf {
template <class T, class... Ps>
Result<T> load(const std::string& _fname) {
const auto read_bytes = [](const auto& _bytes) {
return read<T, Ps...>(_bytes);
};
return rfl::io::load_bytes(_fname).and_then(read_bytes);
}
} // namespace flexbuf
} // namespace rfl
#endif

View file

@ -0,0 +1,50 @@
#ifndef FLEXBUF_READ_HPP_
#define FLEXBUF_READ_HPP_
#include <flatbuffers/flexbuffers.h>
#include <istream>
#include <vector>
#include "../Processors.hpp"
#include "../Result.hpp"
#include "Parser.hpp"
namespace rfl {
namespace flexbuf {
using InputVarType = typename Reader::InputVarType;
/// Parses an object from flexbuf var.
template <class T, class... Ps>
auto read(const InputVarType& _obj) {
const auto r = Reader();
return Parser<T, Processors<Ps...>>::read(r, _obj);
}
/// Parses an object from flexbuf using reflection.
template <class T, class... Ps>
auto read(const char* _bytes, const size_t _size) {
const InputVarType root =
flexbuffers::GetRoot(reinterpret_cast<const uint8_t*>(_bytes), _size);
return read<T, Ps...>(root);
}
/// Parses an object from flexbuf using reflection.
template <class T, class... Ps>
auto read(const std::vector<char>& _bytes) {
return read<T, Ps...>(_bytes.data(), _bytes.size());
}
/// Parses an object directly from a stream.
template <class T, class... Ps>
auto read(std::istream& _stream) {
std::istreambuf_iterator<char> begin(_stream), end;
const auto bytes = std::vector<char>(begin, end);
return read<T, Ps...>(bytes.data(), bytes.size());
}
} // namespace flexbuf
} // namespace rfl
#endif

View file

@ -0,0 +1,26 @@
#ifndef RFL_FLEXBUF_SAVE_HPP_
#define RFL_FLEXBUF_SAVE_HPP_
#include <fstream>
#include <iostream>
#include <string>
#include "../Result.hpp"
#include "../io/save_bytes.hpp"
#include "write.hpp"
namespace rfl {
namespace flexbuf {
template <class... Ps>
Result<Nothing> save(const std::string& _fname, const auto& _obj) {
const auto write_func = [](const auto& _obj, auto& _stream) -> auto& {
return write<Ps...>(_obj, _stream);
};
return rfl::io::save_bytes(_fname, _obj, write_func);
}
} // namespace flexbuf
} // namespace rfl
#endif

View file

@ -0,0 +1,50 @@
#ifndef FLEXBUF_WRITE_HPP_
#define FLEXBUF_WRITE_HPP_
#include <flatbuffers/flexbuffers.h>
#include <cstddef>
#include <ostream>
#include <sstream>
#include <vector>
#include "../Processors.hpp"
#include "../Ref.hpp"
#include "../parsing/Parent.hpp"
#include "Parser.hpp"
namespace rfl {
namespace flexbuf {
template <class... Ps>
std::vector<uint8_t> to_buffer(const auto& _obj) {
using T = std::remove_cvref_t<decltype(_obj)>;
using ParentType = parsing::Parent<Writer>;
const auto fbb = Ref<flexbuffers::Builder>::make();
auto w = Writer(fbb);
Parser<T, Processors<Ps...>>::write(w, _obj, typename ParentType::Root{});
fbb->Finish();
return fbb->GetBuffer();
}
/// Writes an object to flexbuf.
template <class... Ps>
std::vector<char> write(const auto& _obj) {
const auto buffer = to_buffer<Ps...>(_obj);
const auto data = reinterpret_cast<const char*>(buffer.data());
return std::vector<char>(data, data + buffer.size());
}
/// Writes an object to an ostream.
template <class... Ps>
std::ostream& write(const auto& _obj, std::ostream& _stream) {
const auto buffer = to_buffer<Ps...>(_obj);
const auto data = reinterpret_cast<const char*>(buffer.data());
_stream.write(data, buffer.size());
return _stream;
}
} // namespace flexbuf
} // namespace rfl
#endif

14
include/rfl/json.hpp Normal file
View file

@ -0,0 +1,14 @@
#ifndef RFL_JSON_HPP_
#define RFL_JSON_HPP_
#include "../rfl.hpp"
#include "json/Parser.hpp"
#include "json/Reader.hpp"
#include "json/Writer.hpp"
#include "json/load.hpp"
#include "json/read.hpp"
#include "json/save.hpp"
#include "json/to_schema.hpp"
#include "json/write.hpp"
#endif

View file

@ -0,0 +1,15 @@
#ifndef RFL_JSON_PARSER_HPP_
#define RFL_JSON_PARSER_HPP_
#include "../parsing/Parser.hpp"
#include "Reader.hpp"
#include "Writer.hpp"
namespace rfl::json {
template <class T, class ProcessorsType>
using Parser = parsing::Parser<Reader, Writer, T, ProcessorsType>;
} // namespace rfl::json
#endif

154
include/rfl/json/Reader.hpp Normal file
View file

@ -0,0 +1,154 @@
#ifndef RFL_JSON_READER_HPP_
#define RFL_JSON_READER_HPP_
#include <yyjson.h>
#include <array>
#include <concepts>
#include <exception>
#include <map>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <vector>
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl {
namespace json {
struct Reader {
struct YYJSONInputArray {
YYJSONInputArray(yyjson_val* _val) : val_(_val) {}
yyjson_val* val_;
};
struct YYJSONInputObject {
YYJSONInputObject(yyjson_val* _val) : val_(_val) {}
yyjson_val* val_;
};
struct YYJSONInputVar {
YYJSONInputVar() : val_(nullptr) {}
YYJSONInputVar(yyjson_val* _val) : val_(_val) {}
yyjson_val* val_;
};
using InputArrayType = YYJSONInputArray;
using InputObjectType = YYJSONInputObject;
using InputVarType = YYJSONInputVar;
template <class T>
static constexpr bool has_custom_constructor = (requires(InputVarType var) {
T::from_json_obj(var);
});
rfl::Result<InputVarType> get_field(
const std::string& _name, const InputObjectType _obj) const noexcept {
const auto var = InputVarType(yyjson_obj_get(_obj.val_, _name.c_str()));
if (!var.val_) {
return rfl::Error("Object contains no field named '" + _name + "'.");
}
return var;
}
bool is_empty(const InputVarType _var) const noexcept {
return !_var.val_ || yyjson_is_null(_var.val_);
}
template <class ArrayReader>
std::optional<Error> read_array(const ArrayReader& _array_reader,
const InputArrayType& _arr) const noexcept {
yyjson_val* val;
yyjson_arr_iter iter;
yyjson_arr_iter_init(_arr.val_, &iter);
while ((val = yyjson_arr_iter_next(&iter))) {
const auto err = _array_reader.read(InputVarType(val));
if (err) {
return err;
}
}
return std::nullopt;
}
template <class ObjectReader>
std::optional<Error> read_object(const ObjectReader& _object_reader,
const InputObjectType& _obj) const noexcept {
yyjson_obj_iter iter;
yyjson_obj_iter_init(_obj.val_, &iter);
yyjson_val* key;
while ((key = yyjson_obj_iter_next(&iter))) {
const auto name = std::string_view(yyjson_get_str(key));
_object_reader.read(name, InputVarType(yyjson_obj_iter_get_val(key)));
}
return std::nullopt;
}
template <class T>
rfl::Result<T> to_basic_type(const InputVarType _var) const noexcept {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
const auto r = yyjson_get_str(_var.val_);
if (r == NULL) {
return rfl::Error("Could not cast to string.");
}
return std::string(r);
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
if (!yyjson_is_bool(_var.val_)) {
return rfl::Error("Could not cast to boolean.");
}
return yyjson_get_bool(_var.val_);
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
if (!yyjson_is_num(_var.val_)) {
return rfl::Error("Could not cast to double.");
}
return static_cast<T>(yyjson_get_num(_var.val_));
} else if constexpr (std::is_unsigned<std::remove_cvref_t<T>>()) {
if (!yyjson_is_int(_var.val_)) {
return rfl::Error("Could not cast to int.");
}
return static_cast<T>(yyjson_get_uint(_var.val_));
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
if (!yyjson_is_int(_var.val_)) {
return rfl::Error("Could not cast to int.");
}
return static_cast<T>(yyjson_get_sint(_var.val_));
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
}
rfl::Result<InputArrayType> to_array(const InputVarType _var) const noexcept {
if (!yyjson_is_arr(_var.val_)) {
return rfl::Error("Could not cast to array!");
}
return InputArrayType(_var.val_);
}
rfl::Result<InputObjectType> to_object(
const InputVarType _var) const noexcept {
if (!yyjson_is_obj(_var.val_)) {
return rfl::Error("Could not cast to object!");
}
return InputObjectType(_var.val_);
}
template <class T>
rfl::Result<T> use_custom_constructor(
const InputVarType _var) const noexcept {
try {
return T::from_json_obj(_var);
} catch (std::exception& e) {
return rfl::Error(e.what());
}
}
};
} // namespace json
} // namespace rfl
#endif // JSON_PARSER_HPP_

169
include/rfl/json/Writer.hpp Normal file
View file

@ -0,0 +1,169 @@
#ifndef RFL_JSON_WRITER_HPP_
#define RFL_JSON_WRITER_HPP_
#include <yyjson.h>
#include <exception>
#include <map>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl {
namespace json {
class Writer {
public:
struct YYJSONOutputArray {
YYJSONOutputArray(yyjson_mut_val* _val) : val_(_val) {}
yyjson_mut_val* val_;
};
struct YYJSONOutputObject {
YYJSONOutputObject(yyjson_mut_val* _val) : val_(_val) {}
yyjson_mut_val* val_;
};
struct YYJSONOutputVar {
YYJSONOutputVar(yyjson_mut_val* _val) : val_(_val) {}
YYJSONOutputVar(YYJSONOutputArray _arr) : val_(_arr.val_) {}
YYJSONOutputVar(YYJSONOutputObject _obj) : val_(_obj.val_) {}
yyjson_mut_val* val_;
};
using OutputArrayType = YYJSONOutputArray;
using OutputObjectType = YYJSONOutputObject;
using OutputVarType = YYJSONOutputVar;
Writer(yyjson_mut_doc* _doc) : doc_(_doc) {}
~Writer() = default;
OutputArrayType array_as_root(const size_t _size) const noexcept {
const auto arr = yyjson_mut_arr(doc_);
yyjson_mut_doc_set_root(doc_, arr);
return OutputArrayType(arr);
}
OutputObjectType object_as_root(const size_t _size) const noexcept {
const auto obj = yyjson_mut_obj(doc_);
yyjson_mut_doc_set_root(doc_, obj);
return OutputObjectType(obj);
}
OutputVarType null_as_root() const noexcept {
const auto null = yyjson_mut_null(doc_);
yyjson_mut_doc_set_root(doc_, null);
return OutputVarType(null);
}
template <class T>
OutputVarType value_as_root(const T& _var) const noexcept {
const auto val = from_basic_type(_var);
yyjson_mut_doc_set_root(doc_, val.val_);
return OutputVarType(val);
}
OutputArrayType add_array_to_array(const size_t _size,
OutputArrayType* _parent) const noexcept {
const auto arr = yyjson_mut_arr(doc_);
yyjson_mut_arr_add_val(_parent->val_, arr);
return OutputArrayType(arr);
}
OutputArrayType add_array_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
const auto arr = yyjson_mut_arr(doc_);
yyjson_mut_obj_add(_parent->val_, yyjson_mut_strcpy(doc_, _name.data()),
arr);
return OutputArrayType(arr);
}
OutputObjectType add_object_to_array(
const size_t _size, OutputArrayType* _parent) const noexcept {
const auto obj = yyjson_mut_obj(doc_);
yyjson_mut_arr_add_val(_parent->val_, obj);
return OutputObjectType(obj);
}
OutputObjectType add_object_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
const auto obj = yyjson_mut_obj(doc_);
yyjson_mut_obj_add(_parent->val_, yyjson_mut_strcpy(doc_, _name.data()),
obj);
return OutputObjectType(obj);
}
template <class T>
OutputVarType add_value_to_array(const T& _var,
OutputArrayType* _parent) const noexcept {
const auto val = from_basic_type(_var);
yyjson_mut_arr_add_val(_parent->val_, val.val_);
return OutputVarType(val);
}
template <class T>
OutputVarType add_value_to_object(const std::string_view& _name,
const T& _var,
OutputObjectType* _parent) const noexcept {
const auto val = from_basic_type(_var);
yyjson_mut_obj_add(_parent->val_, yyjson_mut_strcpy(doc_, _name.data()),
val.val_);
return OutputVarType(val);
}
OutputVarType add_null_to_array(OutputArrayType* _parent) const noexcept {
const auto null = yyjson_mut_null(doc_);
yyjson_mut_arr_add_val(_parent->val_, null);
return OutputVarType(null);
}
OutputVarType add_null_to_object(const std::string_view& _name,
OutputObjectType* _parent) const noexcept {
const auto null = yyjson_mut_null(doc_);
yyjson_mut_obj_add(_parent->val_, yyjson_mut_strcpy(doc_, _name.data()),
null);
return OutputVarType(null);
}
void end_array(OutputArrayType* _arr) const noexcept {}
void end_object(OutputObjectType* _obj) const noexcept {}
private:
template <class T>
OutputVarType from_basic_type(const T& _var) const noexcept {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
return OutputVarType(yyjson_mut_strcpy(doc_, _var.c_str()));
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
return OutputVarType(yyjson_mut_bool(doc_, _var));
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
return OutputVarType(yyjson_mut_real(doc_, static_cast<double>(_var)));
} else if constexpr (std::is_unsigned<std::remove_cvref_t<T>>()) {
return OutputVarType(yyjson_mut_uint(doc_, static_cast<uint64_t>(_var)));
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
return OutputVarType(yyjson_mut_int(doc_, static_cast<int64_t>(_var)));
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
}
public:
yyjson_mut_doc* doc_;
};
} // namespace json
} // namespace rfl
#endif // JSON_PARSER_HPP_

22
include/rfl/json/load.hpp Normal file
View file

@ -0,0 +1,22 @@
#ifndef RFL_JSON_LOAD_HPP_
#define RFL_JSON_LOAD_HPP_
#include "../Result.hpp"
#include "../io/load_string.hpp"
#include "read.hpp"
namespace rfl {
namespace json {
template <class T, class... Ps>
Result<T> load(const std::string& _fname) {
const auto read_string = [](const auto& _str) {
return read<T, Ps...>(_str);
};
return rfl::io::load_string(_fname).and_then(read_string);
}
} // namespace json
} // namespace rfl
#endif

52
include/rfl/json/read.hpp Normal file
View file

@ -0,0 +1,52 @@
#ifndef RFL_JSON_READ_HPP_
#define RFL_JSON_READ_HPP_
#include <yyjson.h>
#include <istream>
#include <string>
#include "../Processors.hpp"
#include "../internal/wrap_in_rfl_array_t.hpp"
#include "Parser.hpp"
#include "Reader.hpp"
namespace rfl {
namespace json {
using InputObjectType = typename Reader::InputObjectType;
using InputVarType = typename Reader::InputVarType;
/// Parses an object from a JSON var.
template <class T, class... Ps>
auto read(const InputVarType& _obj) {
const auto r = Reader();
return Parser<T, Processors<Ps...>>::read(r, _obj);
}
/// Parses an object from JSON using reflection.
template <class T, class... Ps>
Result<internal::wrap_in_rfl_array_t<T>> read(const std::string& _json_str) {
yyjson_doc* doc = yyjson_read(_json_str.c_str(), _json_str.size(), 0);
if (!doc) {
return Error("Could not parse document");
}
yyjson_val* root = yyjson_doc_get_root(doc);
const auto r = Reader();
auto res = Parser<T, Processors<Ps...>>::read(r, InputVarType(root));
yyjson_doc_free(doc);
return res;
}
/// Parses an object from a stringstream.
template <class T, class... Ps>
auto read(std::istream& _stream) {
const auto json_str = std::string(std::istreambuf_iterator<char>(_stream),
std::istreambuf_iterator<char>());
return read<T, Ps...>(json_str);
}
} // namespace json
} // namespace rfl
#endif

29
include/rfl/json/save.hpp Normal file
View file

@ -0,0 +1,29 @@
#ifndef RFL_JSON_SAVE_HPP_
#define RFL_JSON_SAVE_HPP_
#include <yyjson.h>
#include <fstream>
#include <iostream>
#include <string>
#include "../Result.hpp"
#include "../io/save_string.hpp"
#include "write.hpp"
namespace rfl {
namespace json {
template <class... Ps>
Result<Nothing> save(const std::string& _fname, const auto& _obj,
const yyjson_write_flag _flag = 0) {
const auto write_func = [_flag](const auto& _obj, auto& _stream) -> auto& {
return write<Ps...>(_obj, _stream, _flag);
};
return rfl::io::save_string(_fname, _obj, write_func);
}
} // namespace json
} // namespace rfl
#endif

View file

@ -0,0 +1,25 @@
#ifndef RFL_JSON_SCHEMA_JSONSCHEMA_HPP_
#define RFL_JSON_SCHEMA_JSONSCHEMA_HPP_
#include <map>
#include <string>
#include <variant>
#include "../../Flatten.hpp"
#include "../../Literal.hpp"
#include "../../Rename.hpp"
#include "Type.hpp"
namespace rfl::json::schema {
template <class T>
struct JSONSchema {
Rename<"$schema", Literal<"https://json-schema.org/draft/2020-12/schema">>
schema;
Flatten<T> root;
std::map<std::string, Type> definitions;
};
} // namespace rfl::json::schema
#endif

View file

@ -0,0 +1,147 @@
#ifndef RFL_JSON_SCHEMA_TYPE_HPP_
#define RFL_JSON_SCHEMA_TYPE_HPP_
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <variant>
#include "../../Literal.hpp"
#include "../../Rename.hpp"
namespace rfl::json::schema {
/// The JSON representation of internal::schema::Type.
struct Type {
struct Boolean {
std::optional<std::string> description;
Literal<"boolean"> type;
};
struct Integer {
Literal<"integer"> type;
std::optional<std::string> description;
};
struct Number {
Literal<"number"> type;
std::optional<std::string> description;
};
struct String {
Literal<"string"> type;
std::optional<std::string> description;
};
using NumericType = std::variant<Integer, Number>;
struct AllOf {
std::optional<std::string> description;
std::vector<Type> allOf;
};
struct AnyOf {
std::optional<std::string> description;
std::vector<Type> anyOf;
};
struct ExclusiveMaximum {
std::optional<std::string> description;
std::variant<double, int> exclusiveMaximum;
std::string type;
};
struct ExclusiveMinimum {
std::optional<std::string> description;
std::variant<double, int> exclusiveMinimum;
std::string type;
};
struct FixedSizeTypedArray {
Literal<"array"> type;
std::optional<std::string> description;
rfl::Ref<Type> items;
size_t minContains;
size_t maxContains;
};
struct Maximum {
std::optional<std::string> description;
std::variant<double, int> maximum;
std::string type;
};
struct Minimum {
std::optional<std::string> description;
std::variant<double, int> minimum;
std::string type;
};
struct Null {
Literal<"null"> type;
std::optional<std::string> description;
};
struct Object {
Literal<"object"> type;
std::optional<std::string> description;
std::map<std::string, Type> properties;
std::vector<std::string> required;
};
struct OneOf {
std::optional<std::string> description;
std::vector<Type> oneOf;
};
struct Reference {
Rename<"$ref", std::optional<std::string>> ref;
std::optional<std::string> description;
};
struct Regex {
Literal<"string"> type;
std::optional<std::string> description;
std::string pattern;
};
struct StringEnum {
Literal<"string"> type;
std::optional<std::string> description;
rfl::Rename<"enum", std::vector<std::string>> values;
};
struct StringMap {
Literal<"object"> type;
std::optional<std::string> description;
rfl::Ref<Type> additionalProperties;
};
struct Tuple {
Literal<"array"> type;
std::optional<std::string> description;
std::vector<Type> prefixItems;
bool items = false;
};
struct TypedArray {
Literal<"array"> type;
std::optional<std::string> description;
rfl::Ref<Type> items;
};
using ReflectionType =
std::variant<AllOf, AnyOf, Boolean, ExclusiveMaximum, ExclusiveMinimum,
FixedSizeTypedArray, Integer, Maximum, Minimum, Number, Null,
Object, OneOf, Reference, Regex, String, StringEnum,
StringMap, Tuple, TypedArray>;
const auto& reflection() const { return value; }
ReflectionType value;
};
} // namespace rfl::json::schema
#endif

View file

@ -0,0 +1,262 @@
#ifndef RFL_JSON_TOSCHEMA_HPP_
#define RFL_JSON_TOSCHEMA_HPP_
#include <yyjson.h>
#include <map>
#include <string>
#include <type_traits>
#include <variant>
#include "../Literal.hpp"
#include "../Processors.hpp"
#include "../parsing/schema/Type.hpp"
#include "../parsing/schema/ValidationType.hpp"
#include "../parsing/schema/make.hpp"
#include "Reader.hpp"
#include "Writer.hpp"
#include "schema/JSONSchema.hpp"
#include "schema/Type.hpp"
#include "write.hpp"
namespace rfl::json {
inline schema::Type type_to_json_schema_type(
const parsing::schema::Type& _type);
inline bool is_optional(const parsing::schema::Type& _t) {
const auto handle = [](const auto& _v) -> bool {
using T = std::remove_cvref_t<decltype(_v)>;
return std::is_same<T, parsing::schema::Type::Optional>();
};
return std::visit(handle, _t.variant_);
}
inline std::string numeric_type_to_string(const parsing::schema::Type& _type) {
const auto handle_variant = [](const auto& _t) -> std::string {
using T = std::remove_cvref_t<decltype(_t)>;
using Type = parsing::schema::Type;
if constexpr (std::is_same<T, Type::Int32>() ||
std::is_same<T, Type::Int64>() ||
std::is_same<T, Type::UInt32>() ||
std::is_same<T, Type::UInt64>() ||
std::is_same<T, Type::Integer>()) {
return schema::Type::Integer{}.type.str();
} else {
return schema::Type::Number{}.type.str();
}
};
return std::visit(handle_variant, _type.variant_);
}
inline schema::Type handle_validation_type(
const parsing::schema::Type& _type,
const parsing::schema::ValidationType& _validation_type) {
const auto handle_variant = [&](const auto& _v) -> schema::Type {
using T = std::remove_cvref_t<decltype(_v)>;
using ValidationType = parsing::schema::ValidationType;
if constexpr (std::is_same<T, ValidationType::AllOf>()) {
auto all_of = std::vector<schema::Type>();
for (const auto& t : _v.types_) {
all_of.emplace_back(handle_validation_type(_type, t));
}
return schema::Type{.value = schema::Type::AllOf{.allOf = all_of}};
} else if constexpr (std::is_same<T, ValidationType::AnyOf>()) {
auto any_of = std::vector<schema::Type>();
for (const auto& t : _v.types_) {
any_of.emplace_back(handle_validation_type(_type, t));
}
return schema::Type{.value = schema::Type::AnyOf{.anyOf = any_of}};
} else if constexpr (std::is_same<T, ValidationType::OneOf>()) {
auto one_of = std::vector<schema::Type>();
for (const auto& t : _v.types_) {
one_of.emplace_back(handle_validation_type(_type, t));
}
return schema::Type{.value = schema::Type::OneOf{.oneOf = one_of}};
} else if constexpr (std::is_same<T, ValidationType::Regex>()) {
return schema::Type{.value = schema::Type::Regex{.pattern = _v.pattern_}};
} else if constexpr (std::is_same<T, ValidationType::Size>()) {
return type_to_json_schema_type(_type);
} else if constexpr (std::is_same<T, ValidationType::ExclusiveMaximum>()) {
return schema::Type{.value = schema::Type::ExclusiveMaximum{
.exclusiveMaximum = _v.value_,
.type = numeric_type_to_string(_type)}};
} else if constexpr (std::is_same<T, ValidationType::ExclusiveMinimum>()) {
return schema::Type{.value = schema::Type::ExclusiveMinimum{
.exclusiveMinimum = _v.value_,
.type = numeric_type_to_string(_type)}};
} else if constexpr (std::is_same<T, ValidationType::Maximum>()) {
return schema::Type{
.value = schema::Type::Maximum{
.maximum = _v.value_, .type = numeric_type_to_string(_type)}};
} else if constexpr (std::is_same<T, ValidationType::Minimum>()) {
return schema::Type{
.value = schema::Type::Minimum{
.minimum = _v.value_, .type = numeric_type_to_string(_type)}};
} else if constexpr (std::is_same<T, ValidationType::EqualTo>()) {
const auto maximum = schema::Type{
.value = schema::Type::Maximum{
.maximum = _v.value_, .type = numeric_type_to_string(_type)}};
const auto minimum = schema::Type{
.value = schema::Type::Minimum{
.minimum = _v.value_, .type = numeric_type_to_string(_type)}};
return schema::Type{.value =
schema::Type::AllOf{.allOf = {maximum, minimum}}};
} else if constexpr (std::is_same<T, ValidationType::NotEqualTo>()) {
const auto excl_maximum =
schema::Type{.value = schema::Type::ExclusiveMaximum{
.exclusiveMaximum = _v.value_,
.type = numeric_type_to_string(_type)}};
const auto excl_minimum =
schema::Type{.value = schema::Type::ExclusiveMinimum{
.exclusiveMinimum = _v.value_,
.type = numeric_type_to_string(_type)}};
return schema::Type{
.value = schema::Type::AnyOf{.anyOf = {excl_maximum, excl_minimum}}};
} else {
static_assert(rfl::always_false_v<T>, "Not all cases were covered.");
}
};
return std::visit(handle_variant, _validation_type.variant_);
}
inline schema::Type type_to_json_schema_type(
const parsing::schema::Type& _type) {
const auto handle_variant = [](const auto& _t) -> schema::Type {
using T = std::remove_cvref_t<decltype(_t)>;
using Type = parsing::schema::Type;
if constexpr (std::is_same<T, Type::Boolean>()) {
return schema::Type{.value = schema::Type::Boolean{}};
} else if constexpr (std::is_same<T, Type::Int32>() ||
std::is_same<T, Type::Int64>() ||
std::is_same<T, Type::UInt32>() ||
std::is_same<T, Type::UInt64>() ||
std::is_same<T, Type::Integer>()) {
return schema::Type{.value = schema::Type::Integer{}};
} else if constexpr (std::is_same<T, Type::Float>() ||
std::is_same<T, Type::Double>()) {
return schema::Type{.value = schema::Type::Number{}};
} else if constexpr (std::is_same<T, Type::String>()) {
return schema::Type{.value = schema::Type::String{}};
} else if constexpr (std::is_same<T, Type::AnyOf>()) {
auto any_of = std::vector<schema::Type>();
for (const auto& t : _t.types_) {
any_of.emplace_back(type_to_json_schema_type(t));
}
return schema::Type{.value = schema::Type::AnyOf{.anyOf = any_of}};
} else if constexpr (std::is_same<T, Type::Description>()) {
auto res = type_to_json_schema_type(*_t.type_);
const auto update_prediction = [&](auto _v) -> schema::Type {
_v.description = _t.description_;
return schema::Type{_v};
};
return std::visit(update_prediction, res.value);
} else if constexpr (std::is_same<T, Type::FixedSizeTypedArray>()) {
return schema::Type{.value = schema::Type::FixedSizeTypedArray{
.items = Ref<schema::Type>::make(
type_to_json_schema_type(*_t.type_)),
.minContains = _t.size_,
.maxContains = _t.size_}};
} else if constexpr (std::is_same<T, Type::Literal>()) {
return schema::Type{.value =
schema::Type::StringEnum{.values = _t.values_}};
} else if constexpr (std::is_same<T, Type::Object>()) {
auto properties = std::map<std::string, schema::Type>();
auto required = std::vector<std::string>();
for (const auto& [k, v] : _t.types_) {
properties[k] = type_to_json_schema_type(v);
if (!is_optional(v)) {
required.push_back(k);
}
}
return schema::Type{.value = schema::Type::Object{
.properties = properties, .required = required}};
} else if constexpr (std::is_same<T, Type::Optional>()) {
return schema::Type{.value = schema::Type::AnyOf{
.anyOf = {type_to_json_schema_type(*_t.type_),
schema::Type{schema::Type::Null{}}}}};
} else if constexpr (std::is_same<T, Type::Reference>()) {
return schema::Type{
.value = schema::Type::Reference{.ref = "#/definitions/" + _t.name_}};
} else if constexpr (std::is_same<T, Type::StringMap>()) {
return schema::Type{.value = schema::Type::StringMap{
.additionalProperties = Ref<schema::Type>::make(
type_to_json_schema_type(*_t.value_type_))}};
} else if constexpr (std::is_same<T, Type::Tuple>()) {
auto items = std::vector<schema::Type>();
for (const auto& t : _t.types_) {
items.emplace_back(type_to_json_schema_type(t));
}
return schema::Type{.value = schema::Type::Tuple{.prefixItems = items}};
} else if constexpr (std::is_same<T, Type::TypedArray>()) {
return schema::Type{.value = schema::Type::TypedArray{
.items = Ref<schema::Type>::make(
type_to_json_schema_type(*_t.type_))}};
} else if constexpr (std::is_same<T, Type::Validated>()) {
return handle_validation_type(*_t.type_, _t.validation_);
} else {
static_assert(rfl::always_false_v<T>, "Not all cases were covered.");
}
};
return std::visit(handle_variant, _type.variant_);
}
template <class T>
struct TypeHelper {};
template <class... Ts>
struct TypeHelper<std::variant<Ts...>> {
using JSONSchemaType = std::variant<schema::JSONSchema<Ts>...>;
};
/// Returns the JSON schema for a class.
template <class T, class... Ps>
std::string to_schema(const yyjson_write_flag _flag = 0) {
const auto internal_schema =
parsing::schema::make<Reader, Writer, T, Processors<Ps...>>();
auto definitions = std::map<std::string, schema::Type>();
for (const auto& [k, v] : internal_schema.definitions_) {
definitions[k] = type_to_json_schema_type(v);
}
using JSONSchemaType =
typename TypeHelper<schema::Type::ReflectionType>::JSONSchemaType;
const auto to_schema = [&](auto&& _root) -> JSONSchemaType {
using U = std::decay_t<decltype(_root)>;
return schema::JSONSchema<U>{.root = std::move(_root),
.definitions = definitions};
};
auto root = type_to_json_schema_type(internal_schema.root_);
const auto json_schema = std::visit(to_schema, std::move(root.value));
return write(json_schema, _flag);
}
} // namespace rfl::json
#endif

View file

@ -0,0 +1,52 @@
#ifndef RFL_JSON_WRITE_HPP_
#define RFL_JSON_WRITE_HPP_
#include <yyjson.h>
#include <ostream>
#include <sstream>
#include <string>
#include "../Processors.hpp"
#include "../parsing/Parent.hpp"
#include "Parser.hpp"
namespace rfl {
namespace json {
/// Convenient alias for the YYJSON pretty flag
inline constexpr yyjson_write_flag pretty = YYJSON_WRITE_PRETTY;
/// Returns a JSON string.
template <class... Ps>
std::string write(const auto& _obj, const yyjson_write_flag _flag = 0) {
using T = std::remove_cvref_t<decltype(_obj)>;
using ParentType = parsing::Parent<Writer>;
auto w = Writer(yyjson_mut_doc_new(NULL));
Parser<T, Processors<Ps...>>::write(w, _obj, typename ParentType::Root{});
const char* json_c_str = yyjson_mut_write(w.doc_, _flag, NULL);
const auto json_str = std::string(json_c_str);
free((void*)json_c_str);
yyjson_mut_doc_free(w.doc_);
return json_str;
}
/// Writes a JSON into an ostream.
template <class... Ps>
std::ostream& write(const auto& _obj, std::ostream& _stream,
const yyjson_write_flag _flag = 0) {
using T = std::remove_cvref_t<decltype(_obj)>;
using ParentType = parsing::Parent<Writer>;
auto w = Writer(yyjson_mut_doc_new(NULL));
Parser<T, Processors<Ps...>>::write(w, _obj, typename ParentType::Root{});
const char* json_c_str = yyjson_mut_write(w.doc_, _flag, NULL);
_stream << json_c_str;
free((void*)json_c_str);
yyjson_mut_doc_free(w.doc_);
return _stream;
}
} // namespace json
} // namespace rfl
#endif // JSON_PARSER_HPP_

13
include/rfl/msgpack.hpp Normal file
View file

@ -0,0 +1,13 @@
#ifndef RFL_MSGPACK_HPP_
#define RFL_MSGPACK_HPP_
#include "../rfl.hpp"
#include "msgpack/Parser.hpp"
#include "msgpack/Reader.hpp"
#include "msgpack/Writer.hpp"
#include "msgpack/load.hpp"
#include "msgpack/read.hpp"
#include "msgpack/save.hpp"
#include "msgpack/write.hpp"
#endif

View file

@ -0,0 +1,46 @@
#ifndef RFL_MSGPACK_PARSER_HPP_
#define RFL_MSGPACK_PARSER_HPP_
#include "../parsing/Parser.hpp"
#include "Reader.hpp"
#include "Writer.hpp"
namespace rfl {
namespace parsing {
/// msgpack-c requires us to explicitly set the number of fields in advance.
/// Because of that, we require all of the fields and then set them to nullptr,
/// if necessary.
template <class ProcessorsType, class... FieldTypes>
requires AreReaderAndWriter<msgpack::Reader, msgpack::Writer,
NamedTuple<FieldTypes...>>
struct Parser<msgpack::Reader, msgpack::Writer, NamedTuple<FieldTypes...>,
ProcessorsType>
: public NamedTupleParser<msgpack::Reader, msgpack::Writer,
/*_ignore_empty_containers=*/false,
/*_all_required=*/true, ProcessorsType,
FieldTypes...> {
};
template <class ProcessorsType, class... Ts>
requires AreReaderAndWriter<msgpack::Reader, msgpack::Writer, std::tuple<Ts...>>
struct Parser<msgpack::Reader, msgpack::Writer, std::tuple<Ts...>,
ProcessorsType>
: public TupleParser<msgpack::Reader, msgpack::Writer,
/*_ignore_empty_containers=*/false,
/*_all_required=*/true, ProcessorsType, Ts...> {
};
} // namespace parsing
} // namespace rfl
namespace rfl {
namespace msgpack {
template <class T, class ProcessorsType>
using Parser = parsing::Parser<Reader, Writer, T, ProcessorsType>;
}
} // namespace rfl
#endif

View file

@ -0,0 +1,148 @@
#ifndef RFL_MSGPACK_READER_HPP_
#define RFL_MSGPACK_READER_HPP_
#include <msgpack.h>
#include <array>
#include <concepts>
#include <exception>
#include <map>
#include <memory>
#include <source_location>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <vector>
#include "../Box.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl {
namespace msgpack {
struct Reader {
using InputArrayType = msgpack_object_array;
using InputObjectType = msgpack_object_map;
using InputVarType = msgpack_object;
template <class T>
static constexpr bool has_custom_constructor = (requires(InputVarType var) {
T::from_msgpack_obj(var);
});
rfl::Result<InputVarType> get_field(
const std::string& _name, const InputObjectType& _obj) const noexcept {
for (uint32_t i = 0; i < _obj.size; ++i) {
const auto& key = _obj.ptr[i].key;
if (key.type != MSGPACK_OBJECT_STR) {
return Error("Key in element " + std::to_string(i) +
" was not a string.");
}
const auto current_name =
std::string_view(key.via.str.ptr, key.via.str.size);
if (_name == current_name) {
return _obj.ptr[i].val;
}
}
return Error("No field named '" + _name + "' was found.");
}
bool is_empty(const InputVarType& _var) const noexcept {
return _var.type == MSGPACK_OBJECT_NIL;
}
template <class T>
rfl::Result<T> to_basic_type(const InputVarType& _var) const noexcept {
const auto type = _var.type;
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
if (type != MSGPACK_OBJECT_STR) {
return Error("Could not cast to string.");
}
const auto str = _var.via.str;
return std::string(str.ptr, str.size);
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
if (type != MSGPACK_OBJECT_BOOLEAN) {
return Error("Could not cast to boolean.");
}
return _var.via.boolean;
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>() ||
std::is_integral<std::remove_cvref_t<T>>()) {
if (type == MSGPACK_OBJECT_FLOAT32 || type == MSGPACK_OBJECT_FLOAT64 ||
type == MSGPACK_OBJECT_FLOAT) {
return static_cast<T>(_var.via.f64);
} else if (type == MSGPACK_OBJECT_POSITIVE_INTEGER) {
return static_cast<T>(_var.via.u64);
} else if (type == MSGPACK_OBJECT_NEGATIVE_INTEGER) {
return static_cast<T>(_var.via.i64);
}
return rfl::Error(
"Could not cast to numeric value. The type must be integral, float "
"or double.");
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
}
rfl::Result<InputArrayType> to_array(
const InputVarType& _var) const noexcept {
if (_var.type != MSGPACK_OBJECT_ARRAY) {
return Error("Could not cast to an array.");
}
return _var.via.array;
}
rfl::Result<InputObjectType> to_object(
const InputVarType& _var) const noexcept {
if (_var.type != MSGPACK_OBJECT_MAP) {
return Error("Could not cast to a map.");
}
return _var.via.map;
}
template <class ArrayReader>
std::optional<Error> read_array(const ArrayReader& _array_reader,
const InputArrayType& _arr) const noexcept {
for (uint32_t i = 0; i < _arr.size; ++i) {
const auto err = _array_reader.read(_arr.ptr[i]);
if (err) {
return err;
}
}
return std::nullopt;
}
template <class ObjectReader>
std::optional<Error> read_object(const ObjectReader& _object_reader,
const InputObjectType& _obj) const noexcept {
for (uint32_t i = 0; i < _obj.size; ++i) {
const auto& key = _obj.ptr[i].key;
const auto& val = _obj.ptr[i].val;
if (key.type != MSGPACK_OBJECT_STR) {
return Error("Key in element " + std::to_string(i) +
" was not a string.");
}
const auto name = std::string_view(key.via.str.ptr, key.via.str.size);
_object_reader.read(name, val);
}
return std::nullopt;
}
template <class T>
rfl::Result<T> use_custom_constructor(
const InputVarType& _var) const noexcept {
try {
return T::from_msgpack_obj(_var);
} catch (std::exception& e) {
return rfl::Error(e.what());
}
}
};
} // namespace msgpack
} // namespace rfl
#endif // JSON_PARSER_HPP_

View file

@ -0,0 +1,154 @@
#ifndef RFL_MSGPACK_WRITER_HPP_
#define RFL_MSGPACK_WRITER_HPP_
#include <msgpack.h>
#include <exception>
#include <map>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <variant>
#include <vector>
#include "../Box.hpp"
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl::msgpack {
class Writer {
public:
struct MsgpackOutputArray {};
struct MsgpackOutputObject {};
struct MsgpackOutputVar {};
using OutputArrayType = MsgpackOutputArray;
using OutputObjectType = MsgpackOutputObject;
using OutputVarType = MsgpackOutputVar;
Writer(msgpack_packer* _pk) : pk_(_pk) {}
~Writer() = default;
OutputArrayType array_as_root(const size_t _size) const noexcept {
return new_array(_size);
}
OutputObjectType object_as_root(const size_t _size) const noexcept {
return new_object(_size);
}
OutputVarType null_as_root() const noexcept {
msgpack_pack_nil(pk_);
return OutputVarType{};
}
template <class T>
OutputVarType value_as_root(const T& _var) const noexcept {
return new_value(_var);
}
OutputArrayType add_array_to_array(const size_t _size,
OutputArrayType* _parent) const noexcept {
return new_array(_size);
}
OutputArrayType add_array_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
msgpack_pack_str(pk_, _name.size());
msgpack_pack_str_body(pk_, _name.data(), _name.size());
return new_array(_size);
}
OutputObjectType add_object_to_array(
const size_t _size, OutputArrayType* _parent) const noexcept {
return new_object(_size);
}
OutputObjectType add_object_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
msgpack_pack_str(pk_, _name.size());
msgpack_pack_str_body(pk_, _name.data(), _name.size());
return new_object(_size);
}
template <class T>
OutputVarType add_value_to_array(const T& _var,
OutputArrayType* _parent) const noexcept {
return new_value(_var);
}
template <class T>
OutputVarType add_value_to_object(const std::string_view& _name,
const T& _var,
OutputObjectType* _parent) const noexcept {
msgpack_pack_str(pk_, _name.size());
msgpack_pack_str_body(pk_, _name.data(), _name.size());
return new_value(_var);
}
OutputVarType add_null_to_array(OutputArrayType* _parent) const noexcept {
msgpack_pack_nil(pk_);
return OutputVarType{};
}
OutputVarType add_null_to_object(const std::string_view& _name,
OutputObjectType* _parent) const noexcept {
msgpack_pack_str(pk_, _name.size());
msgpack_pack_str_body(pk_, _name.data(), _name.size());
msgpack_pack_nil(pk_);
return OutputVarType{};
}
void end_array(OutputArrayType* _arr) const noexcept {}
void end_object(OutputObjectType* _obj) const noexcept {}
private:
OutputArrayType new_array(const size_t _size) const noexcept {
msgpack_pack_array(pk_, _size);
return OutputArrayType{};
}
OutputObjectType new_object(const size_t _size) const noexcept {
msgpack_pack_map(pk_, _size);
return OutputObjectType{};
}
template <class T>
OutputVarType new_value(const T& _var) const noexcept {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
msgpack_pack_str(pk_, _var.size());
msgpack_pack_str_body(pk_, _var.c_str(), _var.size());
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
if (_var) {
msgpack_pack_true(pk_);
} else {
msgpack_pack_false(pk_);
}
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
msgpack_pack_double(pk_, static_cast<double>(_var));
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
msgpack_pack_int64(pk_, static_cast<std::int64_t>(_var));
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
return OutputVarType{};
}
private:
/// The underlying packer.
msgpack_packer* pk_;
};
} // namespace rfl::msgpack
#endif // MSGPACK_PARSER_HPP_

View file

@ -0,0 +1,22 @@
#ifndef RFL_MSGPACK_LOAD_HPP_
#define RFL_MSGPACK_LOAD_HPP_
#include "../Result.hpp"
#include "../io/load_bytes.hpp"
#include "read.hpp"
namespace rfl {
namespace msgpack {
template <class T, class... Ps>
Result<T> load(const std::string& _fname) {
const auto read_bytes = [](const auto& _bytes) {
return read<T, Ps...>(_bytes);
};
return rfl::io::load_bytes(_fname).and_then(read_bytes);
}
} // namespace msgpack
} // namespace rfl
#endif

View file

@ -0,0 +1,57 @@
#ifndef RFL_MSGPACK_READ_HPP_
#define RFL_MSGPACK_READ_HPP_
#include <msgpack.h>
#include <istream>
#include <string>
#include "../Processors.hpp"
#include "../internal/wrap_in_rfl_array_t.hpp"
#include "Parser.hpp"
#include "Reader.hpp"
namespace rfl {
namespace msgpack {
using InputObjectType = typename Reader::InputObjectType;
using InputVarType = typename Reader::InputVarType;
/// Parses an object from a MSGPACK var.
template <class T, class... Ps>
auto read(const InputVarType& _obj) {
const auto r = Reader();
return Parser<T, Processors<Ps...>>::read(r, _obj);
}
/// Parses an object from MSGPACK using reflection.
template <class T, class... Ps>
Result<internal::wrap_in_rfl_array_t<T>> read(const char* _bytes,
const size_t _size) {
msgpack_zone mempool;
msgpack_zone_init(&mempool, 2048);
msgpack_object deserialized;
msgpack_unpack(_bytes, _size, NULL, &mempool, &deserialized);
auto r = read<T, Ps...>(deserialized);
msgpack_zone_destroy(&mempool);
return r;
}
/// Parses an object from MSGPACK using reflection.
template <class T, class... Ps>
auto read(const std::vector<char>& _bytes) {
return read<T, Ps...>(_bytes.data(), _bytes.size());
}
/// Parses an object from a stream.
template <class T, class... Ps>
auto read(std::istream& _stream) {
std::istreambuf_iterator<char> begin(_stream), end;
auto bytes = std::vector<char>(begin, end);
return read<T, Ps...>(bytes.data(), bytes.size());
}
} // namespace msgpack
} // namespace rfl
#endif

View file

@ -0,0 +1,26 @@
#ifndef RFL_MSGPACK_SAVE_HPP_
#define RFL_MSGPACK_SAVE_HPP_
#include <fstream>
#include <iostream>
#include <string>
#include "../Result.hpp"
#include "../io/save_bytes.hpp"
#include "write.hpp"
namespace rfl {
namespace msgpack {
template <class... Ps>
Result<Nothing> save(const std::string& _fname, const auto& _obj) {
const auto write_func = [](const auto& _obj, auto& _stream) -> auto& {
return write<Ps...>(_obj, _stream);
};
return rfl::io::save_bytes(_fname, _obj, write_func);
}
} // namespace msgpack
} // namespace rfl
#endif

View file

@ -0,0 +1,44 @@
#ifndef RFL_MSGPACK_WRITE_HPP_
#define RFL_MSGPACK_WRITE_HPP_
#include <msgpack.h>
#include <cstdint>
#include <ostream>
#include <sstream>
#include <string>
#include <utility>
#include "../Processors.hpp"
#include "../parsing/Parent.hpp"
#include "Parser.hpp"
namespace rfl::msgpack {
/// Returns msgpack bytes.
template <class... Ps>
std::vector<char> write(const auto& _obj) noexcept {
using T = std::remove_cvref_t<decltype(_obj)>;
using ParentType = parsing::Parent<Writer>;
msgpack_sbuffer sbuf;
msgpack_sbuffer_init(&sbuf);
msgpack_packer pk;
msgpack_packer_init(&pk, &sbuf, msgpack_sbuffer_write);
auto w = Writer(&pk);
Parser<T, Processors<Ps...>>::write(w, _obj, typename ParentType::Root{});
auto bytes = std::vector<char>(sbuf.data, sbuf.data + sbuf.size);
msgpack_sbuffer_destroy(&sbuf);
return bytes;
}
/// Writes a MSGPACK into an ostream.
template <class... Ps>
std::ostream& write(const auto& _obj, std::ostream& _stream) noexcept {
auto buffer = write<Ps...>(_obj);
_stream.write(buffer.data(), buffer.size());
return _stream;
}
} // namespace rfl::msgpack
#endif

View file

@ -6,6 +6,7 @@
#include <tuple> #include <tuple>
#include <type_traits> #include <type_traits>
#include <unordered_map> #include <unordered_map>
#include <utility>
#include "../NamedTuple.hpp" #include "../NamedTuple.hpp"
#include "../Result.hpp" #include "../Result.hpp"
@ -59,7 +60,7 @@ struct NamedTupleParser {
using ViewType = std::remove_cvref_t<decltype(view)>; using ViewType = std::remove_cvref_t<decltype(view)>;
const auto err = const auto err =
Parser<R, W, ViewType, ProcessorsType>::read_view(_r, _var, &view); Parser<R, W, ViewType, ProcessorsType>::read_view(_r, _var, &view);
if (err) { if (err) [[unlikely]] {
return *err; return *err;
} }
return *ptr; return *ptr;
@ -69,11 +70,11 @@ struct NamedTupleParser {
static std::optional<Error> read_view( static std::optional<Error> read_view(
const R& _r, const InputVarType& _var, const R& _r, const InputVarType& _var,
NamedTuple<FieldTypes...>* _view) noexcept { NamedTuple<FieldTypes...>* _view) noexcept {
const auto obj = _r.to_object(_var); auto obj = _r.to_object(_var);
if (obj) { if (!obj) [[unlikely]] {
return read_object(_r, *obj, _view); return obj.error();
} }
return obj.error(); return read_object(_r, *obj, _view);
} }
/// For writing, we do not need to make the distinction between /// For writing, we do not need to make the distinction between
@ -147,38 +148,43 @@ struct NamedTupleParser {
} }
/// Generates error messages for when fields are missing. /// Generates error messages for when fields are missing.
template <size_t _i = 0> template <int _i>
static void handle_missing_fields(const std::array<bool, size_>& _found, static void handle_one_missing_field(const std::array<bool, size_>& _found,
const NamedTupleType& _view, const NamedTupleType& _view,
std::array<bool, size_>* _set, std::array<bool, size_>* _set,
std::vector<Error>* _errors) noexcept { std::vector<Error>* _errors) noexcept {
if constexpr (_i < sizeof...(FieldTypes)) { using FieldType = std::tuple_element_t<_i, typename NamedTupleType::Fields>;
using FieldType = using ValueType = std::remove_reference_t<
std::tuple_element_t<_i, typename NamedTupleType::Fields>; std::remove_pointer_t<typename FieldType::Type>>;
using ValueType = std::remove_reference_t<
std::remove_pointer_t<typename FieldType::Type>>;
if (!std::get<_i>(_found)) { if (!std::get<_i>(_found)) {
if constexpr (_all_required || if constexpr (_all_required ||
is_required<ValueType, _ignore_empty_containers>()) { is_required<ValueType, _ignore_empty_containers>()) {
constexpr auto current_name = constexpr auto current_name =
std::tuple_element_t<_i, typename NamedTupleType::Fields>::name(); std::tuple_element_t<_i, typename NamedTupleType::Fields>::name();
_errors->push_back("Field named '" + std::string(current_name) + _errors->emplace_back(Error(
"' not found."); "Field named '" + std::string(current_name) + "' not found."));
} else {
if constexpr (!std::is_const_v<ValueType>) {
::new (rfl::get<_i>(_view)) ValueType();
} else { } else {
if constexpr (!std::is_const_v<ValueType>) { using NonConstT = std::remove_const_t<ValueType>;
::new (rfl::get<_i>(_view)) ValueType(); ::new (const_cast<NonConstT*>(rfl::get<_i>(_view))) NonConstT();
} else {
using NonConstT = std::remove_const_t<ValueType>;
::new (const_cast<NonConstT*>(rfl::get<_i>(_view))) NonConstT();
}
std::get<_i>(*_set) = true;
} }
std::get<_i>(*_set) = true;
} }
handle_missing_fields<_i + 1>(_found, _view, _set, _errors);
} }
} }
/// Generates error messages for when fields are missing.
template <int... _is>
static void handle_missing_fields(
const std::array<bool, size_>& _found, const NamedTupleType& _view,
std::array<bool, size_>* _set, std::vector<Error>* _errors,
std::integer_sequence<int, _is...>) noexcept {
(handle_one_missing_field<_is>(_found, _view, _set, _errors), ...);
}
static std::optional<Error> read_object(const R& _r, static std::optional<Error> read_object(const R& _r,
const InputObjectType& _obj, const InputObjectType& _obj,
NamedTupleType* _view) noexcept { NamedTupleType* _view) noexcept {
@ -193,7 +199,8 @@ struct NamedTupleParser {
if (err) { if (err) {
return *err; return *err;
} }
handle_missing_fields(found, *_view, &set, &errors); handle_missing_fields(found, *_view, &set, &errors,
std::make_integer_sequence<int, size_>());
if (errors.size() != 0) { if (errors.size() != 0) {
object_reader.call_destructors_where_necessary(); object_reader.call_destructors_where_necessary();
return to_single_error_message(errors); return to_single_error_message(errors);

View file

@ -17,11 +17,11 @@
#include "../internal/is_validator.hpp" #include "../internal/is_validator.hpp"
#include "../internal/processed_t.hpp" #include "../internal/processed_t.hpp"
#include "../internal/to_ptr_named_tuple.hpp" #include "../internal/to_ptr_named_tuple.hpp"
#include "../to_view.hpp"
#include "../type_name_t.hpp" #include "../type_name_t.hpp"
#include "AreReaderAndWriter.hpp" #include "AreReaderAndWriter.hpp"
#include "Parent.hpp" #include "Parent.hpp"
#include "Parser_base.hpp" #include "Parser_base.hpp"
#include "StructReader.hpp"
#include "is_tagged_union_wrapper.hpp" #include "is_tagged_union_wrapper.hpp"
#include "schema/Type.hpp" #include "schema/Type.hpp"
@ -55,19 +55,13 @@ struct Parser {
return Parser<R, W, ReflectionType, ProcessorsType>::read(_r, _var) return Parser<R, W, ReflectionType, ProcessorsType>::read(_r, _var)
.and_then(wrap_in_t); .and_then(wrap_in_t);
} else if constexpr (std::is_class_v<T> && std::is_aggregate_v<T>) { } else if constexpr (std::is_class_v<T> && std::is_aggregate_v<T>) {
return StructReader<R, W, T, ProcessorsType>::read(_r, _var); return read_struct(_r, _var);
} else if constexpr (std::is_enum_v<T>) { } else if constexpr (std::is_enum_v<T>) {
using StringConverter = internal::enums::StringConverter<T>; using StringConverter = internal::enums::StringConverter<T>;
return _r.template to_basic_type<std::string>(_var).and_then( return _r.template to_basic_type<std::string>(_var).and_then(
StringConverter::string_to_enum); StringConverter::string_to_enum);
} else if constexpr (internal::is_basic_type_v<T>) {
return _r.template to_basic_type<std::remove_cvref_t<T>>(_var);
} else { } else {
static_assert( return _r.template to_basic_type<std::remove_cvref_t<T>>(_var);
always_false_v<T>,
"Unsupported type. Please refer to the sections on custom "
"classes and custom parsers for information on how add "
"support for your own classes.");
} }
} }
} }
@ -93,13 +87,8 @@ struct Parser {
using StringConverter = internal::enums::StringConverter<T>; using StringConverter = internal::enums::StringConverter<T>;
const auto str = StringConverter::enum_to_string(_var); const auto str = StringConverter::enum_to_string(_var);
ParentType::add_value(_w, str, _parent); ParentType::add_value(_w, str, _parent);
} else if constexpr (internal::is_basic_type_v<T>) {
ParentType::add_value(_w, _var, _parent);
} else { } else {
static_assert(always_false_v<T>, ParentType::add_value(_w, _var, _parent);
"Unsupported type. Please refer to the sections on custom "
"classes and custom parsers for information on how add "
"support for your own classes.");
} }
} }
@ -222,6 +211,23 @@ struct Parser {
} }
} }
/// The way this works is that we allocate space on the stack in this size of
/// the struct in which we then write the individual fields using
/// views and placement new. This is how we deal with the fact that some
/// fields might not be default-constructible.
static Result<T> read_struct(const R& _r, const InputVarType& _var) {
alignas(T) unsigned char buf[sizeof(T)];
auto ptr = reinterpret_cast<T*>(buf);
auto view = ProcessorsType::template process<T>(to_view(*ptr));
using ViewType = std::remove_cvref_t<decltype(view)>;
const auto err =
Parser<R, W, ViewType, ProcessorsType>::read_view(_r, _var, &view);
if (err) [[unlikely]] {
return *err;
}
return std::move(*ptr);
}
static std::string replace_non_alphanumeric(std::string _str) { static std::string replace_non_alphanumeric(std::string _str) {
for (auto& ch : _str) { for (auto& ch : _str) {
ch = std::isalnum(ch) ? ch : '_'; ch = std::isalnum(ch) ? ch : '_';

View file

@ -1,41 +0,0 @@
#ifndef RFL_PARSING_STRUCTREADER_HPP_
#define RFL_PARSING_STRUCTREADER_HPP_
#include <array>
#include <string_view>
#include <type_traits>
#include <vector>
#include "../Result.hpp"
#include "../to_view.hpp"
#include "AreReaderAndWriter.hpp"
namespace rfl::parsing {
template <class R, class W, class StructType, class ProcessorsType>
requires AreReaderAndWriter<R, W, StructType>
struct StructReader {
public:
using InputVarType = typename R::InputVarType;
/// The way this works is that we allocate space on the stack in this size of
/// the struct in which we then write the individual fields using
/// views and placement new. This is how we deal with the fact that some
/// fields might not be default-constructible.
static Result<StructType> read(const R& _r, const InputVarType& _var) {
alignas(StructType) unsigned char buf[sizeof(StructType)];
auto ptr = reinterpret_cast<StructType*>(buf);
auto view = ProcessorsType::template process<StructType>(to_view(*ptr));
using ViewType = std::remove_cvref_t<decltype(view)>;
const auto err =
Parser<R, W, ViewType, ProcessorsType>::read_view(_r, _var, &view);
if (err) {
return *err;
}
return std::move(*ptr);
}
};
} // namespace rfl::parsing
#endif

View file

@ -4,6 +4,7 @@
#include <array> #include <array>
#include <string_view> #include <string_view>
#include <type_traits> #include <type_traits>
#include <utility>
#include <vector> #include <vector>
#include "../Result.hpp" #include "../Result.hpp"
@ -24,63 +25,68 @@ class ViewReader {
~ViewReader() = default; ~ViewReader() = default;
template <size_t _i = 0> /// Assigns the parsed version of _var to the field signified by _name, if
/// such a field exists in the underlying view.
void read(const std::string_view& _name, const InputVarType& _var) const { void read(const std::string_view& _name, const InputVarType& _var) const {
if constexpr (_i < size_) { assign_to_matching_field(*r_, _name, _var, view_, errors_, found_, set_,
using FieldType = std::tuple_element_t<_i, typename ViewType::Fields>; std::make_integer_sequence<int, size_>());
using OriginalType = std::remove_cvref_t<typename FieldType::Type>;
using CurrentType =
std::remove_cvref_t<std::remove_pointer_t<OriginalType>>;
constexpr auto current_name = FieldType::name();
if (!std::get<_i>(*found_) && _name == current_name) {
auto res = Parser<R, W, CurrentType, ProcessorsType>::read(*r_, _var);
if (res) {
if constexpr (std::is_pointer_v<OriginalType>) {
move_to(rfl::get<_i>(*view_), &(*res));
} else {
rfl::get<_i>(*view_) = *res;
}
std::get<_i>(*set_) = true;
} else {
errors_->push_back(Error("Failed to parse field '" +
std::string(current_name) +
"': " + res.error()->what()));
}
std::get<_i>(*found_) = true;
return;
}
read<_i + 1>(_name, _var);
}
} }
/// Because of the way we have allocated the fields, we need to manually /// Because of the way we have allocated the fields, we need to manually
/// trigger the destructors. /// trigger the destructors.
template <size_t _i = 0>
void call_destructors_where_necessary() const { void call_destructors_where_necessary() const {
if constexpr (_i < size_) { [&]<int... is>(std::integer_sequence<int, is...>) {
using FieldType = std::tuple_element_t<_i, typename ViewType::Fields>; (call_destructor_on_one_if_necessary<is>(), ...);
using OriginalType = std::remove_cvref_t<typename FieldType::Type>;
using ValueType =
std::remove_cvref_t<std::remove_pointer_t<typename FieldType::Type>>;
if constexpr (!std::is_array_v<ValueType> &&
std::is_pointer_v<OriginalType> &&
std::is_destructible_v<ValueType>) {
if (std::get<_i>(*set_)) {
rfl::get<_i>(*view_)->~ValueType();
}
} else if constexpr (std::is_array_v<ValueType>) {
if (std::get<_i>(*set_)) {
auto ptr = rfl::get<_i>(*view_);
call_destructor_on_array(sizeof(*ptr) / sizeof(**ptr), *ptr);
}
}
call_destructors_where_necessary<_i + 1>();
} }
(std::make_integer_sequence<int, size_>());
} }
private: private:
template <int i>
static void assign_if_field_matches(const R& _r,
const std::string_view& _current_name,
const auto& _var, auto* _view,
auto* _errors, auto* _found, auto* _set,
bool* _already_assigned) {
using FieldType = std::tuple_element_t<i, typename ViewType::Fields>;
using OriginalType = typename FieldType::Type;
using T =
std::remove_cvref_t<std::remove_pointer_t<typename FieldType::Type>>;
constexpr auto name = FieldType::name();
if (!(*_already_assigned) && !std::get<i>(*_found) &&
_current_name == name) {
std::get<i>(*_found) = true;
*_already_assigned = true;
auto res = Parser<R, W, T, ProcessorsType>::read(_r, _var);
if (!res) {
_errors->emplace_back(Error("Failed to parse field '" +
std::string(name) +
"': " + std::move(res.error()->what())));
return;
}
if constexpr (std::is_pointer_v<OriginalType>) {
move_to(rfl::get<i>(*_view), &(*res));
} else {
rfl::get<i>(*_view) = std::move(*res);
}
std::get<i>(*_set) = true;
}
}
template <int... is>
static void assign_to_matching_field(const R& _r,
const std::string_view& _current_name,
const auto& _var, auto* _view,
auto* _errors, auto* _found, auto* _set,
std::integer_sequence<int, is...>) {
bool already_assigned = false;
(assign_if_field_matches<is>(_r, _current_name, _var, _view, _errors,
_found, _set, &already_assigned),
...);
}
template <class T> template <class T>
void call_destructor_on_array(const size_t _size, T* _ptr) const { static void call_destructor_on_array(const size_t _size, T* _ptr) {
for (size_t i = 0; i < _size; ++i) { for (size_t i = 0; i < _size; ++i) {
if constexpr (std::is_array_v<T>) { if constexpr (std::is_array_v<T>) {
call_destructor_on_array(sizeof(*_ptr) / sizeof(**_ptr), *(_ptr + i)); call_destructor_on_array(sizeof(*_ptr) / sizeof(**_ptr), *(_ptr + i));
@ -90,14 +96,34 @@ class ViewReader {
} }
} }
template <int _i>
void call_destructor_on_one_if_necessary() const {
using FieldType = std::tuple_element_t<_i, typename ViewType::Fields>;
using OriginalType = std::remove_cvref_t<typename FieldType::Type>;
using ValueType =
std::remove_cvref_t<std::remove_pointer_t<typename FieldType::Type>>;
if constexpr (!std::is_array_v<ValueType> &&
std::is_pointer_v<OriginalType> &&
std::is_destructible_v<ValueType>) {
if (std::get<_i>(*set_)) {
rfl::get<_i>(*view_)->~ValueType();
}
} else if constexpr (std::is_array_v<ValueType>) {
if (std::get<_i>(*set_)) {
auto ptr = rfl::get<_i>(*view_);
call_destructor_on_array(sizeof(*ptr) / sizeof(**ptr), *ptr);
}
}
}
template <class Target, class Source> template <class Target, class Source>
void move_to(Target* _t, Source* _s) const { static void move_to(Target* _t, Source* _s) {
if constexpr (std::is_const_v<Target>) { if constexpr (std::is_const_v<Target>) {
return move_to(const_cast<std::remove_const_t<Target>*>(_t), _s); return move_to(const_cast<std::remove_const_t<Target>*>(_t), _s);
} else if constexpr (!internal::is_array_v<Source> && } else if constexpr (!rfl::internal::is_array_v<Source> &&
!std::is_array_v<Target>) { !std::is_array_v<Target>) {
::new (_t) Target(std::move(*_s)); ::new (_t) Target(std::move(*_s));
} else if constexpr (internal::is_array_v<Source>) { } else if constexpr (rfl::internal::is_array_v<Source>) {
static_assert(std::is_array_v<Target>, static_assert(std::is_array_v<Target>,
"Expected target to be a c-array."); "Expected target to be a c-array.");
for (size_t i = 0; i < _s->arr_.size(); ++i) { for (size_t i = 0; i < _s->arr_.size(); ++i) {

13
include/rfl/xml.hpp Normal file
View file

@ -0,0 +1,13 @@
#ifndef RFL_XML_HPP_
#define RFL_XML_HPP_
#include "../rfl.hpp"
#include "xml/Parser.hpp"
#include "xml/Reader.hpp"
#include "xml/Writer.hpp"
#include "xml/load.hpp"
#include "xml/read.hpp"
#include "xml/save.hpp"
#include "xml/write.hpp"
#endif

View file

@ -0,0 +1,40 @@
#ifndef RFL_XML_PARSER_HPP_
#define RFL_XML_PARSER_HPP_
#include <type_traits>
#include "../internal/is_attribute.hpp"
#include "../parsing/NamedTupleParser.hpp"
#include "../parsing/Parser.hpp"
#include "Reader.hpp"
#include "Writer.hpp"
namespace rfl {
namespace parsing {
/// XML is very special. It doesn't have proper support for arrays, which means
/// that we just need to ignore empty containers. Therefore, we need to a
/// template specialization for the NamedTuple parser to accommodate for it.
template <class ProcessorsType, class... FieldTypes>
requires AreReaderAndWriter<xml::Reader, xml::Writer, NamedTuple<FieldTypes...>>
struct Parser<xml::Reader, xml::Writer, NamedTuple<FieldTypes...>,
ProcessorsType>
: public NamedTupleParser<xml::Reader, xml::Writer,
/*_ignore_empty_containers=*/true,
/*_all_required=*/ProcessorsType::all_required_,
ProcessorsType, FieldTypes...> {
};
} // namespace parsing
} // namespace rfl
namespace rfl {
namespace xml {
template <class T, class ProcessorsType>
using Parser = parsing::Parser<Reader, Writer, T, ProcessorsType>;
}
} // namespace rfl
#endif

174
include/rfl/xml/Reader.hpp Normal file
View file

@ -0,0 +1,174 @@
#ifndef RFL_XML_READER_HPP_
#define RFL_XML_READER_HPP_
#include <array>
#include <exception>
#include <map>
#include <memory>
#include <optional>
#include <pugixml.hpp>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <variant>
#include <vector>
#include "../Result.hpp"
#include "../always_false.hpp"
#include "../parsing/is_view_reader.hpp"
namespace rfl {
namespace xml {
struct Reader {
struct XMLInputArray {
XMLInputArray(pugi::xml_node _node) : node_(_node) {}
pugi::xml_node node_;
};
struct XMLInputObject {
XMLInputObject(pugi::xml_node _node) : node_(_node) {}
pugi::xml_node node_;
};
struct XMLInputVar {
XMLInputVar() : node_or_attribute_(pugi::xml_node()) {}
XMLInputVar(pugi::xml_attribute _attr) : node_or_attribute_(_attr) {}
XMLInputVar(pugi::xml_node _node) : node_or_attribute_(_node) {}
std::variant<pugi::xml_node, pugi::xml_attribute> node_or_attribute_;
};
using InputArrayType = XMLInputArray;
using InputObjectType = XMLInputObject;
using InputVarType = XMLInputVar;
// TODO
template <class T>
static constexpr bool has_custom_constructor = false;
/// XML-only helper function. This is needed because XML distinguishes between
/// nodes and attributes.
static rfl::Result<pugi::xml_node> cast_as_node(
const std::variant<pugi::xml_node, pugi::xml_attribute>&
_node_or_attribute) {
const auto cast = [](const auto& _n) -> Result<pugi::xml_node> {
using Type = std::remove_cvref_t<decltype(_n)>;
if constexpr (std::is_same<Type, pugi::xml_node>()) {
return _n;
} else {
return Error("Field '" + std::string(_n.name()) + "' is an attribute.");
}
};
return std::visit(cast, _node_or_attribute);
}
rfl::Result<InputVarType> get_field(
const std::string& _name, const InputObjectType _obj) const noexcept {
const auto node = _obj.node_.child(_name.c_str());
if (!node) {
return rfl::Error("Object contains no field named '" + _name + "'.");
}
return InputVarType(node);
}
bool is_empty(const InputVarType _var) const noexcept {
const auto wrap = [](const auto& _node) { return !_node; };
return std::visit(cast_as_node, _var.node_or_attribute_)
.transform(wrap)
.value_or(false);
}
template <class T>
rfl::Result<T> to_basic_type(const InputVarType _var) const noexcept {
const auto get_value = [](const auto& _n) -> std::string {
using Type = std::remove_cvref_t<decltype(_n)>;
if constexpr (std::is_same<Type, pugi::xml_node>()) {
return std::string(_n.child_value());
} else {
return std::string(_n.value());
}
};
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
return std::visit(get_value, _var.node_or_attribute_);
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
return std::visit(get_value, _var.node_or_attribute_) == "true";
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
const auto str = std::visit(get_value, _var.node_or_attribute_);
try {
return static_cast<T>(std::stod(str));
} catch (std::exception& e) {
return Error("Could not cast '" + std::string(str) +
"' to floating point value.");
}
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
const auto str = std::visit(get_value, _var.node_or_attribute_);
try {
return static_cast<T>(std::stoi(str));
} catch (std::exception& e) {
return Error("Could not cast '" + std::string(str) + "' to integer.");
}
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
}
rfl::Result<InputArrayType> to_array(const InputVarType _var) const noexcept {
const auto wrap = [](const auto& _node) { return InputArrayType(_node); };
return std::visit(cast_as_node, _var.node_or_attribute_).transform(wrap);
}
template <class ArrayReader>
std::optional<Error> read_array(const ArrayReader& _array_reader,
const InputArrayType& _arr) const noexcept {
const auto name = _arr.node_.name();
for (auto node = _arr.node_; node; node = node.next_sibling(name)) {
const auto err = _array_reader.read(InputVarType(node));
if (err) {
return err;
}
}
return std::nullopt;
}
template <class ObjectReader>
std::optional<Error> read_object(const ObjectReader& _object_reader,
const InputObjectType& _obj) const noexcept {
for (auto child = _obj.node_.first_child(); child;
child = child.next_sibling()) {
_object_reader.read(std::string_view(child.name()), InputVarType(child));
}
for (auto attr = _obj.node_.first_attribute(); attr;
attr = attr.next_attribute()) {
_object_reader.read(std::string_view(attr.name()), InputVarType(attr));
}
if constexpr (parsing::is_view_reader_v<ObjectReader>) {
_object_reader.read(std::string_view("xml_content"),
InputVarType(_obj.node_));
}
return std::nullopt;
}
rfl::Result<InputObjectType> to_object(
const InputVarType _var) const noexcept {
const auto wrap = [](const auto& _node) { return InputObjectType(_node); };
return std::visit(cast_as_node, _var.node_or_attribute_).transform(wrap);
}
template <class T>
rfl::Result<T> use_custom_constructor(
const InputVarType _var) const noexcept {
return rfl::Error("TODO");
}
};
} // namespace xml
} // namespace rfl
#endif

181
include/rfl/xml/Writer.hpp Normal file
View file

@ -0,0 +1,181 @@
#ifndef RFL_XML_WRITER_HPP_
#define RFL_XML_WRITER_HPP_
#include <exception>
#include <map>
#include <pugixml.hpp>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl {
namespace xml {
struct Writer {
static constexpr const char* XML_CONTENT = "xml_content";
struct XMLOutputArray {
XMLOutputArray(const std::string_view& _name,
const Ref<pugi::xml_node>& _node)
: name_(_name), node_(_node) {}
std::string_view name_;
Ref<pugi::xml_node> node_;
};
struct XMLOutputObject {
XMLOutputObject(const Ref<pugi::xml_node>& _node) : node_(_node) {}
Ref<pugi::xml_node> node_;
};
struct XMLOutputVar {
XMLOutputVar(const Ref<pugi::xml_node>& _node) : node_(_node) {}
Ref<pugi::xml_node> node_;
};
using OutputArrayType = XMLOutputArray;
using OutputObjectType = XMLOutputObject;
using OutputVarType = XMLOutputVar;
Writer(const Ref<pugi::xml_node>& _root, const std::string& _root_name)
: root_(_root), root_name_(_root_name) {}
~Writer() = default;
OutputArrayType array_as_root(const size_t _size) const noexcept {
auto node_child =
Ref<pugi::xml_node>::make(root_->append_child(root_name_.c_str()));
return OutputArrayType(root_name_, node_child);
}
OutputObjectType object_as_root(const size_t _size) const noexcept {
auto node_child =
Ref<pugi::xml_node>::make(root_->append_child(root_name_.c_str()));
return OutputObjectType(node_child);
}
OutputVarType null_as_root() const noexcept {
auto node_child =
Ref<pugi::xml_node>::make(root_->append_child(root_name_.c_str()));
return OutputVarType(node_child);
}
template <class T>
OutputVarType value_as_root(const T& _var) const noexcept {
const auto str = to_string(_var);
auto node_child =
Ref<pugi::xml_node>::make(root_->append_child(root_name_.c_str()));
node_child->append_child(pugi::node_pcdata).set_value(str.c_str());
return OutputVarType(node_child);
}
OutputArrayType add_array_to_array(const size_t _size,
OutputArrayType* _parent) const noexcept {
return *_parent;
}
OutputArrayType add_array_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
return OutputArrayType(_name, _parent->node_);
}
OutputObjectType add_object_to_array(
const size_t _size, OutputArrayType* _parent) const noexcept {
auto node_child = Ref<pugi::xml_node>::make(
_parent->node_->append_child(_parent->name_.data()));
return OutputObjectType(node_child);
}
OutputObjectType add_object_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
auto node_child =
Ref<pugi::xml_node>::make(_parent->node_->append_child(_name.data()));
return OutputObjectType(node_child);
}
template <class T>
OutputVarType add_value_to_array(const T& _var,
OutputArrayType* _parent) const noexcept {
const auto str = to_string(_var);
auto node_child = Ref<pugi::xml_node>::make(
_parent->node_->append_child(_parent->name_.data()));
node_child->append_child(pugi::node_pcdata).set_value(str.c_str());
return OutputVarType(node_child);
}
template <class T>
OutputVarType add_value_to_object(
const std::string_view& _name, const T& _var, OutputObjectType* _parent,
const bool _is_attribute = false) const noexcept {
const auto str = to_string(_var);
if (_is_attribute) {
_parent->node_->append_attribute(_name.data()) = str.c_str();
return OutputVarType(_parent->node_);
} else if (_name == XML_CONTENT) {
_parent->node_->append_child(pugi::node_pcdata).set_value(str.c_str());
return OutputVarType(_parent->node_);
} else {
auto node_child =
Ref<pugi::xml_node>::make(_parent->node_->append_child(_name.data()));
node_child->append_child(pugi::node_pcdata).set_value(str.c_str());
return OutputVarType(node_child);
}
}
OutputVarType add_null_to_array(OutputArrayType* _parent) const noexcept {
auto node_child = Ref<pugi::xml_node>::make(
_parent->node_->append_child(_parent->name_.data()));
return OutputVarType(node_child);
}
OutputVarType add_null_to_object(
const std::string_view& _name, OutputObjectType* _parent,
const bool _is_attribute = false) const noexcept {
if (_is_attribute) {
return OutputVarType(_parent->node_);
} else if (_name == XML_CONTENT) {
return OutputVarType(_parent->node_);
} else {
auto node_child =
Ref<pugi::xml_node>::make(_parent->node_->append_child(_name.data()));
return OutputVarType(node_child);
}
}
void end_array(OutputArrayType* _arr) const noexcept {}
void end_object(OutputObjectType* _obj) const noexcept {}
private:
template <class T>
std::string to_string(const T& _val) const noexcept {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
return _val;
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
return _val ? "true" : "false";
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>() ||
std::is_integral<std::remove_cvref_t<T>>()) {
return std::to_string(_val);
} else {
static_assert(always_false_v<T>, "Unsupported type");
}
}
public:
Ref<pugi::xml_node> root_;
std::string root_name_;
};
} // namespace xml
} // namespace rfl
#endif // XML_PARSER_HPP_

22
include/rfl/xml/load.hpp Normal file
View file

@ -0,0 +1,22 @@
#ifndef RFL_XML_LOAD_HPP_
#define RFL_XML_LOAD_HPP_
#include "../Result.hpp"
#include "../io/load_string.hpp"
#include "read.hpp"
namespace rfl {
namespace xml {
template <class T, class... Ps>
Result<T> load(const std::string& _fname) {
const auto read_string = [](const auto& _str) {
return read<T, Ps...>(_str);
};
return rfl::io::load_string(_fname).and_then(read_string);
}
} // namespace xml
} // namespace rfl
#endif

50
include/rfl/xml/read.hpp Normal file
View file

@ -0,0 +1,50 @@
#ifndef RFL_XML_READ_HPP_
#define RFL_XML_READ_HPP_
#include <istream>
#include <pugixml.hpp>
#include <string>
#include "../Processors.hpp"
#include "../internal/get_type_name.hpp"
#include "../internal/remove_namespaces.hpp"
#include "Parser.hpp"
#include "Reader.hpp"
namespace rfl {
namespace xml {
using InputVarType = typename Reader::InputVarType;
/// Parses an object from a XML var.
template <class T, class... Ps>
auto read(const InputVarType& _var) {
const auto r = Reader();
return Parser<T, Processors<Ps...>>::read(r, _var);
}
/// Parses an object from XML using reflection.
template <class T, class... Ps>
Result<T> read(const std::string& _xml_str) {
pugi::xml_document doc;
const auto result = doc.load_string(_xml_str.c_str());
if (!result) {
return Error("XML string could not be parsed: " +
std::string(result.description()));
}
const auto var = InputVarType(doc.first_child());
return read<T, Ps...>(var);
}
/// Parses an object from a stringstream.
template <class T, class... Ps>
auto read(std::istream& _stream) {
const auto xml_str = std::string(std::istreambuf_iterator<char>(_stream),
std::istreambuf_iterator<char>());
return read<T, Ps...>(xml_str);
}
} // namespace xml
} // namespace rfl
#endif

28
include/rfl/xml/save.hpp Normal file
View file

@ -0,0 +1,28 @@
#ifndef RFL_XML_SAVE_HPP_
#define RFL_XML_SAVE_HPP_
#include <fstream>
#include <iostream>
#include <string>
#include "../Result.hpp"
#include "../internal/StringLiteral.hpp"
#include "../io/save_string.hpp"
#include "write.hpp"
namespace rfl {
namespace xml {
template <internal::StringLiteral _root = internal::StringLiteral(""),
class... Ps>
Result<Nothing> save(const std::string& _fname, const auto& _obj) {
const auto write_func = [](const auto& _obj, auto& _stream) -> auto& {
return write<_root, Ps...>(_obj, _stream);
};
return rfl::io::save_string(_fname, _obj, write_func);
}
} // namespace xml
} // namespace rfl
#endif

74
include/rfl/xml/write.hpp Normal file
View file

@ -0,0 +1,74 @@
#ifndef RFL_XML_WRITE_HPP_
#define RFL_XML_WRITE_HPP_
#include <ostream>
#include <pugixml.hpp>
#include <sstream>
#include <string>
#include <type_traits>
#include "../Processors.hpp"
#include "../internal/StringLiteral.hpp"
#include "../internal/get_type_name.hpp"
#include "../internal/remove_namespaces.hpp"
#include "../parsing/Parent.hpp"
#include "Parser.hpp"
namespace rfl {
namespace xml {
template <internal::StringLiteral _root, class T>
consteval auto get_root_name() {
if constexpr (_root != internal::StringLiteral("")) {
return _root;
} else {
return internal::remove_namespaces<
internal::get_type_name<std::remove_cvref_t<T>>()>();
}
}
/// Writes a XML into an ostream.
template <internal::StringLiteral _root = internal::StringLiteral(""),
class... Ps>
std::ostream& write(const auto& _obj, std::ostream& _stream,
const std::string& _indent = " ") {
using T = std::remove_cvref_t<decltype(_obj)>;
using ParentType = parsing::Parent<Writer>;
constexpr auto root_name = get_root_name<_root, T>();
static_assert(root_name.string_view().find("<") == std::string_view::npos &&
root_name.string_view().find(">") == std::string_view::npos,
"The name of an XML root node cannot contain '<' or '>'. "
"Please assign an "
"explicit root name to rfl::xml::write(...) like this: "
"rfl::xml::write<\"root_name\">(...).");
const auto doc = rfl::Ref<pugi::xml_document>::make();
auto declaration_node = doc->append_child(pugi::node_declaration);
declaration_node.append_attribute("version") = "1.0";
declaration_node.append_attribute("encoding") = "UTF-8";
auto w = Writer(doc, root_name.str());
Parser<T, Processors<Ps...>>::write(w, _obj, typename ParentType::Root{});
doc->save(_stream, _indent.c_str());
return _stream;
}
/// Returns a XML string.
template <internal::StringLiteral _root = internal::StringLiteral(""),
class... Ps>
std::string write(const auto& _obj, const std::string& _indent = " ") {
std::stringstream stream;
write<_root, Ps...>(_obj, stream);
return stream.str();
}
} // namespace xml
} // namespace rfl
#endif // XML_PARSER_HPP_

13
include/rfl/yaml.hpp Normal file
View file

@ -0,0 +1,13 @@
#ifndef RFL_YAML_HPP_
#define RFL_YAML_HPP_
#include "../rfl.hpp"
#include "yaml/Parser.hpp"
#include "yaml/Reader.hpp"
#include "yaml/Writer.hpp"
#include "yaml/load.hpp"
#include "yaml/read.hpp"
#include "yaml/save.hpp"
#include "yaml/write.hpp"
#endif

View file

@ -0,0 +1,17 @@
#ifndef RFL_YAML_PARSER_HPP_
#define RFL_YAML_PARSER_HPP_
#include "../parsing/Parser.hpp"
#include "Reader.hpp"
#include "Writer.hpp"
namespace rfl {
namespace yaml {
template <class T, class ProcessorsType>
using Parser = parsing::Parser<Reader, Writer, T, ProcessorsType>;
}
} // namespace rfl
#endif

137
include/rfl/yaml/Reader.hpp Normal file
View file

@ -0,0 +1,137 @@
#ifndef RFL_YAML_READER_HPP_
#define RFL_YAML_READER_HPP_
#include <yaml-cpp/yaml.h>
#include <array>
#include <exception>
#include <map>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <vector>
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl {
namespace yaml {
struct Reader {
struct YAMLInputArray {
YAMLInputArray(const YAML::Node& _node) : node_(_node) {}
YAML::Node node_;
};
struct YAMLInputObject {
YAMLInputObject(const YAML::Node& _node) : node_(_node) {}
YAML::Node node_;
};
struct YAMLInputVar {
YAMLInputVar(const YAML::Node& _node) : node_(_node) {}
YAML::Node node_;
};
using InputArrayType = YAMLInputArray;
using InputObjectType = YAMLInputObject;
using InputVarType = YAMLInputVar;
template <class T, class = void>
struct has_from_json_obj : std::false_type {};
template <class T>
static constexpr bool has_custom_constructor = (requires(InputVarType var) {
T::from_yaml_obj(var);
});
rfl::Result<InputVarType> get_field(
const std::string& _name, const InputObjectType& _obj) const noexcept {
auto var = InputVarType(_obj.node_[_name]);
if (!var.node_) {
return rfl::Error("Object contains no field named '" + _name + "'.");
}
return var;
}
bool is_empty(const InputVarType& _var) const noexcept {
return !_var.node_ && true;
}
template <class T>
rfl::Result<T> to_basic_type(const InputVarType& _var) const noexcept {
try {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>() ||
std::is_same<std::remove_cvref_t<T>, bool>() ||
std::is_floating_point<std::remove_cvref_t<T>>() ||
std::is_integral<std::remove_cvref_t<T>>()) {
return _var.node_.as<std::remove_cvref_t<T>>();
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
}
} catch (std::exception& e) {
return rfl::Error(e.what());
}
}
rfl::Result<InputArrayType> to_array(
const InputVarType& _var) const noexcept {
if (!_var.node_.IsSequence()) {
return rfl::Error("Could not cast to sequence!");
}
return InputArrayType(_var.node_);
}
template <class ArrayReader>
std::optional<Error> read_array(const ArrayReader& _array_reader,
const InputArrayType& _arr) const noexcept {
for (size_t i = 0; i < _arr.node_.size(); ++i) {
const auto err = _array_reader.read(_arr.node_[i]);
if (err) {
return err;
}
}
return std::nullopt;
}
template <class ObjectReader>
std::optional<Error> read_object(const ObjectReader& _object_reader,
const InputObjectType& _obj) const noexcept {
for (const auto& p : _obj.node_) {
try {
const auto k = p.first.as<std::string>();
_object_reader.read(std::string_view(k), InputVarType(p.second));
} catch (std::exception& e) {
continue;
}
}
return std::nullopt;
}
rfl::Result<InputObjectType> to_object(
const InputVarType& _var) const noexcept {
if (!_var.node_.IsMap()) {
return rfl::Error("Could not cast to map!");
}
return InputObjectType(_var.node_);
}
template <class T>
rfl::Result<T> use_custom_constructor(
const InputVarType _var) const noexcept {
try {
return T::from_yaml_obj(_var);
} catch (std::exception& e) {
return rfl::Error(e.what());
}
}
};
} // namespace yaml
} // namespace rfl
#endif

148
include/rfl/yaml/Writer.hpp Normal file
View file

@ -0,0 +1,148 @@
#ifndef RFL_YAML_WRITER_HPP_
#define RFL_YAML_WRITER_HPP_
#include <yaml-cpp/yaml.h>
#include <exception>
#include <map>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../always_false.hpp"
namespace rfl {
namespace yaml {
class Writer {
public:
struct YAMLArray {};
struct YAMLObject {};
struct YAMLVar {};
using OutputArrayType = YAMLArray;
using OutputObjectType = YAMLObject;
using OutputVarType = YAMLVar;
Writer(const Ref<YAML::Emitter>& _out) : out_(_out) {}
~Writer() = default;
OutputArrayType array_as_root(const size_t _size) const noexcept {
return new_array();
}
OutputObjectType object_as_root(const size_t _size) const noexcept {
return new_object();
}
OutputVarType null_as_root() const noexcept {
return insert_value(YAML::Null);
}
template <class T>
OutputVarType value_as_root(const T& _var) const noexcept {
return insert_value(_var);
}
OutputArrayType add_array_to_array(const size_t _size,
OutputArrayType* _parent) const noexcept {
return new_array();
}
OutputArrayType add_array_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
return new_array(_name);
}
OutputObjectType add_object_to_array(
const size_t _size, OutputArrayType* _parent) const noexcept {
return new_object();
}
OutputObjectType add_object_to_object(
const std::string_view& _name, const size_t _size,
OutputObjectType* _parent) const noexcept {
return new_object(_name);
}
template <class T>
OutputVarType add_value_to_array(const T& _var,
OutputArrayType* _parent) const noexcept {
return insert_value(_var);
}
template <class T>
OutputVarType add_value_to_object(const std::string_view& _name,
const T& _var,
OutputObjectType* _parent) const noexcept {
return insert_value(_name, _var);
}
OutputVarType add_null_to_array(OutputArrayType* _parent) const noexcept {
return insert_value(YAML::Null);
}
OutputVarType add_null_to_object(const std::string_view& _name,
OutputObjectType* _parent) const noexcept {
return insert_value(_name, YAML::Null);
}
void end_array(OutputArrayType* _arr) const noexcept {
(*out_) << YAML::EndSeq;
}
void end_object(OutputObjectType* _obj) const noexcept {
(*out_) << YAML::EndMap;
}
private:
template <class T>
OutputVarType insert_value(const std::string_view& _name,
const T& _var) const noexcept {
(*out_) << YAML::Key << _name.data() << YAML::Value << _var;
return OutputVarType{};
}
template <class T>
OutputVarType insert_value(const T& _var) const noexcept {
(*out_) << _var;
return OutputVarType{};
}
OutputArrayType new_array(const std::string_view& _name) const noexcept {
(*out_) << YAML::Key << _name.data() << YAML::Value << YAML::BeginSeq;
return OutputArrayType{};
}
OutputArrayType new_array() const noexcept {
(*out_) << YAML::BeginSeq;
return OutputArrayType{};
}
OutputObjectType new_object(const std::string_view& _name) const noexcept {
(*out_) << YAML::Key << _name.data() << YAML::Value << YAML::BeginMap;
return OutputObjectType{};
}
OutputObjectType new_object() const noexcept {
(*out_) << YAML::BeginMap;
return OutputObjectType{};
}
public:
const Ref<YAML::Emitter> out_;
};
} // namespace yaml
} // namespace rfl
#endif // JSON_PARSER_HPP_

22
include/rfl/yaml/load.hpp Normal file
View file

@ -0,0 +1,22 @@
#ifndef RFL_YAML_LOAD_HPP_
#define RFL_YAML_LOAD_HPP_
#include "../Result.hpp"
#include "../io/load_string.hpp"
#include "read.hpp"
namespace rfl {
namespace yaml {
template <class T, class... Ps>
Result<T> load(const std::string& _fname) {
const auto read_string = [](const auto& _str) {
return read<T, Ps...>(_str);
};
return rfl::io::load_string(_fname).and_then(read_string);
}
} // namespace yaml
} // namespace rfl
#endif

47
include/rfl/yaml/read.hpp Normal file
View file

@ -0,0 +1,47 @@
#ifndef RFL_YAML_READ_HPP_
#define RFL_YAML_READ_HPP_
#include <yaml-cpp/yaml.h>
#include <istream>
#include <string>
#include "../Processors.hpp"
#include "../internal/wrap_in_rfl_array_t.hpp"
#include "Parser.hpp"
#include "Reader.hpp"
namespace rfl {
namespace yaml {
using InputVarType = typename Reader::InputVarType;
/// Parses an object from a YAML var.
template <class T, class... Ps>
auto read(const InputVarType& _var) {
const auto r = Reader();
return Parser<T, Processors<Ps...>>::read(r, _var);
}
/// Parses an object from YAML using reflection.
template <class T, class... Ps>
Result<internal::wrap_in_rfl_array_t<T>> read(const std::string& _yaml_str) {
try {
const auto var = InputVarType(YAML::Load(_yaml_str));
return read<T, Ps...>(var);
} catch (std::exception& e) {
return Error(e.what());
}
}
/// Parses an object from a stringstream.
template <class T, class... Ps>
auto read(std::istream& _stream) {
const auto yaml_str = std::string(std::istreambuf_iterator<char>(_stream),
std::istreambuf_iterator<char>());
return read<T, Ps...>(yaml_str);
}
} // namespace yaml
} // namespace rfl
#endif

27
include/rfl/yaml/save.hpp Normal file
View file

@ -0,0 +1,27 @@
#ifndef RFL_YAML_SAVE_HPP_
#define RFL_YAML_SAVE_HPP_
#include <fstream>
#include <iostream>
#include <string>
#include "../Processors.hpp"
#include "../Result.hpp"
#include "../io/save_string.hpp"
#include "write.hpp"
namespace rfl {
namespace yaml {
template <class... Ps>
Result<Nothing> save(const std::string& _fname, const auto& _obj) {
const auto write_func = [](const auto& _obj, auto& _stream) -> auto& {
return write<Ps...>(_obj, _stream);
};
return rfl::io::save_string(_fname, _obj, write_func);
}
} // namespace yaml
} // namespace rfl
#endif

View file

@ -0,0 +1,44 @@
#ifndef RFL_YAML_WRITE_HPP_
#define RFL_YAML_WRITE_HPP_
#include <yaml-cpp/yaml.h>
#include <ostream>
#include <sstream>
#include <string>
#include <type_traits>
#include "../Processors.hpp"
#include "../parsing/Parent.hpp"
#include "Parser.hpp"
namespace rfl {
namespace yaml {
/// Writes a YAML into an ostream.
template <class... Ps>
std::ostream& write(const auto& _obj, std::ostream& _stream) {
using T = std::remove_cvref_t<decltype(_obj)>;
using ParentType = parsing::Parent<Writer>;
const auto out = Ref<YAML::Emitter>::make();
auto w = Writer(out);
Parser<T, Processors<Ps...>>::write(w, _obj, typename ParentType::Root{});
_stream << out->c_str();
return _stream;
}
/// Returns a YAML string.
template <class... Ps>
std::string write(const auto& _obj) {
using T = std::remove_cvref_t<decltype(_obj)>;
using ParentType = parsing::Parent<Writer>;
const auto out = Ref<YAML::Emitter>::make();
auto w = Writer(out);
Parser<T, Processors<Ps...>>::write(w, _obj, typename ParentType::Root{});
return out->c_str();
}
} // namespace yaml
} // namespace rfl
#endif

7690
include/yyjson.h Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,10 @@
project( project(
'draconis++', 'cpp', 'draconis++', ['cpp', 'objcpp'],
version: '0.1.0', version: '0.1.0',
default_options: [ default_options: [
'cpp_std=c++26', 'objc_std=c++20',
'objcpp_std=c++20',
'cpp_std=c++20',
'default_library=static', 'default_library=static',
'warning_level=everything', 'warning_level=everything',
'buildtype=debugoptimized' 'buildtype=debugoptimized'
@ -12,6 +14,21 @@ project(
clangtidy = find_program('clang-tidy', required: false) clangtidy = find_program('clang-tidy', required: false)
cpp = meson.get_compiler('cpp') cpp = meson.get_compiler('cpp')
objcpp = meson.get_compiler('objcpp')
add_project_arguments(
objcpp.get_supported_arguments([
'-Wno-c++20-compat',
'-Wno-c++98-compat',
'-Wno-c++98-compat-pedantic',
'-Wno-missing-prototypes',
'-Wno-padded',
'-Wno-pre-c++20-compat-pedantic',
'-Wno-switch-default',
'-Wunused-function',
]),
language: 'objcpp'
)
add_project_arguments( add_project_arguments(
cpp.get_supported_arguments([ cpp.get_supported_arguments([
@ -22,7 +39,7 @@ add_project_arguments(
'-Wno-padded', '-Wno-padded',
'-Wno-pre-c++20-compat-pedantic', '-Wno-pre-c++20-compat-pedantic',
'-Wno-switch-default', '-Wno-switch-default',
'-Wunused-function' '-Wunused-function',
]), ]),
language: 'cpp' language: 'cpp'
) )
@ -40,7 +57,11 @@ if host_machine.system() == 'linux'
endif endif
if host_machine.system() == 'darwin' if host_machine.system() == 'darwin'
source_file_names += ['src/os/macos.cpp'] source_file_names += [
'src/os/macos.cpp',
'src/os/macos/NowPlayingBridge.h',
'src/os/macos/NowPlayingBridge.mm'
]
endif endif
sources = [] sources = []
@ -49,16 +70,16 @@ foreach file : source_file_names
sources += files(file) sources += files(file)
endforeach endforeach
quill = subproject('quill')
deps = [] deps = []
deps += cpp.find_library('cpr') deps += cpp.find_library('cpr')
deps += cpp.find_library('curl') deps += cpp.find_library('curl')
deps += cpp.find_library('tomlplusplus') deps += cpp.find_library('tomlplusplus')
deps += dependency('boost', static: true) deps += dependency('coost')
deps += dependency('fmt') deps += dependency('fmt')
deps += quill.get_variable('quill_dep') deps += dependency('Foundation')
deps += dependency('MediaPlayer')
if host_machine.system() == 'linux' if host_machine.system() == 'linux'
deps += dependency('playerctl') deps += dependency('playerctl')
@ -73,5 +94,7 @@ executable(
'draconis++', 'draconis++',
sources, sources,
dependencies: deps, dependencies: deps,
include_directories: incdir include_directories: incdir,
objc_args: ['-fobjc-arc'],
link_args: ['-framework', 'Foundation', '-framework', 'MediaPlayer']
) )

View file

@ -1,9 +1,3 @@
#include <fmt/core.h>
#include <rfl.hpp>
#include <rfl/toml.hpp>
#include <toml++/toml.h>
#include <unistd.h>
#include "config.h" #include "config.h"
#define DEFINE_GETTER(class_name, type, name) \ #define DEFINE_GETTER(class_name, type, name) \
@ -21,9 +15,11 @@ DEFINE_GETTER(Weather, const string, ApiKey)
DEFINE_GETTER(Weather, const string, Units) DEFINE_GETTER(Weather, const string, Units)
const Config& Config::getInstance() { const Config& Config::getInstance() {
static const Config& INSTANCE = static const auto* INSTANCE =
*new Config(rfl::toml::load<Config>("./config.toml").value()); new Config(rfl::toml::load<Config>("./config.toml").value());
return INSTANCE; static std::once_flag Flag;
std::call_once(Flag, [] { std::atexit([] { delete INSTANCE; }); });
return *INSTANCE;
} }
Config::Config(General general, NowPlaying now_playing, Weather weather) Config::Config(General general, NowPlaying now_playing, Weather weather)

View file

@ -1,13 +1,9 @@
#pragma once #include <co/json.h>
#include <boost/json.hpp>
#include <cpr/cpr.h> #include <cpr/cpr.h>
#include <fmt/core.h> #include <fmt/core.h>
#include <rfl.hpp> #include <rfl.hpp>
#include <rfl/toml.hpp>
#include <string> #include <string>
#include <toml++/impl/parser.hpp>
#include <toml++/toml.h>
#include <unistd.h>
#include <variant> #include <variant>
using std::string; using std::string;
@ -29,7 +25,7 @@ class Weather {
public: public:
Weather(Location location, string api_key, string units); Weather(Location location, string api_key, string units);
[[nodiscard]] boost::json::object getWeatherInfo() const; [[nodiscard]] co::Json getWeatherInfo() const;
[[nodiscard]] const Location getLocation() const; [[nodiscard]] const Location getLocation() const;
[[nodiscard]] const string getApiKey() const; [[nodiscard]] const string getApiKey() const;
[[nodiscard]] const string getUnits() const; [[nodiscard]] const string getUnits() const;

View file

@ -1,83 +1,77 @@
#include <rfl.hpp>
#include <rfl/toml.hpp>
#include <toml++/toml.h>
#include <unistd.h>
#include "config.h" #include "config.h"
using namespace std;
using namespace chrono;
using namespace boost;
// Function to read cache from file // Function to read cache from file
optional<pair<json::object, system_clock::time_point>> ReadCacheFromFile() { std::optional<std::pair<co::Json, std::chrono::system_clock::time_point>>
ReadCacheFromFile() {
const string cacheFile = "/tmp/weather_cache.json"; const string cacheFile = "/tmp/weather_cache.json";
ifstream ifs(cacheFile); std::ifstream ifs(cacheFile);
if (!ifs.is_open()) { if (!ifs.is_open()) {
fmt::println("Cache file not found."); fmt::println("Cache file not found.");
return nullopt; return std::nullopt;
} }
fmt::println("Reading from cache file..."); fmt::println("Reading from cache file...");
json::object cachedData; co::Json val;
system_clock::time_point timestamp; std::chrono::system_clock::time_point timestamp;
try { try {
json::value val; std::stringstream buf;
ifs >> val; buf << ifs.rdbuf();
cachedData = val.as_object();
string tsStr = cachedData["timestamp"].as_string().c_str(); val.parse_from(buf.str());
timestamp = system_clock::time_point(milliseconds(stoll(tsStr)));
cachedData.erase("timestamp"); string tsStr = val["timestamp"].as_string().c_str();
timestamp = std::chrono::system_clock::time_point(
std::chrono::milliseconds(stoll(tsStr)));
val.erase("timestamp");
} catch (...) { } catch (...) {
fmt::println(stderr, "Failed to read from cache file."); fmt::println(stderr, "Failed to read from cache file.");
return nullopt; return std::nullopt;
} }
fmt::println("Successfully read from cache file."); fmt::println("Successfully read from cache file.");
return make_pair(cachedData, timestamp); return make_pair(val, timestamp);
} }
// Function to write cache to file // Function to write cache to file
void WriteCacheToFile(const json::object& data) { void WriteCacheToFile(const co::Json& data) {
const string cacheFile = "/tmp/weather_cache.json"; const string cacheFile = "/tmp/weather_cache.json";
fmt::println("Writing to cache file..."); fmt::println("Writing to cache file...");
ofstream ofs(cacheFile); std::ofstream ofs(cacheFile);
if (!ofs.is_open()) { if (!ofs.is_open()) {
fmt::println(stderr, "Failed to open cache file for writing."); fmt::println(stderr, "Failed to open cache file for writing.");
return; return;
} }
json::object dataToWrite = data; data["timestamp"] =
std::to_string(duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count());
dataToWrite["timestamp"] = to_string( ofs << data.as_string();
duration_cast<milliseconds>(system_clock::now().time_since_epoch())
.count());
ofs << json::serialize(dataToWrite);
fmt::println("Successfully wrote to cache file."); fmt::println("Successfully wrote to cache file.");
} }
// Function to make API request // Function to make API request
json::object MakeApiRequest(const string& url) { co::Json MakeApiRequest(const string& url) {
using namespace cpr; using namespace cpr;
fmt::println("Making API request..."); fmt::println("Making API request...");
const Response res = Get(Url {url}); const Response res = Get(Url {url});
fmt::println("Received response from API."); fmt::println("Received response from API.");
json::value json = json::parse(res.text); co::Json json = json::parse(res.text);
return json.as_object();
return json;
} }
// Core function to get weather information // Core function to get weather information
json::object Weather::getWeatherInfo() const { co::Json Weather::getWeatherInfo() const {
using namespace cpr; using namespace cpr;
const Location loc = m_Location; const Location loc = m_Location;
@ -86,10 +80,11 @@ json::object Weather::getWeatherInfo() const {
// Check if cache is valid // Check if cache is valid
if (auto cachedData = ReadCacheFromFile()) { if (auto cachedData = ReadCacheFromFile()) {
auto [data, timestamp] = *cachedData; auto& [data, timestamp] = *cachedData;
if (system_clock::now() - timestamp < if (std::chrono::system_clock::now() - timestamp <
minutes(10)) { // Assuming cache duration is always 10 minutes std::chrono::minutes(
10)) { // Assuming cache duration is always 10 minutes
fmt::println("Cache is valid. Returning cached data."); fmt::println("Cache is valid. Returning cached data.");
return data; return data;
} }
@ -99,7 +94,7 @@ json::object Weather::getWeatherInfo() const {
fmt::println("No valid cache found."); fmt::println("No valid cache found.");
} }
json::object result; co::Json result;
if (holds_alternative<string>(loc)) { if (holds_alternative<string>(loc)) {
const string city = get<string>(loc); const string city = get<string>(loc);

View file

@ -1,17 +1,10 @@
#include <boost/json/src.hpp> #include <co/log.h>
#include <cpr/cpr.h>
#include <ctime> #include <ctime>
#include <curl/curl.h>
#include <fmt/chrono.h> #include <fmt/chrono.h>
#include <fmt/core.h> #include <fmt/core.h>
#include <fmt/format.h>
#include <rfl.hpp>
#include <rfl/toml.hpp>
#include <rfl/toml/load.hpp>
#include <rfl/toml/read.hpp>
#include <toml++/toml.hpp>
#include "config/config.h" #include "config/config.h"
#include "os/macos/NowPlayingBridge.h"
#include "os/os.h" #include "os/os.h"
using std::string; using std::string;
@ -49,9 +42,10 @@ DateNum ParseDate(string const& input) {
return Default; return Default;
} }
int main() { int main(int argc, char** argv) {
using boost::json::object; flag::parse(argc, argv);
using std::time_t;
LOG << "hello " << 23;
const Config& config = Config::getInstance(); const Config& config = Config::getInstance();
@ -97,12 +91,13 @@ int main() {
fmt::println("{:%B} {}, {:%-I:%0M %p}", localTime, date, localTime); fmt::println("{:%B} {}, {:%-I:%0M %p}", localTime, date, localTime);
object json = config.getWeather().getWeatherInfo(); co::Json json = config.getWeather().getWeatherInfo();
const int temp = json.get("main", "temp").as_int();
const char* townName = const char* townName =
json["name"].is_string() ? json["name"].as_string().c_str() : "Unknown"; json["name"].is_string() ? json["name"].as_string().c_str() : "Unknown";
fmt::println("{}", townName); fmt::println("It is {}°F in {}", temp, townName);
return 0; return 0;
} }

View file

@ -1,8 +1,9 @@
#ifdef __linux__ #ifdef __linux__
#include <fmt/core.h> #include <fmt/core.h>
#include <playerctl/playerctl.h>
#include <fstream> #include <fstream>
#include <playerctl/playerctl.h>
#include "os.h" #include "os.h"
using std::string; using std::string;

View file

@ -1,12 +1,14 @@
#ifdef __APPLE__ #ifdef __APPLE__
#include <sys/sysctl.h>
#include <string> #include <string>
#include <sys/sysctl.h>
#include "macos/NowPlayingBridge.h"
#include "os.h" #include "os.h"
uint64_t GetMemInfo() { uint64_t GetMemInfo() {
uint64_t mem = 0; uint64_t mem = 0;
size_t size = sizeof(mem); size_t size = sizeof(mem);
sysctlbyname("hw.memsize", &mem, &size, nullptr, 0); sysctlbyname("hw.memsize", &mem, &size, nullptr, 0);
@ -14,7 +16,7 @@ uint64_t GetMemInfo() {
} }
std::string GetNowPlaying() { std::string GetNowPlaying() {
return ""; return getCurrentPlayingTitle();
} }
#endif #endif

View file

@ -0,0 +1,14 @@
// NowPlayingBridge.h
#ifdef __OBJC__
#import <Foundation/Foundation.h>
@interface NowPlayingBridge : NSObject
+ (NSDictionary*)currentPlayingMetadata;
@end
#else
extern "C" {
const char* getCurrentPlayingTitle();
const char* getCurrentPlayingArtist();
}
#endif

View file

@ -0,0 +1,90 @@
// NowPlayingBridge.mm
#import "NowPlayingBridge.h"
#import <Foundation/Foundation.h>
#import <dispatch/dispatch.h>
#import <objc/runtime.h>
typedef void (*MRMediaRemoteGetNowPlayingInfoFunction)(
dispatch_queue_t queue, void (^handler)(NSDictionary *information));
@implementation NowPlayingBridge
+ (NSDictionary *)currentPlayingMetadata {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wold-style-cast"
CFURLRef ref = (__bridge CFURLRef)
[NSURL fileURLWithPath:
@"/System/Library/PrivateFrameworks/MediaRemote.framework"];
#pragma clang diagnostic pop
CFBundleRef bundle = CFBundleCreate(kCFAllocatorDefault, ref);
if (!bundle) {
NSLog(@"Failed to load MediaRemote framework");
return nil;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wold-style-cast"
MRMediaRemoteGetNowPlayingInfoFunction MRMediaRemoteGetNowPlayingInfo =
(MRMediaRemoteGetNowPlayingInfoFunction)CFBundleGetFunctionPointerForName(
bundle, CFSTR("MRMediaRemoteGetNowPlayingInfo"));
#pragma clang diagnostic pop
if (!MRMediaRemoteGetNowPlayingInfo) {
NSLog(@"Failed to get function pointer for MRMediaRemoteGetNowPlayingInfo");
CFRelease(bundle);
return nil;
}
__block NSDictionary *nowPlayingInfo = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
MRMediaRemoteGetNowPlayingInfo(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^(NSDictionary *information) {
nowPlayingInfo = [information copy];
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
CFRelease(bundle);
return nowPlayingInfo;
}
@end
extern "C" {
const char *getCurrentPlayingTitle() {
NSDictionary *metadata = [NowPlayingBridge currentPlayingMetadata];
if (metadata == nil) {
return nullptr;
}
NSString *title =
[metadata objectForKey:@"kMRMediaRemoteNowPlayingInfoTitle"];
if (title) {
return strdup([title UTF8String]);
}
return nullptr;
}
const char *getCurrentPlayingArtist() {
NSDictionary *metadata = [NowPlayingBridge currentPlayingMetadata];
if (metadata == nil) {
return nullptr;
}
NSString *artist =
[metadata objectForKey:@"kMRMediaRemoteNowPlayingInfoArtist"];
if (artist) {
return strdup([artist UTF8String]);
}
return nullptr;
}
}

View file

@ -1,45 +0,0 @@
AccessModifierOffset: -2
AlignConsecutiveDeclarations: None
AlignOperands: false
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Never
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
BinPackParameters: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Allman
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
ColumnLimit: 100
CommentPragmas: ''
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 2
ContinuationIndentWidth: 2
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
IndentCaseLabels: false
IndentPPDirectives: BeforeHash
IndentWidth: 2
IndentWrappedFunctionNames: false
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakString: 1000
PenaltyBreakFirstLessLess: 120
PenaltyExcessCharacter: 1
PenaltyReturnTypeOnItsOwnLine: 1000
PointerAlignment: Left
SpaceBeforeAssignmentOperators: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
Cpp11BracedListStyle: true
Standard: c++20
TabWidth: 2
UseTab: Never

View file

@ -1,5 +0,0 @@
ignore:
- "examples"
- "quill/include/quill/bundled"
- "quill/src/bundled"
- "quill/test"

View file

@ -1,33 +0,0 @@
name: coverage
on:
push:
branches:
- master
paths-ignore:
- '**.md'
- 'docs/**'
jobs:
build:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v4
- name: Configure
run: cmake -Bbuild -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_STANDARD=17 -DQUILL_BUILD_TESTS=ON -DQUILL_CODE_COVERAGE=ON -DQUILL_BUILD_EXAMPLES=ON -DQUILL_VERBOSE_MAKEFILE=ON
- name: Build
run: cmake --build build -j4
- name: Test
run: |
cd build
ctest --build-config Debug
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
verbose: true

View file

@ -1,111 +0,0 @@
name: linux
on:
push:
branches:
- master
paths-ignore:
- '**.md'
- 'docs/**'
pull_request:
branches:
- master
paths-ignore:
- '**.md'
- 'docs/**'
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
cxx: [ g++-8, g++-10 ]
build_type: [ Debug, Release ]
std: [ 17 ]
os: [ ubuntu-20.04 ]
with_tests: [ ON ]
include:
# Build and with g++8
- cxx: g++-8
std: 17
os: ubuntu-20.04
with_tests: ON
install: sudo apt -o Acquire::Retries=5 install g++-8
# Build and test as shared library
- cxx: g++-10
build_type: Release
std: 17
os: ubuntu-20.04
with_tests: ON
cmake_options: -DBUILD_SHARED_LIBS=ON -DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON
# Builds with no exceptions
- cxx: g++-10
build_type: Release
std: 17
os: ubuntu-20.04
with_tests: OFF
cmake_options: -DQUILL_NO_EXCEPTIONS=ON
# Build and test with valgrind, sanitizers
- cxx: g++-10
build_type: Release
std: 20
os: ubuntu-20.04
with_tests: ON
cmake_options: -DQUILL_USE_VALGRIND=ON
ctest_options: -T memcheck
install: sudo apt -o Acquire::Retries=5 install valgrind
# Build and test sanitizers
- cxx: clang++-12
build_type: Release
std: 20
os: ubuntu-20.04
with_tests: ON
cmake_options: -DQUILL_SANITIZE_ADDRESS=ON
# Build and test sanitizers
- cxx: clang++-12
build_type: Release
std: 20
os: ubuntu-20.04
with_tests: ON
cmake_options: -DQUILL_SANITIZE_THREAD=ON
steps:
- uses: actions/checkout@v4
- name: Create Build Environment
run: |
sudo apt-get update
${{matrix.install}}
cmake -E make_directory ${{runner.workspace}}/build
- name: Configure
working-directory: ${{runner.workspace}}/build
env:
CXX: ${{matrix.cxx}}
CXXFLAGS: ${{matrix.cxxflags}}
run: |
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.cmake_options}} \
-DCMAKE_CXX_STANDARD=${{matrix.std}} -DQUILL_BUILD_TESTS=${{matrix.with_tests}} \
-DQUILL_BUILD_EXAMPLES=ON -DQUILL_VERBOSE_MAKEFILE=ON $GITHUB_WORKSPACE
- name: Build
working-directory: ${{runner.workspace}}/build
run: |
threads=`nproc`
cmake --build . --config ${{matrix.build_type}} --parallel $threads
- name: Test
working-directory: ${{runner.workspace}}/build
run: |
threads=`nproc`
ctest --build-config ${{matrix.build_type}} ${{matrix.ctest_options}} --parallel $threads --output-on-failure
env:
CTEST_OUTPUT_ON_FAILURE: True

View file

@ -1,50 +0,0 @@
name: macos
on:
push:
branches:
- master
paths-ignore:
- '**.md'
- 'docs/**'
pull_request:
branches:
- master
paths-ignore:
- '**.md'
- 'docs/**'
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ macos-14 ]
build_type: [ Debug, Release ]
std: [ 17, 20 ]
steps:
- uses: actions/checkout@v4
- name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build
- name: Configure
working-directory: ${{runner.workspace}}/build
run: |
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
-DCMAKE_CXX_STANDARD=${{matrix.std}} -DQUILL_BUILD_TESTS=ON \
-DQUILL_BUILD_EXAMPLES=ON -DQUILL_VERBOSE_MAKEFILE=ON $GITHUB_WORKSPACE
- name: Build
working-directory: ${{runner.workspace}}/build
run: |
threads=`sysctl -n hw.logicalcpu`
cmake --build . --config ${{matrix.build_type}} --parallel $threads
- name: Test
working-directory: ${{runner.workspace}}/build
run: |
threads=`sysctl -n hw.logicalcpu`
ctest --build-config ${{matrix.build_type}} --parallel $threads --output-on-failure

View file

@ -1,78 +0,0 @@
name: windows
on:
push:
branches:
- master
paths-ignore:
- '**.md'
- 'docs/**'
pull_request:
branches:
- master
paths-ignore:
- '**.md'
- 'docs/**'
jobs:
build:
# windows-2016 and windows-2019 have MSVC 2017 and 2019 installed
# respectively: https://github.com/actions/virtual-environments.
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ windows-2019, windows-2022 ]
platform: [ x64 ]
build_type: [ Debug, Release ]
std: [ 17, 20 ]
with_tests: [ ON ]
include:
# Builds with no exceptions
- os: windows-2019
platform: x64
build_type: Release
std: 17
with_tests: "OFF"
cmake_options: -DQUILL_NO_EXCEPTIONS=ON
# Builds for ARM
- os: windows-2019
platform: ARM64
build_type: Release
std: 17
with_tests: "OFF"
- os: windows-2019
platform: ARM
build_type: Release
std: 17
with_tests: "OFF"
steps:
- uses: actions/checkout@v4
- name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build
- name: Configure
shell: bash # Use a bash shell for $GITHUB_WORKSPACE.
working-directory: ${{runner.workspace}}/build
run: |
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.cmake_options}} \
-A ${{matrix.platform}} -DCMAKE_CXX_STANDARD=${{matrix.std}} -DQUILL_BUILD_TESTS=${{matrix.with_tests}} \
-DQUILL_BUILD_EXAMPLES=ON -DQUILL_VERBOSE_MAKEFILE=ON $GITHUB_WORKSPACE
- name: Build
working-directory: ${{runner.workspace}}/build
run: |
$threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors
cmake --build . --config ${{matrix.build_type}} --parallel $threads
- name: Test
working-directory: ${{runner.workspace}}/build
run: |
$threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors
ctest --build-config ${{matrix.build_type}} --parallel $threads --output-on-failure

View file

@ -1,8 +0,0 @@
# Project exclude paths
/cmake-build-debug/
/.vs
/out/build/x64-Debug
/CMakeSettings.json
/out/build/x64-Release
/out/build/Mingw64-Debug
/out/build/x64-Debug-2

View file

@ -1 +0,0 @@
768ccd8048e2f53838c17ea6480236bb8d89fa785fb08f378539bfb0aa61ac1f

View file

@ -1,22 +0,0 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the version of Python and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.11"
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py
# We recommend specifying your dependencies to enable reproducible builds:
# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: docs/requirements.txt

File diff suppressed because it is too large Load diff

View file

@ -1,166 +0,0 @@
cmake_minimum_required(VERSION 3.8)
project(quill)
#-------------------------------------------------------------------------------------------------------
# Options
#-------------------------------------------------------------------------------------------------------
# Builds Quill without exceptions by adding the -fno-exceptions flag to the compiler.
option(QUILL_NO_EXCEPTIONS "Build without exceptions using -fno-exceptions flag" OFF)
# Disables features that rely on retrieving the thread name, which is not supported in older versions of Windows (e.g., Windows Server 2012/2016).
# Enabling this option ensures compatibility with older Windows versions.
option(QUILL_NO_THREAD_NAME_SUPPORT "Disable features not supported on Windows Server 2012/2016" OFF)
# Enables the use of _mm_prefetch, _mm_clflush, and _mm_clflushopt instructions to enhance cache coherence performance on x86 architectures.
# When enabled, Quill will utilize these instructions on the frontend's queue operations.
# Ensure to specify the target architecture with -march="..." when compiling to maximize compatibility and performance.
option(QUILL_X86ARCH "Enable optimizations for cache coherence on x86 architectures using specific CPU instructions" OFF)
# When enabled, removes the non-prefixed `LOG_*` macros, leaving only `QUILL_LOG_*` macros available.
# This is useful in scenarios where the original macro names conflict with those of an existing logging library.
option(QUILL_DISABLE_NON_PREFIXED_MACROS "Disable non-prefixed macros" OFF)
option(QUILL_DISABLE_POSITION_INDEPENDENT_CODE "Disable position-independent code" OFF)
option(QUILL_BUILD_EXAMPLES "Build the examples" OFF)
option(QUILL_BUILD_TESTS "Build the tests (Requires https://github.com/google/googletest to be installed)" OFF)
option(QUILL_BUILD_BENCHMARKS "Build the benchmarks" OFF)
option(QUILL_SANITIZE_ADDRESS "Enable address sanitizer in tests" OFF)
option(QUILL_SANITIZE_THREAD "Enable thread sanitizer in tests (Using this option with any other compiler except clang may result to false positives)" OFF)
option(QUILL_CODE_COVERAGE "Enable code coverage" OFF)
option(QUILL_USE_VALGRIND "Use valgrind as the default memcheck tool in CTest (Requires Valgrind)" OFF)
option(QUILL_ENABLE_INSTALL "Enable CMake Install when Quill is not a master project" OFF)
option(QUILL_DOCS_GEN "Generate documentation" OFF)
#-------------------------------------------------------------------------------------------------------
# Use newer policies if possible, up to most recent tested version of CMake.
#-------------------------------------------------------------------------------------------------------
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
#-------------------------------------------------------------------------------------------------------
# Include common compiler options
#-------------------------------------------------------------------------------------------------------
include(${PROJECT_SOURCE_DIR}/cmake/SetCommonCompileOptions.cmake)
#-------------------------------------------------------------------------------------------------------
# Determine if quill is built as a subproject (using add_subdirectory) or if it is the master project.
#-------------------------------------------------------------------------------------------------------
set(QUILL_MASTER_PROJECT FALSE CACHE BOOL "Master Project" FORCE)
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set(QUILL_MASTER_PROJECT TRUE CACHE BOOL "Master Project" FORCE)
endif ()
#-------------------------------------------------------------------------------------------------------
# Custom cmake functions
#-------------------------------------------------------------------------------------------------------
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/quill/cmake")
include(Utility)
#-------------------------------------------------------------------------------------------------------
# Resolve version
#-------------------------------------------------------------------------------------------------------
quill_extract_version()
project(quill VERSION ${QUILL_VERSION} LANGUAGES CXX)
#-------------------------------------------------------------------------------------------------------
# Set default build to release
#-------------------------------------------------------------------------------------------------------
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build" FORCE)
endif ()
#---------------------------------------------------------------------------------------
# Compiler config
#---------------------------------------------------------------------------------------
if (NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
endif ()
#-------------------------------------------------------------------------------------------------------
# Required Packages
#-------------------------------------------------------------------------------------------------------
find_package(Threads REQUIRED)
if (QUILL_BUILD_TESTS)
enable_testing()
if (QUILL_USE_VALGRIND)
# find valgrind
find_program(MEMORYCHECK_COMMAND NAMES valgrind)
if (NOT MEMORYCHECK_COMMAND)
message(WARNING "Valgrind not found")
endif ()
# set valgrind params
set(MEMORYCHECK_COMMAND_OPTIONS "--tool=memcheck --leak-check=full --leak-resolution=med --show-leak-kinds=all --track-origins=yes --vgdb=no --fair-sched=yes")
# add memcheck test action to ctest
include(CTest)
endif ()
endif ()
#-------------------------------------------------------------------------------------------------------
# Log Info
#-------------------------------------------------------------------------------------------------------
if (QUILL_MASTER_PROJECT)
option(QUILL_VERBOSE_MAKEFILE "Verbose make output" OFF)
message(STATUS "CMAKE_BUILD_TYPE: " ${CMAKE_BUILD_TYPE})
message(STATUS "QUILL_VERSION: ${QUILL_VERSION}")
endif ()
message(STATUS "QUILL_NO_EXCEPTIONS: " ${QUILL_NO_EXCEPTIONS})
message(STATUS "QUILL_NO_THREAD_NAME_SUPPORT: " ${QUILL_NO_THREAD_NAME_SUPPORT})
message(STATUS "QUILL_X86ARCH: " ${QUILL_X86ARCH})
message(STATUS "QUILL_DISABLE_NON_PREFIXED_MACROS: " ${QUILL_DISABLE_NON_PREFIXED_MACROS})
#---------------------------------------------------------------------------------------
# Verbose make file option
#---------------------------------------------------------------------------------------
if (QUILL_VERBOSE_MAKEFILE)
set(CMAKE_VERBOSE_MAKEFILE TRUE CACHE BOOL "Verbose output" FORCE)
endif ()
# address sanitizer flags
if (QUILL_SANITIZE_ADDRESS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined -fno-omit-frame-pointer -g")
endif ()
# thread sanitizer flags
if (QUILL_SANITIZE_THREAD)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
endif ()
# Append extra options for coverage
if (QUILL_CODE_COVERAGE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g -fprofile-arcs -ftest-coverage")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fprofile-arcs -ftest-coverage")
endif ()
# Build Examples
if (QUILL_BUILD_EXAMPLES)
add_subdirectory(examples)
endif ()
if (QUILL_BUILD_BENCHMARKS)
add_subdirectory(benchmarks)
endif ()
add_subdirectory(quill)
if (QUILL_DOCS_GEN)
# Add the cmake folder so the FindSphinx module is found
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
add_subdirectory(docs)
endif ()

View file

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2020 - present, Odysseas Georgoudis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,19 +0,0 @@
Copyright (c) 2021 The Meson development team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,511 +0,0 @@
<div align="center">
<a href="https://github.com/odygrd/quill">
<img width="125" src="https://i.postimg.cc/DZrH8HkX/quill-circle-photos-v2-x2-colored-toned.png" alt="Quill logo">
</a>
<h1>Quill</h1>
<div>
<a href="https://github.com/odygrd/quill/actions?query=workflow%3Alinux">
<img src="https://img.shields.io/github/actions/workflow/status/odygrd/quill/linux.yml?branch=master&label=linux&logo=linux&style=flat-square" alt="linux-ci" />
</a>
<a href="https://github.com/odygrd/quill/actions?query=workflow%3Amacos">
<img src="https://img.shields.io/github/actions/workflow/status/odygrd/quill/macos.yml?branch=master&label=macos&logo=apple&logoColor=white&style=flat-square" alt="macos-ci" />
</a>
<a href="https://github.com/odygrd/quill/actions?query=workflow%3Awindows">
<img src="https://img.shields.io/github/actions/workflow/status/odygrd/quill/windows.yml?branch=master&label=windows&logo=windows&logoColor=blue&style=flat-square" alt="windows-ci" />
</a>
</div>
<div>
<a href="https://codecov.io/gh/odygrd/quill">
<img src="https://img.shields.io/codecov/c/gh/odygrd/quill/master.svg?logo=codecov&style=flat-square" alt="Codecov" />
</a>
<a href="https://app.codacy.com/gh/odygrd/quill/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade">
<img src="https://img.shields.io/codacy/grade/cd387bc34658475d98bff84db3ad5287?logo=codacy&style=flat-square" alt="Codacy" />
</a>
<a href="https://www.codefactor.io/repository/github/odygrd/quill">
<img src="https://img.shields.io/codefactor/grade/github/odygrd/quill?logo=codefactor&style=flat-square" alt="CodeFactor" />
</a>
</div>
<div>
<a href="https://opensource.org/licenses/MIT">
<img src="https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square" alt="license" />
</a>
<a href="https://en.wikipedia.org/wiki/C%2B%2B17">
<img src="https://img.shields.io/badge/language-C%2B%2B17-red.svg?style=flat-square" alt="language" />
</a>
</div>
<p><b>Asynchronous Low Latency C++ Logging Library</b></p>
</div>
<br>
- [Introduction](#introduction)
- [Documentation](#documentation)
- [Features](#features)
- [Caveats](#caveats)
- [Performance](#performance)
- [Quick Start](#quick-start)
- [CMake Integration](#cmake-integration)
- [Design](#design)
- [License](#license)
| homebrew | vcpkg | conan |
|:--------------------:|:---------------------:|:-----------------:|
| `brew install quill` | `vcpkg install quill` | `quill/[>=1.2.3]` |
## Introduction
Quill is a high-performance, cross-platform logging library designed for C++17 and onwards.
Quill is a production-ready logging library that has undergone extensive unit testing. It has been successfully utilized
in production environments, including financial trading applications, providing high-performance and reliable logging
capabilities.
## Documentation
For detailed documentation and usage instructions, please visit
the [Quill Documentation on Read the Docs](http://quillcpp.readthedocs.io/). It provides comprehensive information on
how to integrate and utilize Quill in your C++ applications.
Additionally, you can explore the [examples](http://github.com/odygrd/quill/tree/master/examples) folder in the Quill
repository on GitHub. These examples serve as valuable resources to understand different usage scenarios and demonstrate
the capabilities of the library.
## Features
- **Low Latency Logging**: Achieve fast logging performance with low latency. Refer to
the [Benchmarks](http://github.com/odygrd/quill#performance) for more details.
- **Asynchronous logging**: Log arguments and messages are formatted in a backend logging thread, effectively offloading
the formatting overhead from the critical path.
- **Custom Formatters**: Customize log formatting based on user-defined patterns.
Explore [Formatters](http://quillcpp.readthedocs.io/en/latest/tutorial.html#formatters) for further details.
- **Flexible Timestamp Generation**: Choose between rdtsc, chrono, or custom clocks (useful for simulations) for
log message timestamp generation.
- **Log Stack Traces**: Store log messages in a ring buffer and display them later in response to a higher severity log
statement or on demand. Refer
to [Backtrace Logging](http://quillcpp.readthedocs.io/en/latest/tutorial.html#backtrace-logging) for more information.
- **Multiple Logging Sinks**: Utilize various logging targets, including:
- Console logging with color support.
- File logging.
- Rotating log files based on time or size.
- JSON logging.
- Custom sinks.
- **Log Message Filtering**: Apply filters to selectively process log messages. Learn more
about [Filters](http://quillcpp.readthedocs.io/en/latest/tutorial.html#filters).
- **Structured Logging**: Generate JSON structured logs.
See [Structured-Log](http://quillcpp.readthedocs.io/en/latest/tutorial.html#json-log) for details.
- **Blocking or Dropping Message Modes**: Choose between `blocking` or `dropping` message modes in the library.
In `blocking` mode, the hot threads pause and wait when the queue is full until space becomes available, ensuring no
message loss but introducing potential latency. In `dropping` mode, log messages beyond the queue's capacity may be
dropped to prioritize low latency. The library provides reports on dropped messages, queue reallocations, and blocked
hot threads for monitoring purposes.
- **Queue Types**: The library supports different types of queues for transferring logs from the hot path to the backend
thread: bounded queues with a fixed capacity and unbounded queues that start small and can dynamically grow.
- **Wide Character Support**: Wide strings compatible with ASCII encoding are supported, applicable to Windows only.
Additionally, there is support for logging STL containers consisting of wide strings. Note that chaining STL types,
such as `std::vector<std::vector<std::wstring>>` is not supported for wide strings.
- **Ordered Log Statements**: Log statements are ordered by timestamp even when produced by different threads,
facilitating easier debugging of multithreaded applications.
- **Compile-Time Log Level Stripping**: Completely strip out log levels at compile time, reducing `if` branches.
- **Clean and Warning-Free Codebase**: Ensure a clean and warning-free codebase, even with high compiler warning levels.
- **Crash-Safe Behavior**: Benefit from crash-safe behavior with a built-in signal handler.
- **Type-Safe API**: Type safe api using the excellent [{fmt}](http://github.com/fmtlib/fmt) library.
- **Huge Pages**: Benefit from support for huge pages on the hot path. This feature allows for improved performance and
efficiency.
## Caveats
Quill may not work well with `fork()` since it spawns a background thread and `fork()` doesn't work well
with multithreading.
If your application uses `fork()` and you want to log in the child processes as well, you should call
`quill::start()` after the `fork()` call. Additionally, you should ensure that you write to different
files in the parent and child processes to avoid conflicts.
For example :
```c++
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/Logger.h"
#include "quill/sinks/FileSink.h"
int main()
{
// DO NOT CALL THIS BEFORE FORK
// quill::Backend::start();
if (fork() == 0)
{
quill::Backend::start();
// Get or create a handler to the file - Write to a different file
auto file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
"child.log");
quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(file_sink));
QUILL_LOG_INFO(logger, "Hello from Child {}", 123);
}
else
{
quill::Backend::start();
// Get or create a handler to the file - Write to a different file
auto file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
"parent.log");
quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(file_sink));
QUILL_LOG_INFO(logger, "Hello from Parent {}", 123);
}
}
```
## Performance
### Latency
The results presented in the tables below are measured in `nanoseconds (ns)`.
#### Logging Numbers
The following message is logged 100'000 times for each thread:
`LOG_INFO(logger, "Logging int: {}, int: {}, double: {}", i, j, d)`.
##### 1 Thread Logging
| Library | 50th | 75th | 90th | 95th | 99th | 99.9th |
|---------------------------------------------------------------------|:----:|:----:|:----:|:----:|:----:|:------:|
| [Quill v4.1 Bounded Dropping Queue](http://github.com/odygrd/quill) | 7 | 7 | 8 | 8 | 9 | 10 |
| [fmtlog](http://github.com/MengRao/fmtlog) | 8 | 8 | 9 | 9 | 10 | 13 |
| [Quill v4.1 Unbounded Queue](http://github.com/odygrd/quill) | 8 | 8 | 9 | 9 | 10 | 13 |
| [Quill v3.8 Unbounded Queue](http://github.com/odygrd/quill) | 8 | 8 | 9 | 9 | 10 | 13 |
| [PlatformLab NanoLog](http://github.com/PlatformLab/NanoLog) | 11 | 12 | 13 | 14 | 15 | 20 |
| [MS BinLog](http://github.com/Morgan-Stanley/binlog) | 21 | 21 | 22 | 24 | 28 | 57 |
| [Reckless](http://github.com/mattiasflodin/reckless) | 41 | 45 | 47 | 48 | 49 | 69 |
| [Iyengar NanoLog](http://github.com/Iyengar111/NanoLog) | 51 | 54 | 63 | 81 | 113 | 160 |
| [spdlog](http://github.com/gabime/spdlog) | 148 | 153 | 159 | 163 | 169 | 176 |
| [g3log](http://github.com/KjellKod/g3log) | 1192 | 1282 | 1363 | 1440 | 1624 | 1802 |
##### 4 Threads Logging Simultaneously
| Library | 50th | 75th | 90th | 95th | 99th | 99.9th |
|---------------------------------------------------------------------|:----:|:----:|:----:|:----:|:----:|:------:|
| [Quill v4.1 Bounded Dropping Queue](http://github.com/odygrd/quill) | 7 | 8 | 9 | 9 | 10 | 13 |
| [fmtlog](http://github.com/MengRao/fmtlog) | 8 | 8 | 9 | 9 | 11 | 13 |
| [Quill v3.8 Unbounded Queue](http://github.com/odygrd/quill) | 8 | 9 | 10 | 10 | 11 | 13 |
| [Quill v4.1 Unbounded Queue](http://github.com/odygrd/quill) | 9 | 9 | 10 | 11 | 12 | 15 |
| [PlatformLab NanoLog](http://github.com/PlatformLab/NanoLog) | 12 | 13 | 13 | 14 | 15 | 19 |
| [MS BinLog](http://github.com/Morgan-Stanley/binlog) | 21 | 21 | 22 | 22 | 29 | 62 |
| [Reckless](http://github.com/mattiasflodin/reckless) | 42 | 46 | 47 | 48 | 54 | 78 |
| [Iyengar NanoLog](http://github.com/Iyengar111/NanoLog) | 53 | 62 | 93 | 122 | 150 | 216 |
| [spdlog](http://github.com/gabime/spdlog) | 209 | 236 | 276 | 304 | 409 | 700 |
| [g3log](http://github.com/KjellKod/g3log) | 1344 | 1415 | 1489 | 1557 | 1815 | 5855 |
#### Logging Large Strings
The following message is logged 100'000 times for each thread:
`LOG_INFO(logger, "Logging int: {}, int: {}, string: {}", i, j, large_string)`.
The large string used in the log message is over 35 characters to prevent the short string optimization
of `std::string`.
##### 1 Thread Logging
| Library | 50th | 75th | 90th | 95th | 99th | 99.9th |
|---------------------------------------------------------------------|:----:|:----:|:----:|:----:|:----:|:------:|
| [Quill v3.8 Unbounded Queue](http://github.com/odygrd/quill) | 10 | 12 | 13 | 13 | 14 | 16 |
| [Quill v4.1 Bounded Dropping Queue](http://github.com/odygrd/quill) | 11 | 12 | 13 | 14 | 15 | 17 |
| [fmtlog](http://github.com/MengRao/fmtlog) | 11 | 12 | 13 | 14 | 15 | 17 |
| [PlatformLab NanoLog](http://github.com/PlatformLab/NanoLog) | 13 | 14 | 15 | 15 | 17 | 19 |
| [Quill v4.1 Unbounded Queue](http://github.com/odygrd/quill) | 13 | 14 | 16 | 16 | 17 | 21 |
| [MS BinLog](http://github.com/Morgan-Stanley/binlog) | 22 | 23 | 23 | 25 | 30 | 59 |
| [Iyengar NanoLog](http://github.com/Iyengar111/NanoLog) | 52 | 55 | 64 | 83 | 114 | 160 |
| [Reckless](http://github.com/mattiasflodin/reckless) | 102 | 122 | 134 | 137 | 143 | 153 |
| [spdlog](http://github.com/gabime/spdlog) | 120 | 123 | 127 | 130 | 138 | 145 |
| [g3log](http://github.com/KjellKod/g3log) | 955 | 1049 | 1129 | 1190 | 1351 | 1539 |
##### 4 Threads Logging Simultaneously
| Library | 50th | 75th | 90th | 95th | 99th | 99.9th |
|---------------------------------------------------------------------|:----:|:----:|:----:|:----:|:----:|:------:|
| [Quill v4.1 Bounded Dropping Queue](http://github.com/odygrd/quill) | 11 | 12 | 13 | 15 | 16 | 18 |
| [fmtlog](http://github.com/MengRao/fmtlog) | 11 | 12 | 13 | 15 | 16 | 18 |
| [Quill v3.8 Unbounded Queue](http://github.com/odygrd/quill) | 12 | 13 | 14 | 15 | 16 | 19 |
| [PlatformLab NanoLog](http://github.com/PlatformLab/NanoLog) | 13 | 15 | 15 | 16 | 17 | 20 |
| [Quill v4.1 Unbounded Queue](http://github.com/odygrd/quill) | 14 | 16 | 17 | 18 | 19 | 22 |
| [MS BinLog](http://github.com/Morgan-Stanley/binlog) | 23 | 24 | 24 | 25 | 31 | 62 |
| [Iyengar NanoLog](http://github.com/Iyengar111/NanoLog) | 53 | 60 | 92 | 121 | 149 | 212 |
| [Reckless](http://github.com/mattiasflodin/reckless) | 101 | 121 | 133 | 136 | 143 | 160 |
| [spdlog](http://github.com/gabime/spdlog) | 186 | 215 | 266 | 297 | 381 | 641 |
| [g3log](http://github.com/KjellKod/g3log) | 1089 | 1164 | 1252 | 1328 | 1578 | 5563 |
#### Logging Complex Types
The following message is logged 100'000 times for each thread:
`LOG_INFO(logger, "Logging int: {}, int: {}, vector: {}", i, j, v)`.
Logging `std::vector<std::string> v` containing 16 large strings, each ranging from 50 to 60 characters.
The strings used in the log message are over 35 characters to prevent the short string optimization of `std::string`.
##### 1 Thread Logging
| Library | 50th | 75th | 90th | 95th | 99th | 99.9th |
|---------------------------------------------------------------------|:----:|:----:|:----:|:----:|:----:|:------:|
| [Quill v4.1 Unbounded Queue](http://github.com/odygrd/quill) | 52 | 54 | 56 | 58 | 60 | 63 |
| [Quill v4.1 Bounded Dropping Queue](http://github.com/odygrd/quill) | 53 | 55 | 57 | 59 | 62 | 103 |
| [MS BinLog](http://github.com/Morgan-Stanley/binlog) | 66 | 70 | 79 | 81 | 84 | 91 |
| [Quill v3.8 Unbounded Queue](http://github.com/odygrd/quill) | 632 | 651 | 676 | 698 | 737 | 1049 |
| [fmtlog](http://github.com/MengRao/fmtlog) | 724 | 752 | 776 | 789 | 814 | 857 |
| [spdlog](http://github.com/gabime/spdlog) | 6242 | 6331 | 6438 | 6523 | 6782 | 7341 |
##### 4 Threads Logging Simultaneously
| Library | 50th | 75th | 90th | 95th | 99th | 99.9th |
|---------------------------------------------------------------------|:----:|:----:|:----:|:----:|:----:|:------:|
| [Quill v4.1 Bounded Dropping Queue](http://github.com/odygrd/quill) | 55 | 57 | 59 | 61 | 64 | 77 |
| [MS BinLog](http://github.com/Morgan-Stanley/binlog) | 70 | 74 | 83 | 85 | 88 | 102 |
| [Quill v4.1 Unbounded Queue](http://github.com/odygrd/quill) | 92 | 100 | 110 | 118 | 135 | 157 |
| [Quill v3.8 Unbounded Queue](http://github.com/odygrd/quill) | 674 | 694 | 736 | 762 | 805 | 884 |
| [fmtlog](http://github.com/MengRao/fmtlog) | 789 | 813 | 833 | 845 | 872 | 908 |
| [spdlog](http://github.com/gabime/spdlog) | 6500 | 6596 | 6724 | 6848 | 7560 | 9036 |
The benchmark was conducted on `Linux RHEL 9` with an `Intel Core i5-12600` at 4.8 GHz.
The cpus are isolated on this system and each thread was pinned to a different CPU. `GCC 13.1` was used as the compiler.
The benchmark methodology involved logging 20 messages in a loop, calculating and storing the average latency for those
20 messages, then waiting around ~2 milliseconds, and repeating this process for a specified number of iterations.
_In the `Quill Bounded Dropping` benchmarks, the dropping queue size is set to `262,144` bytes, which is double the
default size of `131,072` bytes._
You can find the benchmark code on the [logger_benchmarks](http://github.com/odygrd/logger_benchmarks) repository.
### Throughput
The maximum throughput is measured by determining the maximum number of log messages the backend logging thread can
write to the log file per second.
When measured on the same system as the latency benchmarks mentioned above the average throughput of the backend
logging thread is `4.56 million msgs/sec`
While the primary focus of the library is not on throughput, it does provide efficient handling of log messages across
multiple threads. The backend logging thread, responsible for formatting and ordering log messages from hot threads,
ensures that all queues are emptied on a high priority basis. The backend thread internally buffers the log messages
and then writes them later when the caller thread queues are empty or when a predefined limit,
`backend_thread_transit_events_soft_limit`, is reached. This approach prevents the need for allocating new queues
or dropping messages on the hot path.
Comparing throughput with other logging libraries in an asynchronous logging scenario has proven challenging. Some
libraries may drop log messages, resulting in smaller log files than expected, while others only offer asynchronous
flush, making it difficult to determine when the logging thread has finished processing all messages.
In contrast, Quill provides a blocking flush log guarantee, ensuring that every log message from the hot threads up to
that point is flushed to the file.
For benchmarking purposes, you can find the
code [here](https://github.com/odygrd/quill/blob/master/benchmarks/backend_throughput/quill_backend_throughput.cpp).
### Compilation Time
Compile times are measured using `clang 15` and for `Release` build.
Below, you can find the additional headers that the library will include when you need to log, following
the [recommended_usage](https://github.com/odygrd/quill/blob/master/examples/recommended_usage/recommended_usage.cpp)
example
![quill_v4_1_compiler_profile.speedscope.png](docs%2Fquill_v4_1_compiler_profile.speedscope.png)
There is also a compile-time benchmark measuring the compilation time of 2000 auto-generated log statements with
various arguments. You can find
it [here](https://github.com/odygrd/quill/blob/master/benchmarks/compile_time/compile_time_bench.cpp). It takes approximately 30
seconds to compile.
![quill_v4_compiler_bench.speedscope.png](docs%2Fquill_v4_compiler_bench.speedscope.png)
## Quick Start
```c++
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/Logger.h"
#include "quill/sinks/FileSink.h"
int main()
{
// Start the backend thread
quill::Backend::start();
// Log to file
auto file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
"example_file_logging.log");
quill::Logger* logger =
quill::Frontend::create_or_get_logger("root", std::move(file_sink));
// set the log level of the logger to trace_l3 (default is info)
logger->set_log_level(quill::LogLevel::TraceL3);
LOG_INFO(logger, "Welcome to Quill!");
LOG_ERROR(logger, "An error message. error code {}", 123);
LOG_WARNING(logger, "A warning message.");
LOG_CRITICAL(logger, "A critical error.");
LOG_DEBUG(logger, "Debugging foo {}", 1234);
LOG_TRACE_L1(logger, "{:>30}", "right aligned");
LOG_TRACE_L2(logger, "Positional arguments are {1} {0} ", "too", "supported");
LOG_TRACE_L3(logger, "Support for floats {:03.2f}", 1.23456);
}
```
```c++
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/Logger.h"
#include "quill/sinks/ConsoleSink.h"
int main()
{
// Start the backend thread
quill::Backend::start();
// Frontend
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");
quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(console_sink));
// Change the LogLevel to print everything
logger->set_log_level(quill::LogLevel::TraceL3);
LOG_INFO(logger, "Welcome to Quill!");
LOG_ERROR(logger, "An error message. error code {}", 123);
LOG_WARNING(logger, "A warning message.");
LOG_CRITICAL(logger, "A critical error.");
LOG_DEBUG(logger, "Debugging foo {}", 1234);
LOG_TRACE_L1(logger, "{:>30}", "right aligned");
LOG_TRACE_L2(logger, "Positional arguments are {1} {0} ", "too", "supported");
LOG_TRACE_L3(logger, "Support for floats {:03.2f}", 1.23456);
}
```
### Output
[![Screenshot-2020-08-14-at-01-09-43.png](http://i.postimg.cc/02Vbt8LH/Screenshot-2020-08-14-at-01-09-43.png)](http://postimg.cc/LnZ95M4z)
## CMake-Integration
#### External
##### Building and Installing Quill
```
git clone http://github.com/odygrd/quill.git
mkdir cmake_build
cd cmake_build
cmake ..
make install
```
Note: To install in custom directory invoke cmake with `-DCMAKE_INSTALL_PREFIX=/quill/install-dir/`
Then use the library from a CMake project, you can locate it directly with `find_package()`
##### Directory Structure
```
my_project/
├── CMakeLists.txt
├── main.cpp
```
##### CMakeLists.txt
```cmake
# Set only if needed - quill was installed under a custom non-standard directory
set(CMAKE_PREFIX_PATH /test_quill/usr/local/)
find_package(quill REQUIRED)
# Linking your project against quill
add_executable(example main.cpp)
target_link_libraries(example PUBLIC quill::quill)
```
#### Embedded
To embed the library directly, copy the source [folder](http://github.com/odygrd/quill/tree/master/quill/quill) to your
project and call `add_subdirectory()` in your `CMakeLists.txt` file
##### Directory Structure
```
my_project/
├── quill/ (source folder)
├── CMakeLists.txt
├── main.cpp
```
##### CMakeLists.txt
```cmake
cmake_minimum_required(VERSION 3.1.0)
project(my_project)
set(CMAKE_CXX_STANDARD 17)
add_subdirectory(quill)
add_executable(my_project main.cpp)
target_link_libraries(my_project PUBLIC quill::quill)
```
#### Building Quill for Android NDK
To build Quill for Android NDK add the following `CMake` flags when configuring the build:
```
-DQUILL_NO_THREAD_NAME_SUPPORT:BOOL=ON
```
## Design
### Frontend (caller-thread)
When invoking a `LOG_` macro:
1. Creates a static constexpr metadata object to store `Metadata` such as the format string and source location.
2. Pushes the data SPSC lock-free queue. For each log message, the following variables are pushed
| Variable | Description |
|------------|:--------------------------------------------------------------------------------------------------------------:|
| timestamp | Current timestamp |
| Metadata* | Pointer to metadata information |
| Logger* | Pointer to the logger instance |
| DecodeFunc | A pointer to a templated function containing all the log message argument types, used for decoding the message |
| Args... | A serialized binary copy of each log message argument that was passed to the `LOG_` macro |
### Backend
Consumes each message from the SPSC queue, retrieves all the necessary information and then formats the message.
Subsequently, forwards the log message to all Sinks associated with the Logger.
![design.jpg](docs%2Fdesign.jpg)
## License
Quill is licensed under the [MIT License](http://opensource.org/licenses/MIT)
Quill depends on third party libraries with separate copyright notices and license terms.
Your use of the source code for these subcomponents is subject to the terms and conditions of the following licenses.
- ([MIT License](http://opensource.org/licenses/MIT)) {fmt} (http://github.com/fmtlib/fmt/blob/master/LICENSE.rst)
- ([MIT License](http://opensource.org/licenses/MIT)) doctest (http://github.com/onqtam/doctest/blob/master/LICENSE.txt)

View file

@ -1,3 +0,0 @@
add_subdirectory(hot_path_latency)
add_subdirectory(backend_throughput)
add_subdirectory(compile_time)

View file

@ -1,7 +0,0 @@
add_executable(BENCHMARK_quill_backend_throughput quill_backend_throughput.cpp)
set_common_compile_options(BENCHMARK_quill_backend_throughput)
target_link_libraries(BENCHMARK_quill_backend_throughput quill)
add_executable(BENCHMARK_quill_backend_throughput_no_buffering quill_backend_throughput_no_buffering.cpp)
set_common_compile_options(BENCHMARK_quill_backend_throughput_no_buffering)
target_link_libraries(BENCHMARK_quill_backend_throughput_no_buffering quill)

View file

@ -1,65 +0,0 @@
#include <chrono>
#include <iostream>
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
static constexpr size_t total_iterations = 4'000'000;
/**
* The backend worker just spins, so we just measure the total time elapsed for total_iterations
*/
int main()
{
// main thread affinity
quill::detail::set_cpu_affinity(0);
quill::BackendOptions backend_options;
backend_options.backend_cpu_affinity = 5;
// Start the logging backend thread and give it some tiem to init
quill::Backend::start(backend_options);
std::this_thread::sleep_for(std::chrono::milliseconds{100});
// Create a file sink to write to a file
std::shared_ptr<quill::Sink> file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
"quill_backend_total_time.log",
[]()
{
quill::FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
quill::FileEventNotifier{});
quill::Logger* logger = quill::Frontend::create_or_get_logger(
"bench_logger", std::move(file_sink),
"%(time) [%(thread_id)] %(short_source_location) %(log_level) %(message)");
quill::Frontend::preallocate();
// start counting the time until backend worker finishes
auto const start_time = std::chrono::steady_clock::now();
for (size_t iteration = 0; iteration < total_iterations; ++iteration)
{
LOG_INFO(logger, "Iteration: {} int: {} double: {}", iteration, iteration * 2,
static_cast<double>(iteration) / 2);
}
// block until all messages are flushed
logger->flush_log();
auto const end_time = std::chrono::steady_clock::now();
auto const delta = end_time - start_time;
auto delta_d = std::chrono::duration_cast<std::chrono::duration<double>>(delta).count();
std::cout << fmtquill::format(
"Throughput is {:.2f} million msgs/sec average, total time elapsed: {} ms for {} "
"log messages \n",
total_iterations / delta_d / 1e6,
std::chrono::duration_cast<std::chrono::milliseconds>(delta).count(), total_iterations)
<< std::endl;
}

View file

@ -1,66 +0,0 @@
#include <chrono>
#include <iostream>
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
static constexpr size_t total_iterations = 4'000'000;
/**
* The backend worker just spins, so we just measure the total time elapsed for total_iterations
*/
int main()
{
// main thread affinity
quill::detail::set_cpu_affinity(0);
quill::BackendOptions backend_options;
backend_options.backend_cpu_affinity = 5;
backend_options.transit_events_hard_limit = 1;
// Start the logging backend thread and give it some tiem to init
quill::Backend::start(backend_options);
std::this_thread::sleep_for(std::chrono::milliseconds{100});
// Create a file sink to write to a file
std::shared_ptr<quill::Sink> file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
"quill_backend_total_time.log",
[]()
{
quill::FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
quill::FileEventNotifier{});
quill::Logger* logger = quill::Frontend::create_or_get_logger(
"bench_logger", std::move(file_sink),
"%(time) [%(thread_id)] %(short_source_location) %(log_level) %(message)");
quill::Frontend::preallocate();
// start counting the time until backend worker finishes
auto const start_time = std::chrono::steady_clock::now();
for (size_t iteration = 0; iteration < total_iterations; ++iteration)
{
LOG_INFO(logger, "Iteration: {} int: {} double: {}", iteration, iteration * 2,
static_cast<double>(iteration) / 2);
}
// block until all messages are flushed
logger->flush_log();
auto const end_time = std::chrono::steady_clock::now();
auto const delta = end_time - start_time;
auto delta_d = std::chrono::duration_cast<std::chrono::duration<double>>(delta).count();
std::cout << fmtquill::format(
"Throughput is {:.2f} million msgs/sec average, total time elapsed: {} ms for {} "
"log messages \n",
total_iterations / delta_d / 1e6,
std::chrono::duration_cast<std::chrono::milliseconds>(delta).count(), total_iterations)
<< std::endl;
}

View file

@ -1,5 +0,0 @@
add_subdirectory(qwrapper)
add_executable(BENCHMARK_quill_compile_time compile_time_bench.cpp)
set_common_compile_options(BENCHMARK_quill_compile_time)
target_link_libraries(BENCHMARK_quill_compile_time qwrapper_compile_time_bench)

View file

@ -1,57 +0,0 @@
import random
def generate_log_statements(num_statements):
argument_types = [
'1', '2', '3.0', '4.0f', '5L', '6LL', '7UL', '8ULL', 'true', 'false',
'"example1"', '"example2"', '"example3"', 'std::string("str1")',
'std::string("str2")', 'std::string_view("view1")', 'std::string_view("view2")',
'static_cast<short>(9)', 'static_cast<unsigned short>(10)'
]
random_words = ["quick", "brown", "fox", "jumps", "over", "lazy", "dog", "logging", "test", "example"]
statements = []
for i in range(num_statements):
num_args = random.randint(1, 10) # Number of arguments for the log statement
args = random.sample(argument_types, num_args)
placeholders = ' '.join(["{}" for _ in args])
num_words = random.randint(3, 4) # Number of random words in the log message
words = ' '.join(random.sample(random_words, num_words))
statement = f' LOG_INFO(logger, "{words} {placeholders}", {", ".join(args)});'
statements.append(statement)
return statements
def write_to_file(filename, statements):
with open(filename, 'w') as f:
f.write('#include "quill/Backend.h"\n')
f.write('#include "quill/Frontend.h"\n')
f.write('#include "quill/LogMacros.h"\n')
f.write('#include "quill/Logger.h"\n')
f.write('#include "quill/sinks/ConsoleSink.h"\n')
f.write('#include <string>\n')
f.write('#include <utility>\n\n')
f.write('/**\n')
f.write(' * Trivial logging example to console\n')
f.write(' */\n\n')
f.write('int main()\n')
f.write('{\n')
f.write(' // Start the backend thread\n')
f.write(' quill::BackendOptions backend_options;\n')
f.write(' quill::Backend::start(backend_options);\n\n')
f.write(' // Frontend\n')
f.write(' auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");\n')
f.write(' quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(console_sink));\n\n')
for statement in statements:
f.write(f'{statement}\n')
f.write('\n return 0;\n')
f.write('}\n')
if __name__ == '__main__':
num_statements = 2000
statements = generate_log_statements(num_statements)
write_to_file('log_benchmark.cpp', statements)

View file

@ -1,16 +0,0 @@
set(LIB_NAME qwrapper_compile_time_bench)
add_library(${LIB_NAME} STATIC
include/qwrapper/qwrapper.h
include/qwrapper/qwrapper.cpp)
# Add include directories for this library
target_include_directories(${LIB_NAME}
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR})
# Link quill dependency
target_link_libraries(${LIB_NAME} PUBLIC quill::quill)

Some files were not shown because too many files have changed in this diff Show more