From 543326279168ece4d849e12b992c507a92882bf0 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 22 Dec 2024 16:54:45 +0100 Subject: [PATCH 01/57] Basic setup for Cap'n Proto --- CMakeLists.txt | 15 +- include/rfl/capnproto/Parser.hpp | 122 +++++++++++++++ include/rfl/capnproto/Reader.hpp | 215 +++++++++++++++++++++++++ include/rfl/capnproto/Schema.hpp | 43 +++++ include/rfl/capnproto/SchemaImpl.hpp | 46 ++++++ include/rfl/capnproto/Writer.hpp | 216 ++++++++++++++++++++++++++ include/rfl/capnproto/load.hpp | 23 +++ include/rfl/capnproto/read.hpp | 79 ++++++++++ include/rfl/capnproto/save.hpp | 25 +++ include/rfl/capnproto/schema/Type.hpp | 96 ++++++++++++ include/rfl/capnproto/to_schema.hpp | 35 +++++ include/rfl/capnproto/write.hpp | 63 ++++++++ src/reflectcpp_capnproto.cpp | 36 +++++ vcpkg.json | 4 + 14 files changed, 1017 insertions(+), 1 deletion(-) create mode 100644 include/rfl/capnproto/Parser.hpp create mode 100644 include/rfl/capnproto/Reader.hpp create mode 100644 include/rfl/capnproto/Schema.hpp create mode 100644 include/rfl/capnproto/SchemaImpl.hpp create mode 100644 include/rfl/capnproto/Writer.hpp create mode 100644 include/rfl/capnproto/load.hpp create mode 100644 include/rfl/capnproto/read.hpp create mode 100644 include/rfl/capnproto/save.hpp create mode 100644 include/rfl/capnproto/schema/Type.hpp create mode 100644 include/rfl/capnproto/to_schema.hpp create mode 100644 include/rfl/capnproto/write.hpp create mode 100644 src/reflectcpp_capnproto.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8824fa04..60f29fc1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ option(REFLECTCPP_BUILD_SHARED "Build shared library" ${BUILD_SHARED_LIBS}) option(REFLECTCPP_JSON "Enable JSON support" ON) # enabled by default option(REFLECTCPP_AVRO "Enable AVRO support" OFF) option(REFLECTCPP_BSON "Enable BSON support" OFF) +option(REFLECTCPP_CAPNPROTO "Enable Cap’n Proto support" OFF) option(REFLECTCPP_CBOR "Enable CBOR support" OFF) option(REFLECTCPP_FLEXBUFFERS "Enable flexbuffers support" OFF) option(REFLECTCPP_MSGPACK "Enable msgpack support" OFF) @@ -24,6 +25,7 @@ if(REFLECTCPP_BUILD_BENCHMARKS) set(REFLECTCPP_JSON ON CACHE BOOL "" FORCE) set(REFLECTCPP_AVRO ON CACHE BOOL "" FORCE) set(REFLECTCPP_BSON ON CACHE BOOL "" FORCE) + set(REFLECTCPP_CAPNPROTO ON CACHE BOOL "" FORCE) set(REFLECTCPP_CBOR ON CACHE BOOL "" FORCE) set(REFLECTCPP_FLEXBUFFERS ON CACHE BOOL "" FORCE) set(REFLECTCPP_MSGPACK ON CACHE BOOL "" FORCE) @@ -35,7 +37,8 @@ endif() if (REFLECTCPP_BUILD_TESTS OR REFLECTCPP_BUILD_BENCHMARKS OR (REFLECTCPP_JSON AND NOT REFLECTCPP_USE_BUNDLED_DEPENDENCIES) OR REFLECTCPP_AVRO OR - REFLECTCPP_BSON OR REFLECTCPP_CBOR OR REFLECTCPP_FLEXBUFFERS OR REFLECTCPP_MSGPACK OR REFLECTCPP_XML OR REFLECTCPP_TOML OR REFLECTCPP_UBJSON OR REFLECTCPP_YAML) + REFLECTCPP_BSON OR REFLECTCPP_CAPNPROTO OR REFLECTCPP_CBOR OR REFLECTCPP_FLEXBUFFERS OR + REFLECTCPP_MSGPACK OR REFLECTCPP_XML OR REFLECTCPP_TOML OR REFLECTCPP_UBJSON OR REFLECTCPP_YAML) # enable vcpkg per default if require features other than JSON set(REFLECTCPP_USE_VCPKG_DEFAULT ON) endif() @@ -129,6 +132,16 @@ if (REFLECTCPP_BSON) target_link_libraries(reflectcpp PUBLIC $,mongo::bson_static,mongo::bson_shared>) endif () +if (REFLECTCPP_CAPNPROTO) + list(APPEND REFLECT_CPP_SOURCES + src/reflectcpp_capnproto.cpp + ) + if (NOT TARGET CapnProto) + find_package(CapnProto CONFIG REQUIRED) + endif () + target_link_libraries(reflectcpp PUBLIC CapnProto::kj CapnProto::capnp CapnProto::capnpc CapnProto::kj-gzip) +endif () + if (REFLECTCPP_CBOR) list(APPEND REFLECT_CPP_SOURCES src/reflectcpp_cbor.cpp diff --git a/include/rfl/capnproto/Parser.hpp b/include/rfl/capnproto/Parser.hpp new file mode 100644 index 00000000..0112095b --- /dev/null +++ b/include/rfl/capnproto/Parser.hpp @@ -0,0 +1,122 @@ +#ifndef RFL_CAPNPROTO_PARSER_HPP_ +#define RFL_CAPNPROTO_PARSER_HPP_ + +#include "../Generic.hpp" +#include "../Tuple.hpp" +#include "../always_false.hpp" +#include "../parsing/Parser.hpp" +#include "Reader.hpp" +#include "Writer.hpp" + +namespace rfl { +namespace parsing { + +/// Cap'n proto requires us to explicitly set all fields. Because +/// of that, we require all of the fields and then set them to nullptr, if +/// necessary. +template + requires AreReaderAndWriter> +struct Parser, + ProcessorsType> + : public NamedTupleParser< + capnproto::Reader, capnproto::Writer, + /*_ignore_empty_containers=*/false, + /*_all_required=*/true, + /*_no_field_names=*/ProcessorsType::no_field_names_, ProcessorsType, + FieldTypes...> {}; + +template + requires AreReaderAndWriter> +struct Parser, + ProcessorsType> + : public TupleParser> {}; + +template + requires AreReaderAndWriter> +struct Parser, + ProcessorsType> + : public TupleParser> {}; + +template + requires AreReaderAndWriter +struct Parser { + template + static Result read(const capnproto::Reader&, const T&) noexcept { + static_assert(always_false_v, + "Generics are unsupported in Cap'n Proto."); + return Error("Unsupported"); + } + + template + static void write(const capnproto::Writer&, const Generic&, + const P&) noexcept { + static_assert(always_false_v

, + "Generics are unsupported in Cap'n Proto."); + } + + template + static schema::Type to_schema(T*) { + static_assert(always_false_v, + "Generics are unsupported in Cap'n Proto."); + return schema::Type{}; + } +}; + +template + requires AreReaderAndWriter< + capnproto::Reader, capnproto::Writer, + internal::Skip> +struct Parser, + ProcessorsType> { + using R = capnproto::Reader; + using W = capnproto::Writer; + + template + static Result> + read(const R&, const U&) noexcept { + static_assert(always_false_v, + "rfl::Skip is unsupported in Cap'n Proto."); + return Error("Unsupported"); + } + + template + static void write(const W& _w, + const internal::Skip& _skip, + const P& _parent) noexcept { + static_assert(always_false_v

, + "rfl::Skip is unsupported in Cap'n Proto."); + } + + template + static schema::Type to_schema(U* _definitions) { + static_assert(always_false_v, + "rfl::Skip is unsupported in Cap'n Proto."); + return schema::Type{}; + } +}; + +} // namespace parsing +} // namespace rfl + +namespace rfl { +namespace capnproto { + +template +using Parser = parsing::Parser; + +} +} // namespace rfl + +#endif diff --git a/include/rfl/capnproto/Reader.hpp b/include/rfl/capnproto/Reader.hpp new file mode 100644 index 00000000..6cfe8330 --- /dev/null +++ b/include/rfl/capnproto/Reader.hpp @@ -0,0 +1,215 @@ +#ifndef RFL_CAPNPROTO_READER_HPP_ +#define RFL_CAPNPROTO_READER_HPP_ + +#include + +#include +#include +#include +#include +#include +#include + +#include "../Bytestring.hpp" +#include "../Result.hpp" +#include "../always_false.hpp" +#include "../internal/is_literal.hpp" + +namespace rfl::capnproto { + +struct Reader { + struct CAPNPROTOInputArray { + const capnproto_value_t* val_; + }; + + struct CAPNPROTOInputObject { + const capnproto_value_t* val_; + }; + + struct CAPNPROTOInputMap { + const capnproto_value_t* val_; + }; + + struct CAPNPROTOInputUnion { + const capnproto_value_t* val_; + }; + + struct CAPNPROTOInputVar { + const capnproto_value_t* val_; + }; + + using InputArrayType = CAPNPROTOInputArray; + using InputObjectType = CAPNPROTOInputObject; + using InputMapType = CAPNPROTOInputMap; + using InputUnionType = CAPNPROTOInputUnion; + using InputVarType = CAPNPROTOInputVar; + + template + static constexpr bool has_custom_constructor = + (requires(InputVarType var) { T::from_capnproto_obj(var); }); + + bool is_empty(const InputVarType& _var) const noexcept; + + template + rfl::Result to_basic_type(const InputVarType& _var) const noexcept { + const auto type = capnproto_value_get_type(_var.val_); + if constexpr (std::is_same, std::string>()) { + const char* c_str = nullptr; + size_t size = 0; + const auto err = capnproto_value_get_string(_var.val_, &c_str, &size); + if (err) { + return Error("Could not cast to string."); + } + if (size == 0) { + return std::string(""); + } + return std::string(c_str, size - 1); + } else if constexpr (std::is_same, + rfl::Bytestring>()) { + const void* ptr = nullptr; + size_t size = 0; + const auto err = capnproto_value_get_bytes(_var.val_, &ptr, &size); + if (err) { + return Error("Could not cast to bytestring."); + } + return rfl::Bytestring(static_cast(ptr), size - 1); + } else if constexpr (std::is_same, bool>()) { + if (type != CAPNPROTO_BOOLEAN) { + return rfl::Error("Could not cast to boolean."); + } + int result = 0; + capnproto_value_get_boolean(_var.val_, &result); + return (result != 0); + + } else if constexpr (std::is_floating_point>() || + std::is_integral>()) { + if (type == CAPNPROTO_DOUBLE) { + double result = 0.0; + const auto err = capnproto_value_get_double(_var.val_, &result); + if (err) { + return Error("Could not cast to double."); + } + return static_cast(result); + } else if (type == CAPNPROTO_INT32) { + int32_t result = 0; + const auto err = capnproto_value_get_int(_var.val_, &result); + if (err) { + return Error("Could not cast to int32."); + } + return static_cast(result); + } else if (type == CAPNPROTO_INT64) { + int64_t result = 0; + const auto err = capnproto_value_get_long(_var.val_, &result); + if (err) { + return Error("Could not cast to int64."); + } + return static_cast(result); + } else if (type == CAPNPROTO_FLOAT) { + float result = 0.0; + const auto err = capnproto_value_get_float(_var.val_, &result); + if (err) { + return Error("Could not cast to float."); + } + return static_cast(result); + } + return rfl::Error( + "Could not cast to numeric value. The type must be integral, float " + "or double."); + + } else if constexpr (internal::is_literal_v) { + int value = 0; + const auto err = capnproto_value_get_enum(_var.val_, &value); + if (err) { + return Error("Could not cast to enum."); + } + return std::remove_cvref_t::from_value( + static_cast::ValueType>(value)); + + } else { + static_assert(rfl::always_false_v, "Unsupported type."); + } + } + + rfl::Result to_array(const InputVarType& _var) const noexcept; + + rfl::Result to_object( + const InputVarType& _var) const noexcept; + + rfl::Result to_map(const InputVarType& _var) const noexcept; + + rfl::Result to_union(const InputVarType& _var) const noexcept; + + template + std::optional read_array(const ArrayReader& _array_reader, + const InputArrayType& _arr) const noexcept { + size_t size = 0; + capnproto_value_get_size(_arr.val_, &size); + for (size_t ix = 0; ix < size; ++ix) { + capnproto_value_t element; + capnproto_value_get_by_index(_arr.val_, ix, &element, nullptr); + const auto err = _array_reader.read(InputVarType{&element}); + if (err) { + return err; + } + } + return std::nullopt; + } + + template + std::optional read_map(const MapReader& _map_reader, + const InputMapType& _map) const noexcept { + size_t size = 0; + capnproto_value_get_size(_map.val_, &size); + for (size_t ix = 0; ix < size; ++ix) { + capnproto_value_t element; + const char* key = nullptr; + capnproto_value_get_by_index(_map.val_, ix, &element, &key); + _map_reader.read(std::string_view(key), InputVarType{&element}); + } + return std::nullopt; + } + + template + std::optional read_object(const ObjectReader& _object_reader, + const InputObjectType& _obj) const noexcept { + size_t size = 0; + capnproto_value_get_size(_obj.val_, &size); + for (size_t ix = 0; ix < size; ++ix) { + capnproto_value_t element; + capnproto_value_get_by_index(_obj.val_, ix, &element, nullptr); + _object_reader.read(static_cast(ix), InputVarType{&element}); + } + return std::nullopt; + } + + template + rfl::Result read_union( + const InputUnionType& _union) const noexcept { + int disc = 0; + auto err = capnproto_value_get_discriminant(_union.val_, &disc); + if (err) { + return Error("Could not get the discriminant."); + } + capnproto_value_t value; + err = capnproto_value_get_current_branch(_union.val_, &value); + if (err) { + return Error("Could not cast the union type."); + } + return UnionReaderType::read(*this, static_cast(disc), + InputVarType{&value}); + } + + template + rfl::Result use_custom_constructor( + const InputVarType& _var) const noexcept { + try { + return T::from_capnproto_obj(_var); + } catch (std::exception& e) { + return rfl::Error(e.what()); + } + } +}; + +} // namespace rfl::capnproto + +#endif diff --git a/include/rfl/capnproto/Schema.hpp b/include/rfl/capnproto/Schema.hpp new file mode 100644 index 00000000..147ffedb --- /dev/null +++ b/include/rfl/capnproto/Schema.hpp @@ -0,0 +1,43 @@ +#ifndef RFL_CAPNPROTO_SCHEMA_HPP_ +#define RFL_CAPNPROTO_SCHEMA_HPP_ + +#include + +#include "../Ref.hpp" +#include "SchemaImpl.hpp" + +namespace rfl::capnproto { + +template +class Schema { + public: + using Type = std::remove_cvref_t; + + Schema(const std::string& _json_str) + : impl_(Ref::make(_json_str)) {} + + static Result> from_json(const std::string& _json_str) noexcept { + try { + return Schema(_json_str); + } catch (std::exception& e) { + return Error(e.what()); + } + } + + /// The JSON string used to create this schema. + const std::string& json_str() const { return impl_->json_str(); } + + /// The JSON string used to create this schema. + const std::string& str() const { return impl_->json_str(); } + + /// The interface used to create new values. + capnproto_value_iface_t* iface() const { return impl_->iface(); }; + + private: + /// We are using the "pimpl"-pattern + Ref impl_; +}; + +} // namespace rfl::capnproto + +#endif diff --git a/include/rfl/capnproto/SchemaImpl.hpp b/include/rfl/capnproto/SchemaImpl.hpp new file mode 100644 index 00000000..6730ccc9 --- /dev/null +++ b/include/rfl/capnproto/SchemaImpl.hpp @@ -0,0 +1,46 @@ +#ifndef RFL_CAPNPROTO_SCHEMAIMPL_HPP_ +#define RFL_CAPNPROTO_SCHEMAIMPL_HPP_ + +#include + +#include + +#include "../Box.hpp" +#include "../Result.hpp" + +namespace rfl::capnproto { + +class SchemaImpl { + public: + SchemaImpl(const std::string& _json_str); + + ~SchemaImpl(); + + SchemaImpl(const SchemaImpl& _other) = delete; + + SchemaImpl(SchemaImpl&& _other) noexcept; + + SchemaImpl& operator=(const SchemaImpl& _other) = delete; + + SchemaImpl& operator=(SchemaImpl&& _other) noexcept; + + /// The JSON string used to create this schema. + const std::string& json_str() const { return json_str_; } + + /// The interface used to create new values. + capnproto_value_iface_t* iface() const { return iface_; }; + + private: + /// The JSON string used to create the schema. + std::string json_str_; + + /// The actual schema + Box schema_; + + /// The interface used to create new, generic classes. + capnproto_value_iface_t* iface_; +}; + +} // namespace rfl::capnproto + +#endif diff --git a/include/rfl/capnproto/Writer.hpp b/include/rfl/capnproto/Writer.hpp new file mode 100644 index 00000000..9f101139 --- /dev/null +++ b/include/rfl/capnproto/Writer.hpp @@ -0,0 +1,216 @@ +#ifndef RFL_CAPNPROTO_WRITER_HPP_ +#define RFL_CAPNPROTO_WRITER_HPP_ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../Box.hpp" +#include "../Bytestring.hpp" +#include "../Ref.hpp" +#include "../Result.hpp" +#include "../always_false.hpp" +#include "../internal/is_literal.hpp" + +namespace rfl::capnproto { + +class Writer { + public: + struct CAPNPROTOOutputArray { + capnproto_value_t val_; + }; + + struct CAPNPROTOOutputMap { + capnproto_value_t val_; + }; + + struct CAPNPROTOOutputObject { + capnproto_value_t val_; + }; + + struct CAPNPROTOOutputUnion { + capnproto_value_t val_; + }; + + struct CAPNPROTOOutputVar { + capnproto_value_t val_; + }; + + using OutputArrayType = CAPNPROTOOutputArray; + using OutputMapType = CAPNPROTOOutputMap; + using OutputObjectType = CAPNPROTOOutputObject; + using OutputUnionType = CAPNPROTOOutputUnion; + using OutputVarType = CAPNPROTOOutputVar; + + Writer(capnproto_value_t* _root); + + ~Writer(); + + OutputArrayType array_as_root(const size_t _size) const noexcept; + + OutputMapType map_as_root(const size_t _size) const noexcept; + + OutputObjectType object_as_root(const size_t _size) const noexcept; + + OutputVarType null_as_root() const noexcept; + + OutputUnionType union_as_root() const noexcept; + + template + OutputVarType value_as_root(const T& _var) const noexcept { + set_value(_var, root_); + return OutputVarType{*root_}; + } + + OutputArrayType add_array_to_array(const size_t _size, + OutputArrayType* _parent) const noexcept; + + OutputArrayType add_array_to_map(const std::string_view& _name, + const size_t _size, + OutputMapType* _parent) const noexcept; + + OutputArrayType add_array_to_object(const std::string_view& _name, + const size_t _size, + OutputObjectType* _parent) const noexcept; + + OutputArrayType add_array_to_union(const size_t _index, const size_t _size, + OutputUnionType* _parent) const noexcept; + + OutputMapType add_map_to_array(const size_t _size, + OutputArrayType* _parent) const noexcept; + + OutputMapType add_map_to_map(const std::string_view& _name, + const size_t _size, + OutputMapType* _parent) const noexcept; + + OutputMapType add_map_to_object(const std::string_view& _name, + const size_t _size, + OutputObjectType* _parent) const noexcept; + + OutputMapType add_map_to_union(const size_t _index, const size_t _size, + OutputUnionType* _parent) const noexcept; + + OutputObjectType add_object_to_array(const size_t _size, + OutputArrayType* _parent) const noexcept; + + OutputObjectType add_object_to_map(const std::string_view& _name, + const size_t _size, + OutputMapType* _parent) const noexcept; + + OutputObjectType add_object_to_object( + const std::string_view& _name, const size_t _size, + OutputObjectType* _parent) const noexcept; + + OutputObjectType add_object_to_union(const size_t _index, const size_t _size, + OutputUnionType* _parent) const noexcept; + + OutputUnionType add_union_to_array(OutputArrayType* _parent) const noexcept; + + OutputUnionType add_union_to_map(const std::string_view& _name, + OutputMapType* _parent) const noexcept; + + OutputUnionType add_union_to_object(const std::string_view& _name, + OutputObjectType* _parent) const noexcept; + + OutputUnionType add_union_to_union(const size_t _index, + OutputUnionType* _parent) const noexcept; + + OutputVarType add_null_to_array(OutputArrayType* _parent) const noexcept; + + OutputVarType add_null_to_map(const std::string_view& _name, + OutputMapType* _parent) const noexcept; + + OutputVarType add_null_to_object(const std::string_view& _name, + OutputObjectType* _parent) const noexcept; + + OutputVarType add_null_to_union(const size_t _index, + OutputUnionType* _parent) const noexcept; + + template + OutputVarType add_value_to_array(const T& _var, + OutputArrayType* _parent) const noexcept { + capnproto_value_t new_value; + capnproto_value_append(&_parent->val_, &new_value, nullptr); + set_value(_var, &new_value); + return OutputVarType{new_value}; + } + + template + OutputVarType add_value_to_map(const std::string_view& _name, const T& _var, + OutputMapType* _parent) const noexcept { + capnproto_value_t new_value; + capnproto_value_add(&_parent->val_, _name.data(), &new_value, nullptr, nullptr); + set_value(_var, &new_value); + return OutputVarType{new_value}; + } + + template + OutputVarType add_value_to_object(const std::string_view& _name, + const T& _var, + OutputObjectType* _parent) const noexcept { + capnproto_value_t new_value; + capnproto_value_get_by_name(&_parent->val_, _name.data(), &new_value, nullptr); + set_value(_var, &new_value); + return OutputVarType{new_value}; + } + + template + OutputVarType add_value_to_union(const size_t _index, const T& _var, + OutputUnionType* _parent) const noexcept { + capnproto_value_t new_value; + capnproto_value_set_branch(&_parent->val_, static_cast(_index), &new_value); + set_value(_var, &new_value); + return OutputVarType{new_value}; + } + + void end_array(OutputArrayType* _arr) const noexcept {} + + void end_map(OutputMapType* _obj) const noexcept {} + + void end_object(OutputObjectType* _obj) const noexcept {} + + private: + template + void set_value(const T& _var, capnproto_value_t* _val) const noexcept { + if constexpr (std::is_same, std::string>()) { + capnproto_value_set_string_len(_val, _var.c_str(), _var.size() + 1); + + } else if constexpr (std::is_same, + rfl::Bytestring>()) { + auto var = _var; + capnproto_value_set_bytes(_val, var.data(), var.size() + 1); + + } else if constexpr (std::is_same, bool>()) { + capnproto_value_set_boolean(_val, _var); + + } else if constexpr (std::is_floating_point>()) { + capnproto_value_set_double(_val, static_cast(_var)); + + } else if constexpr (std::is_integral>()) { + capnproto_value_set_long(_val, static_cast(_var)); + + } else if constexpr (internal::is_literal_v) { + capnproto_value_set_enum(_val, static_cast(_var.value())); + + } else { + static_assert(rfl::always_false_v, "Unsupported type."); + } + } + + private: + capnproto_value_t* root_; +}; + +} // namespace rfl::capnproto + +#endif diff --git a/include/rfl/capnproto/load.hpp b/include/rfl/capnproto/load.hpp new file mode 100644 index 00000000..e40c0d96 --- /dev/null +++ b/include/rfl/capnproto/load.hpp @@ -0,0 +1,23 @@ +#ifndef RFL_CAPNPROTO_LOAD_HPP_ +#define RFL_CAPNPROTO_LOAD_HPP_ + +#include "../Processors.hpp" +#include "../Result.hpp" +#include "../io/load_bytes.hpp" +#include "read.hpp" + +namespace rfl { +namespace capnproto { + +template +Result load(const std::string& _fname) { + const auto read_bytes = [](const auto& _bytes) { + return read(_bytes); + }; + return rfl::io::load_bytes(_fname).and_then(read_bytes); +} + +} // namespace capnproto +} // namespace rfl + +#endif diff --git a/include/rfl/capnproto/read.hpp b/include/rfl/capnproto/read.hpp new file mode 100644 index 00000000..01e8c0bd --- /dev/null +++ b/include/rfl/capnproto/read.hpp @@ -0,0 +1,79 @@ +#ifndef RFL_CAPNPROTO_READ_HPP_ +#define RFL_CAPNPROTO_READ_HPP_ + +#include + +#include +#include +#include +#include + +#include "../Processors.hpp" +#include "../internal/wrap_in_rfl_array_t.hpp" +#include "Parser.hpp" +#include "Reader.hpp" +#include "Schema.hpp" +#include "to_schema.hpp" + +namespace rfl::capnproto { + +using InputObjectType = typename Reader::InputObjectType; +using InputVarType = typename Reader::InputVarType; + +/// Parses an object from a CAPNPROTO var. +template +auto read(const InputVarType& _obj) { + const auto r = Reader(); + return Parser>::read(r, _obj); +} + +/// Parses an object from CAPNPROTO using reflection. +template +Result> read( + const char* _bytes, const size_t _size, const Schema& _schema) noexcept { + capnproto_reader_t capnproto_reader = capnproto_reader_memory(_bytes, _size); + capnproto_value_t root; + capnproto_generic_value_new(_schema.iface(), &root); + auto err = capnproto_value_read(capnproto_reader, &root); + if (err) { + capnproto_value_decref(&root); + capnproto_reader_free(capnproto_reader); + return Error(std::string("Could not read root value: ") + + capnproto_strerror()); + } + auto result = read(InputVarType{&root}); + capnproto_value_decref(&root); + capnproto_reader_free(capnproto_reader); + return result; +} + +/// Parses an object from CAPNPROTO using reflection. +template +auto read(const char* _bytes, const size_t _size) noexcept { + const auto schema = to_schema, Ps...>(); + return read(_bytes, _size, schema); +} + +/// Parses an object from CAPNPROTO using reflection. +template +auto read(const std::vector& _bytes, const Schema& _schema) { + return read(_bytes.data(), _bytes.size(), _schema); +} + +/// Parses an object from CAPNPROTO using reflection. +template +auto read(const std::vector& _bytes) { + return read(_bytes.data(), _bytes.size()); +} + +/// Parses an object from a stream. +template +auto read(std::istream& _stream) { + std::istreambuf_iterator begin(_stream), end; + auto bytes = std::vector(begin, end); + return read(bytes.data(), bytes.size()); +} + +} // namespace rfl::capnproto + +#endif diff --git a/include/rfl/capnproto/save.hpp b/include/rfl/capnproto/save.hpp new file mode 100644 index 00000000..2f4afe0f --- /dev/null +++ b/include/rfl/capnproto/save.hpp @@ -0,0 +1,25 @@ +#ifndef RFL_CAPNPROTO_SAVE_HPP_ +#define RFL_CAPNPROTO_SAVE_HPP_ + +#include +#include +#include + +#include "../Result.hpp" +#include "../io/save_bytes.hpp" +#include "write.hpp" + +namespace rfl::capnproto { + +template +Result save(const std::string& _fname, const auto& _obj) { + const auto write_func = [](const auto& _obj, + std::ostream& _stream) -> std::ostream& { + return write(_obj, _stream); + }; + return rfl::io::save_bytes(_fname, _obj, write_func); +} + +} // namespace rfl::capnproto + +#endif diff --git a/include/rfl/capnproto/schema/Type.hpp b/include/rfl/capnproto/schema/Type.hpp new file mode 100644 index 00000000..2a3ce98e --- /dev/null +++ b/include/rfl/capnproto/schema/Type.hpp @@ -0,0 +1,96 @@ +#ifndef RFL_CAPNPROTO_SCHEMA_TYPE_HPP_ +#define RFL_CAPNPROTO_SCHEMA_TYPE_HPP_ + +#include +#include +#include +#include + +#include "../../Literal.hpp" +#include "../../Object.hpp" +#include "../../Rename.hpp" +#include "../../Variant.hpp" + +namespace rfl::capnproto::schema { + +struct Type { + struct Null { + Literal<"null"> type; + }; + + struct Boolean { + Literal<"boolean"> type; + }; + + struct Int { + Literal<"int"> type; + }; + + struct Long { + Literal<"long"> type; + }; + + struct Float { + Literal<"float"> type; + }; + + struct Double { + Literal<"float"> type; + }; + + struct Bytes { + Literal<"bytes"> type; + }; + + struct String { + Literal<"string"> type; + }; + + struct RecordField { + std::string name; + rfl::Ref type; + }; + + struct Record { + Literal<"record"> type; + std::string name; + std::vector fields; + std::optional doc; + }; + + struct Enum { + Literal<"enum"> type; + std::string name; + std::vector symbols; + }; + + struct Array { + Literal<"array"> type; + rfl::Ref items; + Rename<"default", std::vector> default_; + }; + + struct Map { + Literal<"map"> type; + rfl::Ref values; + Rename<"default", std::map> default_; + }; + + struct Reference { + std::string type; + }; + + using ReflectionType = + rfl::Variant>; + + const auto& reflection() const { return value; } + + Type with_name(const std::string& _name) const; + + ReflectionType value; +}; + +} // namespace rfl::capnproto::schema + +#endif diff --git a/include/rfl/capnproto/to_schema.hpp b/include/rfl/capnproto/to_schema.hpp new file mode 100644 index 00000000..11c92794 --- /dev/null +++ b/include/rfl/capnproto/to_schema.hpp @@ -0,0 +1,35 @@ +#ifndef RFL_CAPNPROTO_TOSCHEMA_HPP_ +#define RFL_CAPNPROTO_TOSCHEMA_HPP_ + +#include +#include +#include + +#include "../Literal.hpp" +#include "../Processors.hpp" +#include "../Variant.hpp" +#include "../json.hpp" +#include "../parsing/schema/Type.hpp" +#include "../parsing/schema/ValidationType.hpp" +#include "../parsing/schema/make.hpp" +#include "Reader.hpp" +#include "Schema.hpp" +#include "Writer.hpp" +#include "schema/Type.hpp" + +namespace rfl::capnproto { + +std::string to_json_representation( + const parsing::schema::Definition& internal_schema); + +/// Returns the Capnproto schema for a class. +template +Schema to_schema() noexcept { + const auto internal_schema = + parsing::schema::make>(); + const auto json_str = to_json_representation(internal_schema); + return std::move(Schema::from_json(json_str).value()); +} +} // namespace rfl::capnproto + +#endif diff --git a/include/rfl/capnproto/write.hpp b/include/rfl/capnproto/write.hpp new file mode 100644 index 00000000..70494248 --- /dev/null +++ b/include/rfl/capnproto/write.hpp @@ -0,0 +1,63 @@ +#ifndef RFL_CAPNPROTO_WRITE_HPP_ +#define RFL_CAPNPROTO_WRITE_HPP_ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../parsing/Parent.hpp" +#include "Parser.hpp" +#include "Schema.hpp" +#include "Writer.hpp" +#include "to_schema.hpp" + +namespace rfl::capnproto { + +/// Returns CAPNPROTO bytes. +template +std::vector write(const auto& _obj, const auto& _schema) noexcept { + using T = std::remove_cvref_t; + using U = typename std::remove_cvref_t::Type; + using ParentType = parsing::Parent; + static_assert(std::is_same(), + "The schema must be compatible with the type to write."); + capnproto_value_t root; + capnproto_generic_value_new(_schema.iface(), &root); + const auto writer = Writer(&root); + Parser>::write(writer, _obj, + typename ParentType::Root{}); + size_t size = 0; + capnproto_value_sizeof(&root, &size); + std::vector buffer(size); + capnproto_writer_t capnproto_writer = capnproto_writer_memory(buffer.data(), buffer.size()); + capnproto_value_write(capnproto_writer, &root); + capnproto_value_decref(&root); + capnproto_writer_free(capnproto_writer); + return buffer; +} + +/// Returns CAPNPROTO bytes. +template +std::vector write(const auto& _obj) noexcept { + using T = std::remove_cvref_t; + const auto schema = to_schema(); + return write(_obj, schema); +} + +/// Writes a CAPNPROTO into an ostream. +template +std::ostream& write(const auto& _obj, std::ostream& _stream) noexcept { + auto buffer = write(_obj); + _stream.write(buffer.data(), buffer.size()); + return _stream; +} + +} // namespace rfl::capnproto + +#endif diff --git a/src/reflectcpp_capnproto.cpp b/src/reflectcpp_capnproto.cpp new file mode 100644 index 00000000..877ea0ca --- /dev/null +++ b/src/reflectcpp_capnproto.cpp @@ -0,0 +1,36 @@ +/* + +MIT License + +Copyright (c) 2023-2024 Code17 GmbH + +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. + +*/ + +// This file include all other source files, so that the user of the library +// don't need to add multiple source files into their build. +// Also, this speeds up compile time, compared to multiple separate .cpp files +// compilation. +/* +#include "rfl/avro/Reader.cpp" +#include "rfl/avro/SchemaImpl.cpp" +#include "rfl/avro/Type.cpp" +#include "rfl/avro/Writer.cpp" +#include "rfl/avro/to_schema.cpp"*/ diff --git a/vcpkg.json b/vcpkg.json index ceaa1802..705b2865 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -7,6 +7,10 @@ "name": "avro-c", "version>=": "1.11.3" }, + { + "name": "capnproto", + "version>=": "1.0.2#1" + }, { "name": "ctre", "version>=": "3.8" From 23c76ff16593171ad4af3101284f8840acb8fbda Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 22 Dec 2024 18:28:47 +0100 Subject: [PATCH 02/57] Made sure the schema compiles --- include/rfl/capnproto/Schema.hpp | 15 +++++++------- include/rfl/capnproto/SchemaImpl.hpp | 30 ++++++++++------------------ src/reflectcpp_capnproto.cpp | 3 +++ src/rfl/capnproto/SchemaImpl.cpp | 22 ++++++++++++++++++++ 4 files changed, 43 insertions(+), 27 deletions(-) create mode 100644 src/rfl/capnproto/SchemaImpl.cpp diff --git a/include/rfl/capnproto/Schema.hpp b/include/rfl/capnproto/Schema.hpp index 147ffedb..f254f6d4 100644 --- a/include/rfl/capnproto/Schema.hpp +++ b/include/rfl/capnproto/Schema.hpp @@ -1,6 +1,8 @@ #ifndef RFL_CAPNPROTO_SCHEMA_HPP_ #define RFL_CAPNPROTO_SCHEMA_HPP_ +#include + #include #include "../Ref.hpp" @@ -13,8 +15,7 @@ class Schema { public: using Type = std::remove_cvref_t; - Schema(const std::string& _json_str) - : impl_(Ref::make(_json_str)) {} + Schema(const std::string& _str) : impl_(Ref::make(_str)) {} static Result> from_json(const std::string& _json_str) noexcept { try { @@ -25,14 +26,12 @@ class Schema { } /// The JSON string used to create this schema. - const std::string& json_str() const { return impl_->json_str(); } - - /// The JSON string used to create this schema. - const std::string& str() const { return impl_->json_str(); } + const std::string& str() const { return impl_->str(); } - /// The interface used to create new values. - capnproto_value_iface_t* iface() const { return impl_->iface(); }; + /// The interface used to generate new values. + const capnp::StructSchema& value() const { return impl_->value(); }; + private: private: /// We are using the "pimpl"-pattern Ref impl_; diff --git a/include/rfl/capnproto/SchemaImpl.hpp b/include/rfl/capnproto/SchemaImpl.hpp index 6730ccc9..8c3512fe 100644 --- a/include/rfl/capnproto/SchemaImpl.hpp +++ b/include/rfl/capnproto/SchemaImpl.hpp @@ -1,7 +1,7 @@ #ifndef RFL_CAPNPROTO_SCHEMAIMPL_HPP_ #define RFL_CAPNPROTO_SCHEMAIMPL_HPP_ -#include +#include #include @@ -12,33 +12,25 @@ namespace rfl::capnproto { class SchemaImpl { public: - SchemaImpl(const std::string& _json_str); + SchemaImpl(const std::string& _str); - ~SchemaImpl(); - - SchemaImpl(const SchemaImpl& _other) = delete; - - SchemaImpl(SchemaImpl&& _other) noexcept; - - SchemaImpl& operator=(const SchemaImpl& _other) = delete; - - SchemaImpl& operator=(SchemaImpl&& _other) noexcept; + ~SchemaImpl() = default; /// The JSON string used to create this schema. - const std::string& json_str() const { return json_str_; } + const std::string& str() const { return str_; } /// The interface used to create new values. - capnproto_value_iface_t* iface() const { return iface_; }; + const capnp::StructSchema& value() const { return *schema_; }; private: - /// The JSON string used to create the schema. - std::string json_str_; + static capnp::StructSchema make_schema(const std::string& _str); - /// The actual schema - Box schema_; + private: + /// The string used to create the schema. + std::string str_; - /// The interface used to create new, generic classes. - capnproto_value_iface_t* iface_; + /// The actual schema + Box schema_; }; } // namespace rfl::capnproto diff --git a/src/reflectcpp_capnproto.cpp b/src/reflectcpp_capnproto.cpp index 877ea0ca..dafeb8c7 100644 --- a/src/reflectcpp_capnproto.cpp +++ b/src/reflectcpp_capnproto.cpp @@ -28,9 +28,12 @@ SOFTWARE. // don't need to add multiple source files into their build. // Also, this speeds up compile time, compared to multiple separate .cpp files // compilation. + /* #include "rfl/avro/Reader.cpp" #include "rfl/avro/SchemaImpl.cpp" #include "rfl/avro/Type.cpp" #include "rfl/avro/Writer.cpp" #include "rfl/avro/to_schema.cpp"*/ +#include "rfl/capnproto/SchemaImpl.cpp" + diff --git a/src/rfl/capnproto/SchemaImpl.cpp b/src/rfl/capnproto/SchemaImpl.cpp new file mode 100644 index 00000000..2960da69 --- /dev/null +++ b/src/rfl/capnproto/SchemaImpl.cpp @@ -0,0 +1,22 @@ +#include "rfl/capnproto/SchemaImpl.hpp" + +#include +#include + +namespace rfl::capnproto { + +SchemaImpl::SchemaImpl(const std::string& _str) + : str_(_str), schema_(Box::make(make_schema(_str))) {} + +capnp::StructSchema make_schema(const std::string& _str) { + auto dir = kj::newInMemoryDirectory(kj::nullClock()); + auto path = kj::Path::parse("foo/bar.capnp"); + dir->openFile(path, kj::WriteMode::CREATE | kj::WriteMode::CREATE_PARENT) + ->writeAll(_str); + capnp::SchemaParser parser; + auto parsed_schema = + parser.parseFromDirectory(*dir, std::move(path), nullptr); + return parsed_schema.asStruct(); +} + +} // namespace rfl::capnproto From c22abd070d0ff90a4eed84499f6c8be491d91f35 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 22 Dec 2024 19:48:48 +0100 Subject: [PATCH 03/57] Made sure that the schema compiles --- include/rfl/capnproto.hpp | 15 +++++ include/rfl/capnproto/Schema.hpp | 13 ++-- include/rfl/capnproto/SchemaImpl.hpp | 8 +-- src/rfl/capnproto/SchemaImpl.cpp | 8 +-- tests/CMakeLists.txt | 4 ++ tests/capnproto/CMakeLists.txt | 19 ++++++ tests/capnproto/test_tutorial_example.cpp | 72 +++++++++++++++++++++++ tests/capnproto/write_and_read.hpp | 20 +++++++ 8 files changed, 143 insertions(+), 16 deletions(-) create mode 100644 include/rfl/capnproto.hpp create mode 100644 tests/capnproto/CMakeLists.txt create mode 100644 tests/capnproto/test_tutorial_example.cpp create mode 100644 tests/capnproto/write_and_read.hpp diff --git a/include/rfl/capnproto.hpp b/include/rfl/capnproto.hpp new file mode 100644 index 00000000..96ee70dc --- /dev/null +++ b/include/rfl/capnproto.hpp @@ -0,0 +1,15 @@ +#ifndef RFL_CAPNPROTO_HPP_ +#define RFL_CAPNPROTO_HPP_ + +#include "../rfl.hpp" +// #include "capnproto/Parser.hpp" +// #include "capnproto/Reader.hpp" +#include "capnproto/Schema.hpp" +// #include "capnproto/Writer.hpp" +// #include "capnproto/load.hpp" +// #include "capnproto/read.hpp" +// #include "capnproto/save.hpp" +// #include "capnproto/to_schema.hpp" +// #include "capnproto/write.hpp" + +#endif diff --git a/include/rfl/capnproto/Schema.hpp b/include/rfl/capnproto/Schema.hpp index f254f6d4..2c4e8893 100644 --- a/include/rfl/capnproto/Schema.hpp +++ b/include/rfl/capnproto/Schema.hpp @@ -1,7 +1,7 @@ #ifndef RFL_CAPNPROTO_SCHEMA_HPP_ #define RFL_CAPNPROTO_SCHEMA_HPP_ -#include +#include #include @@ -17,21 +17,20 @@ class Schema { Schema(const std::string& _str) : impl_(Ref::make(_str)) {} - static Result> from_json(const std::string& _json_str) noexcept { + static Result> from_string(const std::string& _str) noexcept { try { - return Schema(_json_str); + return Schema(_str); } catch (std::exception& e) { return Error(e.what()); } } - /// The JSON string used to create this schema. + /// The string used to create this schema. const std::string& str() const { return impl_->str(); } - /// The interface used to generate new values. - const capnp::StructSchema& value() const { return impl_->value(); }; + /// The struct schema used to generate new values. + const capnp::ParsedSchema& value() const { return impl_->value(); }; - private: private: /// We are using the "pimpl"-pattern Ref impl_; diff --git a/include/rfl/capnproto/SchemaImpl.hpp b/include/rfl/capnproto/SchemaImpl.hpp index 8c3512fe..ee9d2ec8 100644 --- a/include/rfl/capnproto/SchemaImpl.hpp +++ b/include/rfl/capnproto/SchemaImpl.hpp @@ -1,7 +1,7 @@ #ifndef RFL_CAPNPROTO_SCHEMAIMPL_HPP_ #define RFL_CAPNPROTO_SCHEMAIMPL_HPP_ -#include +#include #include @@ -20,17 +20,17 @@ class SchemaImpl { const std::string& str() const { return str_; } /// The interface used to create new values. - const capnp::StructSchema& value() const { return *schema_; }; + const capnp::ParsedSchema& value() const { return *schema_; }; private: - static capnp::StructSchema make_schema(const std::string& _str); + static capnp::ParsedSchema make_schema(const std::string& _str); private: /// The string used to create the schema. std::string str_; /// The actual schema - Box schema_; + Box schema_; }; } // namespace rfl::capnproto diff --git a/src/rfl/capnproto/SchemaImpl.cpp b/src/rfl/capnproto/SchemaImpl.cpp index 2960da69..2bf86dbb 100644 --- a/src/rfl/capnproto/SchemaImpl.cpp +++ b/src/rfl/capnproto/SchemaImpl.cpp @@ -6,17 +6,15 @@ namespace rfl::capnproto { SchemaImpl::SchemaImpl(const std::string& _str) - : str_(_str), schema_(Box::make(make_schema(_str))) {} + : str_(_str), schema_(Box::make(make_schema(_str))) {} -capnp::StructSchema make_schema(const std::string& _str) { +capnp::ParsedSchema SchemaImpl::make_schema(const std::string& _str) { auto dir = kj::newInMemoryDirectory(kj::nullClock()); auto path = kj::Path::parse("foo/bar.capnp"); dir->openFile(path, kj::WriteMode::CREATE | kj::WriteMode::CREATE_PARENT) ->writeAll(_str); capnp::SchemaParser parser; - auto parsed_schema = - parser.parseFromDirectory(*dir, std::move(path), nullptr); - return parsed_schema.asStruct(); + return parser.parseFromDirectory(*dir, std::move(path), nullptr); } } // namespace rfl::capnproto diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d99a20a2..cdd1a6d5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -20,6 +20,10 @@ if (REFLECTCPP_BSON) add_subdirectory(bson) endif() +if (REFLECTCPP_CAPNPROTO) + add_subdirectory(capnproto) +endif() + if (REFLECTCPP_CBOR) add_subdirectory(cbor) endif() diff --git a/tests/capnproto/CMakeLists.txt b/tests/capnproto/CMakeLists.txt new file mode 100644 index 00000000..4a6fb365 --- /dev/null +++ b/tests/capnproto/CMakeLists.txt @@ -0,0 +1,19 @@ +project(reflect-cpp-capnproto-tests) + +file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "*.cpp") + +add_executable( + reflect-cpp-capnproto-tests + ${SOURCES} +) +target_precompile_headers(reflect-cpp-capnproto-tests PRIVATE [["rfl.hpp"]] ) + + +target_link_libraries( + reflect-cpp-capnproto-tests + PRIVATE + "${REFLECT_CPP_GTEST_LIB}" +) + +find_package(GTest) +gtest_discover_tests(reflect-cpp-capnproto-tests) diff --git a/tests/capnproto/test_tutorial_example.cpp b/tests/capnproto/test_tutorial_example.cpp new file mode 100644 index 00000000..dc24cddf --- /dev/null +++ b/tests/capnproto/test_tutorial_example.cpp @@ -0,0 +1,72 @@ +#include + +#include +#include +#include +#include +#include + +// #include "write_and_read.hpp" + +/// The basic example from the Avro C tutorial. +namespace test_tutorial_example { + +const std::string ADDRESS_BOOK_SCHEMA = R"( +@0xb07899ba441fd7ec; + +struct Person { + id @0 :UInt32; + name @1 :Text; + email @2 :Text; + phones @3 :List(PhoneNumber); + + struct PhoneNumber { + number @0 :Text; + type @1 :Type; + + enum Type { + mobile @0; + home @1; + work @2; + } + } + + employment :union { + unemployed @4 :Void; + employer @5 :Text; + school @6 :Text; + selfEmployed @7 :Void; + # We assume that a person is only one of these. + } +} + +struct AddressBook { + people @0 :List(Person); +} +)"; + +enum class Type { mobile, home, work }; + +struct PhoneNumber { + std::string number; + std::string email; + Type type; +}; + +struct Person { + uint32_t id; + std::string name; + std::string email; + // TODO: Employment. +}; + +struct AddressBook { + std::vector people; +}; + +TEST(capnproto, test_tutorial_example) { + const auto schema = + rfl::capnproto::Schema::from_string(ADDRESS_BOOK_SCHEMA).value(); + std::cout << schema.str() << std::endl; +} +} // namespace test_tutorial_example diff --git a/tests/capnproto/write_and_read.hpp b/tests/capnproto/write_and_read.hpp new file mode 100644 index 00000000..81692be4 --- /dev/null +++ b/tests/capnproto/write_and_read.hpp @@ -0,0 +1,20 @@ +#ifndef WRITE_AND_READ_ +#define WRITE_AND_READ_ + +#include + +#include +#include +#include + +template +void write_and_read(const auto& _struct) { + using T = std::remove_cvref_t; + const auto serialized1 = rfl::capnproto::write(_struct); + const auto res = rfl::capnproto::read(serialized1); + EXPECT_TRUE(res && true) << "Test failed on read. Error: " + << res.error().value().what(); + const auto serialized2 = rfl::capnproto::write(res.value()); + EXPECT_EQ(serialized1, serialized2); +} +#endif From fd3b991e3302807e5d76b5e1c508cbf946554a0e Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Mon, 23 Dec 2024 00:39:13 +0100 Subject: [PATCH 04/57] Set up the reader --- include/rfl/capnproto.hpp | 2 +- include/rfl/capnproto/Reader.hpp | 171 +++++++++++++------------------ src/reflectcpp_capnproto.cpp | 1 + src/rfl/capnproto/Reader.cpp | 47 +++++++++ 4 files changed, 121 insertions(+), 100 deletions(-) create mode 100644 src/rfl/capnproto/Reader.cpp diff --git a/include/rfl/capnproto.hpp b/include/rfl/capnproto.hpp index 96ee70dc..ebac012a 100644 --- a/include/rfl/capnproto.hpp +++ b/include/rfl/capnproto.hpp @@ -3,7 +3,7 @@ #include "../rfl.hpp" // #include "capnproto/Parser.hpp" -// #include "capnproto/Reader.hpp" +#include "capnproto/Reader.hpp" #include "capnproto/Schema.hpp" // #include "capnproto/Writer.hpp" // #include "capnproto/load.hpp" diff --git a/include/rfl/capnproto/Reader.hpp b/include/rfl/capnproto/Reader.hpp index 6cfe8330..964ef0a3 100644 --- a/include/rfl/capnproto/Reader.hpp +++ b/include/rfl/capnproto/Reader.hpp @@ -1,7 +1,7 @@ #ifndef RFL_CAPNPROTO_READER_HPP_ #define RFL_CAPNPROTO_READER_HPP_ -#include +#include #include #include @@ -18,31 +18,32 @@ namespace rfl::capnproto { struct Reader { - struct CAPNPROTOInputArray { - const capnproto_value_t* val_; + struct CapNProtoInputArray { + capnp::DynamicList::Reader val_; }; - struct CAPNPROTOInputObject { - const capnproto_value_t* val_; + struct CapNProtoInputObject { + capnp::DynamicStruct::Reader val_; }; - struct CAPNPROTOInputMap { - const capnproto_value_t* val_; + struct CapNProtoInputMap { + capnp::DynamicList::Reader val_; }; - struct CAPNPROTOInputUnion { - const capnproto_value_t* val_; + struct CapNProtoInputUnion { + // TODO: Is this right? + capnp::DynamicStruct::Reader val_; }; - struct CAPNPROTOInputVar { - const capnproto_value_t* val_; + struct CapNProtoInputVar { + capnp::DynamicValue::Reader val_; }; - using InputArrayType = CAPNPROTOInputArray; - using InputObjectType = CAPNPROTOInputObject; - using InputMapType = CAPNPROTOInputMap; - using InputUnionType = CAPNPROTOInputUnion; - using InputVarType = CAPNPROTOInputVar; + using InputArrayType = CapNProtoInputArray; + using InputObjectType = CapNProtoInputObject; + using InputMapType = CapNProtoInputMap; + using InputUnionType = CapNProtoInputUnion; + using InputVarType = CapNProtoInputVar; template static constexpr bool has_custom_constructor = @@ -52,79 +53,52 @@ struct Reader { template rfl::Result to_basic_type(const InputVarType& _var) const noexcept { - const auto type = capnproto_value_get_type(_var.val_); + const auto type = _var.val_.getType(); if constexpr (std::is_same, std::string>()) { - const char* c_str = nullptr; - size_t size = 0; - const auto err = capnproto_value_get_string(_var.val_, &c_str, &size); - if (err) { + if (type != capnp::DynamicValue::TEXT) { return Error("Could not cast to string."); } - if (size == 0) { - return std::string(""); - } - return std::string(c_str, size - 1); - } else if constexpr (std::is_same, - rfl::Bytestring>()) { - const void* ptr = nullptr; - size_t size = 0; - const auto err = capnproto_value_get_bytes(_var.val_, &ptr, &size); - if (err) { - return Error("Could not cast to bytestring."); - } - return rfl::Bytestring(static_cast(ptr), size - 1); + return std::string(_var.val_.as().cStr()); + // TODO + /*} else if constexpr (std::is_same, + rfl::Bytestring>()) { + const void* ptr = nullptr; + size_t size = 0; + const auto err = capnproto_value_get_bytes(_var.val_, &ptr, &size); + if (err) { + return Error("Could not cast to bytestring."); + } + return rfl::Bytestring(static_cast(ptr), size - 1);*/ } else if constexpr (std::is_same, bool>()) { - if (type != CAPNPROTO_BOOLEAN) { + if (type != capnp::DynamicValue::BOOL) { return rfl::Error("Could not cast to boolean."); } - int result = 0; - capnproto_value_get_boolean(_var.val_, &result); - return (result != 0); + return _var.val_.as(); } else if constexpr (std::is_floating_point>() || std::is_integral>()) { - if (type == CAPNPROTO_DOUBLE) { - double result = 0.0; - const auto err = capnproto_value_get_double(_var.val_, &result); - if (err) { - return Error("Could not cast to double."); - } - return static_cast(result); - } else if (type == CAPNPROTO_INT32) { - int32_t result = 0; - const auto err = capnproto_value_get_int(_var.val_, &result); - if (err) { - return Error("Could not cast to int32."); - } - return static_cast(result); - } else if (type == CAPNPROTO_INT64) { - int64_t result = 0; - const auto err = capnproto_value_get_long(_var.val_, &result); - if (err) { - return Error("Could not cast to int64."); - } - return static_cast(result); - } else if (type == CAPNPROTO_FLOAT) { - float result = 0.0; - const auto err = capnproto_value_get_float(_var.val_, &result); - if (err) { - return Error("Could not cast to float."); - } - return static_cast(result); + switch (type) { + case capnp::DynamicValue::INT: + return static_cast(_var.val_.as()); + case capnp::DynamicValue::UINT: + return static_cast(_var.val_.as()); + case capnp::DynamicValue::FLOAT: + return static_cast(_var.val_.as()); } return rfl::Error( - "Could not cast to numeric value. The type must be integral, float " - "or double."); - - } else if constexpr (internal::is_literal_v) { - int value = 0; - const auto err = capnproto_value_get_enum(_var.val_, &value); - if (err) { - return Error("Could not cast to enum."); - } - return std::remove_cvref_t::from_value( - static_cast::ValueType>(value)); + "Could not cast to numeric value. The type must be integral, " + "float or double."); + // TODO + /*} else if constexpr (internal::is_literal_v) { + int value = 0; + const auto err = capnproto_value_get_enum(_var.val_, &value); + if (err) { + return Error("Could not cast to enum."); + } + return std::remove_cvref_t::from_value( + static_cast::ValueType>(value));*/ } else { static_assert(rfl::always_false_v, "Unsupported type."); } @@ -142,12 +116,8 @@ struct Reader { template std::optional read_array(const ArrayReader& _array_reader, const InputArrayType& _arr) const noexcept { - size_t size = 0; - capnproto_value_get_size(_arr.val_, &size); - for (size_t ix = 0; ix < size; ++ix) { - capnproto_value_t element; - capnproto_value_get_by_index(_arr.val_, ix, &element, nullptr); - const auto err = _array_reader.read(InputVarType{&element}); + for (auto element : _arr.val_) { + const auto err = _array_reader.read(InputVarType{std::move(element)}); if (err) { return err; } @@ -158,26 +128,27 @@ struct Reader { template std::optional read_map(const MapReader& _map_reader, const InputMapType& _map) const noexcept { - size_t size = 0; - capnproto_value_get_size(_map.val_, &size); - for (size_t ix = 0; ix < size; ++ix) { - capnproto_value_t element; - const char* key = nullptr; - capnproto_value_get_by_index(_map.val_, ix, &element, &key); - _map_reader.read(std::string_view(key), InputVarType{&element}); - } + // TODO + /*size_t size = 0; + capnproto_value_get_size(_map.val_, &size); + for (size_t ix = 0; ix < size; ++ix) { + capnproto_value_t element; + const char* key = nullptr; + capnproto_value_get_by_index(_map.val_, ix, &element, &key); + _map_reader.read(std::string_view(key), InputVarType{&element}); + }*/ return std::nullopt; } template std::optional read_object(const ObjectReader& _object_reader, const InputObjectType& _obj) const noexcept { - size_t size = 0; - capnproto_value_get_size(_obj.val_, &size); - for (size_t ix = 0; ix < size; ++ix) { - capnproto_value_t element; - capnproto_value_get_by_index(_obj.val_, ix, &element, nullptr); - _object_reader.read(static_cast(ix), InputVarType{&element}); + for (auto field : _obj.val_.getSchema().getFields()) { + if (!_obj.val_.has(field)) { + continue; + } + _object_reader.read(field.getProto().getName().cStr(), + InputVarType{_obj.val_.get(field)}); } return std::nullopt; } @@ -185,7 +156,8 @@ struct Reader { template rfl::Result read_union( const InputUnionType& _union) const noexcept { - int disc = 0; + // TODO + /*int disc = 0; auto err = capnproto_value_get_discriminant(_union.val_, &disc); if (err) { return Error("Could not get the discriminant."); @@ -196,7 +168,8 @@ struct Reader { return Error("Could not cast the union type."); } return UnionReaderType::read(*this, static_cast(disc), - InputVarType{&value}); + InputVarType{&value});*/ + return Error("TODO"); } template diff --git a/src/reflectcpp_capnproto.cpp b/src/reflectcpp_capnproto.cpp index dafeb8c7..37713936 100644 --- a/src/reflectcpp_capnproto.cpp +++ b/src/reflectcpp_capnproto.cpp @@ -35,5 +35,6 @@ SOFTWARE. #include "rfl/avro/Type.cpp" #include "rfl/avro/Writer.cpp" #include "rfl/avro/to_schema.cpp"*/ +#include "rfl/capnproto/Reader.cpp" #include "rfl/capnproto/SchemaImpl.cpp" diff --git a/src/rfl/capnproto/Reader.cpp b/src/rfl/capnproto/Reader.cpp new file mode 100644 index 00000000..3bc92f26 --- /dev/null +++ b/src/rfl/capnproto/Reader.cpp @@ -0,0 +1,47 @@ +#include "rfl/capnproto/Reader.hpp" + +#include "rfl/parsing/schemaful/IsSchemafulReader.hpp" + +namespace rfl::capnproto { + +static_assert(parsing::schemaful::IsSchemafulReader, + "This must be a schemaful reader."); + +bool Reader::is_empty(const InputVarType& _var) const noexcept { + // TODO: Is this correct? + return _var.val_.getType() == capnp::DynamicValue::VOID; +} + +rfl::Result Reader::to_array( + const InputVarType& _var) const noexcept { + if (_var.val_.getType() != capnp::DynamicValue::LIST) { + return Error("Could not cast to a list."); + } + return InputArrayType{_var.val_.as()}; +} + +rfl::Result Reader::to_object( + const InputVarType& _var) const noexcept { + if (_var.val_.getType() != capnp::DynamicValue::STRUCT) { + return Error("Could not cast to a struct."); + } + return InputObjectType{_var.val_.as()}; +} + +rfl::Result Reader::to_map( + const InputVarType& _var) const noexcept { + if (_var.val_.getType() != capnp::DynamicValue::LIST) { + return Error("Could not cast to a list."); + } + return InputMapType{_var.val_.as()}; +} + +rfl::Result Reader::to_union( + const InputVarType& _var) const noexcept { + if (_var.val_.getType() != capnp::DynamicValue::STRUCT) { + return Error("Could not cast to a struct."); + } + return InputUnionType{_var.val_.as()}; +} + +} // namespace rfl::capnproto From c8afdf660386f35cf41efd094ab18a648dc4ed28 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Mon, 23 Dec 2024 19:59:50 +0100 Subject: [PATCH 05/57] Added very rudimentary first draft of the Writer --- include/rfl/capnproto/Reader.hpp | 8 +- include/rfl/capnproto/Writer.hpp | 73 +++++------ src/reflectcpp_capnproto.cpp | 1 + src/rfl/capnproto/Writer.cpp | 207 +++++++++++++++++++++++++++++++ 4 files changed, 250 insertions(+), 39 deletions(-) create mode 100644 src/rfl/capnproto/Writer.cpp diff --git a/include/rfl/capnproto/Reader.hpp b/include/rfl/capnproto/Reader.hpp index 964ef0a3..422634b5 100644 --- a/include/rfl/capnproto/Reader.hpp +++ b/include/rfl/capnproto/Reader.hpp @@ -22,14 +22,14 @@ struct Reader { capnp::DynamicList::Reader val_; }; - struct CapNProtoInputObject { - capnp::DynamicStruct::Reader val_; - }; - struct CapNProtoInputMap { capnp::DynamicList::Reader val_; }; + struct CapNProtoInputObject { + capnp::DynamicStruct::Reader val_; + }; + struct CapNProtoInputUnion { // TODO: Is this right? capnp::DynamicStruct::Reader val_; diff --git a/include/rfl/capnproto/Writer.hpp b/include/rfl/capnproto/Writer.hpp index 9f101139..73ddf4b2 100644 --- a/include/rfl/capnproto/Writer.hpp +++ b/include/rfl/capnproto/Writer.hpp @@ -1,7 +1,7 @@ #ifndef RFL_CAPNPROTO_WRITER_HPP_ #define RFL_CAPNPROTO_WRITER_HPP_ -#include +#include #include #include @@ -26,33 +26,34 @@ namespace rfl::capnproto { class Writer { public: - struct CAPNPROTOOutputArray { - capnproto_value_t val_; + struct CapnProtoOutputArray { + capnp::DynamicList::Builder val_; }; - struct CAPNPROTOOutputMap { - capnproto_value_t val_; + struct CapnProtoOutputMap { + capnp::DynamicList::Builder val_; }; - struct CAPNPROTOOutputObject { - capnproto_value_t val_; + struct CapnProtoOutputObject { + capnp::DynamicStruct::Builder val_; }; - struct CAPNPROTOOutputUnion { - capnproto_value_t val_; + struct CapnProtoOutputUnion { + // TODO: Is this right? + capnp::DynamicStruct::Builder val_; }; - struct CAPNPROTOOutputVar { - capnproto_value_t val_; + struct CapnProtoOutputVar { + capnp::DynamicValue::Builder val_; }; - using OutputArrayType = CAPNPROTOOutputArray; - using OutputMapType = CAPNPROTOOutputMap; - using OutputObjectType = CAPNPROTOOutputObject; - using OutputUnionType = CAPNPROTOOutputUnion; - using OutputVarType = CAPNPROTOOutputVar; + using OutputArrayType = CapnProtoOutputArray; + using OutputMapType = CapnProtoOutputMap; + using OutputObjectType = CapnProtoOutputObject; + using OutputUnionType = CapnProtoOutputUnion; + using OutputVarType = CapnProtoOutputVar; - Writer(capnproto_value_t* _root); + Writer(capnp::DynamicValue::Builder* _root); ~Writer(); @@ -139,38 +140,40 @@ class Writer { template OutputVarType add_value_to_array(const T& _var, OutputArrayType* _parent) const noexcept { - capnproto_value_t new_value; - capnproto_value_append(&_parent->val_, &new_value, nullptr); - set_value(_var, &new_value); - return OutputVarType{new_value}; + // TODO + /*capnproto_value_t new_value; + capnproto_value_append(&_parent->val_, &new_value, nullptr); + set_value(_var, &new_value); + return OutputVarType{new_value};*/ } template OutputVarType add_value_to_map(const std::string_view& _name, const T& _var, OutputMapType* _parent) const noexcept { - capnproto_value_t new_value; - capnproto_value_add(&_parent->val_, _name.data(), &new_value, nullptr, nullptr); - set_value(_var, &new_value); - return OutputVarType{new_value}; + // TODO + /*capnproto_value_t new_value; + capnproto_value_add(&_parent->val_, _name.data(), &new_value, nullptr, + nullptr); + set_value(_var, &new_value); + return OutputVarType{new_value};*/ } template OutputVarType add_value_to_object(const std::string_view& _name, const T& _var, OutputObjectType* _parent) const noexcept { - capnproto_value_t new_value; - capnproto_value_get_by_name(&_parent->val_, _name.data(), &new_value, nullptr); - set_value(_var, &new_value); - return OutputVarType{new_value}; + _parent->val_.set(_name, _var); + return OutputVarType{}; } template OutputVarType add_value_to_union(const size_t _index, const T& _var, OutputUnionType* _parent) const noexcept { - capnproto_value_t new_value; - capnproto_value_set_branch(&_parent->val_, static_cast(_index), &new_value); + /*capnproto_value_t new_value; + capnproto_value_set_branch(&_parent->val_, static_cast(_index), + &new_value); set_value(_var, &new_value); - return OutputVarType{new_value}; + return OutputVarType{new_value};*/ } void end_array(OutputArrayType* _arr) const noexcept {} @@ -180,7 +183,7 @@ class Writer { void end_object(OutputObjectType* _obj) const noexcept {} private: - template + /*template void set_value(const T& _var, capnproto_value_t* _val) const noexcept { if constexpr (std::is_same, std::string>()) { capnproto_value_set_string_len(_val, _var.c_str(), _var.size() + 1); @@ -205,10 +208,10 @@ class Writer { } else { static_assert(rfl::always_false_v, "Unsupported type."); } - } + }*/ private: - capnproto_value_t* root_; + capnp::DynamicValue::Builder* root_; }; } // namespace rfl::capnproto diff --git a/src/reflectcpp_capnproto.cpp b/src/reflectcpp_capnproto.cpp index 37713936..eeea9c72 100644 --- a/src/reflectcpp_capnproto.cpp +++ b/src/reflectcpp_capnproto.cpp @@ -37,4 +37,5 @@ SOFTWARE. #include "rfl/avro/to_schema.cpp"*/ #include "rfl/capnproto/Reader.cpp" #include "rfl/capnproto/SchemaImpl.cpp" +#include "rfl/capnproto/Writer.cpp" diff --git a/src/rfl/capnproto/Writer.cpp b/src/rfl/capnproto/Writer.cpp new file mode 100644 index 00000000..99616ce6 --- /dev/null +++ b/src/rfl/capnproto/Writer.cpp @@ -0,0 +1,207 @@ +#include "rfl/capnproto/Writer.hpp" + +#include "rfl/parsing/schemaful/IsSchemafulWriter.hpp" + +namespace rfl::capnproto { + +static_assert(parsing::schemaful::IsSchemafulWriter, + "This must be a schemaful writer."); + +Writer::Writer(capnp::DynamicValue::Builder* _root) : root_(_root){}; + +Writer::~Writer() = default; + +Writer::OutputArrayType Writer::array_as_root( + const size_t _size) const noexcept { + return OutputArrayType{root_->as()}; +} + +Writer::OutputMapType Writer::map_as_root(const size_t _size) const noexcept { + return OutputMapType{root_->as()}; +} + +Writer::OutputObjectType Writer::object_as_root( + const size_t _size) const noexcept { + return OutputObjectType{root_->as()}; +} + +Writer::OutputVarType Writer::null_as_root() const noexcept { + // TODO + // avro_value_set_null(root_); + return OutputVarType{root_}; +} + +Writer::OutputUnionType Writer::union_as_root() const noexcept { + return OutputUnionType{root_->as()}; +} + +Writer::OutputArrayType Writer::add_array_to_array( + const size_t _size, OutputArrayType* _parent) const noexcept { + // TODO + /*avro_value_t new_array; +avro_value_append(&_parent->val_, &new_array, nullptr); +return OutputArrayType{new_array};*/ +} + +Writer::OutputArrayType Writer::add_array_to_map( + const std::string_view& _name, const size_t _size, + OutputMapType* _parent) const noexcept { + // TODO + /*avro_value_t new_array; + avro_value_add(&_parent->val_, _name.data(), &new_array, nullptr, nullptr); + return OutputArrayType{new_array};*/ +} + +Writer::OutputArrayType Writer::add_array_to_object( + const std::string_view& _name, const size_t _size, + OutputObjectType* _parent) const noexcept { + return OutputArrayType{ + _parent->val_.init(_name.data(), _size).as()}; +} + +Writer::OutputArrayType Writer::add_array_to_union( + const size_t _index, const size_t _size, + OutputUnionType* _parent) const noexcept { + /*avro_value_t new_array; + avro_value_set_branch(&_parent->val_, static_cast(_index), &new_array); + return OutputArrayType{new_array};*/ +} + +Writer::OutputMapType Writer::add_map_to_array( + const size_t _size, OutputArrayType* _parent) const noexcept { + // TODO + /*avro_value_t new_map; +avro_value_append(&_parent->val_, &new_map, nullptr); +return OutputMapType{new_map};*/ +} + +Writer::OutputMapType Writer::add_map_to_map( + const std::string_view& _name, const size_t _size, + OutputMapType* _parent) const noexcept { + // TODO + /*avro_value_t new_map; + avro_value_add(&_parent->val_, _name.data(), &new_map, nullptr, nullptr); + return OutputMapType{new_map};*/ +} + +Writer::OutputMapType Writer::add_map_to_object( + const std::string_view& _name, const size_t _size, + OutputObjectType* _parent) const noexcept { + return OutputMapType{ + _parent->val_.init(_name.data(), _size).as()}; +} + +Writer::OutputMapType Writer::add_map_to_union( + const size_t _index, const size_t _size, + OutputUnionType* _parent) const noexcept { + // TODO + /*avro_value_t new_map; +avro_value_set_branch(&_parent->val_, static_cast(_index), &new_map); +return OutputMapType{new_map};*/ +} + +Writer::OutputObjectType Writer::add_object_to_array( + const size_t _size, OutputArrayType* _parent) const noexcept { + // TODO + /*avro_value_t new_object; +avro_value_append(&_parent->val_, &new_object, nullptr); +return OutputObjectType{new_object};*/ +} + +Writer::OutputObjectType Writer::add_object_to_map( + const std::string_view& _name, const size_t _size, + OutputMapType* _parent) const noexcept { + // TODO + /*avro_value_t new_object; + avro_value_add(&_parent->val_, _name.data(), &new_object, nullptr, nullptr); + return OutputObjectType{new_object};*/ +} + +Writer::OutputObjectType Writer::add_object_to_object( + const std::string_view& _name, const size_t _size, + OutputObjectType* _parent) const noexcept { + // TODO + /*avro_value_t new_object; + avro_value_get_by_name(&_parent->val_, _name.data(), &new_object, nullptr); + return OutputObjectType{new_object};*/ +} + +Writer::OutputObjectType Writer::add_object_to_union( + const size_t _index, const size_t _size, + OutputUnionType* _parent) const noexcept { + // TODO + /*avro_value_t new_object; +avro_value_set_branch(&_parent->val_, static_cast(_index), &new_object); +return OutputObjectType{new_object}; +*/ +} + +Writer::OutputUnionType Writer::add_union_to_array( + OutputArrayType* _parent) const noexcept { + // TODO + /*avro_value_t new_union; + avro_value_append(&_parent->val_, &new_union, nullptr); + return OutputUnionType{new_union};*/ +} + +Writer::OutputUnionType Writer::add_union_to_map( + const std::string_view& _name, OutputMapType* _parent) const noexcept { + // TODO + /*avro_value_t new_union; +avro_value_add(&_parent->val_, _name.data(), &new_union, nullptr, nullptr); +return OutputUnionType{new_union};*/ +} + +Writer::OutputUnionType Writer::add_union_to_object( + const std::string_view& _name, OutputObjectType* _parent) const noexcept { + // TODO + /*avro_value_t new_union; +avro_value_get_by_name(&_parent->val_, _name.data(), &new_union, nullptr); +return OutputUnionType{new_union};*/ +} + +Writer::OutputUnionType Writer::add_union_to_union( + const size_t _index, OutputUnionType* _parent) const noexcept { + // TODO + /*avro_value_t new_union; +avro_value_set_branch(&_parent->val_, static_cast(_index), &new_union); +return OutputUnionType{new_union};*/ +} + +Writer::OutputVarType Writer::add_null_to_array( + OutputArrayType* _parent) const noexcept { + // TODO + /*avro_value_t new_null; +avro_value_append(&_parent->val_, &new_null, nullptr); +avro_value_set_null(&new_null); +return OutputVarType{new_null};*/ +} + +Writer::OutputVarType Writer::add_null_to_map( + const std::string_view& _name, OutputMapType* _parent) const noexcept { + // TODO + /*avro_value_t new_null; +avro_value_add(&_parent->val_, _name.data(), &new_null, nullptr, nullptr); +avro_value_set_null(&new_null); +return OutputVarType{new_null};*/ +} + +Writer::OutputVarType Writer::add_null_to_object( + const std::string_view& _name, OutputObjectType* _parent) const noexcept { + // TODO + /*avro_value_t new_null; +avro_value_get_by_name(&_parent->val_, _name.data(), &new_null, nullptr); +avro_value_set_null(&new_null); +return OutputVarType{new_null};*/ +} + +Writer::OutputVarType Writer::add_null_to_union( + const size_t _index, OutputUnionType* _parent) const noexcept { + // TODO + /*avro_value_t new_null; +avro_value_set_branch(&_parent->val_, static_cast(_index), &new_null); +avro_value_set_null(&new_null); +return OutputVarType{new_null};*/ +} + +} // namespace rfl::capnproto From 7c1b288183a29456ce5057d5739a51622257b6fc Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Tue, 24 Dec 2024 12:46:25 +0100 Subject: [PATCH 06/57] Started writing capnproto::write --- include/rfl/capnproto.hpp | 4 ++-- include/rfl/capnproto/Writer.hpp | 41 ++++++++++++++++++++++++-------- include/rfl/capnproto/write.hpp | 38 ++++++++++++++++++++--------- src/rfl/capnproto/Writer.cpp | 23 +++--------------- tests/capnproto/test_person.cpp | 35 +++++++++++++++++++++++++++ 5 files changed, 98 insertions(+), 43 deletions(-) create mode 100644 tests/capnproto/test_person.cpp diff --git a/include/rfl/capnproto.hpp b/include/rfl/capnproto.hpp index ebac012a..d3d74ca0 100644 --- a/include/rfl/capnproto.hpp +++ b/include/rfl/capnproto.hpp @@ -5,11 +5,11 @@ // #include "capnproto/Parser.hpp" #include "capnproto/Reader.hpp" #include "capnproto/Schema.hpp" -// #include "capnproto/Writer.hpp" +#include "capnproto/Writer.hpp" // #include "capnproto/load.hpp" // #include "capnproto/read.hpp" // #include "capnproto/save.hpp" // #include "capnproto/to_schema.hpp" -// #include "capnproto/write.hpp" +#include "capnproto/write.hpp" #endif diff --git a/include/rfl/capnproto/Writer.hpp b/include/rfl/capnproto/Writer.hpp index 73ddf4b2..a027ef30 100644 --- a/include/rfl/capnproto/Writer.hpp +++ b/include/rfl/capnproto/Writer.hpp @@ -53,24 +53,45 @@ class Writer { using OutputUnionType = CapnProtoOutputUnion; using OutputVarType = CapnProtoOutputVar; - Writer(capnp::DynamicValue::Builder* _root); + Writer(capnp::DynamicStruct::Builder* _root); ~Writer(); - OutputArrayType array_as_root(const size_t _size) const noexcept; + template + OutputArrayType array_as_root(const T _size) const noexcept { + static_assert(always_false_v, + "In Cap'n Proto, root values must always be structs."); + throw std::runtime_error("Unsupported."); + } - OutputMapType map_as_root(const size_t _size) const noexcept; + template + OutputMapType map_as_root(const T _size) const noexcept { + static_assert(always_false_v, + "In Cap'n Proto, root values must always be structs."); + throw std::runtime_error("Unsupported."); + } - OutputObjectType object_as_root(const size_t _size) const noexcept; + Writer::OutputObjectType object_as_root(const size_t _size) const noexcept; - OutputVarType null_as_root() const noexcept; + template + OutputVarType null_as_root() const noexcept { + static_assert(always_false_v, + "In Cap'n Proto, root values must always be structs."); + throw std::runtime_error("Unsupported."); + } - OutputUnionType union_as_root() const noexcept; + template + OutputUnionType union_as_root() const noexcept { + static_assert(always_false_v, + "In Cap'n Proto, root values must always be structs."); + throw std::runtime_error("Unsupported."); + } template OutputVarType value_as_root(const T& _var) const noexcept { - set_value(_var, root_); - return OutputVarType{*root_}; + static_assert(always_false_v, + "In Cap'n Proto, root values must always be structs."); + throw std::runtime_error("Unsupported."); } OutputArrayType add_array_to_array(const size_t _size, @@ -162,7 +183,7 @@ class Writer { OutputVarType add_value_to_object(const std::string_view& _name, const T& _var, OutputObjectType* _parent) const noexcept { - _parent->val_.set(_name, _var); + _parent->val_.set(_name.data(), _var); return OutputVarType{}; } @@ -211,7 +232,7 @@ class Writer { }*/ private: - capnp::DynamicValue::Builder* root_; + capnp::DynamicStruct::Builder* root_; }; } // namespace rfl::capnproto diff --git a/include/rfl/capnproto/write.hpp b/include/rfl/capnproto/write.hpp index 70494248..5d08a8fd 100644 --- a/include/rfl/capnproto/write.hpp +++ b/include/rfl/capnproto/write.hpp @@ -1,7 +1,10 @@ #ifndef RFL_CAPNPROTO_WRITE_HPP_ #define RFL_CAPNPROTO_WRITE_HPP_ -#include +#include +#include +#include +#include #include #include @@ -22,24 +25,37 @@ namespace rfl::capnproto { /// Returns CAPNPROTO bytes. template std::vector write(const auto& _obj, const auto& _schema) noexcept { + /* using T = std::remove_cvref_t; + using U = typename std::remove_cvref_t::Type; + using ParentType = parsing::Parent; + static_assert(std::is_same(), + "The schema must be compatible with the type to write."); + capnproto_value_t root; + capnproto_generic_value_new(_schema.iface(), &root); + const auto writer = Writer(&root); + Parser>::write(writer, _obj, + typename ParentType::Root{}); + size_t size = 0; + capnproto_value_sizeof(&root, &size); + std::vector buffer(size); + capnproto_writer_t capnproto_writer = capnproto_writer_memory(buffer.data(), + buffer.size()); capnproto_value_write(capnproto_writer, &root); + capnproto_value_decref(&root); + capnproto_writer_free(capnproto_writer); + return buffer;*/ + using T = std::remove_cvref_t; using U = typename std::remove_cvref_t::Type; using ParentType = parsing::Parent; static_assert(std::is_same(), "The schema must be compatible with the type to write."); - capnproto_value_t root; - capnproto_generic_value_new(_schema.iface(), &root); + capnp::MallocMessageBuilder message; + auto root = + message.initRoot(_schema.value().asStruct()); const auto writer = Writer(&root); Parser>::write(writer, _obj, typename ParentType::Root{}); - size_t size = 0; - capnproto_value_sizeof(&root, &size); - std::vector buffer(size); - capnproto_writer_t capnproto_writer = capnproto_writer_memory(buffer.data(), buffer.size()); - capnproto_value_write(capnproto_writer, &root); - capnproto_value_decref(&root); - capnproto_writer_free(capnproto_writer); - return buffer; + return std::vector(); // TODO } /// Returns CAPNPROTO bytes. diff --git a/src/rfl/capnproto/Writer.cpp b/src/rfl/capnproto/Writer.cpp index 99616ce6..a3aba11a 100644 --- a/src/rfl/capnproto/Writer.cpp +++ b/src/rfl/capnproto/Writer.cpp @@ -1,5 +1,7 @@ #include "rfl/capnproto/Writer.hpp" +#include + #include "rfl/parsing/schemaful/IsSchemafulWriter.hpp" namespace rfl::capnproto { @@ -7,34 +9,15 @@ namespace rfl::capnproto { static_assert(parsing::schemaful::IsSchemafulWriter, "This must be a schemaful writer."); -Writer::Writer(capnp::DynamicValue::Builder* _root) : root_(_root){}; +Writer::Writer(capnp::DynamicStruct::Builder* _root) : root_(_root){}; Writer::~Writer() = default; -Writer::OutputArrayType Writer::array_as_root( - const size_t _size) const noexcept { - return OutputArrayType{root_->as()}; -} - -Writer::OutputMapType Writer::map_as_root(const size_t _size) const noexcept { - return OutputMapType{root_->as()}; -} - Writer::OutputObjectType Writer::object_as_root( const size_t _size) const noexcept { return OutputObjectType{root_->as()}; } -Writer::OutputVarType Writer::null_as_root() const noexcept { - // TODO - // avro_value_set_null(root_); - return OutputVarType{root_}; -} - -Writer::OutputUnionType Writer::union_as_root() const noexcept { - return OutputUnionType{root_->as()}; -} - Writer::OutputArrayType Writer::add_array_to_array( const size_t _size, OutputArrayType* _parent) const noexcept { // TODO diff --git a/tests/capnproto/test_person.cpp b/tests/capnproto/test_person.cpp new file mode 100644 index 00000000..d1335b56 --- /dev/null +++ b/tests/capnproto/test_person.cpp @@ -0,0 +1,35 @@ +#include + +#include +#include +#include +#include +#include + +// #include "write_and_read.hpp" + +/// The basic example from the Avro C tutorial. +namespace test_tutorial_example { + +const std::string PERSON_SCHEMA = R"( +@0xb07899ba441fd7ec; + +struct Person { + first_name @0 :Text; + last_name @2 :Text; +}; +)"; + +struct Person { + std::string first_name; + std::string last_name; +}; + +TEST(capnproto, test_person) { + const auto schema = + rfl::capnproto::Schema::from_string(PERSON_SCHEMA).value(); + std::cout << schema.str() << std::endl; + const auto homer = Person{.first_name = "Homer", .last_name = "Simpson"}; + std::cout << rfl::capnproto::write(homer, schema).data() << std::endl; +} +} // namespace test_tutorial_example From 0376a31ef871c02a4202712d948e31916fa0c2e8 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Thu, 26 Dec 2024 00:01:21 +0100 Subject: [PATCH 07/57] rfl::capnproto::write(...) works in principle --- include/rfl/capnproto/SchemaImpl.hpp | 9 ++++-- include/rfl/capnproto/Writer.hpp | 15 +++++++++- include/rfl/capnproto/write.hpp | 34 ++++++++--------------- src/rfl/capnproto/SchemaImpl.cpp | 11 +++++--- tests/capnproto/test_person.cpp | 14 ++++------ tests/capnproto/test_tutorial_example.cpp | 4 +-- 6 files changed, 47 insertions(+), 40 deletions(-) diff --git a/include/rfl/capnproto/SchemaImpl.hpp b/include/rfl/capnproto/SchemaImpl.hpp index ee9d2ec8..b9142a77 100644 --- a/include/rfl/capnproto/SchemaImpl.hpp +++ b/include/rfl/capnproto/SchemaImpl.hpp @@ -3,9 +3,9 @@ #include +#include #include -#include "../Box.hpp" #include "../Result.hpp" namespace rfl::capnproto { @@ -23,12 +23,17 @@ class SchemaImpl { const capnp::ParsedSchema& value() const { return *schema_; }; private: - static capnp::ParsedSchema make_schema(const std::string& _str); + static capnp::ParsedSchema make_schema(const std::string& _str, + capnp::SchemaParser* _parser); private: /// The string used to create the schema. std::string str_; + /// The schema parser - we need to hold onto this during the lifetime of the + /// schema. + Box schema_parser_; + /// The actual schema Box schema_; }; diff --git a/include/rfl/capnproto/Writer.hpp b/include/rfl/capnproto/Writer.hpp index a027ef30..e5cb832b 100644 --- a/include/rfl/capnproto/Writer.hpp +++ b/include/rfl/capnproto/Writer.hpp @@ -183,7 +183,20 @@ class Writer { OutputVarType add_value_to_object(const std::string_view& _name, const T& _var, OutputObjectType* _parent) const noexcept { - _parent->val_.set(_name.data(), _var); + if constexpr (std::is_same, std::string>()) { + _parent->val_.set(_name.data(), _var.c_str()); + + } else if constexpr (std::is_floating_point>() || + std::is_same, bool>()) { + _parent->val_.set(_name.data(), _var); + + } else if constexpr (std::is_integral>()) { + _parent->val_.set(_name.data(), static_cast(_var)); + + } else { + static_assert(rfl::always_false_v, "Unsupported type."); + } + return OutputVarType{}; } diff --git a/include/rfl/capnproto/write.hpp b/include/rfl/capnproto/write.hpp index 5d08a8fd..e93a23cb 100644 --- a/include/rfl/capnproto/write.hpp +++ b/include/rfl/capnproto/write.hpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include @@ -14,6 +16,7 @@ #include #include +#include "../internal/ptr_cast.hpp" #include "../parsing/Parent.hpp" #include "Parser.hpp" #include "Schema.hpp" @@ -25,37 +28,24 @@ namespace rfl::capnproto { /// Returns CAPNPROTO bytes. template std::vector write(const auto& _obj, const auto& _schema) noexcept { - /* using T = std::remove_cvref_t; - using U = typename std::remove_cvref_t::Type; - using ParentType = parsing::Parent; - static_assert(std::is_same(), - "The schema must be compatible with the type to write."); - capnproto_value_t root; - capnproto_generic_value_new(_schema.iface(), &root); - const auto writer = Writer(&root); - Parser>::write(writer, _obj, - typename ParentType::Root{}); - size_t size = 0; - capnproto_value_sizeof(&root, &size); - std::vector buffer(size); - capnproto_writer_t capnproto_writer = capnproto_writer_memory(buffer.data(), - buffer.size()); capnproto_value_write(capnproto_writer, &root); - capnproto_value_decref(&root); - capnproto_writer_free(capnproto_writer); - return buffer;*/ - using T = std::remove_cvref_t; using U = typename std::remove_cvref_t::Type; using ParentType = parsing::Parent; static_assert(std::is_same(), "The schema must be compatible with the type to write."); - capnp::MallocMessageBuilder message; + // TODO: Person is hardcoded. + const auto root_schema = _schema.value().getNested("Person"); + capnp::MallocMessageBuilder message_builder; auto root = - message.initRoot(_schema.value().asStruct()); + message_builder.initRoot(root_schema.asStruct()); const auto writer = Writer(&root); Parser>::write(writer, _obj, typename ParentType::Root{}); - return std::vector(); // TODO + kj::VectorOutputStream output_stream; + capnp::writePackedMessage(output_stream, message_builder); + auto arr_ptr = output_stream.getArray(); + return std::vector(internal::ptr_cast(arr_ptr.begin()), + internal::ptr_cast(arr_ptr.end())); } /// Returns CAPNPROTO bytes. diff --git a/src/rfl/capnproto/SchemaImpl.cpp b/src/rfl/capnproto/SchemaImpl.cpp index 2bf86dbb..15078e5c 100644 --- a/src/rfl/capnproto/SchemaImpl.cpp +++ b/src/rfl/capnproto/SchemaImpl.cpp @@ -6,15 +6,18 @@ namespace rfl::capnproto { SchemaImpl::SchemaImpl(const std::string& _str) - : str_(_str), schema_(Box::make(make_schema(_str))) {} + : str_(_str), + schema_parser_(Box::make()), + schema_(Box::make( + make_schema(str_, schema_parser_.get()))) {} -capnp::ParsedSchema SchemaImpl::make_schema(const std::string& _str) { +capnp::ParsedSchema SchemaImpl::make_schema( + const std::string& _str, capnp::SchemaParser* _schema_parser) { auto dir = kj::newInMemoryDirectory(kj::nullClock()); auto path = kj::Path::parse("foo/bar.capnp"); dir->openFile(path, kj::WriteMode::CREATE | kj::WriteMode::CREATE_PARENT) ->writeAll(_str); - capnp::SchemaParser parser; - return parser.parseFromDirectory(*dir, std::move(path), nullptr); + return _schema_parser->parseFromDirectory(*dir, std::move(path), nullptr); } } // namespace rfl::capnproto diff --git a/tests/capnproto/test_person.cpp b/tests/capnproto/test_person.cpp index d1335b56..67b8dbe8 100644 --- a/tests/capnproto/test_person.cpp +++ b/tests/capnproto/test_person.cpp @@ -8,28 +8,26 @@ // #include "write_and_read.hpp" -/// The basic example from the Avro C tutorial. namespace test_tutorial_example { const std::string PERSON_SCHEMA = R"( @0xb07899ba441fd7ec; struct Person { - first_name @0 :Text; - last_name @2 :Text; -}; + firstName @0 :Text; + lastName @1 :Text; +} )"; struct Person { - std::string first_name; - std::string last_name; + std::string firstName; + std::string lastName; }; TEST(capnproto, test_person) { const auto schema = rfl::capnproto::Schema::from_string(PERSON_SCHEMA).value(); - std::cout << schema.str() << std::endl; - const auto homer = Person{.first_name = "Homer", .last_name = "Simpson"}; + const auto homer = Person{.firstName = "Homer", .lastName = "Simpson"}; std::cout << rfl::capnproto::write(homer, schema).data() << std::endl; } } // namespace test_tutorial_example diff --git a/tests/capnproto/test_tutorial_example.cpp b/tests/capnproto/test_tutorial_example.cpp index dc24cddf..b5b699bc 100644 --- a/tests/capnproto/test_tutorial_example.cpp +++ b/tests/capnproto/test_tutorial_example.cpp @@ -65,8 +65,6 @@ struct AddressBook { }; TEST(capnproto, test_tutorial_example) { - const auto schema = - rfl::capnproto::Schema::from_string(ADDRESS_BOOK_SCHEMA).value(); - std::cout << schema.str() << std::endl; + rfl::capnproto::Schema::from_string(ADDRESS_BOOK_SCHEMA).value(); } } // namespace test_tutorial_example From 06ed35ae41a8c63af47b327cd0be0e2fb90166e8 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Thu, 26 Dec 2024 12:45:12 +0100 Subject: [PATCH 08/57] Implemented rfl::capnproto::read --- include/rfl/capnproto.hpp | 4 ++-- include/rfl/capnproto/read.hpp | 27 ++++++++++++--------------- tests/capnproto/test_person.cpp | 7 +++++-- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/include/rfl/capnproto.hpp b/include/rfl/capnproto.hpp index d3d74ca0..52a35c06 100644 --- a/include/rfl/capnproto.hpp +++ b/include/rfl/capnproto.hpp @@ -2,12 +2,12 @@ #define RFL_CAPNPROTO_HPP_ #include "../rfl.hpp" -// #include "capnproto/Parser.hpp" +#include "capnproto/Parser.hpp" #include "capnproto/Reader.hpp" #include "capnproto/Schema.hpp" #include "capnproto/Writer.hpp" // #include "capnproto/load.hpp" -// #include "capnproto/read.hpp" +#include "capnproto/read.hpp" // #include "capnproto/save.hpp" // #include "capnproto/to_schema.hpp" #include "capnproto/write.hpp" diff --git a/include/rfl/capnproto/read.hpp b/include/rfl/capnproto/read.hpp index 01e8c0bd..f8d95efb 100644 --- a/include/rfl/capnproto/read.hpp +++ b/include/rfl/capnproto/read.hpp @@ -1,7 +1,9 @@ #ifndef RFL_CAPNPROTO_READ_HPP_ #define RFL_CAPNPROTO_READ_HPP_ -#include +#include +#include +#include #include #include @@ -31,20 +33,15 @@ auto read(const InputVarType& _obj) { template Result> read( const char* _bytes, const size_t _size, const Schema& _schema) noexcept { - capnproto_reader_t capnproto_reader = capnproto_reader_memory(_bytes, _size); - capnproto_value_t root; - capnproto_generic_value_new(_schema.iface(), &root); - auto err = capnproto_value_read(capnproto_reader, &root); - if (err) { - capnproto_value_decref(&root); - capnproto_reader_free(capnproto_reader); - return Error(std::string("Could not read root value: ") + - capnproto_strerror()); - } - auto result = read(InputVarType{&root}); - capnproto_value_decref(&root); - capnproto_reader_free(capnproto_reader); - return result; + const auto array_ptr = kj::ArrayPtr( + internal::ptr_cast(_bytes), _size); + auto input_stream = kj::ArrayInputStream(array_ptr); + auto message_reader = capnp::PackedMessageReader(input_stream); + // TODO: Person is hardcoded. + const auto root_schema = _schema.value().getNested("Person"); + const auto input_var = InputVarType{ + message_reader.getRoot(root_schema.asStruct())}; + return read(input_var); } /// Parses an object from CAPNPROTO using reflection. diff --git a/tests/capnproto/test_person.cpp b/tests/capnproto/test_person.cpp index 67b8dbe8..f3e16383 100644 --- a/tests/capnproto/test_person.cpp +++ b/tests/capnproto/test_person.cpp @@ -27,7 +27,10 @@ struct Person { TEST(capnproto, test_person) { const auto schema = rfl::capnproto::Schema::from_string(PERSON_SCHEMA).value(); - const auto homer = Person{.firstName = "Homer", .lastName = "Simpson"}; - std::cout << rfl::capnproto::write(homer, schema).data() << std::endl; + const auto homer1 = Person{.firstName = "Homer", .lastName = "Simpson"}; + const auto serialized1 = rfl::capnproto::write(homer1, schema); + const auto homer2 = rfl::capnproto::read(serialized1, schema).value(); + const auto serialized2 = rfl::capnproto::write(homer2, schema); + EXPECT_EQ(serialized1, serialized2); } } // namespace test_tutorial_example From b7cdc350cbe66b0d408abf5467af26702c695ae4 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Thu, 26 Dec 2024 13:57:55 +0100 Subject: [PATCH 09/57] Started developing the schema --- include/rfl/capnproto/schema/Type.hpp | 75 ++++++++++----------------- 1 file changed, 27 insertions(+), 48 deletions(-) diff --git a/include/rfl/capnproto/schema/Type.hpp b/include/rfl/capnproto/schema/Type.hpp index 2a3ce98e..6d27c92b 100644 --- a/include/rfl/capnproto/schema/Type.hpp +++ b/include/rfl/capnproto/schema/Type.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "../../Literal.hpp" @@ -14,66 +15,45 @@ namespace rfl::capnproto::schema { struct Type { - struct Null { - Literal<"null"> type; - }; + struct Void {}; - struct Boolean { - Literal<"boolean"> type; - }; + struct Bool {}; - struct Int { - Literal<"int"> type; - }; + struct Int8 {}; - struct Long { - Literal<"long"> type; - }; + struct Int16 {}; - struct Float { - Literal<"float"> type; - }; + struct Int32 {}; - struct Double { - Literal<"float"> type; - }; + struct Int64 {}; - struct Bytes { - Literal<"bytes"> type; - }; + struct UInt8 {}; - struct String { - Literal<"string"> type; - }; + struct UInt16 {}; - struct RecordField { - std::string name; - rfl::Ref type; - }; + struct UInt32 {}; - struct Record { - Literal<"record"> type; - std::string name; - std::vector fields; - std::optional doc; - }; + struct UInt64 {}; + + struct Float32 {}; + + struct Float64 {}; - struct Enum { - Literal<"enum"> type; + struct Data {}; + + struct Text {}; + + struct Struct { std::string name; - std::vector symbols; + std::vector> fields; }; - struct Array { - Literal<"array"> type; - rfl::Ref items; - Rename<"default", std::vector> default_; + struct List { + rfl::Ref type; }; struct Map { - Literal<"map"> type; - rfl::Ref values; - Rename<"default", std::map> default_; + rfl::Ref type; }; struct Reference { @@ -81,13 +61,12 @@ struct Type { }; using ReflectionType = - rfl::Variant>; + rfl::Variant; const auto& reflection() const { return value; } - Type with_name(const std::string& _name) const; - ReflectionType value; }; From 5a898afc4dc9cf4f463ca19b35f5c3bc5cc455a7 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Thu, 26 Dec 2024 16:31:31 +0100 Subject: [PATCH 10/57] First draft for the schema generator --- include/rfl/capnproto/schema/Type.hpp | 9 ++- src/reflectcpp_capnproto.cpp | 1 + src/rfl/capnproto/Type.cpp | 109 ++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 src/rfl/capnproto/Type.cpp diff --git a/include/rfl/capnproto/schema/Type.hpp b/include/rfl/capnproto/schema/Type.hpp index 6d27c92b..8b20ed2d 100644 --- a/include/rfl/capnproto/schema/Type.hpp +++ b/include/rfl/capnproto/schema/Type.hpp @@ -1,10 +1,10 @@ #ifndef RFL_CAPNPROTO_SCHEMA_TYPE_HPP_ #define RFL_CAPNPROTO_SCHEMA_TYPE_HPP_ +#include #include #include #include -#include #include #include "../../Literal.hpp" @@ -57,12 +57,12 @@ struct Type { }; struct Reference { - std::string type; + std::string type_name; }; using ReflectionType = rfl::Variant; const auto& reflection() const { return value; } @@ -70,6 +70,7 @@ struct Type { ReflectionType value; }; -} // namespace rfl::capnproto::schema +std::ostream& operator<<(std::ostream& os, const Type& _t); +} // namespace rfl::capnproto::schema #endif diff --git a/src/reflectcpp_capnproto.cpp b/src/reflectcpp_capnproto.cpp index eeea9c72..65defc58 100644 --- a/src/reflectcpp_capnproto.cpp +++ b/src/reflectcpp_capnproto.cpp @@ -37,5 +37,6 @@ SOFTWARE. #include "rfl/avro/to_schema.cpp"*/ #include "rfl/capnproto/Reader.cpp" #include "rfl/capnproto/SchemaImpl.cpp" +#include "rfl/capnproto/Type.cpp" #include "rfl/capnproto/Writer.cpp" diff --git a/src/rfl/capnproto/Type.cpp b/src/rfl/capnproto/Type.cpp new file mode 100644 index 00000000..ef0ad5d2 --- /dev/null +++ b/src/rfl/capnproto/Type.cpp @@ -0,0 +1,109 @@ +/* + +MIT License + +Copyright (c) 2023-2024 Code17 GmbH + +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. + +*/ + +#include "rfl/capnproto/schema/Type.hpp" + +namespace rfl::capnproto::schema { + +std::ostream& operator<<(std::ostream& _os, const Type::Void&) { + return _os << "Void"; +} + +std::ostream& operator<<(std::ostream& _os, const Type::Bool&) { + return _os << "Bool"; +} + +std::ostream& operator<<(std::ostream& _os, const Type::Int8&) { + return _os << "Int8"; +} + +std::ostream& operator<<(std::ostream& _os, const Type::Int16&) { + return _os << "Int16"; +} + +std::ostream& operator<<(std::ostream& _os, const Type::Int32&) { + return _os << "Int32"; +} + +std::ostream& operator<<(std::ostream& _os, const Type::Int64&) { + return _os << "Int64"; +} + +std::ostream& operator<<(std::ostream& _os, const Type::UInt8&) { + return _os << "UInt8"; +} + +std::ostream& operator<<(std::ostream& _os, const Type::UInt16&) { + return _os << "UInt16"; +} + +std::ostream& operator<<(std::ostream& _os, const Type::UInt32&) { + return _os << "UInt32"; +} + +std::ostream& operator<<(std::ostream& _os, const Type::UInt64&) { + return _os << "UInt64"; +} + +std::ostream& operator<<(std::ostream& _os, const Type::Float32&) { + return _os << "Float32"; +} + +std::ostream& operator<<(std::ostream& _os, const Type::Float64&) { + return _os << "Float64"; +} + +std::ostream& operator<<(std::ostream& _os, const Type::Data&) { + return _os << "Data"; +} + +std::ostream& operator<<(std::ostream& _os, const Type::Text&) { + return _os << "Text"; +} + +std::ostream& operator<<(std::ostream& _os, const Type::Struct& _s) { + _os << "struct " << _s.name << " {" << std::endl; + for (size_t i = 0; i < _s.fields.size(); ++i) { + const auto& [name, type] = _s.fields[i]; + _os << " " << name << " @" << i << " :" << type << ";" << std::endl; + } + return _os << "}" << std::endl; +} + +std::ostream& operator<<(std::ostream& _os, const Type::List& _l) { + return _os << "List(" << *_l.type << ")"; +} + +std::ostream& operator<<(std::ostream& _os, const Type::Reference& _r) { + return _os << _r.type_name; +} + +std::ostream& operator<<(std::ostream& _os, const Type& _t) { + return _t.reflection().visit( + [&](const auto& _r) -> std::ostream& { return _os << _r; }); +} + +} // namespace rfl::capnproto::schema From 74195336b8e4b554c5869877e3d7812510c90466 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Thu, 26 Dec 2024 17:25:44 +0100 Subject: [PATCH 11/57] Added basic schema generation --- include/rfl/capnproto.hpp | 2 +- include/rfl/capnproto/schema/Type.hpp | 2 + include/rfl/capnproto/to_schema.hpp | 8 +- src/reflectcpp_capnproto.cpp | 2 +- src/rfl/capnproto/Type.cpp | 16 ++- src/rfl/capnproto/to_schema.cpp | 181 ++++++++++++++++++++++++++ tests/capnproto/test_person.cpp | 12 +- 7 files changed, 205 insertions(+), 18 deletions(-) create mode 100644 src/rfl/capnproto/to_schema.cpp diff --git a/include/rfl/capnproto.hpp b/include/rfl/capnproto.hpp index 52a35c06..3a3e9bc8 100644 --- a/include/rfl/capnproto.hpp +++ b/include/rfl/capnproto.hpp @@ -9,7 +9,7 @@ // #include "capnproto/load.hpp" #include "capnproto/read.hpp" // #include "capnproto/save.hpp" -// #include "capnproto/to_schema.hpp" +#include "capnproto/to_schema.hpp" #include "capnproto/write.hpp" #endif diff --git a/include/rfl/capnproto/schema/Type.hpp b/include/rfl/capnproto/schema/Type.hpp index 8b20ed2d..d72618ba 100644 --- a/include/rfl/capnproto/schema/Type.hpp +++ b/include/rfl/capnproto/schema/Type.hpp @@ -67,6 +67,8 @@ struct Type { const auto& reflection() const { return value; } + Type with_name(const std::string& _name) const; + ReflectionType value; }; diff --git a/include/rfl/capnproto/to_schema.hpp b/include/rfl/capnproto/to_schema.hpp index 11c92794..a24d4bd1 100644 --- a/include/rfl/capnproto/to_schema.hpp +++ b/include/rfl/capnproto/to_schema.hpp @@ -19,16 +19,16 @@ namespace rfl::capnproto { -std::string to_json_representation( +std::string to_string_representation( const parsing::schema::Definition& internal_schema); -/// Returns the Capnproto schema for a class. +/// Returns the Cap'n Proto schema for a class. template Schema to_schema() noexcept { const auto internal_schema = parsing::schema::make>(); - const auto json_str = to_json_representation(internal_schema); - return std::move(Schema::from_json(json_str).value()); + const auto str = to_string_representation(internal_schema); + return Schema::from_string(str).value(); } } // namespace rfl::capnproto diff --git a/src/reflectcpp_capnproto.cpp b/src/reflectcpp_capnproto.cpp index 65defc58..217e3f23 100644 --- a/src/reflectcpp_capnproto.cpp +++ b/src/reflectcpp_capnproto.cpp @@ -39,4 +39,4 @@ SOFTWARE. #include "rfl/capnproto/SchemaImpl.cpp" #include "rfl/capnproto/Type.cpp" #include "rfl/capnproto/Writer.cpp" - +#include "rfl/capnproto/to_schema.cpp" diff --git a/src/rfl/capnproto/Type.cpp b/src/rfl/capnproto/Type.cpp index ef0ad5d2..7a77055f 100644 --- a/src/rfl/capnproto/Type.cpp +++ b/src/rfl/capnproto/Type.cpp @@ -28,6 +28,20 @@ SOFTWARE. namespace rfl::capnproto::schema { +Type Type::with_name(const std::string& _name) const { + const auto set_name = [&](const auto& _v) -> ReflectionType { + using T = std::remove_cvref_t; + if constexpr (std::is_same()) { + auto v_with_name = _v; + v_with_name.name = _name; + return v_with_name; + } else { + return _v; + } + }; + return Type{.value = value.visit(set_name)}; +} + std::ostream& operator<<(std::ostream& _os, const Type::Void&) { return _os << "Void"; } @@ -98,7 +112,7 @@ std::ostream& operator<<(std::ostream& _os, const Type::List& _l) { } std::ostream& operator<<(std::ostream& _os, const Type::Reference& _r) { - return _os << _r.type_name; + return _os; } std::ostream& operator<<(std::ostream& _os, const Type& _t) { diff --git a/src/rfl/capnproto/to_schema.cpp b/src/rfl/capnproto/to_schema.cpp new file mode 100644 index 00000000..181d10f7 --- /dev/null +++ b/src/rfl/capnproto/to_schema.cpp @@ -0,0 +1,181 @@ +/* + +MIT License + +Copyright (c) 2023-2024 Code17 GmbH + +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. + +*/ + +#include "rfl/capnproto/to_schema.hpp" + +#include + +#include "rfl/capnproto/schema/Type.hpp" +#include "rfl/internal/strings/split.hpp" +#include "rfl/json.hpp" +#include "rfl/parsing/schemaful/tuple_to_object.hpp" + +namespace rfl::capnproto { + +inline bool is_named_type(const parsing::schema::Type& _type) { + return _type.variant_.visit([&](const auto& _v) -> bool { + using T = std::remove_cvref_t; + return std::is_same() || + std::is_same(); + }); +} + +schema::Type type_to_capnproto_schema_type( + const parsing::schema::Type& _type, + const std::map& _definitions, + std::set* _already_known, size_t* _num_unnamed) { + auto handle_variant = [&](const auto& _t) -> schema::Type { + using T = std::remove_cvref_t; + using Type = parsing::schema::Type; + if constexpr (std::is_same()) { + return schema::Type{.value = schema::Type::Bool{}}; + + } else if constexpr (std::is_same()) { + return schema::Type{.value = schema::Type::Data{}}; + + } else if constexpr (std::is_same() || + std::is_same()) { + return schema::Type{.value = schema::Type::Int32{}}; + + } else if constexpr (std::is_same()) { + return schema::Type{.value = schema::Type::Int64{}}; + + } else if constexpr (std::is_same()) { + return schema::Type{.value = schema::Type::UInt32{}}; + + } else if constexpr (std::is_same()) { + return schema::Type{.value = schema::Type::UInt64{}}; + + } else if constexpr (std::is_same()) { + return schema::Type{.value = schema::Type::Float32{}}; + + } else if constexpr (std::is_same()) { + return schema::Type{.value = schema::Type::Float64{}}; + + } else if constexpr (std::is_same()) { + return schema::Type{.value = schema::Type::Text{}}; + + } else if constexpr (std::is_same()) { + // TODO + return schema::Type{.value = schema::Type::Void{}}; + + } else if constexpr (std::is_same()) { + return type_to_capnproto_schema_type(*_t.type_, _definitions, + _already_known, _num_unnamed); + + } else if constexpr (std::is_same()) { + return schema::Type{ + .value = schema::Type::List{ + .type = Ref::make(type_to_capnproto_schema_type( + *_t.type_, _definitions, _already_known, _num_unnamed))}}; + + } else if constexpr (std::is_same()) { + // TODO + return schema::Type{.value = schema::Type::Void{}}; + /*return schema::Type{ + .value = schema::Type::Enum{.name = std::string("unnamed_") + + std::to_string(++(*_num_unnamed)), + .symbols = _t.values_}};*/ + + } else if constexpr (std::is_same()) { + auto struct_schema = schema::Type::Struct{ + .name = std::string("Unnamed") + std::to_string(++(*_num_unnamed))}; + for (const auto& [k, v] : _t.types_) { + struct_schema.fields.push_back(std::make_pair( + k, type_to_capnproto_schema_type(v, _definitions, _already_known, + _num_unnamed))); + } + return schema::Type{.value = struct_schema}; + + } else if constexpr (std::is_same()) { + // TODO + return schema::Type{.value = schema::Type::Void{}}; + /*return schema::Type{ + .value = std::vector( + {type_to_capnproto_schema_type(*_t.type_, _definitions, + _already_known, _num_unnamed), + schema::Type{schema::Type::Null{}}})};*/ + + } else if constexpr (std::is_same()) { + return schema::Type{.value = + schema::Type::Reference{.type_name = _t.name_}}; + + } else if constexpr (std::is_same()) { + // TODO + return schema::Type{.value = schema::Type::Void{}}; + /*return schema::Type{ + .value = schema::Type::Map{ + .values = Ref::make(type_to_capnproto_schema_type( + *_t.value_type_, _definitions, _already_known, + _num_unnamed))}};*/ + + } else if constexpr (std::is_same()) { + return type_to_capnproto_schema_type( + Type{parsing::schemaful::tuple_to_object(_t)}, _definitions, + _already_known, _num_unnamed); + + } else if constexpr (std::is_same()) { + return schema::Type{ + .value = schema::Type::List{ + .type = Ref::make(type_to_capnproto_schema_type( + *_t.type_, _definitions, _already_known, _num_unnamed))}}; + + } else if constexpr (std::is_same()) { + // Cap'n Proto knows no validation. + return type_to_capnproto_schema_type(*_t.type_, _definitions, + _already_known, _num_unnamed); + + } else { + static_assert(rfl::always_false_v, "Not all cases were covered."); + } + }; + + return rfl::visit(handle_variant, _type.variant_); +} + +std::string to_string_representation( + const parsing::schema::Definition& _internal_schema) { + std::set already_known; + size_t num_unnamed = 0; + const auto capnproto_schema = type_to_capnproto_schema_type( + _internal_schema.root_, _internal_schema.definitions_, &already_known, + &num_unnamed); + std::stringstream sstream; + // TODO: ID is hardcoded. + sstream << "@0xdbb9ad1f14bf0b36;" << std::endl + << std::endl + << capnproto_schema; + for (const auto& [name, def] : _internal_schema.definitions_) { + sstream << type_to_capnproto_schema_type(def, _internal_schema.definitions_, + &already_known, &num_unnamed) + .with_name(internal::strings::split(name, "__").back()) + << std::endl + << std::endl; + } + return sstream.str(); +} + +} // namespace rfl::capnproto diff --git a/tests/capnproto/test_person.cpp b/tests/capnproto/test_person.cpp index f3e16383..2ab4f846 100644 --- a/tests/capnproto/test_person.cpp +++ b/tests/capnproto/test_person.cpp @@ -10,23 +10,13 @@ namespace test_tutorial_example { -const std::string PERSON_SCHEMA = R"( -@0xb07899ba441fd7ec; - -struct Person { - firstName @0 :Text; - lastName @1 :Text; -} -)"; - struct Person { std::string firstName; std::string lastName; }; TEST(capnproto, test_person) { - const auto schema = - rfl::capnproto::Schema::from_string(PERSON_SCHEMA).value(); + const auto schema = rfl::capnproto::to_schema(); const auto homer1 = Person{.firstName = "Homer", .lastName = "Simpson"}; const auto serialized1 = rfl::capnproto::write(homer1, schema); const auto homer2 = rfl::capnproto::read(serialized1, schema).value(); From 3031862ea709e2c2bf24e69a8735398d7c57aade Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Thu, 26 Dec 2024 18:59:14 +0100 Subject: [PATCH 12/57] More robust handling of the type names --- include/rfl/capnproto/read.hpp | 10 +++-- include/rfl/capnproto/write.hpp | 12 ++++-- .../rfl/internal/strings/to_pascal_case.hpp | 40 +++++++++++++++++++ include/rfl/parsing/Parser_default.hpp | 19 +-------- include/rfl/parsing/make_type_name.hpp | 30 ++++++++++++++ src/rfl/capnproto/Type.cpp | 4 +- src/rfl/capnproto/to_schema.cpp | 32 ++++++--------- 7 files changed, 102 insertions(+), 45 deletions(-) create mode 100644 include/rfl/internal/strings/to_pascal_case.hpp create mode 100644 include/rfl/parsing/make_type_name.hpp diff --git a/include/rfl/capnproto/read.hpp b/include/rfl/capnproto/read.hpp index f8d95efb..ac683319 100644 --- a/include/rfl/capnproto/read.hpp +++ b/include/rfl/capnproto/read.hpp @@ -11,7 +11,10 @@ #include #include "../Processors.hpp" +#include "../SnakeCaseToCamelCase.hpp" +#include "../internal/strings/to_pascal_case.hpp" #include "../internal/wrap_in_rfl_array_t.hpp" +#include "../parsing/make_type_name.hpp" #include "Parser.hpp" #include "Reader.hpp" #include "Schema.hpp" @@ -26,7 +29,7 @@ using InputVarType = typename Reader::InputVarType; template auto read(const InputVarType& _obj) { const auto r = Reader(); - return Parser>::read(r, _obj); + return Parser>::read(r, _obj); } /// Parses an object from CAPNPROTO using reflection. @@ -37,8 +40,9 @@ Result> read( internal::ptr_cast(_bytes), _size); auto input_stream = kj::ArrayInputStream(array_ptr); auto message_reader = capnp::PackedMessageReader(input_stream); - // TODO: Person is hardcoded. - const auto root_schema = _schema.value().getNested("Person"); + const auto root_name = internal::strings::to_pascal_case( + parsing::make_type_name>()); + const auto root_schema = _schema.value().getNested(root_name.c_str()); const auto input_var = InputVarType{ message_reader.getRoot(root_schema.asStruct())}; return read(input_var); diff --git a/include/rfl/capnproto/write.hpp b/include/rfl/capnproto/write.hpp index e93a23cb..81fbec32 100644 --- a/include/rfl/capnproto/write.hpp +++ b/include/rfl/capnproto/write.hpp @@ -16,8 +16,11 @@ #include #include +#include "../SnakeCaseToCamelCase.hpp" #include "../internal/ptr_cast.hpp" +#include "../internal/strings/to_pascal_case.hpp" #include "../parsing/Parent.hpp" +#include "../parsing/make_type_name.hpp" #include "Parser.hpp" #include "Schema.hpp" #include "Writer.hpp" @@ -33,14 +36,15 @@ std::vector write(const auto& _obj, const auto& _schema) noexcept { using ParentType = parsing::Parent; static_assert(std::is_same(), "The schema must be compatible with the type to write."); - // TODO: Person is hardcoded. - const auto root_schema = _schema.value().getNested("Person"); + const auto root_name = internal::strings::to_pascal_case( + parsing::make_type_name>()); + const auto root_schema = _schema.value().getNested(root_name.c_str()); capnp::MallocMessageBuilder message_builder; auto root = message_builder.initRoot(root_schema.asStruct()); const auto writer = Writer(&root); - Parser>::write(writer, _obj, - typename ParentType::Root{}); + Parser>::write( + writer, _obj, typename ParentType::Root{}); kj::VectorOutputStream output_stream; capnp::writePackedMessage(output_stream, message_builder); auto arr_ptr = output_stream.getArray(); diff --git a/include/rfl/internal/strings/to_pascal_case.hpp b/include/rfl/internal/strings/to_pascal_case.hpp new file mode 100644 index 00000000..283053aa --- /dev/null +++ b/include/rfl/internal/strings/to_pascal_case.hpp @@ -0,0 +1,40 @@ +#ifndef RFL_INTERNAL_STRINGS_TOPASCALCASE_HPP_ +#define RFL_INTERNAL_STRINGS_TOPASCALCASE_HPP_ + +#include +#include + +namespace rfl { +namespace internal { +namespace strings { + +inline char to_upper(const char ch) { + if (ch >= 'a' && ch <= 'z') { + return ch + ('A' - 'a'); + } else { + return ch; + } +} + +/// Splits a string alongside the delimiter +inline std::string to_pascal_case(const std::string& _str) { + std::string result; + bool capitalize = true; + for (const char ch : _str) { + if (ch == '_') { + capitalize = true; + } else if (capitalize) { + result.push_back(to_upper(ch)); + capitalize = false; + } else { + result.push_back(ch); + } + } + return result; +} + +} // namespace strings +} // namespace internal +} // namespace rfl + +#endif diff --git a/include/rfl/parsing/Parser_default.hpp b/include/rfl/parsing/Parser_default.hpp index 38f4945c..7c56a5b4 100644 --- a/include/rfl/parsing/Parser_default.hpp +++ b/include/rfl/parsing/Parser_default.hpp @@ -22,12 +22,12 @@ #include "../internal/ptr_cast.hpp" #include "../internal/to_ptr_named_tuple.hpp" #include "../to_view.hpp" -#include "../type_name_t.hpp" #include "AreReaderAndWriter.hpp" #include "Parent.hpp" #include "Parser_base.hpp" #include "call_destructors_where_necessary.hpp" #include "is_tagged_union_wrapper.hpp" +#include "make_type_name.hpp" #include "schema/Type.hpp" #include "schemaful/IsSchemafulReader.hpp" #include "schemaful/IsSchemafulWriter.hpp" @@ -265,16 +265,6 @@ struct Parser { .validation_ = ValidationType::template to_schema()}}; } - template - static std::string make_type_name() { - if constexpr (is_tagged_union_wrapper_v) { - return replace_non_alphanumeric(type_name_t().str() + - "__tagged"); - } else { - return replace_non_alphanumeric(type_name_t().str()); - } - } - /// 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 @@ -312,13 +302,6 @@ struct Parser { } return t; } - - static std::string replace_non_alphanumeric(std::string _str) { - for (auto& ch : _str) { - ch = std::isalnum(ch) ? ch : '_'; - } - return _str; - } }; } // namespace rfl::parsing diff --git a/include/rfl/parsing/make_type_name.hpp b/include/rfl/parsing/make_type_name.hpp new file mode 100644 index 00000000..306520fc --- /dev/null +++ b/include/rfl/parsing/make_type_name.hpp @@ -0,0 +1,30 @@ +#ifndef RFL_PARSING_MAKETYPENAME_HPP_ +#define RFL_PARSING_MAKETYPENAME_HPP_ + +#include "../type_name_t.hpp" +#include "is_tagged_union_wrapper.hpp" + +namespace rfl { +namespace parsing { + +inline std::string replace_non_alphanumeric(std::string _str) { + for (auto& ch : _str) { + ch = std::isalnum(ch) ? ch : '_'; + } + return _str; +} + +template +static std::string make_type_name() { + if constexpr (is_tagged_union_wrapper_v) { + return replace_non_alphanumeric(type_name_t().str() + + "__tagged"); + } else { + return replace_non_alphanumeric(type_name_t().str()); + } +} + +} // namespace parsing +} // namespace rfl + +#endif diff --git a/src/rfl/capnproto/Type.cpp b/src/rfl/capnproto/Type.cpp index 7a77055f..412d3aec 100644 --- a/src/rfl/capnproto/Type.cpp +++ b/src/rfl/capnproto/Type.cpp @@ -26,6 +26,8 @@ SOFTWARE. #include "rfl/capnproto/schema/Type.hpp" +#include "rfl/internal/strings/to_pascal_case.hpp" + namespace rfl::capnproto::schema { Type Type::with_name(const std::string& _name) const { @@ -112,7 +114,7 @@ std::ostream& operator<<(std::ostream& _os, const Type::List& _l) { } std::ostream& operator<<(std::ostream& _os, const Type::Reference& _r) { - return _os; + return _os << internal::strings::to_pascal_case(_r.type_name); } std::ostream& operator<<(std::ostream& _os, const Type& _t) { diff --git a/src/rfl/capnproto/to_schema.cpp b/src/rfl/capnproto/to_schema.cpp index 181d10f7..8b2dd734 100644 --- a/src/rfl/capnproto/to_schema.cpp +++ b/src/rfl/capnproto/to_schema.cpp @@ -30,6 +30,7 @@ SOFTWARE. #include "rfl/capnproto/schema/Type.hpp" #include "rfl/internal/strings/split.hpp" +#include "rfl/internal/strings/to_pascal_case.hpp" #include "rfl/json.hpp" #include "rfl/parsing/schemaful/tuple_to_object.hpp" @@ -46,7 +47,7 @@ inline bool is_named_type(const parsing::schema::Type& _type) { schema::Type type_to_capnproto_schema_type( const parsing::schema::Type& _type, const std::map& _definitions, - std::set* _already_known, size_t* _num_unnamed) { + size_t* _num_unnamed) { auto handle_variant = [&](const auto& _t) -> schema::Type { using T = std::remove_cvref_t; using Type = parsing::schema::Type; @@ -84,13 +85,13 @@ schema::Type type_to_capnproto_schema_type( } else if constexpr (std::is_same()) { return type_to_capnproto_schema_type(*_t.type_, _definitions, - _already_known, _num_unnamed); + _num_unnamed); } else if constexpr (std::is_same()) { return schema::Type{ .value = schema::Type::List{ .type = Ref::make(type_to_capnproto_schema_type( - *_t.type_, _definitions, _already_known, _num_unnamed))}}; + *_t.type_, _definitions, _num_unnamed))}}; } else if constexpr (std::is_same()) { // TODO @@ -105,8 +106,7 @@ schema::Type type_to_capnproto_schema_type( .name = std::string("Unnamed") + std::to_string(++(*_num_unnamed))}; for (const auto& [k, v] : _t.types_) { struct_schema.fields.push_back(std::make_pair( - k, type_to_capnproto_schema_type(v, _definitions, _already_known, - _num_unnamed))); + k, type_to_capnproto_schema_type(v, _definitions, _num_unnamed))); } return schema::Type{.value = struct_schema}; @@ -116,7 +116,7 @@ schema::Type type_to_capnproto_schema_type( /*return schema::Type{ .value = std::vector( {type_to_capnproto_schema_type(*_t.type_, _definitions, - _already_known, _num_unnamed), + _num_unnamed), schema::Type{schema::Type::Null{}}})};*/ } else if constexpr (std::is_same()) { @@ -129,24 +129,24 @@ schema::Type type_to_capnproto_schema_type( /*return schema::Type{ .value = schema::Type::Map{ .values = Ref::make(type_to_capnproto_schema_type( - *_t.value_type_, _definitions, _already_known, + *_t.value_type_, _definitions, _num_unnamed))}};*/ } else if constexpr (std::is_same()) { return type_to_capnproto_schema_type( Type{parsing::schemaful::tuple_to_object(_t)}, _definitions, - _already_known, _num_unnamed); + _num_unnamed); } else if constexpr (std::is_same()) { return schema::Type{ .value = schema::Type::List{ .type = Ref::make(type_to_capnproto_schema_type( - *_t.type_, _definitions, _already_known, _num_unnamed))}}; + *_t.type_, _definitions, _num_unnamed))}}; } else if constexpr (std::is_same()) { // Cap'n Proto knows no validation. return type_to_capnproto_schema_type(*_t.type_, _definitions, - _already_known, _num_unnamed); + _num_unnamed); } else { static_assert(rfl::always_false_v, "Not all cases were covered."); @@ -158,20 +158,14 @@ schema::Type type_to_capnproto_schema_type( std::string to_string_representation( const parsing::schema::Definition& _internal_schema) { - std::set already_known; size_t num_unnamed = 0; - const auto capnproto_schema = type_to_capnproto_schema_type( - _internal_schema.root_, _internal_schema.definitions_, &already_known, - &num_unnamed); std::stringstream sstream; // TODO: ID is hardcoded. - sstream << "@0xdbb9ad1f14bf0b36;" << std::endl - << std::endl - << capnproto_schema; + sstream << "@0xdbb9ad1f14bf0b36;" << std::endl << std::endl; for (const auto& [name, def] : _internal_schema.definitions_) { sstream << type_to_capnproto_schema_type(def, _internal_schema.definitions_, - &already_known, &num_unnamed) - .with_name(internal::strings::split(name, "__").back()) + &num_unnamed) + .with_name(internal::strings::to_pascal_case(name)) << std::endl << std::endl; } From 9535f0fa1e455fa757b7a2b722058a43c9e53d02 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Thu, 26 Dec 2024 19:01:45 +0100 Subject: [PATCH 13/57] Make sure that field names are automatically transformed to camelCase --- include/rfl/capnproto/to_schema.hpp | 4 +++- tests/capnproto/test_person.cpp | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/include/rfl/capnproto/to_schema.hpp b/include/rfl/capnproto/to_schema.hpp index a24d4bd1..ea429110 100644 --- a/include/rfl/capnproto/to_schema.hpp +++ b/include/rfl/capnproto/to_schema.hpp @@ -7,6 +7,7 @@ #include "../Literal.hpp" #include "../Processors.hpp" +#include "../SnakeCaseToCamelCase.hpp" #include "../Variant.hpp" #include "../json.hpp" #include "../parsing/schema/Type.hpp" @@ -26,7 +27,8 @@ std::string to_string_representation( template Schema to_schema() noexcept { const auto internal_schema = - parsing::schema::make>(); + parsing::schema::make>(); const auto str = to_string_representation(internal_schema); return Schema::from_string(str).value(); } diff --git a/tests/capnproto/test_person.cpp b/tests/capnproto/test_person.cpp index 2ab4f846..363fbcb3 100644 --- a/tests/capnproto/test_person.cpp +++ b/tests/capnproto/test_person.cpp @@ -11,13 +11,13 @@ namespace test_tutorial_example { struct Person { - std::string firstName; - std::string lastName; + std::string first_name; + std::string last_name; }; TEST(capnproto, test_person) { const auto schema = rfl::capnproto::to_schema(); - const auto homer1 = Person{.firstName = "Homer", .lastName = "Simpson"}; + const auto homer1 = Person{.first_name = "Homer", .last_name = "Simpson"}; const auto serialized1 = rfl::capnproto::write(homer1, schema); const auto homer2 = rfl::capnproto::read(serialized1, schema).value(); const auto serialized2 = rfl::capnproto::write(homer2, schema); From 886017c031ca2b0cc8ac53e0cff0982c8c42d165 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sat, 28 Dec 2024 12:14:31 +0100 Subject: [PATCH 14/57] Use a memoization-like pattern to generate the schema --- include/rfl/capnproto/to_schema.hpp | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/include/rfl/capnproto/to_schema.hpp b/include/rfl/capnproto/to_schema.hpp index ea429110..d5819540 100644 --- a/include/rfl/capnproto/to_schema.hpp +++ b/include/rfl/capnproto/to_schema.hpp @@ -7,13 +7,13 @@ #include "../Literal.hpp" #include "../Processors.hpp" +#include "../Result.hpp" #include "../SnakeCaseToCamelCase.hpp" #include "../Variant.hpp" #include "../json.hpp" #include "../parsing/schema/Type.hpp" #include "../parsing/schema/ValidationType.hpp" #include "../parsing/schema/make.hpp" -#include "Reader.hpp" #include "Schema.hpp" #include "Writer.hpp" #include "schema/Type.hpp" @@ -23,14 +23,26 @@ namespace rfl::capnproto { std::string to_string_representation( const parsing::schema::Definition& internal_schema); +template +struct SchemaHolder { + rfl::Result> schema_; + static SchemaHolder make() noexcept { + const auto internal_schema = + parsing::schema::make>(); + const auto str = to_string_representation(internal_schema); + return SchemaHolder{Schema::from_string(str)}; + } +}; + +template +static const SchemaHolder schema_holder = + SchemaHolder::make(); + /// Returns the Cap'n Proto schema for a class. template -Schema to_schema() noexcept { - const auto internal_schema = - parsing::schema::make>(); - const auto str = to_string_representation(internal_schema); - return Schema::from_string(str).value(); +Schema to_schema() { + return schema_holder.schema_.value(); } } // namespace rfl::capnproto From eddff206bca61179605be20119579c002203e2c4 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sat, 28 Dec 2024 12:16:15 +0100 Subject: [PATCH 15/57] Minor code beautifications --- include/rfl/capnproto/to_schema.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/rfl/capnproto/to_schema.hpp b/include/rfl/capnproto/to_schema.hpp index d5819540..24d66448 100644 --- a/include/rfl/capnproto/to_schema.hpp +++ b/include/rfl/capnproto/to_schema.hpp @@ -23,9 +23,9 @@ namespace rfl::capnproto { std::string to_string_representation( const parsing::schema::Definition& internal_schema); +/// This ensures that the schema is only generated once. template struct SchemaHolder { - rfl::Result> schema_; static SchemaHolder make() noexcept { const auto internal_schema = parsing::schema::make{Schema::from_string(str)}; } + + rfl::Result> schema_; }; template From a396fedaecd917b5446754547a131dc078ddefcb Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 29 Dec 2024 12:49:42 +0100 Subject: [PATCH 16/57] Make sure arrays work as intended --- include/rfl/capnproto/Writer.hpp | 1 + src/rfl/capnproto/Writer.cpp | 6 ++---- tests/capnproto/test_person.cpp | 22 ++++++++++++++-------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/include/rfl/capnproto/Writer.hpp b/include/rfl/capnproto/Writer.hpp index e5cb832b..0f7fc17e 100644 --- a/include/rfl/capnproto/Writer.hpp +++ b/include/rfl/capnproto/Writer.hpp @@ -28,6 +28,7 @@ class Writer { public: struct CapnProtoOutputArray { capnp::DynamicList::Builder val_; + size_t ix_ = 0; }; struct CapnProtoOutputMap { diff --git a/src/rfl/capnproto/Writer.cpp b/src/rfl/capnproto/Writer.cpp index a3aba11a..da0cccaa 100644 --- a/src/rfl/capnproto/Writer.cpp +++ b/src/rfl/capnproto/Writer.cpp @@ -85,10 +85,8 @@ return OutputMapType{new_map};*/ Writer::OutputObjectType Writer::add_object_to_array( const size_t _size, OutputArrayType* _parent) const noexcept { - // TODO - /*avro_value_t new_object; -avro_value_append(&_parent->val_, &new_object, nullptr); -return OutputObjectType{new_object};*/ + return OutputObjectType{ + _parent->val_[_parent->ix_++].as()}; } Writer::OutputObjectType Writer::add_object_to_map( diff --git a/tests/capnproto/test_person.cpp b/tests/capnproto/test_person.cpp index 363fbcb3..1e469935 100644 --- a/tests/capnproto/test_person.cpp +++ b/tests/capnproto/test_person.cpp @@ -6,21 +6,27 @@ #include #include -// #include "write_and_read.hpp" +#include "write_and_read.hpp" namespace test_tutorial_example { struct Person { std::string first_name; - std::string last_name; + std::string last_name = "Simpson"; + std::vector children; }; TEST(capnproto, test_person) { - const auto schema = rfl::capnproto::to_schema(); - const auto homer1 = Person{.first_name = "Homer", .last_name = "Simpson"}; - const auto serialized1 = rfl::capnproto::write(homer1, schema); - const auto homer2 = rfl::capnproto::read(serialized1, schema).value(); - const auto serialized2 = rfl::capnproto::write(homer2, schema); - EXPECT_EQ(serialized1, serialized2); + const auto bart = Person{.first_name = "Bart"}; + + const auto lisa = Person{.first_name = "Lisa"}; + + const auto maggie = Person{.first_name = "Maggie"}; + + const auto homer = + Person{.first_name = "Homer", + .children = std::vector({bart, lisa, maggie})}; + + write_and_read(homer); } } // namespace test_tutorial_example From bb78efd14c0744a8a0032352747897d84738e01c Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 29 Dec 2024 15:18:44 +0100 Subject: [PATCH 17/57] Started adding union support --- include/rfl/capnproto/Reader.hpp | 1 - include/rfl/capnproto/Writer.hpp | 26 ++++++++++++++++------ include/rfl/capnproto/schema/Type.hpp | 6 +++++- include/rfl/capnproto/to_schema.hpp | 1 + src/rfl/capnproto/Type.cpp | 22 ++++++++++++++++--- src/rfl/capnproto/Writer.cpp | 31 ++++++++++++++++----------- 6 files changed, 63 insertions(+), 24 deletions(-) diff --git a/include/rfl/capnproto/Reader.hpp b/include/rfl/capnproto/Reader.hpp index 422634b5..eea0d0ef 100644 --- a/include/rfl/capnproto/Reader.hpp +++ b/include/rfl/capnproto/Reader.hpp @@ -31,7 +31,6 @@ struct Reader { }; struct CapNProtoInputUnion { - // TODO: Is this right? capnp::DynamicStruct::Reader val_; }; diff --git a/include/rfl/capnproto/Writer.hpp b/include/rfl/capnproto/Writer.hpp index 0f7fc17e..a70b6c89 100644 --- a/include/rfl/capnproto/Writer.hpp +++ b/include/rfl/capnproto/Writer.hpp @@ -40,7 +40,6 @@ class Writer { }; struct CapnProtoOutputUnion { - // TODO: Is this right? capnp::DynamicStruct::Builder val_; }; @@ -197,18 +196,31 @@ class Writer { } else { static_assert(rfl::always_false_v, "Unsupported type."); } - return OutputVarType{}; } template OutputVarType add_value_to_union(const size_t _index, const T& _var, OutputUnionType* _parent) const noexcept { - /*capnproto_value_t new_value; - capnproto_value_set_branch(&_parent->val_, static_cast(_index), - &new_value); - set_value(_var, &new_value); - return OutputVarType{new_value};*/ + const auto field_maybe = _parent->val_.getSchema().getFieldByDiscriminant( + static_cast(_index)); + field_maybe.map([&](const auto& _field) { + if constexpr (std::is_same, std::string>()) { + _parent->val_.set(_field, _var.c_str()); + + } else if constexpr (std::is_floating_point>() || + std::is_same, bool>()) { + _parent->val_.set(_field, _var); + + } else if constexpr (std::is_integral>()) { + _parent->val_.set(_field, static_cast(_var)); + + } else { + static_assert(rfl::always_false_v, "Unsupported type."); + } + return _field; + }); + return OutputVarType{}; } void end_array(OutputArrayType* _arr) const noexcept {} diff --git a/include/rfl/capnproto/schema/Type.hpp b/include/rfl/capnproto/schema/Type.hpp index d72618ba..7050d12b 100644 --- a/include/rfl/capnproto/schema/Type.hpp +++ b/include/rfl/capnproto/schema/Type.hpp @@ -41,6 +41,10 @@ struct Type { struct Data {}; + struct Optional { + rfl::Ref type; + }; + struct Text {}; struct Struct { @@ -63,7 +67,7 @@ struct Type { using ReflectionType = rfl::Variant; + Optional, Reference>; const auto& reflection() const { return value; } diff --git a/include/rfl/capnproto/to_schema.hpp b/include/rfl/capnproto/to_schema.hpp index 24d66448..5f64ab7d 100644 --- a/include/rfl/capnproto/to_schema.hpp +++ b/include/rfl/capnproto/to_schema.hpp @@ -31,6 +31,7 @@ struct SchemaHolder { parsing::schema::make>(); const auto str = to_string_representation(internal_schema); + std::cout << str << std::endl; return SchemaHolder{Schema::from_string(str)}; } diff --git a/src/rfl/capnproto/Type.cpp b/src/rfl/capnproto/Type.cpp index 412d3aec..7032742a 100644 --- a/src/rfl/capnproto/Type.cpp +++ b/src/rfl/capnproto/Type.cpp @@ -26,6 +26,8 @@ SOFTWARE. #include "rfl/capnproto/schema/Type.hpp" +#include + #include "rfl/internal/strings/to_pascal_case.hpp" namespace rfl::capnproto::schema { @@ -96,15 +98,29 @@ std::ostream& operator<<(std::ostream& _os, const Type::Data&) { return _os << "Data"; } +std::ostream& operator<<(std::ostream& _os, const Type::Optional&) { + return _os << "TODO"; +} + std::ostream& operator<<(std::ostream& _os, const Type::Text&) { return _os << "Text"; } std::ostream& operator<<(std::ostream& _os, const Type::Struct& _s) { _os << "struct " << _s.name << " {" << std::endl; - for (size_t i = 0; i < _s.fields.size(); ++i) { - const auto& [name, type] = _s.fields[i]; - _os << " " << name << " @" << i << " :" << type << ";" << std::endl; + size_t ix = 0; + for (const auto& [name, type] : _s.fields) { + type.reflection().visit([&](const auto& _r) { + using R = std::remove_cvref_t; + if constexpr (std::is_same()) { + _os << " " << name << " :union {" << std::endl + << " some @" << ix++ << " :" << *_r.type << ";" << std::endl + << " none @" << ix++ << " :Void;" << std::endl + << " }" << std::endl; + } else { + _os << " " << name << " @" << ix++ << " :" << _r << ";" << std::endl; + } + }); } return _os << "}" << std::endl; } diff --git a/src/rfl/capnproto/Writer.cpp b/src/rfl/capnproto/Writer.cpp index da0cccaa..952b8a54 100644 --- a/src/rfl/capnproto/Writer.cpp +++ b/src/rfl/capnproto/Writer.cpp @@ -45,9 +45,16 @@ Writer::OutputArrayType Writer::add_array_to_object( Writer::OutputArrayType Writer::add_array_to_union( const size_t _index, const size_t _size, OutputUnionType* _parent) const noexcept { - /*avro_value_t new_array; - avro_value_set_branch(&_parent->val_, static_cast(_index), &new_array); - return OutputArrayType{new_array};*/ + const auto field_maybe = _parent->val_.getSchema().getFieldByDiscriminant( + static_cast(_index)); + return OutputArrayType{ + field_maybe + .map([&](const auto& _field) { + return _parent->val_.init(_field, _size) + .template as(); + }) + .orDefault(_parent->val_.init("indexOutOfRange", _size) + .template as())}; } Writer::OutputMapType Writer::add_map_to_array( @@ -135,10 +142,8 @@ return OutputUnionType{new_union};*/ Writer::OutputUnionType Writer::add_union_to_object( const std::string_view& _name, OutputObjectType* _parent) const noexcept { - // TODO - /*avro_value_t new_union; -avro_value_get_by_name(&_parent->val_, _name.data(), &new_union, nullptr); -return OutputUnionType{new_union};*/ + return OutputUnionType{ + _parent->val_.get(_name.data()).as()}; } Writer::OutputUnionType Writer::add_union_to_union( @@ -178,11 +183,13 @@ return OutputVarType{new_null};*/ Writer::OutputVarType Writer::add_null_to_union( const size_t _index, OutputUnionType* _parent) const noexcept { - // TODO - /*avro_value_t new_null; -avro_value_set_branch(&_parent->val_, static_cast(_index), &new_null); -avro_value_set_null(&new_null); -return OutputVarType{new_null};*/ + const auto field_maybe = _parent->val_.getSchema().getFieldByDiscriminant( + static_cast(_index)); + field_maybe.map([&](const auto& _field) { + _parent->val_.set(_field, capnp::VOID); + return _field; + }); + return OutputVarType{}; } } // namespace rfl::capnproto From 96c1ec856387db47f976a0447e9cada426531527 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 29 Dec 2024 19:08:15 +0100 Subject: [PATCH 18/57] Added support for optionals --- include/rfl/capnproto/Reader.hpp | 27 +++++------ include/rfl/capnproto/Writer.hpp | 60 ++++++------------------ src/rfl/capnproto/Reader.cpp | 13 ++++- src/rfl/capnproto/Writer.cpp | 19 ++------ src/rfl/capnproto/to_schema.cpp | 17 ++----- tests/capnproto/test_optional_fields.cpp | 30 ++++++++++++ tests/capnproto/write_and_read.hpp | 4 ++ 7 files changed, 79 insertions(+), 91 deletions(-) create mode 100644 tests/capnproto/test_optional_fields.cpp diff --git a/include/rfl/capnproto/Reader.hpp b/include/rfl/capnproto/Reader.hpp index eea0d0ef..427be2d5 100644 --- a/include/rfl/capnproto/Reader.hpp +++ b/include/rfl/capnproto/Reader.hpp @@ -17,7 +17,8 @@ namespace rfl::capnproto { -struct Reader { +class Reader { + public: struct CapNProtoInputArray { capnp::DynamicList::Reader val_; }; @@ -143,9 +144,6 @@ struct Reader { std::optional read_object(const ObjectReader& _object_reader, const InputObjectType& _obj) const noexcept { for (auto field : _obj.val_.getSchema().getFields()) { - if (!_obj.val_.has(field)) { - continue; - } _object_reader.read(field.getProto().getName().cStr(), InputVarType{_obj.val_.get(field)}); } @@ -155,20 +153,13 @@ struct Reader { template rfl::Result read_union( const InputUnionType& _union) const noexcept { - // TODO - /*int disc = 0; - auto err = capnproto_value_get_discriminant(_union.val_, &disc); - if (err) { + const auto opt_pair = identify_discriminant(_union); + if (!opt_pair) { return Error("Could not get the discriminant."); } - capnproto_value_t value; - err = capnproto_value_get_current_branch(_union.val_, &value); - if (err) { - return Error("Could not cast the union type."); - } - return UnionReaderType::read(*this, static_cast(disc), - InputVarType{&value});*/ - return Error("TODO"); + const auto& [field, disc] = *opt_pair; + return UnionReaderType::read(*this, disc, + InputVarType{_union.val_.get(field)}); } template @@ -180,6 +171,10 @@ struct Reader { return rfl::Error(e.what()); } } + + private: + std::optional> + identify_discriminant(const InputUnionType& _union) const noexcept; }; } // namespace rfl::capnproto diff --git a/include/rfl/capnproto/Writer.hpp b/include/rfl/capnproto/Writer.hpp index a70b6c89..27e61551 100644 --- a/include/rfl/capnproto/Writer.hpp +++ b/include/rfl/capnproto/Writer.hpp @@ -202,60 +202,28 @@ class Writer { template OutputVarType add_value_to_union(const size_t _index, const T& _var, OutputUnionType* _parent) const noexcept { - const auto field_maybe = _parent->val_.getSchema().getFieldByDiscriminant( - static_cast(_index)); - field_maybe.map([&](const auto& _field) { - if constexpr (std::is_same, std::string>()) { - _parent->val_.set(_field, _var.c_str()); - - } else if constexpr (std::is_floating_point>() || - std::is_same, bool>()) { - _parent->val_.set(_field, _var); - - } else if constexpr (std::is_integral>()) { - _parent->val_.set(_field, static_cast(_var)); - - } else { - static_assert(rfl::always_false_v, "Unsupported type."); - } - return _field; - }); - return OutputVarType{}; - } - - void end_array(OutputArrayType* _arr) const noexcept {} - - void end_map(OutputMapType* _obj) const noexcept {} - - void end_object(OutputObjectType* _obj) const noexcept {} - - private: - /*template - void set_value(const T& _var, capnproto_value_t* _val) const noexcept { + const auto field = _parent->val_.getSchema().getFields()[_index]; if constexpr (std::is_same, std::string>()) { - capnproto_value_set_string_len(_val, _var.c_str(), _var.size() + 1); - - } else if constexpr (std::is_same, - rfl::Bytestring>()) { - auto var = _var; - capnproto_value_set_bytes(_val, var.data(), var.size() + 1); - - } else if constexpr (std::is_same, bool>()) { - capnproto_value_set_boolean(_val, _var); + _parent->val_.set(field, _var.c_str()); - } else if constexpr (std::is_floating_point>()) { - capnproto_value_set_double(_val, static_cast(_var)); + } else if constexpr (std::is_floating_point>() || + std::is_same, bool>()) { + _parent->val_.set(field, _var); } else if constexpr (std::is_integral>()) { - capnproto_value_set_long(_val, static_cast(_var)); - - } else if constexpr (internal::is_literal_v) { - capnproto_value_set_enum(_val, static_cast(_var.value())); + _parent->val_.set(field, static_cast(_var)); } else { static_assert(rfl::always_false_v, "Unsupported type."); } - }*/ + return OutputVarType{}; + } + + void end_array(OutputArrayType* _arr) const noexcept {} + + void end_map(OutputMapType* _obj) const noexcept {} + + void end_object(OutputObjectType* _obj) const noexcept {} private: capnp::DynamicStruct::Builder* root_; diff --git a/src/rfl/capnproto/Reader.cpp b/src/rfl/capnproto/Reader.cpp index 3bc92f26..bd1b7fe4 100644 --- a/src/rfl/capnproto/Reader.cpp +++ b/src/rfl/capnproto/Reader.cpp @@ -7,8 +7,19 @@ namespace rfl::capnproto { static_assert(parsing::schemaful::IsSchemafulReader, "This must be a schemaful reader."); +std::optional> +Reader::identify_discriminant(const InputUnionType& _union) const noexcept { + size_t ix = 0; + for (auto field : _union.val_.getSchema().getFields()) { + if (_union.val_.has(field)) { + return std::make_pair(field, ix); + } + ++ix; + } + return std::nullopt; +} + bool Reader::is_empty(const InputVarType& _var) const noexcept { - // TODO: Is this correct? return _var.val_.getType() == capnp::DynamicValue::VOID; } diff --git a/src/rfl/capnproto/Writer.cpp b/src/rfl/capnproto/Writer.cpp index 952b8a54..17a6431a 100644 --- a/src/rfl/capnproto/Writer.cpp +++ b/src/rfl/capnproto/Writer.cpp @@ -45,16 +45,9 @@ Writer::OutputArrayType Writer::add_array_to_object( Writer::OutputArrayType Writer::add_array_to_union( const size_t _index, const size_t _size, OutputUnionType* _parent) const noexcept { - const auto field_maybe = _parent->val_.getSchema().getFieldByDiscriminant( - static_cast(_index)); + const auto field = _parent->val_.getSchema().getFields()[_index]; return OutputArrayType{ - field_maybe - .map([&](const auto& _field) { - return _parent->val_.init(_field, _size) - .template as(); - }) - .orDefault(_parent->val_.init("indexOutOfRange", _size) - .template as())}; + _parent->val_.init(field, _size).template as()}; } Writer::OutputMapType Writer::add_map_to_array( @@ -183,12 +176,8 @@ return OutputVarType{new_null};*/ Writer::OutputVarType Writer::add_null_to_union( const size_t _index, OutputUnionType* _parent) const noexcept { - const auto field_maybe = _parent->val_.getSchema().getFieldByDiscriminant( - static_cast(_index)); - field_maybe.map([&](const auto& _field) { - _parent->val_.set(_field, capnp::VOID); - return _field; - }); + const auto field = _parent->val_.getSchema().getFields()[_index]; + _parent->val_.set(field, capnp::VOID); return OutputVarType{}; } diff --git a/src/rfl/capnproto/to_schema.cpp b/src/rfl/capnproto/to_schema.cpp index 8b2dd734..d5cf43a4 100644 --- a/src/rfl/capnproto/to_schema.cpp +++ b/src/rfl/capnproto/to_schema.cpp @@ -111,18 +111,13 @@ schema::Type type_to_capnproto_schema_type( return schema::Type{.value = struct_schema}; } else if constexpr (std::is_same()) { - // TODO - return schema::Type{.value = schema::Type::Void{}}; - /*return schema::Type{ - .value = std::vector( - {type_to_capnproto_schema_type(*_t.type_, _definitions, - _num_unnamed), - schema::Type{schema::Type::Null{}}})};*/ - + return schema::Type{ + .value = schema::Type::Optional{ + .type = Ref::make(type_to_capnproto_schema_type( + *_t.type_, _definitions, _num_unnamed))}}; } else if constexpr (std::is_same()) { return schema::Type{.value = schema::Type::Reference{.type_name = _t.name_}}; - } else if constexpr (std::is_same()) { // TODO return schema::Type{.value = schema::Type::Void{}}; @@ -131,23 +126,19 @@ schema::Type type_to_capnproto_schema_type( .values = Ref::make(type_to_capnproto_schema_type( *_t.value_type_, _definitions, _num_unnamed))}};*/ - } else if constexpr (std::is_same()) { return type_to_capnproto_schema_type( Type{parsing::schemaful::tuple_to_object(_t)}, _definitions, _num_unnamed); - } else if constexpr (std::is_same()) { return schema::Type{ .value = schema::Type::List{ .type = Ref::make(type_to_capnproto_schema_type( *_t.type_, _definitions, _num_unnamed))}}; - } else if constexpr (std::is_same()) { // Cap'n Proto knows no validation. return type_to_capnproto_schema_type(*_t.type_, _definitions, _num_unnamed); - } else { static_assert(rfl::always_false_v, "Not all cases were covered."); } diff --git a/tests/capnproto/test_optional_fields.cpp b/tests/capnproto/test_optional_fields.cpp new file mode 100644 index 00000000..6456fec4 --- /dev/null +++ b/tests/capnproto/test_optional_fields.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_optional_fields { + +struct Person { + std::string first_name; + std::string last_name = "Simpson"; + std::optional> children; +}; + +TEST(capnproto, test_optional_fields) { + const auto bart = Person{.first_name = "Bart"}; + + const auto lisa = Person{.first_name = "Lisa"}; + + const auto maggie = Person{.first_name = "Maggie"}; + + const auto homer = + Person{.first_name = "Homer", + .children = std::vector({bart, lisa, maggie})}; + + write_and_read(homer); +} +} // namespace test_optional_fields diff --git a/tests/capnproto/write_and_read.hpp b/tests/capnproto/write_and_read.hpp index 81692be4..671daec0 100644 --- a/tests/capnproto/write_and_read.hpp +++ b/tests/capnproto/write_and_read.hpp @@ -5,6 +5,7 @@ #include #include +#include #include template @@ -16,5 +17,8 @@ void write_and_read(const auto& _struct) { << res.error().value().what(); const auto serialized2 = rfl::capnproto::write(res.value()); EXPECT_EQ(serialized1, serialized2); + + // For temporary testing only, remove later. + std::cout << rfl::json::write(res.value()) << std::endl; } #endif From e5020bf554abc3f03d84d132b533d50a96219e18 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 29 Dec 2024 19:34:24 +0100 Subject: [PATCH 19/57] Filled the gaps in the writer for everything but maps --- include/rfl/capnproto/Writer.hpp | 19 +++++++++---- src/rfl/capnproto/Writer.cpp | 47 +++++++++++--------------------- 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/include/rfl/capnproto/Writer.hpp b/include/rfl/capnproto/Writer.hpp index 27e61551..653fdea0 100644 --- a/include/rfl/capnproto/Writer.hpp +++ b/include/rfl/capnproto/Writer.hpp @@ -161,11 +161,20 @@ class Writer { template OutputVarType add_value_to_array(const T& _var, OutputArrayType* _parent) const noexcept { - // TODO - /*capnproto_value_t new_value; - capnproto_value_append(&_parent->val_, &new_value, nullptr); - set_value(_var, &new_value); - return OutputVarType{new_value};*/ + if constexpr (std::is_same, std::string>()) { + _parent->val_.set(_parent->ix_++, _var.c_str()); + + } else if constexpr (std::is_floating_point>() || + std::is_same, bool>()) { + _parent->val_.set(_parent->ix_++, _var); + + } else if constexpr (std::is_integral>()) { + _parent->val_.set(_parent->ix_++, static_cast(_var)); + + } else { + static_assert(rfl::always_false_v, "Unsupported type."); + } + return OutputVarType{}; } template diff --git a/src/rfl/capnproto/Writer.cpp b/src/rfl/capnproto/Writer.cpp index 17a6431a..2f6a878b 100644 --- a/src/rfl/capnproto/Writer.cpp +++ b/src/rfl/capnproto/Writer.cpp @@ -20,10 +20,8 @@ Writer::OutputObjectType Writer::object_as_root( Writer::OutputArrayType Writer::add_array_to_array( const size_t _size, OutputArrayType* _parent) const noexcept { - // TODO - /*avro_value_t new_array; -avro_value_append(&_parent->val_, &new_array, nullptr); -return OutputArrayType{new_array};*/ + return OutputArrayType{ + _parent->val_.init(_parent->ix_++, _size).as()}; } Writer::OutputArrayType Writer::add_array_to_map( @@ -101,28 +99,22 @@ Writer::OutputObjectType Writer::add_object_to_map( Writer::OutputObjectType Writer::add_object_to_object( const std::string_view& _name, const size_t _size, OutputObjectType* _parent) const noexcept { - // TODO - /*avro_value_t new_object; - avro_value_get_by_name(&_parent->val_, _name.data(), &new_object, nullptr); - return OutputObjectType{new_object};*/ + return OutputObjectType{ + _parent->val_.get(_name.data()).as()}; } Writer::OutputObjectType Writer::add_object_to_union( const size_t _index, const size_t _size, OutputUnionType* _parent) const noexcept { - // TODO - /*avro_value_t new_object; -avro_value_set_branch(&_parent->val_, static_cast(_index), &new_object); -return OutputObjectType{new_object}; -*/ + const auto field = _parent->val_.getSchema().getFields()[_index]; + return OutputObjectType{ + _parent->val_.get(field).template as()}; } Writer::OutputUnionType Writer::add_union_to_array( OutputArrayType* _parent) const noexcept { - // TODO - /*avro_value_t new_union; - avro_value_append(&_parent->val_, &new_union, nullptr); - return OutputUnionType{new_union};*/ + return OutputUnionType{ + _parent->val_[_parent->ix_++].as()}; } Writer::OutputUnionType Writer::add_union_to_map( @@ -141,19 +133,15 @@ Writer::OutputUnionType Writer::add_union_to_object( Writer::OutputUnionType Writer::add_union_to_union( const size_t _index, OutputUnionType* _parent) const noexcept { - // TODO - /*avro_value_t new_union; -avro_value_set_branch(&_parent->val_, static_cast(_index), &new_union); -return OutputUnionType{new_union};*/ + const auto field = _parent->val_.getSchema().getFields()[_index]; + return OutputUnionType{ + _parent->val_.get(field).template as()}; } Writer::OutputVarType Writer::add_null_to_array( OutputArrayType* _parent) const noexcept { - // TODO - /*avro_value_t new_null; -avro_value_append(&_parent->val_, &new_null, nullptr); -avro_value_set_null(&new_null); -return OutputVarType{new_null};*/ + _parent->val_.set(_parent->ix_++, capnp::VOID); + return OutputVarType{}; } Writer::OutputVarType Writer::add_null_to_map( @@ -167,11 +155,8 @@ return OutputVarType{new_null};*/ Writer::OutputVarType Writer::add_null_to_object( const std::string_view& _name, OutputObjectType* _parent) const noexcept { - // TODO - /*avro_value_t new_null; -avro_value_get_by_name(&_parent->val_, _name.data(), &new_null, nullptr); -avro_value_set_null(&new_null); -return OutputVarType{new_null};*/ + _parent->val_.set(_name.data(), capnp::VOID); + return OutputVarType{}; } Writer::OutputVarType Writer::add_null_to_union( From c8defbf4a00419381357247f4533f0c63903af02 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 29 Dec 2024 20:06:57 +0100 Subject: [PATCH 20/57] Added (basic) support for variants --- include/rfl/capnproto/schema/Type.hpp | 26 ++++++++++++--------- src/rfl/capnproto/Type.cpp | 11 +++++++++ src/rfl/capnproto/Writer.cpp | 4 ++-- src/rfl/capnproto/to_schema.cpp | 13 +++++++++-- tests/capnproto/test_variant.cpp | 33 +++++++++++++++++++++++++++ 5 files changed, 72 insertions(+), 15 deletions(-) create mode 100644 tests/capnproto/test_variant.cpp diff --git a/include/rfl/capnproto/schema/Type.hpp b/include/rfl/capnproto/schema/Type.hpp index 7050d12b..29245a74 100644 --- a/include/rfl/capnproto/schema/Type.hpp +++ b/include/rfl/capnproto/schema/Type.hpp @@ -41,17 +41,8 @@ struct Type { struct Data {}; - struct Optional { - rfl::Ref type; - }; - struct Text {}; - struct Struct { - std::string name; - std::vector> fields; - }; - struct List { rfl::Ref type; }; @@ -60,14 +51,27 @@ struct Type { rfl::Ref type; }; + struct Optional { + rfl::Ref type; + }; + struct Reference { std::string type_name; }; + struct Struct { + std::string name; + std::vector> fields; + }; + + struct Variant { + std::vector types; + }; + using ReflectionType = rfl::Variant; + UInt64, Float32, Float64, Data, Text, List, + /*Map,*/ Optional, Reference, Struct, Variant>; const auto& reflection() const { return value; } diff --git a/src/rfl/capnproto/Type.cpp b/src/rfl/capnproto/Type.cpp index 7032742a..6f6512fd 100644 --- a/src/rfl/capnproto/Type.cpp +++ b/src/rfl/capnproto/Type.cpp @@ -117,6 +117,13 @@ std::ostream& operator<<(std::ostream& _os, const Type::Struct& _s) { << " some @" << ix++ << " :" << *_r.type << ";" << std::endl << " none @" << ix++ << " :Void;" << std::endl << " }" << std::endl; + } else if constexpr (std::is_same()) { + _os << " " << name << " :union {" << std::endl; + for (size_t i = 0; i < _r.types.size(); ++i) { + _os << " option" << i + 1 << " @" << ix++ << " :" << _r.types[i] + << ";" << std::endl; + } + _os << " }" << std::endl; } else { _os << " " << name << " @" << ix++ << " :" << _r << ";" << std::endl; } @@ -129,6 +136,10 @@ std::ostream& operator<<(std::ostream& _os, const Type::List& _l) { return _os << "List(" << *_l.type << ")"; } +std::ostream& operator<<(std::ostream& _os, const Type::Variant& _v) { + return _os << "TODO"; +} + std::ostream& operator<<(std::ostream& _os, const Type::Reference& _r) { return _os << internal::strings::to_pascal_case(_r.type_name); } diff --git a/src/rfl/capnproto/Writer.cpp b/src/rfl/capnproto/Writer.cpp index 2f6a878b..abe5b520 100644 --- a/src/rfl/capnproto/Writer.cpp +++ b/src/rfl/capnproto/Writer.cpp @@ -108,7 +108,7 @@ Writer::OutputObjectType Writer::add_object_to_union( OutputUnionType* _parent) const noexcept { const auto field = _parent->val_.getSchema().getFields()[_index]; return OutputObjectType{ - _parent->val_.get(field).template as()}; + _parent->val_.init(field).template as()}; } Writer::OutputUnionType Writer::add_union_to_array( @@ -135,7 +135,7 @@ Writer::OutputUnionType Writer::add_union_to_union( const size_t _index, OutputUnionType* _parent) const noexcept { const auto field = _parent->val_.getSchema().getFields()[_index]; return OutputUnionType{ - _parent->val_.get(field).template as()}; + _parent->val_.init(field).template as()}; } Writer::OutputVarType Writer::add_null_to_array( diff --git a/src/rfl/capnproto/to_schema.cpp b/src/rfl/capnproto/to_schema.cpp index d5cf43a4..f07bb645 100644 --- a/src/rfl/capnproto/to_schema.cpp +++ b/src/rfl/capnproto/to_schema.cpp @@ -80,8 +80,12 @@ schema::Type type_to_capnproto_schema_type( return schema::Type{.value = schema::Type::Text{}}; } else if constexpr (std::is_same()) { - // TODO - return schema::Type{.value = schema::Type::Void{}}; + auto value = schema::Type::Variant{}; + for (const auto& type : _t.types_) { + value.types.push_back( + type_to_capnproto_schema_type(type, _definitions, _num_unnamed)); + } + return schema::Type{.value = value}; } else if constexpr (std::is_same()) { return type_to_capnproto_schema_type(*_t.type_, _definitions, @@ -115,9 +119,11 @@ schema::Type type_to_capnproto_schema_type( .value = schema::Type::Optional{ .type = Ref::make(type_to_capnproto_schema_type( *_t.type_, _definitions, _num_unnamed))}}; + } else if constexpr (std::is_same()) { return schema::Type{.value = schema::Type::Reference{.type_name = _t.name_}}; + } else if constexpr (std::is_same()) { // TODO return schema::Type{.value = schema::Type::Void{}}; @@ -126,15 +132,18 @@ schema::Type type_to_capnproto_schema_type( .values = Ref::make(type_to_capnproto_schema_type( *_t.value_type_, _definitions, _num_unnamed))}};*/ + } else if constexpr (std::is_same()) { return type_to_capnproto_schema_type( Type{parsing::schemaful::tuple_to_object(_t)}, _definitions, _num_unnamed); + } else if constexpr (std::is_same()) { return schema::Type{ .value = schema::Type::List{ .type = Ref::make(type_to_capnproto_schema_type( *_t.type_, _definitions, _num_unnamed))}}; + } else if constexpr (std::is_same()) { // Cap'n Proto knows no validation. return type_to_capnproto_schema_type(*_t.type_, _definitions, diff --git a/tests/capnproto/test_variant.cpp b/tests/capnproto/test_variant.cpp new file mode 100644 index 00000000..6ed47dc9 --- /dev/null +++ b/tests/capnproto/test_variant.cpp @@ -0,0 +1,33 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_variant { + +struct Circle { + double radius; +}; + +struct Rectangle { + double height; + double width; +}; + +struct Square { + double width; +}; + +struct Shapes { + std::variant root; +}; + +TEST(capnproto, test_variant) { + const auto r = Shapes{Rectangle{.height = 10, .width = 5}}; + + write_and_read(r); +} +} // namespace test_variant From 840272c4f3c10c5945489ef412ed354c72788f55 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 29 Dec 2024 20:15:57 +0100 Subject: [PATCH 21/57] Got rid of ugly warning message --- include/rfl/capnproto/Reader.hpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/include/rfl/capnproto/Reader.hpp b/include/rfl/capnproto/Reader.hpp index 427be2d5..aa58ebd0 100644 --- a/include/rfl/capnproto/Reader.hpp +++ b/include/rfl/capnproto/Reader.hpp @@ -80,14 +80,18 @@ class Reader { switch (type) { case capnp::DynamicValue::INT: return static_cast(_var.val_.as()); + case capnp::DynamicValue::UINT: return static_cast(_var.val_.as()); + case capnp::DynamicValue::FLOAT: return static_cast(_var.val_.as()); + + default: + return rfl::Error( + "Could not cast to numeric value. The type must be integral, " + "float or double."); } - return rfl::Error( - "Could not cast to numeric value. The type must be integral, " - "float or double."); // TODO /*} else if constexpr (internal::is_literal_v) { From c7c8ae20c6d0c02194bba67e23fcc2b3d90efb61 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 29 Dec 2024 22:52:27 +0100 Subject: [PATCH 22/57] Added support for nested unions --- include/rfl/capnproto/schema/Type.hpp | 8 +--- src/rfl/capnproto/Type.cpp | 68 +++++++++++++++++---------- src/rfl/capnproto/to_schema.cpp | 18 ++++--- tests/capnproto/test_variant.cpp | 2 +- 4 files changed, 58 insertions(+), 38 deletions(-) diff --git a/include/rfl/capnproto/schema/Type.hpp b/include/rfl/capnproto/schema/Type.hpp index 29245a74..ab91bedd 100644 --- a/include/rfl/capnproto/schema/Type.hpp +++ b/include/rfl/capnproto/schema/Type.hpp @@ -51,10 +51,6 @@ struct Type { rfl::Ref type; }; - struct Optional { - rfl::Ref type; - }; - struct Reference { std::string type_name; }; @@ -65,13 +61,13 @@ struct Type { }; struct Variant { - std::vector types; + std::vector> fields; }; using ReflectionType = rfl::Variant; + /*Map,*/ Reference, Struct, Variant>; const auto& reflection() const { return value; } diff --git a/src/rfl/capnproto/Type.cpp b/src/rfl/capnproto/Type.cpp index 6f6512fd..bad10dab 100644 --- a/src/rfl/capnproto/Type.cpp +++ b/src/rfl/capnproto/Type.cpp @@ -26,12 +26,55 @@ SOFTWARE. #include "rfl/capnproto/schema/Type.hpp" +#include #include #include "rfl/internal/strings/to_pascal_case.hpp" namespace rfl::capnproto::schema { +/// Cap'n proto has a somewhat weird way of handling union fields, please refer +/// to the schema for further explanation. This is a workaround that ensures +/// that we can also support complex, nested unions. +void handle_fields_in_structs(const auto& _struct_or_variant, + const size_t _indent, std::ostream* _os, + size_t* _ix) { + for (const auto& [name, type] : _struct_or_variant.fields) { + // Because of the way Cap'n proto handles unions, we need a special case for + // them. + type.reflection().visit([&](const auto& _r) { + using R = std::remove_cvref_t; + if constexpr (std::is_same()) { + // Special case: Union field. + *_os << std::string(_indent * 2, ' ') << name << " :union {" + << std::endl; + for (size_t i = 0; i < _r.fields.size(); ++i) { + _r.fields[i].second.reflection().visit([&](const auto& _union_field) { + using U = std::remove_cvref_t; + if constexpr (std::is_same()) { + *_os << std::string(_indent * 2 + 2, ' ') << _r.fields[i].first + << " :union {" << std::endl; + handle_fields_in_structs(_union_field, _indent + 2, _os, _ix); + *_os << std::string(_indent * 2 + 2, ' ') << "}" << std::endl; + + } else { + *_os << std::string(_indent * 2 + 2, ' ') << _r.fields[i].first + << " @" << (*_ix)++ << " :" << _union_field << ";" + << std::endl; + } + }); + } + *_os << std::string(_indent * 2, ' ') << "}" << std::endl; + + } else { + // Standard case: Non-union field. + *_os << std::string(_indent * 2, ' ') << name << " @" << (*_ix)++ + << " :" << _r << ";" << std::endl; + } + }); + } +} + Type Type::with_name(const std::string& _name) const { const auto set_name = [&](const auto& _v) -> ReflectionType { using T = std::remove_cvref_t; @@ -98,10 +141,6 @@ std::ostream& operator<<(std::ostream& _os, const Type::Data&) { return _os << "Data"; } -std::ostream& operator<<(std::ostream& _os, const Type::Optional&) { - return _os << "TODO"; -} - std::ostream& operator<<(std::ostream& _os, const Type::Text&) { return _os << "Text"; } @@ -109,26 +148,7 @@ std::ostream& operator<<(std::ostream& _os, const Type::Text&) { std::ostream& operator<<(std::ostream& _os, const Type::Struct& _s) { _os << "struct " << _s.name << " {" << std::endl; size_t ix = 0; - for (const auto& [name, type] : _s.fields) { - type.reflection().visit([&](const auto& _r) { - using R = std::remove_cvref_t; - if constexpr (std::is_same()) { - _os << " " << name << " :union {" << std::endl - << " some @" << ix++ << " :" << *_r.type << ";" << std::endl - << " none @" << ix++ << " :Void;" << std::endl - << " }" << std::endl; - } else if constexpr (std::is_same()) { - _os << " " << name << " :union {" << std::endl; - for (size_t i = 0; i < _r.types.size(); ++i) { - _os << " option" << i + 1 << " @" << ix++ << " :" << _r.types[i] - << ";" << std::endl; - } - _os << " }" << std::endl; - } else { - _os << " " << name << " @" << ix++ << " :" << _r << ";" << std::endl; - } - }); - } + handle_fields_in_structs(_s, 1, &_os, &ix); return _os << "}" << std::endl; } diff --git a/src/rfl/capnproto/to_schema.cpp b/src/rfl/capnproto/to_schema.cpp index f07bb645..fa6e312a 100644 --- a/src/rfl/capnproto/to_schema.cpp +++ b/src/rfl/capnproto/to_schema.cpp @@ -81,9 +81,11 @@ schema::Type type_to_capnproto_schema_type( } else if constexpr (std::is_same()) { auto value = schema::Type::Variant{}; + size_t i = 1; for (const auto& type : _t.types_) { - value.types.push_back( - type_to_capnproto_schema_type(type, _definitions, _num_unnamed)); + value.fields.push_back(std::make_pair( + std::string("option" + std::to_string(i++)), + type_to_capnproto_schema_type(type, _definitions, _num_unnamed))); } return schema::Type{.value = value}; @@ -116,14 +118,17 @@ schema::Type type_to_capnproto_schema_type( } else if constexpr (std::is_same()) { return schema::Type{ - .value = schema::Type::Optional{ - .type = Ref::make(type_to_capnproto_schema_type( - *_t.type_, _definitions, _num_unnamed))}}; + .value = schema::Type::Variant{ + .fields = std::vector( + {std::make_pair(std::string("some"), + type_to_capnproto_schema_type( + *_t.type_, _definitions, _num_unnamed)), + std::make_pair(std::string("none"), + schema::Type{schema::Type::Void{}})})}}; } else if constexpr (std::is_same()) { return schema::Type{.value = schema::Type::Reference{.type_name = _t.name_}}; - } else if constexpr (std::is_same()) { // TODO return schema::Type{.value = schema::Type::Void{}}; @@ -132,7 +137,6 @@ schema::Type type_to_capnproto_schema_type( .values = Ref::make(type_to_capnproto_schema_type( *_t.value_type_, _definitions, _num_unnamed))}};*/ - } else if constexpr (std::is_same()) { return type_to_capnproto_schema_type( Type{parsing::schemaful::tuple_to_object(_t)}, _definitions, diff --git a/tests/capnproto/test_variant.cpp b/tests/capnproto/test_variant.cpp index 6ed47dc9..f1cfe4d9 100644 --- a/tests/capnproto/test_variant.cpp +++ b/tests/capnproto/test_variant.cpp @@ -22,7 +22,7 @@ struct Square { }; struct Shapes { - std::variant root; + std::variant> root; }; TEST(capnproto, test_variant) { From 01e9786a2c938640195d32f01892d62498f3f0f2 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 29 Dec 2024 22:53:50 +0100 Subject: [PATCH 23/57] Better name --- src/rfl/capnproto/Type.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/rfl/capnproto/Type.cpp b/src/rfl/capnproto/Type.cpp index bad10dab..2bde2c9c 100644 --- a/src/rfl/capnproto/Type.cpp +++ b/src/rfl/capnproto/Type.cpp @@ -36,9 +36,9 @@ namespace rfl::capnproto::schema { /// Cap'n proto has a somewhat weird way of handling union fields, please refer /// to the schema for further explanation. This is a workaround that ensures /// that we can also support complex, nested unions. -void handle_fields_in_structs(const auto& _struct_or_variant, - const size_t _indent, std::ostream* _os, - size_t* _ix) { +void handle_fields_in_structs_or_variants(const auto& _struct_or_variant, + const size_t _indent, + std::ostream* _os, size_t* _ix) { for (const auto& [name, type] : _struct_or_variant.fields) { // Because of the way Cap'n proto handles unions, we need a special case for // them. @@ -54,7 +54,8 @@ void handle_fields_in_structs(const auto& _struct_or_variant, if constexpr (std::is_same()) { *_os << std::string(_indent * 2 + 2, ' ') << _r.fields[i].first << " :union {" << std::endl; - handle_fields_in_structs(_union_field, _indent + 2, _os, _ix); + handle_fields_in_structs_or_variants(_union_field, _indent + 2, + _os, _ix); *_os << std::string(_indent * 2 + 2, ' ') << "}" << std::endl; } else { @@ -148,7 +149,7 @@ std::ostream& operator<<(std::ostream& _os, const Type::Text&) { std::ostream& operator<<(std::ostream& _os, const Type::Struct& _s) { _os << "struct " << _s.name << " {" << std::endl; size_t ix = 0; - handle_fields_in_structs(_s, 1, &_os, &ix); + handle_fields_in_structs_or_variants(_s, 1, &_os, &ix); return _os << "}" << std::endl; } From ea1e3e6bf91dadc8545eb6446271d8f979382060 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 29 Dec 2024 23:14:34 +0100 Subject: [PATCH 24/57] Make sure that there can be no duplicate field names --- .../rfl/internal/strings/to_camel_case.hpp | 51 +++++++++++++++++++ .../rfl/internal/strings/to_pascal_case.hpp | 25 ++------- src/rfl/capnproto/Type.cpp | 21 +++++--- src/rfl/capnproto/to_schema.cpp | 6 +-- 4 files changed, 72 insertions(+), 31 deletions(-) create mode 100644 include/rfl/internal/strings/to_camel_case.hpp diff --git a/include/rfl/internal/strings/to_camel_case.hpp b/include/rfl/internal/strings/to_camel_case.hpp new file mode 100644 index 00000000..2f5350db --- /dev/null +++ b/include/rfl/internal/strings/to_camel_case.hpp @@ -0,0 +1,51 @@ +#ifndef RFL_INTERNAL_STRINGS_TOCAMELCASE_HPP_ +#define RFL_INTERNAL_STRINGS_TOCAMELCASE_HPP_ + +#include +#include + +namespace rfl { +namespace internal { +namespace strings { + +inline char to_lower(const char ch) { + if (ch >= 'A' && ch <= 'Z') { + return ch + ('a' - 'A'); + } else { + return ch; + } +} + +inline char to_upper(const char ch) { + if (ch >= 'a' && ch <= 'z') { + return ch + ('A' - 'a'); + } else { + return ch; + } +} + +/// Splits a string alongside the delimiter +inline std::string to_camel_case(const std::string& _str) { + std::string result; + bool capitalize = false; + for (const char ch : _str) { + if (ch == '_') { + capitalize = true; + } else if (capitalize) { + result.push_back(to_upper(ch)); + capitalize = false; + } else { + result.push_back(ch); + } + } + if (result.size() > 0) { + result[0] = to_lower(result[0]); + } + return result; +} + +} // namespace strings +} // namespace internal +} // namespace rfl + +#endif diff --git a/include/rfl/internal/strings/to_pascal_case.hpp b/include/rfl/internal/strings/to_pascal_case.hpp index 283053aa..44bd1d54 100644 --- a/include/rfl/internal/strings/to_pascal_case.hpp +++ b/include/rfl/internal/strings/to_pascal_case.hpp @@ -1,34 +1,17 @@ #ifndef RFL_INTERNAL_STRINGS_TOPASCALCASE_HPP_ #define RFL_INTERNAL_STRINGS_TOPASCALCASE_HPP_ -#include -#include +#include "to_camel_case.hpp" namespace rfl { namespace internal { namespace strings { -inline char to_upper(const char ch) { - if (ch >= 'a' && ch <= 'z') { - return ch + ('A' - 'a'); - } else { - return ch; - } -} - /// Splits a string alongside the delimiter inline std::string to_pascal_case(const std::string& _str) { - std::string result; - bool capitalize = true; - for (const char ch : _str) { - if (ch == '_') { - capitalize = true; - } else if (capitalize) { - result.push_back(to_upper(ch)); - capitalize = false; - } else { - result.push_back(ch); - } + auto result = to_camel_case("_" + _str); + if (result.size() > 0) { + result[0] = to_upper(result[0]); } return result; } diff --git a/src/rfl/capnproto/Type.cpp b/src/rfl/capnproto/Type.cpp index 2bde2c9c..c83f8e4f 100644 --- a/src/rfl/capnproto/Type.cpp +++ b/src/rfl/capnproto/Type.cpp @@ -38,6 +38,7 @@ namespace rfl::capnproto::schema { /// that we can also support complex, nested unions. void handle_fields_in_structs_or_variants(const auto& _struct_or_variant, const size_t _indent, + const std::string& _namespace, std::ostream* _os, size_t* _ix) { for (const auto& [name, type] : _struct_or_variant.fields) { // Because of the way Cap'n proto handles unions, we need a special case for @@ -52,14 +53,19 @@ void handle_fields_in_structs_or_variants(const auto& _struct_or_variant, _r.fields[i].second.reflection().visit([&](const auto& _union_field) { using U = std::remove_cvref_t; if constexpr (std::is_same()) { - *_os << std::string(_indent * 2 + 2, ' ') << _r.fields[i].first + *_os << std::string(_indent * 2 + 2, ' ') + << internal::strings::to_camel_case(_namespace + name + "_" + + _r.fields[i].first) << " :union {" << std::endl; - handle_fields_in_structs_or_variants(_union_field, _indent + 2, - _os, _ix); + handle_fields_in_structs_or_variants( + _union_field, _indent + 2, + _namespace + name + "_" + _r.fields[i].first + "_", _os, _ix); *_os << std::string(_indent * 2 + 2, ' ') << "}" << std::endl; } else { - *_os << std::string(_indent * 2 + 2, ' ') << _r.fields[i].first + *_os << std::string(_indent * 2 + 2, ' ') + << internal::strings::to_camel_case(_namespace + name + "_" + + _r.fields[i].first) << " @" << (*_ix)++ << " :" << _union_field << ";" << std::endl; } @@ -69,8 +75,9 @@ void handle_fields_in_structs_or_variants(const auto& _struct_or_variant, } else { // Standard case: Non-union field. - *_os << std::string(_indent * 2, ' ') << name << " @" << (*_ix)++ - << " :" << _r << ";" << std::endl; + *_os << std::string(_indent * 2, ' ') + << internal::strings::to_camel_case(_namespace + name) << " @" + << (*_ix)++ << " :" << _r << ";" << std::endl; } }); } @@ -149,7 +156,7 @@ std::ostream& operator<<(std::ostream& _os, const Type::Text&) { std::ostream& operator<<(std::ostream& _os, const Type::Struct& _s) { _os << "struct " << _s.name << " {" << std::endl; size_t ix = 0; - handle_fields_in_structs_or_variants(_s, 1, &_os, &ix); + handle_fields_in_structs_or_variants(_s, 1, "", &_os, &ix); return _os << "}" << std::endl; } diff --git a/src/rfl/capnproto/to_schema.cpp b/src/rfl/capnproto/to_schema.cpp index fa6e312a..7c1e58e4 100644 --- a/src/rfl/capnproto/to_schema.cpp +++ b/src/rfl/capnproto/to_schema.cpp @@ -84,7 +84,7 @@ schema::Type type_to_capnproto_schema_type( size_t i = 1; for (const auto& type : _t.types_) { value.fields.push_back(std::make_pair( - std::string("option" + std::to_string(i++)), + std::string("Opt" + std::to_string(i++)), type_to_capnproto_schema_type(type, _definitions, _num_unnamed))); } return schema::Type{.value = value}; @@ -120,10 +120,10 @@ schema::Type type_to_capnproto_schema_type( return schema::Type{ .value = schema::Type::Variant{ .fields = std::vector( - {std::make_pair(std::string("some"), + {std::make_pair(std::string("Some"), type_to_capnproto_schema_type( *_t.type_, _definitions, _num_unnamed)), - std::make_pair(std::string("none"), + std::make_pair(std::string("None"), schema::Type{schema::Type::Void{}})})}}; } else if constexpr (std::is_same()) { From 8ed58b72b41d50589770498fa4924278bc1bc7b7 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Mon, 30 Dec 2024 23:45:20 +0100 Subject: [PATCH 25/57] Make sure that the first letter of the snake case is lowercase --- include/rfl/internal/transform_snake_case.hpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/include/rfl/internal/transform_snake_case.hpp b/include/rfl/internal/transform_snake_case.hpp index a6a246b0..cf7360d5 100644 --- a/include/rfl/internal/transform_snake_case.hpp +++ b/include/rfl/internal/transform_snake_case.hpp @@ -15,19 +15,36 @@ consteval char to_upper() { } } +template +consteval char to_lower() { + if constexpr (c >= 'A' && c <= 'Z') { + return c - ('A' - 'a'); + } else { + return c; + } +} + /// Transforms the field name from snake case to camel case. template consteval auto transform_snake_case() { if constexpr (_i == _name.arr_.size()) { return StringLiteral(chars...); + } else if constexpr (_name.arr_[_i] == '_') { return transform_snake_case<_name, true, _i + 1, chars...>(); + } else if constexpr (_name.arr_[_i] == '\0') { return transform_snake_case<_name, false, _name.arr_.size(), chars...>(); + } else if constexpr (_capitalize) { return transform_snake_case<_name, false, _i + 1, chars..., to_upper<_name.arr_[_i]>()>(); + + } else if constexpr (sizeof...(chars) == 0) { + return transform_snake_case<_name, false, _i + 1, + to_lower<_name.arr_[_i]>()>(); + } else { return transform_snake_case<_name, false, _i + 1, chars..., _name.arr_[_i]>(); From 9efd99661997e5b92f86f15bbcbfeaba673df3ee Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Tue, 31 Dec 2024 00:03:11 +0100 Subject: [PATCH 26/57] Moved string functions to source files --- include/rfl/capnproto/read.hpp | 2 +- include/rfl/capnproto/write.hpp | 2 +- .../rfl/internal/enums/StringConverter.hpp | 3 +- include/rfl/internal/strings/join.hpp | 28 ------- include/rfl/internal/strings/replace_all.hpp | 28 ------- include/rfl/internal/strings/split.hpp | 29 ------- include/rfl/internal/strings/strings.hpp | 29 +++++++ .../rfl/internal/strings/to_camel_case.hpp | 51 ----------- .../rfl/internal/strings/to_pascal_case.hpp | 23 ----- include/rfl/parsing/NamedTupleParser.hpp | 1 - include/rfl/parsing/Parser_tagged_union.hpp | 2 +- .../rfl/parsing/to_single_error_message.hpp | 2 +- src/reflectcpp.cpp | 1 + src/rfl/capnproto/Type.cpp | 2 +- src/rfl/capnproto/to_schema.cpp | 3 +- src/rfl/internal/strings/strings.cpp | 84 +++++++++++++++++++ 16 files changed, 121 insertions(+), 169 deletions(-) delete mode 100644 include/rfl/internal/strings/join.hpp delete mode 100644 include/rfl/internal/strings/replace_all.hpp delete mode 100644 include/rfl/internal/strings/split.hpp create mode 100644 include/rfl/internal/strings/strings.hpp delete mode 100644 include/rfl/internal/strings/to_camel_case.hpp delete mode 100644 include/rfl/internal/strings/to_pascal_case.hpp create mode 100644 src/rfl/internal/strings/strings.cpp diff --git a/include/rfl/capnproto/read.hpp b/include/rfl/capnproto/read.hpp index ac683319..03163da3 100644 --- a/include/rfl/capnproto/read.hpp +++ b/include/rfl/capnproto/read.hpp @@ -12,7 +12,7 @@ #include "../Processors.hpp" #include "../SnakeCaseToCamelCase.hpp" -#include "../internal/strings/to_pascal_case.hpp" +#include "../internal/strings/strings.hpp" #include "../internal/wrap_in_rfl_array_t.hpp" #include "../parsing/make_type_name.hpp" #include "Parser.hpp" diff --git a/include/rfl/capnproto/write.hpp b/include/rfl/capnproto/write.hpp index 81fbec32..0f43df31 100644 --- a/include/rfl/capnproto/write.hpp +++ b/include/rfl/capnproto/write.hpp @@ -18,7 +18,7 @@ #include "../SnakeCaseToCamelCase.hpp" #include "../internal/ptr_cast.hpp" -#include "../internal/strings/to_pascal_case.hpp" +#include "../internal/strings/strings.hpp" #include "../parsing/Parent.hpp" #include "../parsing/make_type_name.hpp" #include "Parser.hpp" diff --git a/include/rfl/internal/enums/StringConverter.hpp b/include/rfl/internal/enums/StringConverter.hpp index f5ee9a16..4e6a0e7a 100644 --- a/include/rfl/internal/enums/StringConverter.hpp +++ b/include/rfl/internal/enums/StringConverter.hpp @@ -8,8 +8,7 @@ #include #include "../../Result.hpp" -#include "../../internal/strings/join.hpp" -#include "../../internal/strings/split.hpp" +#include "../../internal/strings/strings.hpp" #include "../../type_name_t.hpp" #include "get_enum_names.hpp" #include "is_flag_enum.hpp" diff --git a/include/rfl/internal/strings/join.hpp b/include/rfl/internal/strings/join.hpp deleted file mode 100644 index c562e773..00000000 --- a/include/rfl/internal/strings/join.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef RFL_INTERNAL_STRINGS_JOIN_HPP_ -#define RFL_INTERNAL_STRINGS_JOIN_HPP_ - -#include -#include - -namespace rfl { -namespace internal { -namespace strings { - -/// Joins a string using the delimiter -inline std::string join(const std::string& _delimiter, - const std::vector& _strings) { - if (_strings.size() == 0) { - return ""; - } - auto res = _strings[0]; - for (size_t i = 1; i < _strings.size(); ++i) { - res += _delimiter + _strings[i]; - } - return res; -} - -} // namespace strings -} // namespace internal -} // namespace rfl - -#endif diff --git a/include/rfl/internal/strings/replace_all.hpp b/include/rfl/internal/strings/replace_all.hpp deleted file mode 100644 index bd77338c..00000000 --- a/include/rfl/internal/strings/replace_all.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef RFL_INTERNAL_STRINGS_REPLACE_ALL_HPP_ -#define RFL_INTERNAL_STRINGS_REPLACE_ALL_HPP_ - -#include -#include - -namespace rfl { -namespace internal { -namespace strings { - -inline std::string replace_all(const std::string& _str, - const std::string& _from, - const std::string& _to) { - auto str = _str; - - size_t pos = 0; - while ((pos = str.find(_from, pos)) != std::string::npos) { - str.replace(pos, _from.length(), _to); - pos += _to.length(); - } - return str; -} - -} // namespace strings -} // namespace internal -} // namespace rfl - -#endif diff --git a/include/rfl/internal/strings/split.hpp b/include/rfl/internal/strings/split.hpp deleted file mode 100644 index 6bf29027..00000000 --- a/include/rfl/internal/strings/split.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef RFL_INTERNAL_STRINGS_SPLIT_HPP_ -#define RFL_INTERNAL_STRINGS_SPLIT_HPP_ - -#include -#include - -namespace rfl { -namespace internal { -namespace strings { - -/// Splits a string alongside the delimiter -inline std::vector split(const std::string& _str, - const std::string& _delimiter) { - auto str = _str; - size_t pos = 0; - std::vector result; - while ((pos = str.find(_delimiter)) != std::string::npos) { - result.emplace_back(str.substr(0, pos)); - str.erase(0, pos + _delimiter.length()); - } - result.emplace_back(std::move(str)); - return result; -} - -} // namespace strings -} // namespace internal -} // namespace rfl - -#endif diff --git a/include/rfl/internal/strings/strings.hpp b/include/rfl/internal/strings/strings.hpp new file mode 100644 index 00000000..6db4118e --- /dev/null +++ b/include/rfl/internal/strings/strings.hpp @@ -0,0 +1,29 @@ +#ifndef RFL_INTERNAL_STRINGS_STRINGS_HPP_ +#define RFL_INTERNAL_STRINGS_STRINGS_HPP_ + +#include +#include + +namespace rfl::internal::strings { + +/// Joins a series of strings. +std::string join(const std::string& _delimiter, + const std::vector& _strings); + +/// Replace all occurences of _from with _to. +std::string replace_all(const std::string& _str, const std::string& _from, + const std::string& _to); + +/// Splits _str along _delimiter. +std::vector split(const std::string& _str, + const std::string& _delimiter); + +/// Transforms the string to camel case. +std::string to_camel_case(const std::string& _str); + +/// Transforms the string to pascal case. +std::string to_pascal_case(const std::string& _str); + +} // namespace rfl::internal::strings + +#endif diff --git a/include/rfl/internal/strings/to_camel_case.hpp b/include/rfl/internal/strings/to_camel_case.hpp deleted file mode 100644 index 2f5350db..00000000 --- a/include/rfl/internal/strings/to_camel_case.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef RFL_INTERNAL_STRINGS_TOCAMELCASE_HPP_ -#define RFL_INTERNAL_STRINGS_TOCAMELCASE_HPP_ - -#include -#include - -namespace rfl { -namespace internal { -namespace strings { - -inline char to_lower(const char ch) { - if (ch >= 'A' && ch <= 'Z') { - return ch + ('a' - 'A'); - } else { - return ch; - } -} - -inline char to_upper(const char ch) { - if (ch >= 'a' && ch <= 'z') { - return ch + ('A' - 'a'); - } else { - return ch; - } -} - -/// Splits a string alongside the delimiter -inline std::string to_camel_case(const std::string& _str) { - std::string result; - bool capitalize = false; - for (const char ch : _str) { - if (ch == '_') { - capitalize = true; - } else if (capitalize) { - result.push_back(to_upper(ch)); - capitalize = false; - } else { - result.push_back(ch); - } - } - if (result.size() > 0) { - result[0] = to_lower(result[0]); - } - return result; -} - -} // namespace strings -} // namespace internal -} // namespace rfl - -#endif diff --git a/include/rfl/internal/strings/to_pascal_case.hpp b/include/rfl/internal/strings/to_pascal_case.hpp deleted file mode 100644 index 44bd1d54..00000000 --- a/include/rfl/internal/strings/to_pascal_case.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef RFL_INTERNAL_STRINGS_TOPASCALCASE_HPP_ -#define RFL_INTERNAL_STRINGS_TOPASCALCASE_HPP_ - -#include "to_camel_case.hpp" - -namespace rfl { -namespace internal { -namespace strings { - -/// Splits a string alongside the delimiter -inline std::string to_pascal_case(const std::string& _str) { - auto result = to_camel_case("_" + _str); - if (result.size() > 0) { - result[0] = to_upper(result[0]); - } - return result; -} - -} // namespace strings -} // namespace internal -} // namespace rfl - -#endif diff --git a/include/rfl/parsing/NamedTupleParser.hpp b/include/rfl/parsing/NamedTupleParser.hpp index 4cf8d203..eb530271 100644 --- a/include/rfl/parsing/NamedTupleParser.hpp +++ b/include/rfl/parsing/NamedTupleParser.hpp @@ -20,7 +20,6 @@ #include "../internal/no_duplicate_field_names.hpp" #include "../internal/nth_element_t.hpp" #include "../internal/ptr_cast.hpp" -#include "../internal/strings/replace_all.hpp" #include "../to_view.hpp" #include "AreReaderAndWriter.hpp" #include "Parent.hpp" diff --git a/include/rfl/parsing/Parser_tagged_union.hpp b/include/rfl/parsing/Parser_tagged_union.hpp index 49c10695..2f9b493b 100644 --- a/include/rfl/parsing/Parser_tagged_union.hpp +++ b/include/rfl/parsing/Parser_tagged_union.hpp @@ -8,7 +8,7 @@ #include "../Result.hpp" #include "../TaggedUnion.hpp" #include "../always_false.hpp" -#include "../internal/strings/join.hpp" +#include "../internal/strings/strings.hpp" #include "../named_tuple_t.hpp" #include "Parser_base.hpp" #include "TaggedUnionWrapper.hpp" diff --git a/include/rfl/parsing/to_single_error_message.hpp b/include/rfl/parsing/to_single_error_message.hpp index b5e633b9..3d42b7c6 100644 --- a/include/rfl/parsing/to_single_error_message.hpp +++ b/include/rfl/parsing/to_single_error_message.hpp @@ -6,7 +6,7 @@ #include #include -#include "../internal/strings/replace_all.hpp" +#include "../internal/strings/strings.hpp" namespace rfl::parsing { diff --git a/src/reflectcpp.cpp b/src/reflectcpp.cpp index 52cf1c11..654626ed 100644 --- a/src/reflectcpp.cpp +++ b/src/reflectcpp.cpp @@ -32,4 +32,5 @@ SOFTWARE. #include "rfl/Generic.cpp" #include "rfl/generic/Reader.cpp" #include "rfl/generic/Writer.cpp" +#include "rfl/internal/strings/strings.cpp" #include "rfl/parsing/schema/Type.cpp" diff --git a/src/rfl/capnproto/Type.cpp b/src/rfl/capnproto/Type.cpp index c83f8e4f..d246f0c4 100644 --- a/src/rfl/capnproto/Type.cpp +++ b/src/rfl/capnproto/Type.cpp @@ -29,7 +29,7 @@ SOFTWARE. #include #include -#include "rfl/internal/strings/to_pascal_case.hpp" +#include "rfl/internal/strings/strings.hpp" namespace rfl::capnproto::schema { diff --git a/src/rfl/capnproto/to_schema.cpp b/src/rfl/capnproto/to_schema.cpp index 7c1e58e4..2568d6f6 100644 --- a/src/rfl/capnproto/to_schema.cpp +++ b/src/rfl/capnproto/to_schema.cpp @@ -29,8 +29,7 @@ SOFTWARE. #include #include "rfl/capnproto/schema/Type.hpp" -#include "rfl/internal/strings/split.hpp" -#include "rfl/internal/strings/to_pascal_case.hpp" +#include "rfl/internal/strings/strings.hpp" #include "rfl/json.hpp" #include "rfl/parsing/schemaful/tuple_to_object.hpp" diff --git a/src/rfl/internal/strings/strings.cpp b/src/rfl/internal/strings/strings.cpp new file mode 100644 index 00000000..9692ceb1 --- /dev/null +++ b/src/rfl/internal/strings/strings.cpp @@ -0,0 +1,84 @@ + +namespace rfl ::internal ::strings { + +char to_lower(const char ch) { + if (ch >= 'A' && ch <= 'Z') { + return ch + ('a' - 'A'); + } else { + return ch; + } +} + +char to_upper(const char ch) { + if (ch >= 'a' && ch <= 'z') { + return ch + ('A' - 'a'); + } else { + return ch; + } +} + +std::string join(const std::string& _delimiter, + const std::vector& _strings) { + if (_strings.size() == 0) { + return ""; + } + auto res = _strings[0]; + for (size_t i = 1; i < _strings.size(); ++i) { + res += _delimiter + _strings[i]; + } + return res; +} + +std::string replace_all(const std::string& _str, const std::string& _from, + const std::string& _to) { + auto str = _str; + + size_t pos = 0; + while ((pos = str.find(_from, pos)) != std::string::npos) { + str.replace(pos, _from.length(), _to); + pos += _to.length(); + } + return str; +} + +std::vector split(const std::string& _str, + const std::string& _delimiter) { + auto str = _str; + size_t pos = 0; + std::vector result; + while ((pos = str.find(_delimiter)) != std::string::npos) { + result.emplace_back(str.substr(0, pos)); + str.erase(0, pos + _delimiter.length()); + } + result.emplace_back(std::move(str)); + return result; +} + +std::string to_camel_case(const std::string& _str) { + std::string result; + bool capitalize = false; + for (const char ch : _str) { + if (ch == '_') { + capitalize = true; + } else if (capitalize) { + result.push_back(to_upper(ch)); + capitalize = false; + } else { + result.push_back(ch); + } + } + if (result.size() > 0) { + result[0] = to_lower(result[0]); + } + return result; +} + +std::string to_pascal_case(const std::string& _str) { + auto result = to_camel_case("_" + _str); + if (result.size() > 0) { + result[0] = to_upper(result[0]); + } + return result; +} + +} // namespace rfl::internal::strings From 7c6f5c80f5a5330d87e95c7e62ac072b4bf914f6 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Wed, 1 Jan 2025 12:43:03 +0100 Subject: [PATCH 27/57] Added support for standalone-unions --- include/rfl/capnproto/schema/Type.hpp | 1 + src/rfl/capnproto/Type.cpp | 7 +- src/rfl/capnproto/to_schema.cpp | 137 +++++++++++++----- tests/capnproto/test_optionals_in_vectors.cpp | 30 ++++ tests/capnproto/test_variants_in_vectors.cpp | 32 ++++ 5 files changed, 168 insertions(+), 39 deletions(-) create mode 100644 tests/capnproto/test_optionals_in_vectors.cpp create mode 100644 tests/capnproto/test_variants_in_vectors.cpp diff --git a/include/rfl/capnproto/schema/Type.hpp b/include/rfl/capnproto/schema/Type.hpp index ab91bedd..c6d0cbcb 100644 --- a/include/rfl/capnproto/schema/Type.hpp +++ b/include/rfl/capnproto/schema/Type.hpp @@ -61,6 +61,7 @@ struct Type { }; struct Variant { + std::string name; std::vector> fields; }; diff --git a/src/rfl/capnproto/Type.cpp b/src/rfl/capnproto/Type.cpp index d246f0c4..1d8d51e5 100644 --- a/src/rfl/capnproto/Type.cpp +++ b/src/rfl/capnproto/Type.cpp @@ -86,7 +86,7 @@ void handle_fields_in_structs_or_variants(const auto& _struct_or_variant, Type Type::with_name(const std::string& _name) const { const auto set_name = [&](const auto& _v) -> ReflectionType { using T = std::remove_cvref_t; - if constexpr (std::is_same()) { + if constexpr (std::is_same() || std::is_same()) { auto v_with_name = _v; v_with_name.name = _name; return v_with_name; @@ -165,7 +165,10 @@ std::ostream& operator<<(std::ostream& _os, const Type::List& _l) { } std::ostream& operator<<(std::ostream& _os, const Type::Variant& _v) { - return _os << "TODO"; + _os << "struct " << _v.name << " {" << std::endl << " union {" << std::endl; + size_t ix = 0; + handle_fields_in_structs_or_variants(_v, 2, "", &_os, &ix); + return _os << " }" << std::endl << "}" << std::endl; } std::ostream& operator<<(std::ostream& _os, const Type::Reference& _r) { diff --git a/src/rfl/capnproto/to_schema.cpp b/src/rfl/capnproto/to_schema.cpp index 2568d6f6..9934139c 100644 --- a/src/rfl/capnproto/to_schema.cpp +++ b/src/rfl/capnproto/to_schema.cpp @@ -26,7 +26,10 @@ SOFTWARE. #include "rfl/capnproto/to_schema.hpp" +#include +#include #include +#include #include "rfl/capnproto/schema/Type.hpp" #include "rfl/internal/strings/strings.hpp" @@ -35,6 +38,32 @@ SOFTWARE. namespace rfl::capnproto { +constexpr bool PARENT_IS_STRUCT = true; +constexpr bool PARENT_IS_NOT_STRUCT = false; + +struct CapnProtoTypes { + std::map structs_; + std::map maps_; + std::map unions_; +}; + +std::ostream& operator<<(std::ostream& _os, + const std::map& _map) { + for (const auto& [name, type] : _map) { + _os << type.with_name(internal::strings::to_pascal_case(name)) << std::endl + << std::endl; + } + return _os; +} + +std::ostream& operator<<(std::ostream& _os, const CapnProtoTypes& _cnp_types) { + // TODO: ID is hardcoded. + _os << "@0xdbb9ad1f14bf0b36;" << std::endl + << std::endl + << _cnp_types.structs_ << _cnp_types.maps_ << _cnp_types.unions_; + return _os; +} + inline bool is_named_type(const parsing::schema::Type& _type) { return _type.variant_.visit([&](const auto& _v) -> bool { using T = std::remove_cvref_t; @@ -46,7 +75,56 @@ inline bool is_named_type(const parsing::schema::Type& _type) { schema::Type type_to_capnproto_schema_type( const parsing::schema::Type& _type, const std::map& _definitions, - size_t* _num_unnamed) { + const bool _parent_is_struct, CapnProtoTypes* _cnp_types); + +schema::Type any_of_to_capnproto_schema_type( + const parsing::schema::Type::AnyOf& _any_of, + const std::map& _definitions, + const bool _parent_is_struct, CapnProtoTypes* _cnp_types) { + auto value = schema::Type::Variant{}; + size_t i = 1; + for (const auto& type : _any_of.types_) { + value.fields.push_back( + std::make_pair(std::string("Opt" + std::to_string(i++)), + type_to_capnproto_schema_type( + type, _definitions, _parent_is_struct, _cnp_types))); + } + if (_parent_is_struct) { + return schema::Type{.value = value}; + } else { + const auto name = + std::string("Union") + std::to_string(_cnp_types->unions_.size() + 1); + _cnp_types->unions_[name] = schema::Type{.value = value}; + return schema::Type{.value = schema::Type::Reference{name}}; + } +} + +schema::Type optional_to_capnproto_schema_type( + const parsing::schema::Type::Optional& _optional, + const std::map& _definitions, + const bool _parent_is_struct, CapnProtoTypes* _cnp_types) { + const auto value = schema::Type::Variant{ + .fields = + std::vector({std::make_pair(std::string("Some"), + type_to_capnproto_schema_type( + *_optional.type_, _definitions, + _parent_is_struct, _cnp_types)), + std::make_pair(std::string("None"), + schema::Type{schema::Type::Void{}})})}; + if (_parent_is_struct) { + return schema::Type{.value = value}; + } else { + const auto name = + std::string("Union") + std::to_string(_cnp_types->unions_.size() + 1); + _cnp_types->unions_[name] = schema::Type{.value = value}; + return schema::Type{.value = schema::Type::Reference{name}}; + } +} + +schema::Type type_to_capnproto_schema_type( + const parsing::schema::Type& _type, + const std::map& _definitions, + const bool _parent_is_struct, CapnProtoTypes* _cnp_types) { auto handle_variant = [&](const auto& _t) -> schema::Type { using T = std::remove_cvref_t; using Type = parsing::schema::Type; @@ -79,51 +157,39 @@ schema::Type type_to_capnproto_schema_type( return schema::Type{.value = schema::Type::Text{}}; } else if constexpr (std::is_same()) { - auto value = schema::Type::Variant{}; - size_t i = 1; - for (const auto& type : _t.types_) { - value.fields.push_back(std::make_pair( - std::string("Opt" + std::to_string(i++)), - type_to_capnproto_schema_type(type, _definitions, _num_unnamed))); - } - return schema::Type{.value = value}; + return any_of_to_capnproto_schema_type(_t, _definitions, + _parent_is_struct, _cnp_types); } else if constexpr (std::is_same()) { return type_to_capnproto_schema_type(*_t.type_, _definitions, - _num_unnamed); + _parent_is_struct, _cnp_types); } else if constexpr (std::is_same()) { return schema::Type{ .value = schema::Type::List{ .type = Ref::make(type_to_capnproto_schema_type( - *_t.type_, _definitions, _num_unnamed))}}; + *_t.type_, _definitions, PARENT_IS_NOT_STRUCT, _cnp_types))}}; } else if constexpr (std::is_same()) { // TODO return schema::Type{.value = schema::Type::Void{}}; /*return schema::Type{ .value = schema::Type::Enum{.name = std::string("unnamed_") + - std::to_string(++(*_num_unnamed)), + std::to_string(++(*_cnp_types)), .symbols = _t.values_}};*/ } else if constexpr (std::is_same()) { - auto struct_schema = schema::Type::Struct{ - .name = std::string("Unnamed") + std::to_string(++(*_num_unnamed))}; + schema::Type::Struct struct_schema; for (const auto& [k, v] : _t.types_) { struct_schema.fields.push_back(std::make_pair( - k, type_to_capnproto_schema_type(v, _definitions, _num_unnamed))); + k, type_to_capnproto_schema_type(v, _definitions, PARENT_IS_STRUCT, + _cnp_types))); } return schema::Type{.value = struct_schema}; } else if constexpr (std::is_same()) { - return schema::Type{ - .value = schema::Type::Variant{ - .fields = std::vector( - {std::make_pair(std::string("Some"), - type_to_capnproto_schema_type( - *_t.type_, _definitions, _num_unnamed)), - std::make_pair(std::string("None"), - schema::Type{schema::Type::Void{}})})}}; + return optional_to_capnproto_schema_type(_t, _definitions, + _parent_is_struct, _cnp_types); } else if constexpr (std::is_same()) { return schema::Type{.value = @@ -134,23 +200,23 @@ schema::Type type_to_capnproto_schema_type( /*return schema::Type{ .value = schema::Type::Map{ .values = Ref::make(type_to_capnproto_schema_type( - *_t.value_type_, _definitions, - _num_unnamed))}};*/ + *_t.value_type_, _definitions, PARENT_IS_NOT_STRUCT, + _cnp_types))}};*/ } else if constexpr (std::is_same()) { return type_to_capnproto_schema_type( Type{parsing::schemaful::tuple_to_object(_t)}, _definitions, - _num_unnamed); + PARENT_IS_NOT_STRUCT, _cnp_types); } else if constexpr (std::is_same()) { return schema::Type{ .value = schema::Type::List{ .type = Ref::make(type_to_capnproto_schema_type( - *_t.type_, _definitions, _num_unnamed))}}; + *_t.type_, _definitions, PARENT_IS_NOT_STRUCT, _cnp_types))}}; } else if constexpr (std::is_same()) { // Cap'n Proto knows no validation. return type_to_capnproto_schema_type(*_t.type_, _definitions, - _num_unnamed); + _parent_is_struct, _cnp_types); } else { static_assert(rfl::always_false_v, "Not all cases were covered."); } @@ -161,17 +227,14 @@ schema::Type type_to_capnproto_schema_type( std::string to_string_representation( const parsing::schema::Definition& _internal_schema) { - size_t num_unnamed = 0; - std::stringstream sstream; - // TODO: ID is hardcoded. - sstream << "@0xdbb9ad1f14bf0b36;" << std::endl << std::endl; + CapnProtoTypes cnp_types; for (const auto& [name, def] : _internal_schema.definitions_) { - sstream << type_to_capnproto_schema_type(def, _internal_schema.definitions_, - &num_unnamed) - .with_name(internal::strings::to_pascal_case(name)) - << std::endl - << std::endl; + cnp_types.structs_[name] = type_to_capnproto_schema_type( + def, _internal_schema.definitions_, PARENT_IS_NOT_STRUCT, &cnp_types); } + + std::stringstream sstream; + sstream << cnp_types; return sstream.str(); } diff --git a/tests/capnproto/test_optionals_in_vectors.cpp b/tests/capnproto/test_optionals_in_vectors.cpp new file mode 100644 index 00000000..10e49302 --- /dev/null +++ b/tests/capnproto/test_optionals_in_vectors.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_optionals_in_vectors { + +struct Person { + std::string first_name; + std::string last_name = "Simpson"; + std::vector> children; +}; + +TEST(capnproto, test_optionals_in_vectors) { + const std::optional bart = Person{.first_name = "Bart"}; + + const std::optional lisa = Person{.first_name = "Lisa"}; + + const std::optional maggie = Person{.first_name = "Maggie"}; + + const auto homer = Person{.first_name = "Homer", + .children = std::vector>( + {bart, lisa, maggie, std::nullopt})}; + + write_and_read(homer); +} +} // namespace test_optionals_in_vectors diff --git a/tests/capnproto/test_variants_in_vectors.cpp b/tests/capnproto/test_variants_in_vectors.cpp new file mode 100644 index 00000000..8e9633fe --- /dev/null +++ b/tests/capnproto/test_variants_in_vectors.cpp @@ -0,0 +1,32 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_variants_in_vectors { + +struct Person { + std::string first_name; + std::string last_name = "Simpson"; + std::vector> children; +}; + +TEST(capnproto, test_variants_in_vectors) { + const std::variant bart = Person{.first_name = "Bart"}; + + const std::variant lisa = Person{.first_name = "Lisa"}; + + const std::variant maggie = + Person{.first_name = "Maggie"}; + + const auto homer = + Person{.first_name = "Homer", + .children = std::vector>( + {bart, lisa, maggie, "Unknown"})}; + + write_and_read(homer); +} +} // namespace test_variants_in_vectors From 1654c5d81c17b7a8826da7b891b5961fc4d7506a Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Wed, 1 Jan 2025 13:24:32 +0100 Subject: [PATCH 28/57] Added maps as a schema type --- .../rfl/capnproto/schema/CapnProtoTypes.hpp | 46 +++++++++++++++++++ include/rfl/capnproto/schema/Type.hpp | 4 +- src/rfl/capnproto/Type.cpp | 12 +++-- src/rfl/capnproto/to_schema.cpp | 45 +++++------------- tests/capnproto/test_map.cpp | 29 ++++++++++++ 5 files changed, 97 insertions(+), 39 deletions(-) create mode 100644 include/rfl/capnproto/schema/CapnProtoTypes.hpp create mode 100644 tests/capnproto/test_map.cpp diff --git a/include/rfl/capnproto/schema/CapnProtoTypes.hpp b/include/rfl/capnproto/schema/CapnProtoTypes.hpp new file mode 100644 index 00000000..536eb170 --- /dev/null +++ b/include/rfl/capnproto/schema/CapnProtoTypes.hpp @@ -0,0 +1,46 @@ +#ifndef RFL_CAPNPROTO_SCHEMA_CAPNPROTOTYPES_HPP_ +#define RFL_CAPNPROTO_SCHEMA_CAPNPROTOTYPES_HPP_ + +#include + +#include "Type.hpp" + +namespace rfl::capnproto::schema { + +struct CapnProtoTypes { + bool has_maps_ = false; + std::map structs_; + std::map unions_; +}; + +const char* MAP_DEFINITION = R"( +struct Map(Value) { + entries @0 :List(Entry); + struct Entry { + key @0 :Text; + value @1 :Value; + } +})"; + +std::ostream& operator<<(std::ostream& _os, + const std::map& _map) { + for (const auto& [name, type] : _map) { + _os << type.with_name(internal::strings::to_pascal_case(name)) << std::endl + << std::endl; + } + return _os; +} + +std::ostream& operator<<(std::ostream& _os, const CapnProtoTypes& _cnp_types) { + // TODO: ID is hardcoded. + _os << "@0xdbb9ad1f14bf0b36;" << std::endl << std::endl; + if (_cnp_types.has_maps_) { + _os << MAP_DEFINITION << std::endl << std::endl; + } + _os << _cnp_types.structs_ << _cnp_types.unions_; + return _os; +} + +} // namespace rfl::capnproto::schema + +#endif diff --git a/include/rfl/capnproto/schema/Type.hpp b/include/rfl/capnproto/schema/Type.hpp index c6d0cbcb..b10c73c1 100644 --- a/include/rfl/capnproto/schema/Type.hpp +++ b/include/rfl/capnproto/schema/Type.hpp @@ -67,8 +67,8 @@ struct Type { using ReflectionType = rfl::Variant; + UInt64, Float32, Float64, Data, Text, List, Map, Reference, + Struct, Variant>; const auto& reflection() const { return value; } diff --git a/src/rfl/capnproto/Type.cpp b/src/rfl/capnproto/Type.cpp index 1d8d51e5..1ee8fc2e 100644 --- a/src/rfl/capnproto/Type.cpp +++ b/src/rfl/capnproto/Type.cpp @@ -149,6 +149,14 @@ std::ostream& operator<<(std::ostream& _os, const Type::Data&) { return _os << "Data"; } +std::ostream& operator<<(std::ostream& _os, const Type::List& _l) { + return _os << "List(" << *_l.type << ")"; +} + +std::ostream& operator<<(std::ostream& _os, const Type::Map& _m) { + return _os << "Map(" << *_m.type << ")"; +} + std::ostream& operator<<(std::ostream& _os, const Type::Text&) { return _os << "Text"; } @@ -160,10 +168,6 @@ std::ostream& operator<<(std::ostream& _os, const Type::Struct& _s) { return _os << "}" << std::endl; } -std::ostream& operator<<(std::ostream& _os, const Type::List& _l) { - return _os << "List(" << *_l.type << ")"; -} - std::ostream& operator<<(std::ostream& _os, const Type::Variant& _v) { _os << "struct " << _v.name << " {" << std::endl << " union {" << std::endl; size_t ix = 0; diff --git a/src/rfl/capnproto/to_schema.cpp b/src/rfl/capnproto/to_schema.cpp index 9934139c..2138d732 100644 --- a/src/rfl/capnproto/to_schema.cpp +++ b/src/rfl/capnproto/to_schema.cpp @@ -31,6 +31,7 @@ SOFTWARE. #include #include +#include "rfl/capnproto/schema/CapnProtoTypes.hpp" #include "rfl/capnproto/schema/Type.hpp" #include "rfl/internal/strings/strings.hpp" #include "rfl/json.hpp" @@ -41,29 +42,6 @@ namespace rfl::capnproto { constexpr bool PARENT_IS_STRUCT = true; constexpr bool PARENT_IS_NOT_STRUCT = false; -struct CapnProtoTypes { - std::map structs_; - std::map maps_; - std::map unions_; -}; - -std::ostream& operator<<(std::ostream& _os, - const std::map& _map) { - for (const auto& [name, type] : _map) { - _os << type.with_name(internal::strings::to_pascal_case(name)) << std::endl - << std::endl; - } - return _os; -} - -std::ostream& operator<<(std::ostream& _os, const CapnProtoTypes& _cnp_types) { - // TODO: ID is hardcoded. - _os << "@0xdbb9ad1f14bf0b36;" << std::endl - << std::endl - << _cnp_types.structs_ << _cnp_types.maps_ << _cnp_types.unions_; - return _os; -} - inline bool is_named_type(const parsing::schema::Type& _type) { return _type.variant_.visit([&](const auto& _v) -> bool { using T = std::remove_cvref_t; @@ -75,12 +53,12 @@ inline bool is_named_type(const parsing::schema::Type& _type) { schema::Type type_to_capnproto_schema_type( const parsing::schema::Type& _type, const std::map& _definitions, - const bool _parent_is_struct, CapnProtoTypes* _cnp_types); + const bool _parent_is_struct, schema::CapnProtoTypes* _cnp_types); schema::Type any_of_to_capnproto_schema_type( const parsing::schema::Type::AnyOf& _any_of, const std::map& _definitions, - const bool _parent_is_struct, CapnProtoTypes* _cnp_types) { + const bool _parent_is_struct, schema::CapnProtoTypes* _cnp_types) { auto value = schema::Type::Variant{}; size_t i = 1; for (const auto& type : _any_of.types_) { @@ -102,7 +80,7 @@ schema::Type any_of_to_capnproto_schema_type( schema::Type optional_to_capnproto_schema_type( const parsing::schema::Type::Optional& _optional, const std::map& _definitions, - const bool _parent_is_struct, CapnProtoTypes* _cnp_types) { + const bool _parent_is_struct, schema::CapnProtoTypes* _cnp_types) { const auto value = schema::Type::Variant{ .fields = std::vector({std::make_pair(std::string("Some"), @@ -124,7 +102,7 @@ schema::Type optional_to_capnproto_schema_type( schema::Type type_to_capnproto_schema_type( const parsing::schema::Type& _type, const std::map& _definitions, - const bool _parent_is_struct, CapnProtoTypes* _cnp_types) { + const bool _parent_is_struct, schema::CapnProtoTypes* _cnp_types) { auto handle_variant = [&](const auto& _t) -> schema::Type { using T = std::remove_cvref_t; using Type = parsing::schema::Type; @@ -194,14 +172,15 @@ schema::Type type_to_capnproto_schema_type( } else if constexpr (std::is_same()) { return schema::Type{.value = schema::Type::Reference{.type_name = _t.name_}}; + } else if constexpr (std::is_same()) { - // TODO - return schema::Type{.value = schema::Type::Void{}}; - /*return schema::Type{ + _cnp_types->has_maps_ = true; + return schema::Type{ .value = schema::Type::Map{ - .values = Ref::make(type_to_capnproto_schema_type( + .type = Ref::make(type_to_capnproto_schema_type( *_t.value_type_, _definitions, PARENT_IS_NOT_STRUCT, - _cnp_types))}};*/ + _cnp_types))}}; + } else if constexpr (std::is_same()) { return type_to_capnproto_schema_type( Type{parsing::schemaful::tuple_to_object(_t)}, _definitions, @@ -227,7 +206,7 @@ schema::Type type_to_capnproto_schema_type( std::string to_string_representation( const parsing::schema::Definition& _internal_schema) { - CapnProtoTypes cnp_types; + schema::CapnProtoTypes cnp_types; for (const auto& [name, def] : _internal_schema.definitions_) { cnp_types.structs_[name] = type_to_capnproto_schema_type( def, _internal_schema.definitions_, PARENT_IS_NOT_STRUCT, &cnp_types); diff --git a/tests/capnproto/test_map.cpp b/tests/capnproto/test_map.cpp new file mode 100644 index 00000000..b2d683aa --- /dev/null +++ b/tests/capnproto/test_map.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_map { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::shared_ptr> children; +}; + +TEST(capnproto, test_map) { + auto children = std::make_unique>(); + children->insert(std::make_pair("Bart", Person{.first_name = "Bart"})); + children->insert(std::make_pair("Lisa", Person{.first_name = "Lisa"})); + children->insert(std::make_pair("Maggie", Person{.first_name = "Maggie"})); + + const auto homer = + Person{.first_name = "Homer", .children = std::move(children)}; + + rfl::capnproto::to_schema(); + + // write_and_read(homer); +} +} // namespace test_map From fd224d7179eb715e8290036534c13aea779e8fe1 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Wed, 1 Jan 2025 13:43:27 +0100 Subject: [PATCH 29/57] Variant -> Union --- include/rfl/capnproto/schema/Type.hpp | 4 ++-- src/rfl/capnproto/Type.cpp | 26 +++++++++++++------------- src/rfl/capnproto/to_schema.cpp | 12 ++---------- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/include/rfl/capnproto/schema/Type.hpp b/include/rfl/capnproto/schema/Type.hpp index b10c73c1..40758d2b 100644 --- a/include/rfl/capnproto/schema/Type.hpp +++ b/include/rfl/capnproto/schema/Type.hpp @@ -60,7 +60,7 @@ struct Type { std::vector> fields; }; - struct Variant { + struct Union { std::string name; std::vector> fields; }; @@ -68,7 +68,7 @@ struct Type { using ReflectionType = rfl::Variant; + Struct, Union>; const auto& reflection() const { return value; } diff --git a/src/rfl/capnproto/Type.cpp b/src/rfl/capnproto/Type.cpp index 1ee8fc2e..4e1f655f 100644 --- a/src/rfl/capnproto/Type.cpp +++ b/src/rfl/capnproto/Type.cpp @@ -36,28 +36,28 @@ namespace rfl::capnproto::schema { /// Cap'n proto has a somewhat weird way of handling union fields, please refer /// to the schema for further explanation. This is a workaround that ensures /// that we can also support complex, nested unions. -void handle_fields_in_structs_or_variants(const auto& _struct_or_variant, - const size_t _indent, - const std::string& _namespace, - std::ostream* _os, size_t* _ix) { - for (const auto& [name, type] : _struct_or_variant.fields) { +void handle_fields_in_structs_or_unions(const auto& _struct_or_union, + const size_t _indent, + const std::string& _namespace, + std::ostream* _os, size_t* _ix) { + for (const auto& [name, type] : _struct_or_union.fields) { // Because of the way Cap'n proto handles unions, we need a special case for // them. type.reflection().visit([&](const auto& _r) { using R = std::remove_cvref_t; - if constexpr (std::is_same()) { + if constexpr (std::is_same()) { // Special case: Union field. *_os << std::string(_indent * 2, ' ') << name << " :union {" << std::endl; for (size_t i = 0; i < _r.fields.size(); ++i) { _r.fields[i].second.reflection().visit([&](const auto& _union_field) { using U = std::remove_cvref_t; - if constexpr (std::is_same()) { + if constexpr (std::is_same()) { *_os << std::string(_indent * 2 + 2, ' ') << internal::strings::to_camel_case(_namespace + name + "_" + _r.fields[i].first) << " :union {" << std::endl; - handle_fields_in_structs_or_variants( + handle_fields_in_structs_or_unions( _union_field, _indent + 2, _namespace + name + "_" + _r.fields[i].first + "_", _os, _ix); *_os << std::string(_indent * 2 + 2, ' ') << "}" << std::endl; @@ -86,7 +86,7 @@ void handle_fields_in_structs_or_variants(const auto& _struct_or_variant, Type Type::with_name(const std::string& _name) const { const auto set_name = [&](const auto& _v) -> ReflectionType { using T = std::remove_cvref_t; - if constexpr (std::is_same() || std::is_same()) { + if constexpr (std::is_same() || std::is_same()) { auto v_with_name = _v; v_with_name.name = _name; return v_with_name; @@ -164,14 +164,14 @@ std::ostream& operator<<(std::ostream& _os, const Type::Text&) { std::ostream& operator<<(std::ostream& _os, const Type::Struct& _s) { _os << "struct " << _s.name << " {" << std::endl; size_t ix = 0; - handle_fields_in_structs_or_variants(_s, 1, "", &_os, &ix); + handle_fields_in_structs_or_unions(_s, 1, "", &_os, &ix); return _os << "}" << std::endl; } -std::ostream& operator<<(std::ostream& _os, const Type::Variant& _v) { - _os << "struct " << _v.name << " {" << std::endl << " union {" << std::endl; +std::ostream& operator<<(std::ostream& _os, const Type::Union& _u) { + _os << "struct " << _u.name << " {" << std::endl << " union {" << std::endl; size_t ix = 0; - handle_fields_in_structs_or_variants(_v, 2, "", &_os, &ix); + handle_fields_in_structs_or_unions(_u, 2, "", &_os, &ix); return _os << " }" << std::endl << "}" << std::endl; } diff --git a/src/rfl/capnproto/to_schema.cpp b/src/rfl/capnproto/to_schema.cpp index 2138d732..b77453d9 100644 --- a/src/rfl/capnproto/to_schema.cpp +++ b/src/rfl/capnproto/to_schema.cpp @@ -42,14 +42,6 @@ namespace rfl::capnproto { constexpr bool PARENT_IS_STRUCT = true; constexpr bool PARENT_IS_NOT_STRUCT = false; -inline bool is_named_type(const parsing::schema::Type& _type) { - return _type.variant_.visit([&](const auto& _v) -> bool { - using T = std::remove_cvref_t; - return std::is_same() || - std::is_same(); - }); -} - schema::Type type_to_capnproto_schema_type( const parsing::schema::Type& _type, const std::map& _definitions, @@ -59,7 +51,7 @@ schema::Type any_of_to_capnproto_schema_type( const parsing::schema::Type::AnyOf& _any_of, const std::map& _definitions, const bool _parent_is_struct, schema::CapnProtoTypes* _cnp_types) { - auto value = schema::Type::Variant{}; + auto value = schema::Type::Union{}; size_t i = 1; for (const auto& type : _any_of.types_) { value.fields.push_back( @@ -81,7 +73,7 @@ schema::Type optional_to_capnproto_schema_type( const parsing::schema::Type::Optional& _optional, const std::map& _definitions, const bool _parent_is_struct, schema::CapnProtoTypes* _cnp_types) { - const auto value = schema::Type::Variant{ + const auto value = schema::Type::Union{ .fields = std::vector({std::make_pair(std::string("Some"), type_to_capnproto_schema_type( From 163aafaa23c0f30d5423e683485b5528190c3a20 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Wed, 1 Jan 2025 14:37:24 +0100 Subject: [PATCH 30/57] Implemented the writer functions for the map --- include/rfl/capnproto/Writer.hpp | 13 ++++--- src/rfl/capnproto/Writer.cpp | 60 ++++++++++++++++---------------- 2 files changed, 36 insertions(+), 37 deletions(-) diff --git a/include/rfl/capnproto/Writer.hpp b/include/rfl/capnproto/Writer.hpp index 653fdea0..0045a087 100644 --- a/include/rfl/capnproto/Writer.hpp +++ b/include/rfl/capnproto/Writer.hpp @@ -32,7 +32,7 @@ class Writer { }; struct CapnProtoOutputMap { - capnp::DynamicList::Builder val_; + capnp::DynamicStruct::Builder val_; }; struct CapnProtoOutputObject { @@ -180,12 +180,11 @@ class Writer { template OutputVarType add_value_to_map(const std::string_view& _name, const T& _var, OutputMapType* _parent) const noexcept { - // TODO - /*capnproto_value_t new_value; - capnproto_value_add(&_parent->val_, _name.data(), &new_value, nullptr, - nullptr); - set_value(_var, &new_value); - return OutputVarType{new_value};*/ + auto entries = + OutputArrayType{_parent->val_.get("entries").as()}; + auto new_entry = add_object_to_array(2, &entries); + add_value_to_object("key", std::string(_name), &new_entry); + return add_value_to_object("value", _var, &new_entry); } template diff --git a/src/rfl/capnproto/Writer.cpp b/src/rfl/capnproto/Writer.cpp index abe5b520..0a85a4c9 100644 --- a/src/rfl/capnproto/Writer.cpp +++ b/src/rfl/capnproto/Writer.cpp @@ -27,10 +27,11 @@ Writer::OutputArrayType Writer::add_array_to_array( Writer::OutputArrayType Writer::add_array_to_map( const std::string_view& _name, const size_t _size, OutputMapType* _parent) const noexcept { - // TODO - /*avro_value_t new_array; - avro_value_add(&_parent->val_, _name.data(), &new_array, nullptr, nullptr); - return OutputArrayType{new_array};*/ + auto entries = + OutputArrayType{_parent->val_.get("entries").as()}; + auto new_entry = add_object_to_array(2, &entries); + add_value_to_object("key", std::string(_name), &new_entry); + return add_array_to_object("value", _size, &new_entry); } Writer::OutputArrayType Writer::add_array_to_object( @@ -50,35 +51,32 @@ Writer::OutputArrayType Writer::add_array_to_union( Writer::OutputMapType Writer::add_map_to_array( const size_t _size, OutputArrayType* _parent) const noexcept { - // TODO - /*avro_value_t new_map; -avro_value_append(&_parent->val_, &new_map, nullptr); -return OutputMapType{new_map};*/ + auto new_map = add_object_to_array(1, _parent); + auto entries = add_array_to_object("entries", _size, &new_map); + return OutputMapType{new_map.val_}; } Writer::OutputMapType Writer::add_map_to_map( const std::string_view& _name, const size_t _size, OutputMapType* _parent) const noexcept { - // TODO - /*avro_value_t new_map; - avro_value_add(&_parent->val_, _name.data(), &new_map, nullptr, nullptr); - return OutputMapType{new_map};*/ + auto new_map = add_object_to_map(_name, 1, _parent); + auto entries = add_array_to_object("entries", _size, &new_map); + return OutputMapType{new_map.val_}; } Writer::OutputMapType Writer::add_map_to_object( const std::string_view& _name, const size_t _size, OutputObjectType* _parent) const noexcept { return OutputMapType{ - _parent->val_.init(_name.data(), _size).as()}; + _parent->val_.init(_name.data(), _size).as()}; } Writer::OutputMapType Writer::add_map_to_union( const size_t _index, const size_t _size, OutputUnionType* _parent) const noexcept { - // TODO - /*avro_value_t new_map; -avro_value_set_branch(&_parent->val_, static_cast(_index), &new_map); -return OutputMapType{new_map};*/ + auto new_map = add_object_to_union(_index, 1, _parent); + auto entries = add_array_to_object("entries", _size, &new_map); + return OutputMapType{new_map.val_}; } Writer::OutputObjectType Writer::add_object_to_array( @@ -90,10 +88,11 @@ Writer::OutputObjectType Writer::add_object_to_array( Writer::OutputObjectType Writer::add_object_to_map( const std::string_view& _name, const size_t _size, OutputMapType* _parent) const noexcept { - // TODO - /*avro_value_t new_object; - avro_value_add(&_parent->val_, _name.data(), &new_object, nullptr, nullptr); - return OutputObjectType{new_object};*/ + auto entries = + OutputArrayType{_parent->val_.get("entries").as()}; + auto new_entry = add_object_to_array(2, &entries); + add_value_to_object("key", std::string(_name), &new_entry); + return add_object_to_object("value", _size, &new_entry); } Writer::OutputObjectType Writer::add_object_to_object( @@ -119,10 +118,11 @@ Writer::OutputUnionType Writer::add_union_to_array( Writer::OutputUnionType Writer::add_union_to_map( const std::string_view& _name, OutputMapType* _parent) const noexcept { - // TODO - /*avro_value_t new_union; -avro_value_add(&_parent->val_, _name.data(), &new_union, nullptr, nullptr); -return OutputUnionType{new_union};*/ + auto entries = + OutputArrayType{_parent->val_.get("entries").as()}; + auto new_entry = add_object_to_array(2, &entries); + add_value_to_object("key", std::string(_name), &new_entry); + return add_union_to_object("value", &new_entry); } Writer::OutputUnionType Writer::add_union_to_object( @@ -146,11 +146,11 @@ Writer::OutputVarType Writer::add_null_to_array( Writer::OutputVarType Writer::add_null_to_map( const std::string_view& _name, OutputMapType* _parent) const noexcept { - // TODO - /*avro_value_t new_null; -avro_value_add(&_parent->val_, _name.data(), &new_null, nullptr, nullptr); -avro_value_set_null(&new_null); -return OutputVarType{new_null};*/ + auto entries = + OutputArrayType{_parent->val_.get("entries").as()}; + auto new_entry = add_object_to_array(2, &entries); + add_value_to_object("key", std::string(_name), &new_entry); + return add_null_to_object("value", &new_entry); } Writer::OutputVarType Writer::add_null_to_object( From 90676dbeafb2f498c47a93c8dc378e4e785cda7d Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Wed, 1 Jan 2025 15:45:40 +0100 Subject: [PATCH 31/57] Made sure the tests for the maps run through --- include/rfl/capnproto/Reader.hpp | 23 ++++++++++++----------- include/rfl/capnproto/Writer.hpp | 1 + src/rfl/capnproto/Reader.cpp | 6 +++--- src/rfl/capnproto/Writer.cpp | 31 ++++++++++++++++++------------- tests/capnproto/test_map.cpp | 5 ++--- tests/capnproto/test_map2.cpp | 28 ++++++++++++++++++++++++++++ 6 files changed, 64 insertions(+), 30 deletions(-) create mode 100644 tests/capnproto/test_map2.cpp diff --git a/include/rfl/capnproto/Reader.hpp b/include/rfl/capnproto/Reader.hpp index aa58ebd0..4c725fb0 100644 --- a/include/rfl/capnproto/Reader.hpp +++ b/include/rfl/capnproto/Reader.hpp @@ -24,7 +24,7 @@ class Reader { }; struct CapNProtoInputMap { - capnp::DynamicList::Reader val_; + capnp::DynamicStruct::Reader val_; }; struct CapNProtoInputObject { @@ -132,16 +132,17 @@ class Reader { template std::optional read_map(const MapReader& _map_reader, const InputMapType& _map) const noexcept { - // TODO - /*size_t size = 0; - capnproto_value_get_size(_map.val_, &size); - for (size_t ix = 0; ix < size; ++ix) { - capnproto_value_t element; - const char* key = nullptr; - capnproto_value_get_by_index(_map.val_, ix, &element, &key); - _map_reader.read(std::string_view(key), InputVarType{&element}); - }*/ - return std::nullopt; + try { + const auto entries = _map.val_.get("entries").as(); + for (auto entry : entries) { + auto s = entry.template as(); + const char* key = s.get("key").as().cStr(); + _map_reader.read(std::string_view(key), InputVarType{s.get("value")}); + } + return std::nullopt; + } catch (std::exception& e) { + return Error{e.what()}; + } } template diff --git a/include/rfl/capnproto/Writer.hpp b/include/rfl/capnproto/Writer.hpp index 0045a087..70d924f9 100644 --- a/include/rfl/capnproto/Writer.hpp +++ b/include/rfl/capnproto/Writer.hpp @@ -33,6 +33,7 @@ class Writer { struct CapnProtoOutputMap { capnp::DynamicStruct::Builder val_; + size_t ix_ = 0; }; struct CapnProtoOutputObject { diff --git a/src/rfl/capnproto/Reader.cpp b/src/rfl/capnproto/Reader.cpp index bd1b7fe4..f6fa03bf 100644 --- a/src/rfl/capnproto/Reader.cpp +++ b/src/rfl/capnproto/Reader.cpp @@ -41,10 +41,10 @@ rfl::Result Reader::to_object( rfl::Result Reader::to_map( const InputVarType& _var) const noexcept { - if (_var.val_.getType() != capnp::DynamicValue::LIST) { - return Error("Could not cast to a list."); + if (_var.val_.getType() != capnp::DynamicValue::STRUCT) { + return Error("Could not cast to a map."); } - return InputMapType{_var.val_.as()}; + return InputMapType{_var.val_.as()}; } rfl::Result Reader::to_union( diff --git a/src/rfl/capnproto/Writer.cpp b/src/rfl/capnproto/Writer.cpp index 0a85a4c9..cf575a1d 100644 --- a/src/rfl/capnproto/Writer.cpp +++ b/src/rfl/capnproto/Writer.cpp @@ -27,8 +27,9 @@ Writer::OutputArrayType Writer::add_array_to_array( Writer::OutputArrayType Writer::add_array_to_map( const std::string_view& _name, const size_t _size, OutputMapType* _parent) const noexcept { - auto entries = - OutputArrayType{_parent->val_.get("entries").as()}; + auto entries = OutputArrayType{ + .val_ = _parent->val_.get("entries").as(), + .ix_ = _parent->ix_++}; auto new_entry = add_object_to_array(2, &entries); add_value_to_object("key", std::string(_name), &new_entry); return add_array_to_object("value", _size, &new_entry); @@ -52,7 +53,7 @@ Writer::OutputArrayType Writer::add_array_to_union( Writer::OutputMapType Writer::add_map_to_array( const size_t _size, OutputArrayType* _parent) const noexcept { auto new_map = add_object_to_array(1, _parent); - auto entries = add_array_to_object("entries", _size, &new_map); + add_array_to_object("entries", _size, &new_map); return OutputMapType{new_map.val_}; } @@ -60,22 +61,23 @@ Writer::OutputMapType Writer::add_map_to_map( const std::string_view& _name, const size_t _size, OutputMapType* _parent) const noexcept { auto new_map = add_object_to_map(_name, 1, _parent); - auto entries = add_array_to_object("entries", _size, &new_map); + add_array_to_object("entries", _size, &new_map); return OutputMapType{new_map.val_}; } Writer::OutputMapType Writer::add_map_to_object( const std::string_view& _name, const size_t _size, OutputObjectType* _parent) const noexcept { - return OutputMapType{ - _parent->val_.init(_name.data(), _size).as()}; + auto new_map = add_object_to_object(_name, 1, _parent); + add_array_to_object("entries", _size, &new_map); + return OutputMapType{new_map.val_}; } Writer::OutputMapType Writer::add_map_to_union( const size_t _index, const size_t _size, OutputUnionType* _parent) const noexcept { auto new_map = add_object_to_union(_index, 1, _parent); - auto entries = add_array_to_object("entries", _size, &new_map); + add_array_to_object("entries", _size, &new_map); return OutputMapType{new_map.val_}; } @@ -88,8 +90,9 @@ Writer::OutputObjectType Writer::add_object_to_array( Writer::OutputObjectType Writer::add_object_to_map( const std::string_view& _name, const size_t _size, OutputMapType* _parent) const noexcept { - auto entries = - OutputArrayType{_parent->val_.get("entries").as()}; + auto entries = OutputArrayType{ + .val_ = _parent->val_.get("entries").as(), + .ix_ = _parent->ix_++}; auto new_entry = add_object_to_array(2, &entries); add_value_to_object("key", std::string(_name), &new_entry); return add_object_to_object("value", _size, &new_entry); @@ -118,8 +121,9 @@ Writer::OutputUnionType Writer::add_union_to_array( Writer::OutputUnionType Writer::add_union_to_map( const std::string_view& _name, OutputMapType* _parent) const noexcept { - auto entries = - OutputArrayType{_parent->val_.get("entries").as()}; + auto entries = OutputArrayType{ + .val_ = _parent->val_.get("entries").as(), + .ix_ = _parent->ix_++}; auto new_entry = add_object_to_array(2, &entries); add_value_to_object("key", std::string(_name), &new_entry); return add_union_to_object("value", &new_entry); @@ -146,8 +150,9 @@ Writer::OutputVarType Writer::add_null_to_array( Writer::OutputVarType Writer::add_null_to_map( const std::string_view& _name, OutputMapType* _parent) const noexcept { - auto entries = - OutputArrayType{_parent->val_.get("entries").as()}; + auto entries = OutputArrayType{ + .val_ = _parent->val_.get("entries").as(), + .ix_ = _parent->ix_++}; auto new_entry = add_object_to_array(2, &entries); add_value_to_object("key", std::string(_name), &new_entry); return add_null_to_object("value", &new_entry); diff --git a/tests/capnproto/test_map.cpp b/tests/capnproto/test_map.cpp index b2d683aa..f1c321fb 100644 --- a/tests/capnproto/test_map.cpp +++ b/tests/capnproto/test_map.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include "write_and_read.hpp" @@ -22,8 +23,6 @@ TEST(capnproto, test_map) { const auto homer = Person{.first_name = "Homer", .children = std::move(children)}; - rfl::capnproto::to_schema(); - - // write_and_read(homer); + write_and_read(homer); } } // namespace test_map diff --git a/tests/capnproto/test_map2.cpp b/tests/capnproto/test_map2.cpp new file mode 100644 index 00000000..ac2a5acc --- /dev/null +++ b/tests/capnproto/test_map2.cpp @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_map2 { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::map children; +}; + +TEST(capnproto, test_map2) { + auto children = std::map(); + children.insert(std::make_pair("Bart", Person{.first_name = "Bart"})); + children.insert(std::make_pair("Lisa", Person{.first_name = "Lisa"})); + children.insert(std::make_pair("Maggie", Person{.first_name = "Maggie"})); + + const auto homer = + Person{.first_name = "Homer", .children = std::move(children)}; + + write_and_read(homer); +} +} // namespace test_map2 From f4aebd18f3e7cebcaaa4d6fe4a36aef07cd7dc62 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Thu, 2 Jan 2025 21:02:11 +0100 Subject: [PATCH 32/57] Make sure we support tuples --- .../rfl/capnproto/schema/CapnProtoTypes.hpp | 3 +- src/rfl/capnproto/to_schema.cpp | 80 +++++++++++-------- tests/capnproto/test_tuple.cpp | 33 ++++++++ 3 files changed, 83 insertions(+), 33 deletions(-) create mode 100644 tests/capnproto/test_tuple.cpp diff --git a/include/rfl/capnproto/schema/CapnProtoTypes.hpp b/include/rfl/capnproto/schema/CapnProtoTypes.hpp index 536eb170..9c71bda2 100644 --- a/include/rfl/capnproto/schema/CapnProtoTypes.hpp +++ b/include/rfl/capnproto/schema/CapnProtoTypes.hpp @@ -11,6 +11,7 @@ struct CapnProtoTypes { bool has_maps_ = false; std::map structs_; std::map unions_; + std::map tuples_; }; const char* MAP_DEFINITION = R"( @@ -37,7 +38,7 @@ std::ostream& operator<<(std::ostream& _os, const CapnProtoTypes& _cnp_types) { if (_cnp_types.has_maps_) { _os << MAP_DEFINITION << std::endl << std::endl; } - _os << _cnp_types.structs_ << _cnp_types.unions_; + _os << _cnp_types.structs_ << _cnp_types.unions_ << _cnp_types.tuples_; return _os; } diff --git a/src/rfl/capnproto/to_schema.cpp b/src/rfl/capnproto/to_schema.cpp index b77453d9..8827ca35 100644 --- a/src/rfl/capnproto/to_schema.cpp +++ b/src/rfl/capnproto/to_schema.cpp @@ -39,27 +39,26 @@ SOFTWARE. namespace rfl::capnproto { -constexpr bool PARENT_IS_STRUCT = true; -constexpr bool PARENT_IS_NOT_STRUCT = false; +enum struct Parent { is_top_level, is_a_struct, is_not_a_struct }; schema::Type type_to_capnproto_schema_type( const parsing::schema::Type& _type, const std::map& _definitions, - const bool _parent_is_struct, schema::CapnProtoTypes* _cnp_types); + const Parent _parent, schema::CapnProtoTypes* _cnp_types); schema::Type any_of_to_capnproto_schema_type( const parsing::schema::Type::AnyOf& _any_of, const std::map& _definitions, - const bool _parent_is_struct, schema::CapnProtoTypes* _cnp_types) { + const Parent _parent, schema::CapnProtoTypes* _cnp_types) { auto value = schema::Type::Union{}; size_t i = 1; for (const auto& type : _any_of.types_) { value.fields.push_back( std::make_pair(std::string("Opt" + std::to_string(i++)), - type_to_capnproto_schema_type( - type, _definitions, _parent_is_struct, _cnp_types))); + type_to_capnproto_schema_type(type, _definitions, + _parent, _cnp_types))); } - if (_parent_is_struct) { + if (_parent == Parent::is_a_struct) { return schema::Type{.value = value}; } else { const auto name = @@ -69,19 +68,39 @@ schema::Type any_of_to_capnproto_schema_type( } } +schema::Type object_to_capnproto_schema_type( + const parsing::schema::Type::Object& _obj, + const std::map& _definitions, + const Parent _parent, schema::CapnProtoTypes* _cnp_types) { + schema::Type::Struct struct_schema; + for (const auto& [k, v] : _obj.types_) { + struct_schema.fields.push_back(std::make_pair( + k, type_to_capnproto_schema_type(v, _definitions, Parent::is_a_struct, + _cnp_types))); + } + if (_parent == Parent::is_top_level) { + return schema::Type{.value = struct_schema}; + } else { + const auto name = + std::string("Tuple") + std::to_string(_cnp_types->tuples_.size() + 1); + _cnp_types->tuples_[name] = schema::Type{.value = struct_schema}; + return schema::Type{.value = schema::Type::Reference{name}}; + } +} + schema::Type optional_to_capnproto_schema_type( const parsing::schema::Type::Optional& _optional, const std::map& _definitions, - const bool _parent_is_struct, schema::CapnProtoTypes* _cnp_types) { + const Parent _parent, schema::CapnProtoTypes* _cnp_types) { const auto value = schema::Type::Union{ .fields = std::vector({std::make_pair(std::string("Some"), type_to_capnproto_schema_type( *_optional.type_, _definitions, - _parent_is_struct, _cnp_types)), + _parent, _cnp_types)), std::make_pair(std::string("None"), schema::Type{schema::Type::Void{}})})}; - if (_parent_is_struct) { + if (_parent == Parent::is_a_struct) { return schema::Type{.value = value}; } else { const auto name = @@ -94,7 +113,7 @@ schema::Type optional_to_capnproto_schema_type( schema::Type type_to_capnproto_schema_type( const parsing::schema::Type& _type, const std::map& _definitions, - const bool _parent_is_struct, schema::CapnProtoTypes* _cnp_types) { + const Parent _parent, schema::CapnProtoTypes* _cnp_types) { auto handle_variant = [&](const auto& _t) -> schema::Type { using T = std::remove_cvref_t; using Type = parsing::schema::Type; @@ -127,18 +146,19 @@ schema::Type type_to_capnproto_schema_type( return schema::Type{.value = schema::Type::Text{}}; } else if constexpr (std::is_same()) { - return any_of_to_capnproto_schema_type(_t, _definitions, - _parent_is_struct, _cnp_types); + return any_of_to_capnproto_schema_type(_t, _definitions, _parent, + _cnp_types); } else if constexpr (std::is_same()) { - return type_to_capnproto_schema_type(*_t.type_, _definitions, - _parent_is_struct, _cnp_types); + return type_to_capnproto_schema_type(*_t.type_, _definitions, _parent, + _cnp_types); } else if constexpr (std::is_same()) { return schema::Type{ .value = schema::Type::List{ .type = Ref::make(type_to_capnproto_schema_type( - *_t.type_, _definitions, PARENT_IS_NOT_STRUCT, _cnp_types))}}; + *_t.type_, _definitions, Parent::is_not_a_struct, + _cnp_types))}}; } else if constexpr (std::is_same()) { // TODO @@ -149,17 +169,12 @@ schema::Type type_to_capnproto_schema_type( .symbols = _t.values_}};*/ } else if constexpr (std::is_same()) { - schema::Type::Struct struct_schema; - for (const auto& [k, v] : _t.types_) { - struct_schema.fields.push_back(std::make_pair( - k, type_to_capnproto_schema_type(v, _definitions, PARENT_IS_STRUCT, - _cnp_types))); - } - return schema::Type{.value = struct_schema}; + return object_to_capnproto_schema_type(_t, _definitions, _parent, + _cnp_types); } else if constexpr (std::is_same()) { - return optional_to_capnproto_schema_type(_t, _definitions, - _parent_is_struct, _cnp_types); + return optional_to_capnproto_schema_type(_t, _definitions, _parent, + _cnp_types); } else if constexpr (std::is_same()) { return schema::Type{.value = @@ -170,24 +185,25 @@ schema::Type type_to_capnproto_schema_type( return schema::Type{ .value = schema::Type::Map{ .type = Ref::make(type_to_capnproto_schema_type( - *_t.value_type_, _definitions, PARENT_IS_NOT_STRUCT, + *_t.value_type_, _definitions, Parent::is_not_a_struct, _cnp_types))}}; } else if constexpr (std::is_same()) { return type_to_capnproto_schema_type( - Type{parsing::schemaful::tuple_to_object(_t)}, _definitions, - PARENT_IS_NOT_STRUCT, _cnp_types); + Type{parsing::schemaful::tuple_to_object(_t)}, _definitions, _parent, + _cnp_types); } else if constexpr (std::is_same()) { return schema::Type{ .value = schema::Type::List{ .type = Ref::make(type_to_capnproto_schema_type( - *_t.type_, _definitions, PARENT_IS_NOT_STRUCT, _cnp_types))}}; + *_t.type_, _definitions, Parent::is_not_a_struct, + _cnp_types))}}; } else if constexpr (std::is_same()) { // Cap'n Proto knows no validation. - return type_to_capnproto_schema_type(*_t.type_, _definitions, - _parent_is_struct, _cnp_types); + return type_to_capnproto_schema_type(*_t.type_, _definitions, _parent, + _cnp_types); } else { static_assert(rfl::always_false_v, "Not all cases were covered."); } @@ -201,7 +217,7 @@ std::string to_string_representation( schema::CapnProtoTypes cnp_types; for (const auto& [name, def] : _internal_schema.definitions_) { cnp_types.structs_[name] = type_to_capnproto_schema_type( - def, _internal_schema.definitions_, PARENT_IS_NOT_STRUCT, &cnp_types); + def, _internal_schema.definitions_, Parent::is_top_level, &cnp_types); } std::stringstream sstream; diff --git a/tests/capnproto/test_tuple.cpp b/tests/capnproto/test_tuple.cpp new file mode 100644 index 00000000..d1e8e4d4 --- /dev/null +++ b/tests/capnproto/test_tuple.cpp @@ -0,0 +1,33 @@ + +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_tuple { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::shared_ptr> children = nullptr; +}; + +TEST(capnproto, test_tuple) { + auto bart = Person{.first_name = "Bart"}; + + auto lisa = Person{.first_name = "Lisa"}; + + auto maggie = Person{.first_name = "Maggie"}; + + const auto homer = + Person{.first_name = "Homer", + .children = std::make_unique>( + std::tuple{ + std::move(bart), std::move(lisa), std::move(maggie)})}; + + write_and_read(homer); +} +} // namespace test_tuple From 2109008fe3d0aa1867360ebca18f759add8b139a Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Thu, 2 Jan 2025 21:58:54 +0100 Subject: [PATCH 33/57] Added benchmarks --- benchmarks/all/canada_read.cpp | 13 +++++++++++++ benchmarks/all/canada_write.cpp | 13 +++++++++++++ benchmarks/all/licenses_read.cpp | 13 +++++++++++++ benchmarks/all/licenses_write.cpp | 13 +++++++++++++ benchmarks/all/person_read.cpp | 13 +++++++++++++ benchmarks/all/person_write.cpp | 13 +++++++++++++ include/rfl/capnproto/Reader.hpp | 18 ++++++------------ include/rfl/capnproto/Writer.hpp | 12 ++++++++++++ include/rfl/parsing/TupleParser.hpp | 2 +- .../parsing/schemaful/tuple_to_named_tuple.hpp | 4 ++-- .../rfl/parsing/schemaful/tuple_to_object.hpp | 14 +------------- src/reflectcpp.cpp | 1 + src/rfl/capnproto/to_schema.cpp | 4 ++-- src/rfl/parsing/schemaful/tuple_to_object.cpp | 18 ++++++++++++++++++ 14 files changed, 121 insertions(+), 30 deletions(-) create mode 100644 src/rfl/parsing/schemaful/tuple_to_object.cpp diff --git a/benchmarks/all/canada_read.cpp b/benchmarks/all/canada_read.cpp index 21574752..fd803dbf 100644 --- a/benchmarks/all/canada_read.cpp +++ b/benchmarks/all/canada_read.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -71,6 +72,18 @@ static void BM_canada_read_reflect_cpp_bson(benchmark::State &state) { } BENCHMARK(BM_canada_read_reflect_cpp_bson); +static void BM_canada_read_reflect_cpp_capnproto(benchmark::State &state) { + const auto schema = rfl::capnproto::to_schema(); + const auto data = rfl::capnproto::write(load_data(), schema); + for (auto _ : state) { + const auto res = rfl::capnproto::read(data, schema); + if (!res) { + std::cout << res.error()->what() << std::endl; + } + } +} +BENCHMARK(BM_canada_read_reflect_cpp_capnproto); + static void BM_canada_read_reflect_cpp_cbor(benchmark::State &state) { const auto data = rfl::cbor::write(load_data()); for (auto _ : state) { diff --git a/benchmarks/all/canada_write.cpp b/benchmarks/all/canada_write.cpp index 88c37282..cdb0beae 100644 --- a/benchmarks/all/canada_write.cpp +++ b/benchmarks/all/canada_write.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -71,6 +72,18 @@ static void BM_canada_write_reflect_cpp_bson(benchmark::State &state) { } BENCHMARK(BM_canada_write_reflect_cpp_bson); +static void BM_canada_write_reflect_cpp_capnproto(benchmark::State &state) { + const auto schema = rfl::capnproto::to_schema(); + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::capnproto::write(data, schema); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_canada_write_reflect_cpp_capnproto); + static void BM_canada_write_reflect_cpp_cbor(benchmark::State &state) { const auto data = load_data(); for (auto _ : state) { diff --git a/benchmarks/all/licenses_read.cpp b/benchmarks/all/licenses_read.cpp index 7b53cd56..a04e8af1 100644 --- a/benchmarks/all/licenses_read.cpp +++ b/benchmarks/all/licenses_read.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -87,6 +88,18 @@ static void BM_licenses_read_reflect_cpp_bson(benchmark::State &state) { } BENCHMARK(BM_licenses_read_reflect_cpp_bson); +static void BM_licenses_read_reflect_cpp_capnproto(benchmark::State &state) { + const auto schema = rfl::capnproto::to_schema(); + const auto data = rfl::capnproto::write(load_data(), schema); + for (auto _ : state) { + const auto res = rfl::capnproto::read(data, schema); + if (!res) { + std::cout << res.error()->what() << std::endl; + } + } +} +BENCHMARK(BM_licenses_read_reflect_cpp_capnproto); + static void BM_licenses_read_reflect_cpp_cbor(benchmark::State &state) { const auto data = rfl::cbor::write(load_data()); for (auto _ : state) { diff --git a/benchmarks/all/licenses_write.cpp b/benchmarks/all/licenses_write.cpp index ba68d917..ee9fe84c 100644 --- a/benchmarks/all/licenses_write.cpp +++ b/benchmarks/all/licenses_write.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -87,6 +88,18 @@ static void BM_licenses_write_reflect_cpp_bson(benchmark::State &state) { } BENCHMARK(BM_licenses_write_reflect_cpp_bson); +static void BM_licenses_write_reflect_cpp_capnproto(benchmark::State &state) { + const auto schema = rfl::capnproto::to_schema(); + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::capnproto::write(data, schema); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_licenses_write_reflect_cpp_capnproto); + static void BM_licenses_write_reflect_cpp_cbor(benchmark::State &state) { const auto data = load_data(); for (auto _ : state) { diff --git a/benchmarks/all/person_read.cpp b/benchmarks/all/person_read.cpp index 0fae1193..64114840 100644 --- a/benchmarks/all/person_read.cpp +++ b/benchmarks/all/person_read.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -66,6 +67,18 @@ static void BM_person_read_reflect_cpp_bson(benchmark::State &state) { } BENCHMARK(BM_person_read_reflect_cpp_bson); +static void BM_person_read_reflect_cpp_capnproto(benchmark::State &state) { + const auto schema = rfl::capnproto::to_schema(); + const auto data = rfl::capnproto::write(load_data(), schema); + for (auto _ : state) { + const auto res = rfl::capnproto::read(data, schema); + if (!res) { + std::cout << res.error()->what() << std::endl; + } + } +} +BENCHMARK(BM_person_read_reflect_cpp_capnproto); + static void BM_person_read_reflect_cpp_cbor(benchmark::State &state) { const auto data = rfl::cbor::write(load_data()); for (auto _ : state) { diff --git a/benchmarks/all/person_write.cpp b/benchmarks/all/person_write.cpp index fdf6f95b..0f5e13fe 100644 --- a/benchmarks/all/person_write.cpp +++ b/benchmarks/all/person_write.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -67,6 +68,18 @@ static void BM_person_write_reflect_cpp_bson(benchmark::State &state) { } BENCHMARK(BM_person_write_reflect_cpp_bson); +static void BM_person_write_reflect_cpp_capnproto(benchmark::State &state) { + const auto schema = rfl::capnproto::to_schema(); + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::capnproto::write(data, schema); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_person_write_reflect_cpp_capnproto); + static void BM_person_write_reflect_cpp_cbor(benchmark::State &state) { const auto data = load_data(); for (auto _ : state) { diff --git a/include/rfl/capnproto/Reader.hpp b/include/rfl/capnproto/Reader.hpp index 4c725fb0..d7d904b8 100644 --- a/include/rfl/capnproto/Reader.hpp +++ b/include/rfl/capnproto/Reader.hpp @@ -93,16 +93,10 @@ class Reader { "float or double."); } - // TODO - /*} else if constexpr (internal::is_literal_v) { - int value = 0; - const auto err = capnproto_value_get_enum(_var.val_, &value); - if (err) { - return Error("Could not cast to enum."); - } - return std::remove_cvref_t::from_value( - static_cast::ValueType>(value));*/ + // TODO: read as enum + } else if constexpr (internal::is_literal_v) { + return to_basic_type(_var).and_then(T::from_string); + } else { static_assert(rfl::always_false_v, "Unsupported type."); } @@ -148,9 +142,9 @@ class Reader { template std::optional read_object(const ObjectReader& _object_reader, const InputObjectType& _obj) const noexcept { + int i = 0; for (auto field : _obj.val_.getSchema().getFields()) { - _object_reader.read(field.getProto().getName().cStr(), - InputVarType{_obj.val_.get(field)}); + _object_reader.read(i++, InputVarType{_obj.val_.get(field)}); } return std::nullopt; } diff --git a/include/rfl/capnproto/Writer.hpp b/include/rfl/capnproto/Writer.hpp index 70d924f9..a32a148d 100644 --- a/include/rfl/capnproto/Writer.hpp +++ b/include/rfl/capnproto/Writer.hpp @@ -172,6 +172,10 @@ class Writer { } else if constexpr (std::is_integral>()) { _parent->val_.set(_parent->ix_++, static_cast(_var)); + } else if constexpr (internal::is_literal_v) { + // TODO: Transform to enum + return add_value_to_array(_var.str(), _parent); + } else { static_assert(rfl::always_false_v, "Unsupported type."); } @@ -202,6 +206,10 @@ class Writer { } else if constexpr (std::is_integral>()) { _parent->val_.set(_name.data(), static_cast(_var)); + } else if constexpr (internal::is_literal_v) { + // TODO: Transform to enum + return add_value_to_object(_name, _var.str(), _parent); + } else { static_assert(rfl::always_false_v, "Unsupported type."); } @@ -222,6 +230,10 @@ class Writer { } else if constexpr (std::is_integral>()) { _parent->val_.set(field, static_cast(_var)); + } else if constexpr (internal::is_literal_v) { + // TODO: Transform to enum + return add_value_to_union(_index, _var.str(), _parent); + } else { static_assert(rfl::always_false_v, "Unsupported type."); } diff --git a/include/rfl/parsing/TupleParser.hpp b/include/rfl/parsing/TupleParser.hpp index 5e916b86..e09b24ab 100644 --- a/include/rfl/parsing/TupleParser.hpp +++ b/include/rfl/parsing/TupleParser.hpp @@ -46,7 +46,7 @@ struct TupleParser { } else { const auto parse = [&](const InputArrayType& _arr) -> Result { - alignas(TupleType) unsigned char buf[sizeof(TupleType)]; + alignas(TupleType) unsigned char buf[sizeof(TupleType)]{}; auto ptr = internal::ptr_cast(&buf); const auto tuple_reader = TupleReader -consteval auto to_field_name() { +inline consteval auto to_field_name() { return internal::StringLiteral<5>('f', static_cast('0' + ((_i / 100) % 10)), static_cast('0' + ((_i / 10) % 10)), @@ -21,7 +21,7 @@ consteval auto to_field_name() { } template -auto to_field(const auto& _t) { +inline auto to_field(const auto& _t) { using T = std::remove_cvref_t; return rfl::Field(), const T*>(&_t); } diff --git a/include/rfl/parsing/schemaful/tuple_to_object.hpp b/include/rfl/parsing/schemaful/tuple_to_object.hpp index a4cad6ad..b01da602 100644 --- a/include/rfl/parsing/schemaful/tuple_to_object.hpp +++ b/include/rfl/parsing/schemaful/tuple_to_object.hpp @@ -7,21 +7,9 @@ namespace rfl::parsing::schemaful { -std::string to_field_name(const size_t _i) { - return std::string({'f', static_cast('0' + ((_i / 100) % 10)), - static_cast('0' + ((_i / 10) % 10)), - static_cast('0' + (_i % 10))}); -} - /// Schemaful formats often don't have an explicit tuple representation. /// This is the required workaround. -schema::Type::Object tuple_to_object(const schema::Type::Tuple& _tup) { - auto obj = schema::Type::Object{}; - for (size_t i = 0; i < _tup.types_.size(); ++i) { - obj.types_[to_field_name(i)] = _tup.types_[i]; - } - return obj; -} +schema::Type::Object tuple_to_object(const schema::Type::Tuple& _tup); } // namespace rfl::parsing::schemaful diff --git a/src/reflectcpp.cpp b/src/reflectcpp.cpp index 654626ed..5fa52a99 100644 --- a/src/reflectcpp.cpp +++ b/src/reflectcpp.cpp @@ -34,3 +34,4 @@ SOFTWARE. #include "rfl/generic/Writer.cpp" #include "rfl/internal/strings/strings.cpp" #include "rfl/parsing/schema/Type.cpp" +#include "rfl/parsing/schemaful/tuple_to_object.cpp" diff --git a/src/rfl/capnproto/to_schema.cpp b/src/rfl/capnproto/to_schema.cpp index 8827ca35..0fbb67a5 100644 --- a/src/rfl/capnproto/to_schema.cpp +++ b/src/rfl/capnproto/to_schema.cpp @@ -161,8 +161,8 @@ schema::Type type_to_capnproto_schema_type( _cnp_types))}}; } else if constexpr (std::is_same()) { - // TODO - return schema::Type{.value = schema::Type::Void{}}; + // TODO: As enum + return schema::Type{.value = schema::Type::Text{}}; /*return schema::Type{ .value = schema::Type::Enum{.name = std::string("unnamed_") + std::to_string(++(*_cnp_types)), diff --git a/src/rfl/parsing/schemaful/tuple_to_object.cpp b/src/rfl/parsing/schemaful/tuple_to_object.cpp new file mode 100644 index 00000000..e75f9a33 --- /dev/null +++ b/src/rfl/parsing/schemaful/tuple_to_object.cpp @@ -0,0 +1,18 @@ +#include "rfl/parsing/schemaful/tuple_to_object.hpp" + +namespace rfl::parsing::schemaful { + +std::string to_field_name(const size_t _i) { + return std::string({'f', static_cast('0' + ((_i / 100) % 10)), + static_cast('0' + ((_i / 10) % 10)), + static_cast('0' + (_i % 10))}); +} + +schema::Type::Object tuple_to_object(const schema::Type::Tuple& _tup) { + auto obj = schema::Type::Object{}; + for (size_t i = 0; i < _tup.types_.size(); ++i) { + obj.types_[to_field_name(i)] = _tup.types_[i]; + } + return obj; +} +} // namespace rfl::parsing::schemaful From 7a047e742dd8677acc4ec183b20bfa8864c11753 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Fri, 3 Jan 2025 22:33:50 +0100 Subject: [PATCH 34/57] Bugfix processed_t --- include/rfl/internal/processed_t.hpp | 12 +++++---- include/rfl/internal/remove_ptrs_nt.hpp | 34 +++++++++++++++++++++++++ include/rfl/named_tuple_t.hpp | 26 +++---------------- tests/capnproto/test_map.cpp | 2 +- 4 files changed, 45 insertions(+), 29 deletions(-) create mode 100644 include/rfl/internal/remove_ptrs_nt.hpp diff --git a/include/rfl/internal/processed_t.hpp b/include/rfl/internal/processed_t.hpp index 69842a59..1e6250c5 100644 --- a/include/rfl/internal/processed_t.hpp +++ b/include/rfl/internal/processed_t.hpp @@ -4,7 +4,8 @@ #include #include "../Processors.hpp" -#include "../named_tuple_t.hpp" +#include "ptr_named_tuple_t.hpp" +#include "remove_ptrs_nt.hpp" namespace rfl::internal { @@ -13,10 +14,11 @@ struct Processed; template struct Processed> { - using NamedTupleType = named_tuple_t; - using type = typename std::invoke_result< - decltype(Processors::template process), - NamedTupleType>::type; + using PtrNamedTupleType = ptr_named_tuple_t; + using type = typename remove_ptrs_nt< + std::invoke_result_t::template process< + StructType, PtrNamedTupleType>), + PtrNamedTupleType>>::NamedTupleType; }; template diff --git a/include/rfl/internal/remove_ptrs_nt.hpp b/include/rfl/internal/remove_ptrs_nt.hpp new file mode 100644 index 00000000..dbd3eff8 --- /dev/null +++ b/include/rfl/internal/remove_ptrs_nt.hpp @@ -0,0 +1,34 @@ +#ifndef RFL_INTERNAL_REMOVE_PTRS_NT_HPP_ +#define RFL_INTERNAL_REMOVE_PTRS_NT_HPP_ + +#include +#include + +#include "../Field.hpp" +#include "StringLiteral.hpp" +#include "wrap_in_rfl_array_t.hpp" + +namespace rfl::internal { + +template +struct remove_ptr; + +template +struct remove_ptr> { + using FieldType = + Field<_name, internal::wrap_in_rfl_array_t< + std::remove_cvref_t>>>; +}; + +template +struct remove_ptrs_nt; + +template +struct remove_ptrs_nt> { + using NamedTupleType = + NamedTuple::FieldType...>; +}; + +} // namespace rfl::internal + +#endif diff --git a/include/rfl/named_tuple_t.hpp b/include/rfl/named_tuple_t.hpp index d1bfd385..4cb7786a 100644 --- a/include/rfl/named_tuple_t.hpp +++ b/include/rfl/named_tuple_t.hpp @@ -6,38 +6,18 @@ #include #include "NamedTuple.hpp" -#include "internal/is_named_tuple.hpp" #include "internal/ptr_named_tuple_t.hpp" -#include "internal/wrap_in_rfl_array_t.hpp" +#include "internal/remove_ptrs_nt.hpp" #include "to_named_tuple.hpp" namespace rfl { -template -struct remove_ptr; - -template -struct remove_ptr> { - using FieldType = - Field<_name, internal::wrap_in_rfl_array_t< - std::remove_cvref_t>>>; -}; - -template -struct remove_ptrs_nt; - -template -struct remove_ptrs_nt> { - using NamedTupleType = - NamedTuple::FieldType...>; -}; - /// Generates the named tuple that is equivalent to the struct T. /// This is the result you would expect from calling to_named_tuple(my_struct). /// All fields of the struct must be an rfl::Field. template -using named_tuple_t = - typename remove_ptrs_nt>::NamedTupleType; +using named_tuple_t = typename internal::remove_ptrs_nt< + internal::ptr_named_tuple_t>::NamedTupleType; } // namespace rfl diff --git a/tests/capnproto/test_map.cpp b/tests/capnproto/test_map.cpp index f1c321fb..a0e17f51 100644 --- a/tests/capnproto/test_map.cpp +++ b/tests/capnproto/test_map.cpp @@ -11,7 +11,7 @@ namespace test_map { struct Person { rfl::Rename<"firstName", std::string> first_name; rfl::Rename<"lastName", std::string> last_name = "Simpson"; - std::shared_ptr> children; + std::unique_ptr> children; }; TEST(capnproto, test_map) { From b9e267274fc308211f67e7493909c5080ec45cdc Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Fri, 3 Jan 2025 22:39:41 +0100 Subject: [PATCH 35/57] Use unique_ptr in the tests, where appropriate --- tests/capnproto/test_tuple.cpp | 2 +- tests/capnproto/test_variant.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/capnproto/test_tuple.cpp b/tests/capnproto/test_tuple.cpp index d1e8e4d4..0aa9af4d 100644 --- a/tests/capnproto/test_tuple.cpp +++ b/tests/capnproto/test_tuple.cpp @@ -12,7 +12,7 @@ namespace test_tuple { struct Person { rfl::Rename<"firstName", std::string> first_name; rfl::Rename<"lastName", std::string> last_name = "Simpson"; - std::shared_ptr> children = nullptr; + std::unique_ptr> children = nullptr; }; TEST(capnproto, test_tuple) { diff --git a/tests/capnproto/test_variant.cpp b/tests/capnproto/test_variant.cpp index f1cfe4d9..1f68ee2c 100644 --- a/tests/capnproto/test_variant.cpp +++ b/tests/capnproto/test_variant.cpp @@ -22,7 +22,7 @@ struct Square { }; struct Shapes { - std::variant> root; + std::variant> root; }; TEST(capnproto, test_variant) { From 6e14f2bd3334e74d48f46a9a688a9ce402eacd3a Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Fri, 3 Jan 2025 22:57:33 +0100 Subject: [PATCH 36/57] Added more tests for Cap'n Proto --- tests/capnproto/test_add_struct_name.cpp | 47 +++++++++++++++ tests/capnproto/test_array.cpp | 35 +++++++++++ tests/capnproto/test_box.cpp | 44 ++++++++++++++ tests/capnproto/test_custom_class1.cpp | 37 ++++++++++++ tests/capnproto/test_custom_class3.cpp | 65 ++++++++++++++++++++ tests/capnproto/test_custom_class4.cpp | 66 +++++++++++++++++++++ tests/capnproto/test_default_values.cpp | 27 +++++++++ tests/capnproto/test_deque.cpp | 28 +++++++++ tests/capnproto/test_enum.cpp | 25 ++++++++ tests/capnproto/test_field_variant.cpp | 36 +++++++++++ tests/capnproto/test_field_variant_std.cpp | 36 +++++++++++ tests/capnproto/test_flag_enum.cpp | 35 +++++++++++ tests/capnproto/test_flag_enum_with_int.cpp | 34 +++++++++++ tests/capnproto/test_flatten.cpp | 33 +++++++++++ tests/capnproto/test_flatten_anonymous.cpp | 34 +++++++++++ tests/capnproto/test_forward_list.cpp | 28 +++++++++ tests/capnproto/test_literal.cpp | 26 ++++++++ tests/capnproto/test_literal_map.cpp | 26 ++++++++ tests/capnproto/test_monster_example.cpp | 60 +++++++++++++++++++ tests/capnproto/test_readme_example.cpp | 47 +++++++++++++++ tests/capnproto/test_readme_example2.cpp | 22 +++++++ tests/capnproto/test_readme_example3.cpp | 32 ++++++++++ tests/capnproto/test_ref.cpp | 44 ++++++++++++++ tests/capnproto/test_rfl_tuple.cpp | 33 +++++++++++ tests/capnproto/test_rfl_variant.cpp | 33 +++++++++++ tests/capnproto/test_set.cpp | 25 ++++++++ tests/capnproto/test_shared_ptr.cpp | 29 +++++++++ tests/capnproto/test_size.cpp | 39 ++++++++++++ tests/capnproto/test_string_map.cpp | 25 ++++++++ tests/capnproto/test_tagged_union.cpp | 32 ++++++++++ tests/capnproto/test_timestamp.cpp | 32 ++++++++++ tests/capnproto/test_tutorial_example.cpp | 2 +- tests/capnproto/test_unique_ptr.cpp | 29 +++++++++ tests/capnproto/test_unique_ptr2.cpp | 43 ++++++++++++++ tests/capnproto/test_wstring.cpp | 21 +++++++ 35 files changed, 1209 insertions(+), 1 deletion(-) create mode 100644 tests/capnproto/test_add_struct_name.cpp create mode 100644 tests/capnproto/test_array.cpp create mode 100644 tests/capnproto/test_box.cpp create mode 100644 tests/capnproto/test_custom_class1.cpp create mode 100644 tests/capnproto/test_custom_class3.cpp create mode 100644 tests/capnproto/test_custom_class4.cpp create mode 100644 tests/capnproto/test_default_values.cpp create mode 100644 tests/capnproto/test_deque.cpp create mode 100644 tests/capnproto/test_enum.cpp create mode 100644 tests/capnproto/test_field_variant.cpp create mode 100644 tests/capnproto/test_field_variant_std.cpp create mode 100644 tests/capnproto/test_flag_enum.cpp create mode 100644 tests/capnproto/test_flag_enum_with_int.cpp create mode 100644 tests/capnproto/test_flatten.cpp create mode 100644 tests/capnproto/test_flatten_anonymous.cpp create mode 100644 tests/capnproto/test_forward_list.cpp create mode 100644 tests/capnproto/test_literal.cpp create mode 100644 tests/capnproto/test_literal_map.cpp create mode 100644 tests/capnproto/test_monster_example.cpp create mode 100644 tests/capnproto/test_readme_example.cpp create mode 100644 tests/capnproto/test_readme_example2.cpp create mode 100644 tests/capnproto/test_readme_example3.cpp create mode 100644 tests/capnproto/test_ref.cpp create mode 100644 tests/capnproto/test_rfl_tuple.cpp create mode 100644 tests/capnproto/test_rfl_variant.cpp create mode 100644 tests/capnproto/test_set.cpp create mode 100644 tests/capnproto/test_shared_ptr.cpp create mode 100644 tests/capnproto/test_size.cpp create mode 100644 tests/capnproto/test_string_map.cpp create mode 100644 tests/capnproto/test_tagged_union.cpp create mode 100644 tests/capnproto/test_timestamp.cpp create mode 100644 tests/capnproto/test_unique_ptr.cpp create mode 100644 tests/capnproto/test_unique_ptr2.cpp create mode 100644 tests/capnproto/test_wstring.cpp diff --git a/tests/capnproto/test_add_struct_name.cpp b/tests/capnproto/test_add_struct_name.cpp new file mode 100644 index 00000000..d3497903 --- /dev/null +++ b/tests/capnproto/test_add_struct_name.cpp @@ -0,0 +1,47 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_add_struct_name { + +using Age = rfl::Validator, rfl::Maximum<130>>; + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::string town = "Springfield"; + rfl::Timestamp<"%Y-%m-%d"> birthday; + Age age; + rfl::Email email; + std::vector children; +}; + +TEST(capnproto, test_add_struct_name) { + // TODO + /* const auto bart = Person{.first_name = "Bart", + .birthday = "1987-04-19", + .age = 10, + .email = "bart@simpson.com"}; + + const auto lisa = Person{.first_name = "Lisa", + .birthday = "1987-04-19", + .age = 8, + .email = "lisa@simpson.com"}; + + const auto maggie = Person{.first_name = "Maggie", + .birthday = "1987-04-19", + .age = 0, + .email = "maggie@simpson.com"}; + + const auto homer = + Person{.first_name = "Homer", + .birthday = "1987-04-19", + .age = 45, + .email = "homer@simpson.com", + .children = std::vector({bart, lisa, maggie})}; + + write_and_read>(homer);*/ +} +} // namespace test_add_struct_name diff --git a/tests/capnproto/test_array.cpp b/tests/capnproto/test_array.cpp new file mode 100644 index 00000000..bbaa618c --- /dev/null +++ b/tests/capnproto/test_array.cpp @@ -0,0 +1,35 @@ +#include +#include +#include +#include +#include + +// Make sure things still compile when +// rfl.hpp is included after rfl/capnproto.hpp. +#include + +#include "write_and_read.hpp" + +namespace test_array { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::unique_ptr> children = nullptr; +}; + +TEST(capnproto, test_array) { + auto bart = Person{.first_name = "Bart"}; + + auto lisa = Person{.first_name = "Lisa"}; + + auto maggie = Person{.first_name = "Maggie"}; + + const auto homer = Person{ + .first_name = "Homer", + .children = std::make_unique>(std::array{ + std::move(bart), std::move(lisa), std::move(maggie)})}; + + write_and_read(homer); +} +} // namespace test_array diff --git a/tests/capnproto/test_box.cpp b/tests/capnproto/test_box.cpp new file mode 100644 index 00000000..def342eb --- /dev/null +++ b/tests/capnproto/test_box.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_box { + +struct DecisionTree { + struct Leaf { + using Tag = rfl::Literal<"Leaf">; + double value; + }; + + struct Node { + using Tag = rfl::Literal<"Node">; + rfl::Rename<"criticalValue", double> critical_value; + rfl::Box lesser; + rfl::Box greater; + }; + + using LeafOrNode = rfl::TaggedUnion<"type", Leaf, Node>; + + rfl::Field<"leafOrNode", LeafOrNode> leaf_or_node; +}; + +TEST(capnproto, test_box) { + auto leaf1 = DecisionTree::Leaf{.value = 3.0}; + + auto leaf2 = DecisionTree::Leaf{.value = 5.0}; + + auto node = DecisionTree::Node{ + .critical_value = 10.0, + .lesser = rfl::make_box(DecisionTree{leaf1}), + .greater = rfl::make_box(DecisionTree{leaf2})}; + + const DecisionTree tree{.leaf_or_node = std::move(node)}; + + write_and_read(tree); + +} +} // namespace test_box diff --git a/tests/capnproto/test_custom_class1.cpp b/tests/capnproto/test_custom_class1.cpp new file mode 100644 index 00000000..7d3b2f83 --- /dev/null +++ b/tests/capnproto/test_custom_class1.cpp @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_custom_class1 { + +struct Person { + struct PersonImpl { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::vector children; + }; + + using ReflectionType = PersonImpl; + + Person(const PersonImpl& _impl) : impl(_impl) {} + + Person(const std::string& _first_name) + : impl(PersonImpl{.first_name = _first_name}) {} + + const ReflectionType& reflection() const { return impl; }; + + private: + PersonImpl impl; +}; + +TEST(capnproto, test_custom_class1) { + // TODO + /* const auto bart = Person("Bart"); + + write_and_read(bart);*/ +} +} // namespace test_custom_class1 diff --git a/tests/capnproto/test_custom_class3.cpp b/tests/capnproto/test_custom_class3.cpp new file mode 100644 index 00000000..e2f665d3 --- /dev/null +++ b/tests/capnproto/test_custom_class3.cpp @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_custom_class3 { + +struct Person { + Person(const std::string& _first_name, const std::string& _last_name, + const int _age) + : first_name_(_first_name), last_name_(_last_name), age_(_age) {} + + const auto& first_name() const { return first_name_; } + + const auto& last_name() const { return last_name_; } + + auto age() const { return age_; } + + private: + std::string first_name_; + std::string last_name_; + int age_; +}; + +struct PersonImpl { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name; + int age; + + static PersonImpl from_class(const Person& _p) noexcept { + return PersonImpl{.first_name = _p.first_name(), + .last_name = _p.last_name(), + .age = _p.age()}; + } + + Person to_class() const { return Person(first_name(), last_name(), age); } +}; +} // namespace test_custom_class3 + +namespace rfl { +namespace parsing { + +template +struct Parser + : public CustomParser {}; + +} // namespace parsing +} // namespace rfl + +namespace test_custom_class3 { + +TEST(capnproto, test_custom_class3) { + // TODO + /* const auto bart = Person("Bart", "Simpson", 10); + + write_and_read(bart);*/ +} + +} // namespace test_custom_class3 diff --git a/tests/capnproto/test_custom_class4.cpp b/tests/capnproto/test_custom_class4.cpp new file mode 100644 index 00000000..e6358d16 --- /dev/null +++ b/tests/capnproto/test_custom_class4.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_custom_class4 { + +struct Person { + Person(const std::string& _first_name, + const rfl::Box& _last_name, int _age) + : first_name_(_first_name), + last_name_(rfl::make_box(*_last_name)), + age_(_age) {} + + const auto& first_name() const { return first_name_; } + + const auto& last_name() const { return last_name_; } + + auto age() const { return age_; } + + private: + std::string first_name_; + rfl::Box last_name_; + int age_; +}; + +struct PersonImpl { + rfl::Field<"firstName", std::string> first_name; + rfl::Field<"lastName", rfl::Box> last_name; + rfl::Field<"age", int> age; + + static PersonImpl from_class(const Person& _p) noexcept { + return PersonImpl{.first_name = _p.first_name(), + .last_name = rfl::make_box(*_p.last_name()), + .age = _p.age()}; + } +}; + +} // namespace test_custom_class4 + +namespace rfl { +namespace parsing { + +template +struct Parser + : public CustomParser {}; + +} // namespace parsing +} // namespace rfl + +namespace test_custom_class4 { + +TEST(capnproto, test_custom_class4) { + // TODO + /* const auto bart = test_custom_class4::Person( + "Bart", rfl::make_box("Simpson"), 10); + + write_and_read(bart);*/ +} +} // namespace test_custom_class4 diff --git a/tests/capnproto/test_default_values.cpp b/tests/capnproto/test_default_values.cpp new file mode 100644 index 00000000..6d456271 --- /dev/null +++ b/tests/capnproto/test_default_values.cpp @@ -0,0 +1,27 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_default_values { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::vector children; +}; + +TEST(capnproto, test_default_values) { + const auto bart = Person{.first_name = "Bart"}; + const auto lisa = Person{.first_name = "Lisa"}; + const auto maggie = Person{.first_name = "Maggie"}; + const auto homer = + Person{.first_name = "Homer", + .children = std::vector({bart, lisa, maggie})}; + + write_and_read(homer); +} +} // namespace test_default_values diff --git a/tests/capnproto/test_deque.cpp b/tests/capnproto/test_deque.cpp new file mode 100644 index 00000000..250005fa --- /dev/null +++ b/tests/capnproto/test_deque.cpp @@ -0,0 +1,28 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_deque { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::unique_ptr> children; +}; + +TEST(capnproto, test_default_values) { + auto children = std::make_unique>(); + children->emplace_back(Person{.first_name = "Bart"}); + children->emplace_back(Person{.first_name = "Lisa"}); + children->emplace_back(Person{.first_name = "Maggie"}); + + const auto homer = + Person{.first_name = "Homer", .children = std::move(children)}; + + write_and_read(homer); + +} +} // namespace test_deque diff --git a/tests/capnproto/test_enum.cpp b/tests/capnproto/test_enum.cpp new file mode 100644 index 00000000..fd92b6bc --- /dev/null +++ b/tests/capnproto/test_enum.cpp @@ -0,0 +1,25 @@ +#include +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_enum { + +enum class Color { red, green, blue, yellow }; + +struct Circle { + float radius; + Color color; +}; + +TEST(capnproto, test_enum) { + const auto circle = Circle{.radius = 2.0, .color = Color::green}; + + write_and_read(circle); +} + +} // namespace test_enum diff --git a/tests/capnproto/test_field_variant.cpp b/tests/capnproto/test_field_variant.cpp new file mode 100644 index 00000000..1418e9af --- /dev/null +++ b/tests/capnproto/test_field_variant.cpp @@ -0,0 +1,36 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_field_variant { + +struct Circle { + double radius; +}; + +struct Rectangle { + double height; + double width; +}; + +struct Square { + double width; +}; + +struct Shapes { + rfl::Variant, rfl::Field<"rectangle", Rectangle>, + rfl::Field<"square", rfl::Box>> + root; +}; + +TEST(capnproto, test_field_variant_std) { + const auto r = + Shapes{rfl::make_field<"rectangle">(Rectangle{.height = 10, .width = 5})}; + + write_and_read(r); +} +} // namespace test_field_variant diff --git a/tests/capnproto/test_field_variant_std.cpp b/tests/capnproto/test_field_variant_std.cpp new file mode 100644 index 00000000..2262fe2d --- /dev/null +++ b/tests/capnproto/test_field_variant_std.cpp @@ -0,0 +1,36 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_field_variant_std { + +struct Circle { + double radius; +}; + +struct Rectangle { + double height; + double width; +}; + +struct Square { + double width; +}; + +struct Shapes { + std::variant, rfl::Field<"rectangle", Rectangle>, + rfl::Field<"square", rfl::Box>> + root; +}; + +TEST(capnproto, test_field_variant_std) { + const auto r = + Shapes{rfl::make_field<"rectangle">(Rectangle{.height = 10, .width = 5})}; + + write_and_read(r); +} +} // namespace test_field_variant_std diff --git a/tests/capnproto/test_flag_enum.cpp b/tests/capnproto/test_flag_enum.cpp new file mode 100644 index 00000000..17687653 --- /dev/null +++ b/tests/capnproto/test_flag_enum.cpp @@ -0,0 +1,35 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_flag_enum { + +enum class Color { + red = 256, + green = 512, + blue = 1024, + yellow = 2048, + orange = (256 | 2048) // red + yellow = orange +}; + +inline Color operator|(Color c1, Color c2) { + return static_cast(static_cast(c1) | static_cast(c2)); +} + +struct Circle { + float radius; + Color color; +}; + +TEST(capnproto, test_flag_enum) { + const auto circle = + Circle{.radius = 2.0, .color = Color::blue | Color::orange}; + + write_and_read(circle); +} + +} // namespace test_flag_enum diff --git a/tests/capnproto/test_flag_enum_with_int.cpp b/tests/capnproto/test_flag_enum_with_int.cpp new file mode 100644 index 00000000..08de18e2 --- /dev/null +++ b/tests/capnproto/test_flag_enum_with_int.cpp @@ -0,0 +1,34 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_flag_enum_with_int { + +enum class Color { + red = 256, + green = 512, + blue = 1024, + yellow = 2048, + orange = (256 | 2048) // red + yellow = orange +}; + +inline Color operator|(Color c1, Color c2) { + return static_cast(static_cast(c1) | static_cast(c2)); +} + +struct Circle { + float radius; + Color color; +}; + +TEST(capnproto, test_flag_enum_with_int) { + const auto circle = Circle{.radius = 2.0, .color = static_cast(10000)}; + + write_and_read(circle); +} + +} // namespace test_flag_enum_with_int diff --git a/tests/capnproto/test_flatten.cpp b/tests/capnproto/test_flatten.cpp new file mode 100644 index 00000000..e461ac35 --- /dev/null +++ b/tests/capnproto/test_flatten.cpp @@ -0,0 +1,33 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_flatten { + +struct Person { + rfl::Field<"firstName", std::string> first_name; + rfl::Field<"lastName", rfl::Box> last_name; + rfl::Field<"age", int> age; +}; + +struct Employee { + rfl::Flatten person; + rfl::Field<"employer", rfl::Box> employer; + rfl::Field<"salary", float> salary; +}; + +TEST(capnproto, test_flatten) { + const auto employee = Employee{ + .person = Person{.first_name = "Homer", + .last_name = rfl::make_box("Simpson"), + .age = 45}, + .employer = rfl::make_box("Mr. Burns"), + .salary = 60000.0}; + + write_and_read(employee); +} +} // namespace test_flatten diff --git a/tests/capnproto/test_flatten_anonymous.cpp b/tests/capnproto/test_flatten_anonymous.cpp new file mode 100644 index 00000000..03749f15 --- /dev/null +++ b/tests/capnproto/test_flatten_anonymous.cpp @@ -0,0 +1,34 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_flatten_anonymous { + +struct Person { + std::string first_name; + rfl::Box last_name; + int age; +}; + +struct Employee { + rfl::Flatten person; + rfl::Box employer; + float salary; +}; + +TEST(capnproto, test_flatten_anonymous) { + const auto employee = Employee{ + .person = Person{.first_name = "Homer", + .last_name = rfl::make_box("Simpson"), + .age = 45}, + .employer = rfl::make_box("Mr. Burns"), + .salary = 60000.0}; + + write_and_read(employee); +} + +} // namespace test_flatten_anonymous diff --git a/tests/capnproto/test_forward_list.cpp b/tests/capnproto/test_forward_list.cpp new file mode 100644 index 00000000..256c7437 --- /dev/null +++ b/tests/capnproto/test_forward_list.cpp @@ -0,0 +1,28 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_forward_list { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::unique_ptr> children; +}; + +TEST(capnproto, test_forward_list) { + auto children = std::make_unique>(); + children->emplace_front(Person{.first_name = "Maggie"}); + children->emplace_front(Person{.first_name = "Lisa"}); + children->emplace_front(Person{.first_name = "Bart"}); + + const auto homer = + Person{.first_name = "Homer", .children = std::move(children)}; + + write_and_read(homer); + +} +} // namespace test_forward_list diff --git a/tests/capnproto/test_literal.cpp b/tests/capnproto/test_literal.cpp new file mode 100644 index 00000000..441feec7 --- /dev/null +++ b/tests/capnproto/test_literal.cpp @@ -0,0 +1,26 @@ +#include +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_literal { + +using FirstName = rfl::Literal<"Homer", "Marge", "Bart", "Lisa", "Maggie">; +using LastName = rfl::Literal<"Simpson">; + +struct Person { + rfl::Rename<"firstName", FirstName> first_name; + rfl::Rename<"lastName", LastName> last_name; + std::vector children; +}; + +TEST(capnproto, test_literal) { + const auto bart = Person{.first_name = FirstName::make<"Bart">()}; + + write_and_read(bart); +} +} // namespace test_literal diff --git a/tests/capnproto/test_literal_map.cpp b/tests/capnproto/test_literal_map.cpp new file mode 100644 index 00000000..e3149d5d --- /dev/null +++ b/tests/capnproto/test_literal_map.cpp @@ -0,0 +1,26 @@ +#include +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_literal_map { + +using FieldName = rfl::Literal<"firstName", "lastName">; + +struct RootStruct { + std::map> root; +}; + +TEST(capnproto, test_literal_map) { + RootStruct homer; + homer.root.insert(std::make_pair(FieldName::make<"firstName">(), + std::make_unique("Homer"))); + homer.root.insert(std::make_pair(FieldName::make<"lastName">(), + std::make_unique("Simpson"))); + write_and_read(homer); +} +} // namespace test_literal_map diff --git a/tests/capnproto/test_monster_example.cpp b/tests/capnproto/test_monster_example.cpp new file mode 100644 index 00000000..d5a52e62 --- /dev/null +++ b/tests/capnproto/test_monster_example.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_monster_example { + +using Color = rfl::Literal<"Red", "Green", "Blue">; + +struct Weapon { + std::string name; + short damage; +}; + +using Equipment = + rfl::Variant, rfl::Field<"None", int>>; + +struct Vec3 { + float x; + float y; + float z; +}; + +struct Monster { + Vec3 pos; + short mana = 150; + short hp = 100; + std::string name; + bool friendly = false; + std::vector inventory; + Color color = Color::make<"Blue">(); + std::vector weapons; + Equipment equipped; + std::vector path; +}; + +TEST(capnproto, test_monster_example) { + const auto sword = Weapon{.name = "Sword", .damage = 3}; + const auto axe = Weapon{.name = "Axe", .damage = 5}; + + const auto weapons = std::vector({sword, axe}); + + const auto position = Vec3{1.0f, 2.0f, 3.0f}; + + const auto inventory = std::vector({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + + const auto orc = Monster{.pos = position, + .mana = 150, + .hp = 80, + .name = "MyMonster", + .inventory = inventory, + .color = Color::make<"Red">(), + .weapons = weapons, + .equipped = rfl::make_field<"weapon">(axe)}; + + write_and_read(orc); +} +} // namespace test_monster_example diff --git a/tests/capnproto/test_readme_example.cpp b/tests/capnproto/test_readme_example.cpp new file mode 100644 index 00000000..09265833 --- /dev/null +++ b/tests/capnproto/test_readme_example.cpp @@ -0,0 +1,47 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_readme_example { + +using Age = rfl::Validator, rfl::Maximum<130>>; + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::string town = "Springfield"; + rfl::Timestamp<"%Y-%m-%d"> birthday; + Age age; + rfl::Email email; + std::vector child; +}; + +TEST(capnproto, test_readme_example) { + const auto bart = Person{.first_name = "Bart", + .birthday = "1987-04-19", + .age = 10, + .email = "bart@simpson.com"}; + + const auto lisa = Person{.first_name = "Lisa", + .birthday = "1987-04-19", + .age = 8, + .email = "lisa@simpson.com"}; + + const auto maggie = Person{.first_name = "Maggie", + .birthday = "1987-04-19", + .age = 0, + .email = "maggie@simpson.com"}; + + const auto homer = Person{.first_name = "Homer", + .birthday = "1987-04-19", + .age = 45, + .email = "homer@simpson.com", + .child = std::vector({bart, lisa, maggie})}; + + // TODO + // write_and_read(homer); +} +} // namespace test_readme_example diff --git a/tests/capnproto/test_readme_example2.cpp b/tests/capnproto/test_readme_example2.cpp new file mode 100644 index 00000000..2f6f9b44 --- /dev/null +++ b/tests/capnproto/test_readme_example2.cpp @@ -0,0 +1,22 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_readme_example2 { + +struct Person { + std::string first_name; + std::string last_name; + int age; +}; + +TEST(capnproto, test_readme_example2) { + const auto homer = + Person{.first_name = "Homer", .last_name = "Simpson", .age = 45}; + + write_and_read(homer); +} +} // namespace test_readme_example2 diff --git a/tests/capnproto/test_readme_example3.cpp b/tests/capnproto/test_readme_example3.cpp new file mode 100644 index 00000000..f5294be0 --- /dev/null +++ b/tests/capnproto/test_readme_example3.cpp @@ -0,0 +1,32 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_readme_example3 { + +struct Person { + std::string first_name; + std::string last_name; + rfl::Timestamp<"%Y-%m-%d"> birthday; + std::vector children; +}; + +TEST(capnproto, test_readme_example3) { + /* const auto bart = Person{.first_name = "Bart", .birthday = "1987-04-19"}; + + const auto lisa = Person{.first_name = "Lisa", .birthday = "1987-04-19"}; + + const auto maggie = Person{.first_name = "Maggie", .birthday = + "1987-04-19"}; + + const auto homer = + Person{.first_name = "Homer", + .birthday = "1987-04-19", + .children = std::vector({bart, lisa, maggie})}; + + write_and_read(homer);*/ +} +} // namespace test_readme_example3 diff --git a/tests/capnproto/test_ref.cpp b/tests/capnproto/test_ref.cpp new file mode 100644 index 00000000..c9408b53 --- /dev/null +++ b/tests/capnproto/test_ref.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_ref { + +struct DecisionTree { + struct Leaf { + using Tag = rfl::Literal<"Leaf">; + double value; + }; + + struct Node { + using Tag = rfl::Literal<"Node">; + rfl::Rename<"criticalValue", double> critical_value; + rfl::Ref lesser; + rfl::Ref greater; + }; + + using LeafOrNode = rfl::TaggedUnion<"type", Leaf, Node>; + + rfl::Field<"leafOrNode", LeafOrNode> leaf_or_node; +}; + +TEST(capnproto, test_ref) { + const auto leaf1 = DecisionTree::Leaf{.value = 3.0}; + + const auto leaf2 = DecisionTree::Leaf{.value = 5.0}; + + auto node = DecisionTree::Node{ + .critical_value = 10.0, + .lesser = rfl::make_ref(DecisionTree{leaf1}), + .greater = rfl::make_ref(DecisionTree{leaf2})}; + + const DecisionTree tree{.leaf_or_node = std::move(node)}; + + write_and_read(tree); + +} +} // namespace test_ref diff --git a/tests/capnproto/test_rfl_tuple.cpp b/tests/capnproto/test_rfl_tuple.cpp new file mode 100644 index 00000000..bcb7a162 --- /dev/null +++ b/tests/capnproto/test_rfl_tuple.cpp @@ -0,0 +1,33 @@ + +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_rfl_tuple { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::unique_ptr> children = nullptr; +}; + +TEST(capnproto, test_rfl_tuple) { + auto bart = Person{.first_name = "Bart"}; + + auto lisa = Person{.first_name = "Lisa"}; + + auto maggie = Person{.first_name = "Maggie"}; + + const auto homer = + Person{.first_name = "Homer", + .children = std::make_unique>( + rfl::Tuple{ + std::move(bart), std::move(lisa), std::move(maggie)})}; + + write_and_read(homer); +} +} // namespace test_rfl_tuple diff --git a/tests/capnproto/test_rfl_variant.cpp b/tests/capnproto/test_rfl_variant.cpp new file mode 100644 index 00000000..45da5b74 --- /dev/null +++ b/tests/capnproto/test_rfl_variant.cpp @@ -0,0 +1,33 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_variant { + +struct Circle { + double radius; +}; + +struct Rectangle { + double height; + double width; +}; + +struct Square { + double width; +}; + +struct Shapes { + rfl::Variant> root; +}; + +TEST(capnproto, test_rfl_variant) { + const auto r = Shapes{Rectangle{.height = 10, .width = 5}}; + + write_and_read(r); +} +} // namespace test_variant diff --git a/tests/capnproto/test_set.cpp b/tests/capnproto/test_set.cpp new file mode 100644 index 00000000..d2f2732d --- /dev/null +++ b/tests/capnproto/test_set.cpp @@ -0,0 +1,25 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_set { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::unique_ptr> children; +}; + +TEST(capnproto, test_set) { + auto children = std::make_unique>( + std::set({"Bart", "Lisa", "Maggie"})); + + const auto homer = + Person{.first_name = "Homer", .children = std::move(children)}; + + write_and_read(homer); +} +} // namespace test_set diff --git a/tests/capnproto/test_shared_ptr.cpp b/tests/capnproto/test_shared_ptr.cpp new file mode 100644 index 00000000..62857583 --- /dev/null +++ b/tests/capnproto/test_shared_ptr.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_shared_ptr { + +struct Person { + std::string first_name; + std::string last_name = "Simpson"; + std::shared_ptr> children; +}; + +TEST(capnproto, test_shared_ptr) { + auto children = std::make_shared>(); + children->emplace_back(Person{.first_name = "Bart"}); + children->emplace_back(Person{.first_name = "Lisa"}); + children->emplace_back(Person{.first_name = "Maggie"}); + + const auto homer = + Person{.first_name = "Homer", .children = std::move(children)}; + + write_and_read(homer); +} +} // namespace test_shared_ptr diff --git a/tests/capnproto/test_size.cpp b/tests/capnproto/test_size.cpp new file mode 100644 index 00000000..139a4515 --- /dev/null +++ b/tests/capnproto/test_size.cpp @@ -0,0 +1,39 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_size { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name; + rfl::Timestamp<"%Y-%m-%d"> birthday; + rfl::Validator, + rfl::Size, rfl::EqualTo<3>>>> + children; +}; + +TEST(capnproto, test_size) { + // TODO + /* const auto bart = Person{ + .first_name = "Bart", .last_name = "Simpson", .birthday = "1987-04-19"}; + + const auto lisa = Person{ + .first_name = "Lisa", .last_name = "Simpson", .birthday = "1987-04-19"}; + + const auto maggie = Person{ + .first_name = "Maggie", .last_name = "Simpson", .birthday = + "1987-04-19"}; + + const auto homer = + Person{.first_name = "Homer", + .last_name = "Simpson", + .birthday = "1987-04-19", + .children = std::vector({bart, lisa, maggie})}; + + write_and_read(homer);*/ +} +} // namespace test_size diff --git a/tests/capnproto/test_string_map.cpp b/tests/capnproto/test_string_map.cpp new file mode 100644 index 00000000..e1580d59 --- /dev/null +++ b/tests/capnproto/test_string_map.cpp @@ -0,0 +1,25 @@ +#include +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_string_map { + +struct RootStruct { + std::map> root; +}; + +TEST(capnproto, test_string_map) { + RootStruct homer; + homer.root.insert( + std::make_pair("firstName", std::make_unique("Homer"))); + homer.root.insert( + std::make_pair("lastName", std::make_unique("Simpson"))); + + write_and_read(homer); +} +} // namespace test_string_map diff --git a/tests/capnproto/test_tagged_union.cpp b/tests/capnproto/test_tagged_union.cpp new file mode 100644 index 00000000..112cb448 --- /dev/null +++ b/tests/capnproto/test_tagged_union.cpp @@ -0,0 +1,32 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_tagged_union { + +struct Circle { + double radius; +}; + +struct Rectangle { + double height; + double width; +}; + +struct Square { + double width; +}; + +struct Shapes { + rfl::TaggedUnion<"shape", Rectangle, Circle, Square> root; +}; + +TEST(capnproto, test_tagged_union) { + const auto r = Shapes{Rectangle{.height = 10, .width = 5}}; + write_and_read(r); +} +} // namespace test_tagged_union diff --git a/tests/capnproto/test_timestamp.cpp b/tests/capnproto/test_timestamp.cpp new file mode 100644 index 00000000..453525ff --- /dev/null +++ b/tests/capnproto/test_timestamp.cpp @@ -0,0 +1,32 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_timestamp { + +using TS = rfl::Timestamp<"%Y-%m-%d">; + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + TS birthday; +}; + +TEST(capnproto, test_timestamp) { + // TODO + /* const auto result = TS::from_string("nonsense"); + + if (result) { + std::cout << "Failed: Expected an error, but got none." << std::endl; + return; + } + + const auto bart = Person{.first_name = "Bart", .birthday = "1987-04-19"}; + + write_and_read(bart);*/ +} +} // namespace test_timestamp diff --git a/tests/capnproto/test_tutorial_example.cpp b/tests/capnproto/test_tutorial_example.cpp index b5b699bc..5271148f 100644 --- a/tests/capnproto/test_tutorial_example.cpp +++ b/tests/capnproto/test_tutorial_example.cpp @@ -8,7 +8,7 @@ // #include "write_and_read.hpp" -/// The basic example from the Avro C tutorial. +/// The basic example from the capnproto C tutorial. namespace test_tutorial_example { const std::string ADDRESS_BOOK_SCHEMA = R"( diff --git a/tests/capnproto/test_unique_ptr.cpp b/tests/capnproto/test_unique_ptr.cpp new file mode 100644 index 00000000..ca5964b8 --- /dev/null +++ b/tests/capnproto/test_unique_ptr.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_unique_ptr { + +struct Person { + std::string first_name; + std::string last_name = "Simpson"; + std::unique_ptr> children; +}; + +TEST(capnproto, test_unique_ptr) { + auto children = std::make_unique>(); + children->emplace_back(Person{.first_name = "Bart"}); + children->emplace_back(Person{.first_name = "Lisa"}); + children->emplace_back(Person{.first_name = "Maggie"}); + + const auto homer = + Person{.first_name = "Homer", .children = std::move(children)}; + + write_and_read(homer); +} +} // namespace test_unique_ptr diff --git a/tests/capnproto/test_unique_ptr2.cpp b/tests/capnproto/test_unique_ptr2.cpp new file mode 100644 index 00000000..b3459221 --- /dev/null +++ b/tests/capnproto/test_unique_ptr2.cpp @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_unique_ptr2 { + +struct DecisionTree { + struct Leaf { + using Tag = rfl::Literal<"Leaf">; + double value; + }; + + struct Node { + using Tag = rfl::Literal<"Node">; + rfl::Rename<"criticalValue", double> critical_value; + std::unique_ptr lesser; + std::unique_ptr greater; + }; + + using LeafOrNode = rfl::TaggedUnion<"type", Leaf, Node>; + + rfl::Field<"leafOrNode", LeafOrNode> leaf_or_node; +}; + +TEST(capnproto, test_unique_ptr2) { + auto leaf1 = DecisionTree::Leaf{.value = 3.0}; + + auto leaf2 = DecisionTree::Leaf{.value = 5.0}; + + auto node = DecisionTree::Node{ + .critical_value = 10.0, + .lesser = std::make_unique(DecisionTree{leaf1}), + .greater = std::make_unique(DecisionTree{leaf2})}; + + const DecisionTree tree{.leaf_or_node = std::move(node)}; + + write_and_read(tree); +} +} // namespace test_unique_ptr2 diff --git a/tests/capnproto/test_wstring.cpp b/tests/capnproto/test_wstring.cpp new file mode 100644 index 00000000..a71e1571 --- /dev/null +++ b/tests/capnproto/test_wstring.cpp @@ -0,0 +1,21 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +struct TestStruct { + std::string theNormalString; + std::wstring theWiderString; +}; + +namespace test_wstring { +TEST(capnproto, test_wstring) { + const auto test = TestStruct{.theNormalString = "The normal string", + .theWiderString = L"The wider string"}; + + write_and_read(test); +} +} // namespace test_wstring From bebbd8075ef0a3087d7d7374ee8f4f904d568d32 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sat, 4 Jan 2025 10:31:52 +0100 Subject: [PATCH 37/57] Removed noexcept --- include/rfl/avro/read.hpp | 4 ++-- include/rfl/avro/to_schema.hpp | 2 +- include/rfl/avro/write.hpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/rfl/avro/read.hpp b/include/rfl/avro/read.hpp index c3964e75..7b343664 100644 --- a/include/rfl/avro/read.hpp +++ b/include/rfl/avro/read.hpp @@ -48,14 +48,14 @@ Result> read( /// Parses an object from AVRO using reflection. template -auto read(const char* _bytes, const size_t _size) noexcept { +auto read(const char* _bytes, const size_t _size) { const auto schema = to_schema, Ps...>(); return read(_bytes, _size, schema); } /// Parses an object from AVRO using reflection. template -auto read(const std::vector& _bytes, const Schema& _schema) { +auto read(const std::vector& _bytes, const Schema& _schema) noexcept { return read(_bytes.data(), _bytes.size(), _schema); } diff --git a/include/rfl/avro/to_schema.hpp b/include/rfl/avro/to_schema.hpp index 1eb04dfb..ec5417fc 100644 --- a/include/rfl/avro/to_schema.hpp +++ b/include/rfl/avro/to_schema.hpp @@ -24,7 +24,7 @@ std::string to_json_representation( /// Returns the Avro schema for a class. template -Schema to_schema() noexcept { +Schema to_schema() { const auto internal_schema = parsing::schema::make>(); const auto json_str = to_json_representation(internal_schema); diff --git a/include/rfl/avro/write.hpp b/include/rfl/avro/write.hpp index 7440238b..2ae7dfab 100644 --- a/include/rfl/avro/write.hpp +++ b/include/rfl/avro/write.hpp @@ -44,7 +44,7 @@ std::vector write(const auto& _obj, const auto& _schema) noexcept { /// Returns AVRO bytes. template -std::vector write(const auto& _obj) noexcept { +std::vector write(const auto& _obj) { using T = std::remove_cvref_t; const auto schema = to_schema(); return write(_obj, schema); @@ -52,7 +52,7 @@ std::vector write(const auto& _obj) noexcept { /// Writes a AVRO into an ostream. template -std::ostream& write(const auto& _obj, std::ostream& _stream) noexcept { +std::ostream& write(const auto& _obj, std::ostream& _stream) { auto buffer = write(_obj); _stream.write(buffer.data(), buffer.size()); return _stream; From 9ceb5b5d4fb0a2fdeeb5c9f6d8c00662f2cf2bd0 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sat, 4 Jan 2025 10:32:11 +0100 Subject: [PATCH 38/57] Minor bugfixes --- include/rfl/capnproto/get_root_name.hpp | 22 ++++++++++++ include/rfl/capnproto/is_named_type.hpp | 18 ++++++++++ include/rfl/capnproto/read.hpp | 14 ++++---- include/rfl/capnproto/write.hpp | 11 +++--- src/rfl/capnproto/to_schema.cpp | 22 ++++++++++-- tests/capnproto/test_add_struct_name.cpp | 43 ++++++++++++------------ tests/capnproto/test_custom_class1.cpp | 5 ++- tests/capnproto/test_custom_class3.cpp | 5 ++- tests/capnproto/test_custom_class4.cpp | 7 ++-- tests/capnproto/test_readme_example.cpp | 3 +- tests/capnproto/test_readme_example3.cpp | 17 +++++----- tests/capnproto/test_timestamp.cpp | 15 ++++----- 12 files changed, 116 insertions(+), 66 deletions(-) create mode 100644 include/rfl/capnproto/get_root_name.hpp create mode 100644 include/rfl/capnproto/is_named_type.hpp diff --git a/include/rfl/capnproto/get_root_name.hpp b/include/rfl/capnproto/get_root_name.hpp new file mode 100644 index 00000000..494abf1c --- /dev/null +++ b/include/rfl/capnproto/get_root_name.hpp @@ -0,0 +1,22 @@ +#ifndef RFL_CAPNPROTO_GET_ROOT_NAME_HPP_ +#define RFL_CAPNPROTO_GET_ROOT_NAME_HPP_ + +#include + +#include "../internal/has_reflection_type_v.hpp" +#include "../parsing/make_type_name.hpp" + +namespace rfl::capnproto { + +template +inline std::string get_root_name() { + if constexpr (internal::has_reflection_type_v) { + return get_root_name>(); + } else { + return internal::strings::to_pascal_case(parsing::make_type_name()); + } +} + +} // namespace rfl::capnproto + +#endif diff --git a/include/rfl/capnproto/is_named_type.hpp b/include/rfl/capnproto/is_named_type.hpp new file mode 100644 index 00000000..0f7971c7 --- /dev/null +++ b/include/rfl/capnproto/is_named_type.hpp @@ -0,0 +1,18 @@ +#ifndef RFL_CAPNPROTO_IS_NAMED_TYPE_HPP_ +#define RFL_CAPNPROTO_IS_NAMED_TYPE_HPP_ + +namespace rfl::capnproto { + +inline bool is_named_type(const parsing::schema::Type& _type) { + return _type.variant_.visit([&](const auto& _v) -> bool { + using T = std::remove_cvref_t; + return std::is_same() || + std::is_same() || + std::is_same(); + }); +} + +} // namespace rfl::capnproto + +#endif + diff --git a/include/rfl/capnproto/read.hpp b/include/rfl/capnproto/read.hpp index 03163da3..48ac4404 100644 --- a/include/rfl/capnproto/read.hpp +++ b/include/rfl/capnproto/read.hpp @@ -14,10 +14,10 @@ #include "../SnakeCaseToCamelCase.hpp" #include "../internal/strings/strings.hpp" #include "../internal/wrap_in_rfl_array_t.hpp" -#include "../parsing/make_type_name.hpp" #include "Parser.hpp" #include "Reader.hpp" #include "Schema.hpp" +#include "get_root_name.hpp" #include "to_schema.hpp" namespace rfl::capnproto { @@ -34,14 +34,14 @@ auto read(const InputVarType& _obj) { /// Parses an object from CAPNPROTO using reflection. template -Result> read( - const char* _bytes, const size_t _size, const Schema& _schema) noexcept { +Result> read(const char* _bytes, + const size_t _size, + const Schema& _schema) { const auto array_ptr = kj::ArrayPtr( internal::ptr_cast(_bytes), _size); auto input_stream = kj::ArrayInputStream(array_ptr); auto message_reader = capnp::PackedMessageReader(input_stream); - const auto root_name = internal::strings::to_pascal_case( - parsing::make_type_name>()); + const auto root_name = get_root_name>(); const auto root_schema = _schema.value().getNested(root_name.c_str()); const auto input_var = InputVarType{ message_reader.getRoot(root_schema.asStruct())}; @@ -50,14 +50,14 @@ Result> read( /// Parses an object from CAPNPROTO using reflection. template -auto read(const char* _bytes, const size_t _size) noexcept { +auto read(const char* _bytes, const size_t _size) { const auto schema = to_schema, Ps...>(); return read(_bytes, _size, schema); } /// Parses an object from CAPNPROTO using reflection. template -auto read(const std::vector& _bytes, const Schema& _schema) { +auto read(const std::vector& _bytes, const Schema& _schema) noexcept { return read(_bytes.data(), _bytes.size(), _schema); } diff --git a/include/rfl/capnproto/write.hpp b/include/rfl/capnproto/write.hpp index 0f43df31..cad0eb48 100644 --- a/include/rfl/capnproto/write.hpp +++ b/include/rfl/capnproto/write.hpp @@ -20,24 +20,23 @@ #include "../internal/ptr_cast.hpp" #include "../internal/strings/strings.hpp" #include "../parsing/Parent.hpp" -#include "../parsing/make_type_name.hpp" #include "Parser.hpp" #include "Schema.hpp" #include "Writer.hpp" +#include "get_root_name.hpp" #include "to_schema.hpp" namespace rfl::capnproto { /// Returns CAPNPROTO bytes. template -std::vector write(const auto& _obj, const auto& _schema) noexcept { +std::vector write(const auto& _obj, const auto& _schema) { using T = std::remove_cvref_t; using U = typename std::remove_cvref_t::Type; using ParentType = parsing::Parent; static_assert(std::is_same(), "The schema must be compatible with the type to write."); - const auto root_name = internal::strings::to_pascal_case( - parsing::make_type_name>()); + const auto root_name = get_root_name(); const auto root_schema = _schema.value().getNested(root_name.c_str()); capnp::MallocMessageBuilder message_builder; auto root = @@ -54,7 +53,7 @@ std::vector write(const auto& _obj, const auto& _schema) noexcept { /// Returns CAPNPROTO bytes. template -std::vector write(const auto& _obj) noexcept { +std::vector write(const auto& _obj) { using T = std::remove_cvref_t; const auto schema = to_schema(); return write(_obj, schema); @@ -62,7 +61,7 @@ std::vector write(const auto& _obj) noexcept { /// Writes a CAPNPROTO into an ostream. template -std::ostream& write(const auto& _obj, std::ostream& _stream) noexcept { +std::ostream& write(const auto& _obj, std::ostream& _stream) { auto buffer = write(_obj); _stream.write(buffer.data(), buffer.size()); return _stream; diff --git a/src/rfl/capnproto/to_schema.cpp b/src/rfl/capnproto/to_schema.cpp index 0fbb67a5..63f13550 100644 --- a/src/rfl/capnproto/to_schema.cpp +++ b/src/rfl/capnproto/to_schema.cpp @@ -31,6 +31,7 @@ SOFTWARE. #include #include +#include "rfl/capnproto/is_named_type.hpp" #include "rfl/capnproto/schema/CapnProtoTypes.hpp" #include "rfl/capnproto/schema/Type.hpp" #include "rfl/internal/strings/strings.hpp" @@ -110,6 +111,20 @@ schema::Type optional_to_capnproto_schema_type( } } +schema::Type reference_to_capnproto_schema_type( + const parsing::schema::Type::Reference& _reference, + const std::map& _definitions, + const Parent _parent, schema::CapnProtoTypes* _cnp_types) { + const auto it = _definitions.find(_reference.name_); + if (it == _definitions.end() || is_named_type(it->second)) { + return schema::Type{ + .value = schema::Type::Reference{.type_name = _reference.name_}}; + } else { + return type_to_capnproto_schema_type(it->second, _definitions, _parent, + _cnp_types); + } +} + schema::Type type_to_capnproto_schema_type( const parsing::schema::Type& _type, const std::map& _definitions, @@ -177,8 +192,8 @@ schema::Type type_to_capnproto_schema_type( _cnp_types); } else if constexpr (std::is_same()) { - return schema::Type{.value = - schema::Type::Reference{.type_name = _t.name_}}; + return reference_to_capnproto_schema_type(_t, _definitions, _parent, + _cnp_types); } else if constexpr (std::is_same()) { _cnp_types->has_maps_ = true; @@ -216,6 +231,9 @@ std::string to_string_representation( const parsing::schema::Definition& _internal_schema) { schema::CapnProtoTypes cnp_types; for (const auto& [name, def] : _internal_schema.definitions_) { + if (!is_named_type(def)) { + continue; + } cnp_types.structs_[name] = type_to_capnproto_schema_type( def, _internal_schema.definitions_, Parent::is_top_level, &cnp_types); } diff --git a/tests/capnproto/test_add_struct_name.cpp b/tests/capnproto/test_add_struct_name.cpp index d3497903..3fc04e97 100644 --- a/tests/capnproto/test_add_struct_name.cpp +++ b/tests/capnproto/test_add_struct_name.cpp @@ -19,29 +19,28 @@ struct Person { }; TEST(capnproto, test_add_struct_name) { - // TODO - /* const auto bart = Person{.first_name = "Bart", - .birthday = "1987-04-19", - .age = 10, - .email = "bart@simpson.com"}; + const auto bart = Person{.first_name = "Bart", + .birthday = "1987-04-19", + .age = 10, + .email = "bart@simpson.com"}; + + const auto lisa = Person{.first_name = "Lisa", + .birthday = "1987-04-19", + .age = 8, + .email = "lisa@simpson.com"}; - const auto lisa = Person{.first_name = "Lisa", + const auto maggie = Person{.first_name = "Maggie", .birthday = "1987-04-19", - .age = 8, - .email = "lisa@simpson.com"}; - - const auto maggie = Person{.first_name = "Maggie", - .birthday = "1987-04-19", - .age = 0, - .email = "maggie@simpson.com"}; - - const auto homer = - Person{.first_name = "Homer", - .birthday = "1987-04-19", - .age = 45, - .email = "homer@simpson.com", - .children = std::vector({bart, lisa, maggie})}; - - write_and_read>(homer);*/ + .age = 0, + .email = "maggie@simpson.com"}; + + const auto homer = + Person{.first_name = "Homer", + .birthday = "1987-04-19", + .age = 45, + .email = "homer@simpson.com", + .children = std::vector({bart, lisa, maggie})}; + + write_and_read>(homer); } } // namespace test_add_struct_name diff --git a/tests/capnproto/test_custom_class1.cpp b/tests/capnproto/test_custom_class1.cpp index 7d3b2f83..e57e9d85 100644 --- a/tests/capnproto/test_custom_class1.cpp +++ b/tests/capnproto/test_custom_class1.cpp @@ -29,9 +29,8 @@ struct Person { }; TEST(capnproto, test_custom_class1) { - // TODO - /* const auto bart = Person("Bart"); + const auto bart = Person("Bart"); - write_and_read(bart);*/ + write_and_read(bart); } } // namespace test_custom_class1 diff --git a/tests/capnproto/test_custom_class3.cpp b/tests/capnproto/test_custom_class3.cpp index e2f665d3..0e90b9ba 100644 --- a/tests/capnproto/test_custom_class3.cpp +++ b/tests/capnproto/test_custom_class3.cpp @@ -56,10 +56,9 @@ struct Parser("Simpson"), 10); + const auto bart = test_custom_class4::Person( + "Bart", rfl::make_box("Simpson"), 10); - write_and_read(bart);*/ + write_and_read(bart); } } // namespace test_custom_class4 diff --git a/tests/capnproto/test_readme_example.cpp b/tests/capnproto/test_readme_example.cpp index 09265833..3e911aa6 100644 --- a/tests/capnproto/test_readme_example.cpp +++ b/tests/capnproto/test_readme_example.cpp @@ -41,7 +41,6 @@ TEST(capnproto, test_readme_example) { .email = "homer@simpson.com", .child = std::vector({bart, lisa, maggie})}; - // TODO - // write_and_read(homer); + write_and_read(homer); } } // namespace test_readme_example diff --git a/tests/capnproto/test_readme_example3.cpp b/tests/capnproto/test_readme_example3.cpp index f5294be0..1e898c40 100644 --- a/tests/capnproto/test_readme_example3.cpp +++ b/tests/capnproto/test_readme_example3.cpp @@ -15,18 +15,17 @@ struct Person { }; TEST(capnproto, test_readme_example3) { - /* const auto bart = Person{.first_name = "Bart", .birthday = "1987-04-19"}; + const auto bart = Person{.first_name = "Bart", .birthday = "1987-04-19"}; - const auto lisa = Person{.first_name = "Lisa", .birthday = "1987-04-19"}; + const auto lisa = Person{.first_name = "Lisa", .birthday = "1987-04-19"}; - const auto maggie = Person{.first_name = "Maggie", .birthday = - "1987-04-19"}; + const auto maggie = Person{.first_name = "Maggie", .birthday = "1987-04-19"}; - const auto homer = - Person{.first_name = "Homer", - .birthday = "1987-04-19", - .children = std::vector({bart, lisa, maggie})}; + const auto homer = + Person{.first_name = "Homer", + .birthday = "1987-04-19", + .children = std::vector({bart, lisa, maggie})}; - write_and_read(homer);*/ + write_and_read(homer); } } // namespace test_readme_example3 diff --git a/tests/capnproto/test_timestamp.cpp b/tests/capnproto/test_timestamp.cpp index 453525ff..1ea5b48a 100644 --- a/tests/capnproto/test_timestamp.cpp +++ b/tests/capnproto/test_timestamp.cpp @@ -17,16 +17,15 @@ struct Person { }; TEST(capnproto, test_timestamp) { - // TODO - /* const auto result = TS::from_string("nonsense"); + const auto result = TS::from_string("nonsense"); - if (result) { - std::cout << "Failed: Expected an error, but got none." << std::endl; - return; - } + if (result) { + std::cout << "Failed: Expected an error, but got none." << std::endl; + return; + } - const auto bart = Person{.first_name = "Bart", .birthday = "1987-04-19"}; + const auto bart = Person{.first_name = "Bart", .birthday = "1987-04-19"}; - write_and_read(bart);*/ + write_and_read(bart); } } // namespace test_timestamp From 541a2f2f92635b3c4cf4f21d058255eb0470626a Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sat, 4 Jan 2025 12:24:30 +0100 Subject: [PATCH 39/57] Made sure custom classes work --- include/rfl/capnproto/get_root_name.hpp | 9 ++++++++- include/rfl/capnproto/read.hpp | 2 +- include/rfl/capnproto/write.hpp | 2 +- include/rfl/internal/has_custom_parser.hpp | 23 ++++++++++++++++++++++ include/rfl/parsing/CustomParser.hpp | 13 +++++++----- include/rfl/parsing/Parser_default.hpp | 2 +- 6 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 include/rfl/internal/has_custom_parser.hpp diff --git a/include/rfl/capnproto/get_root_name.hpp b/include/rfl/capnproto/get_root_name.hpp index 494abf1c..729f4979 100644 --- a/include/rfl/capnproto/get_root_name.hpp +++ b/include/rfl/capnproto/get_root_name.hpp @@ -3,15 +3,22 @@ #include +#include "../Processors.hpp" +#include "../internal/has_custom_parser.hpp" #include "../internal/has_reflection_type_v.hpp" #include "../parsing/make_type_name.hpp" +#include "Parser.hpp" namespace rfl::capnproto { -template +template inline std::string get_root_name() { if constexpr (internal::has_reflection_type_v) { return get_root_name>(); + } else if constexpr (internal::has_custom_parser>) { + return get_root_name< + typename Parser>::CustomParserHelperStruct>(); } else { return internal::strings::to_pascal_case(parsing::make_type_name()); } diff --git a/include/rfl/capnproto/read.hpp b/include/rfl/capnproto/read.hpp index 48ac4404..f136505b 100644 --- a/include/rfl/capnproto/read.hpp +++ b/include/rfl/capnproto/read.hpp @@ -41,7 +41,7 @@ Result> read(const char* _bytes, internal::ptr_cast(_bytes), _size); auto input_stream = kj::ArrayInputStream(array_ptr); auto message_reader = capnp::PackedMessageReader(input_stream); - const auto root_name = get_root_name>(); + const auto root_name = get_root_name, Ps...>(); const auto root_schema = _schema.value().getNested(root_name.c_str()); const auto input_var = InputVarType{ message_reader.getRoot(root_schema.asStruct())}; diff --git a/include/rfl/capnproto/write.hpp b/include/rfl/capnproto/write.hpp index cad0eb48..3ce04530 100644 --- a/include/rfl/capnproto/write.hpp +++ b/include/rfl/capnproto/write.hpp @@ -36,7 +36,7 @@ std::vector write(const auto& _obj, const auto& _schema) { using ParentType = parsing::Parent; static_assert(std::is_same(), "The schema must be compatible with the type to write."); - const auto root_name = get_root_name(); + const auto root_name = get_root_name(); const auto root_schema = _schema.value().getNested(root_name.c_str()); capnp::MallocMessageBuilder message_builder; auto root = diff --git a/include/rfl/internal/has_custom_parser.hpp b/include/rfl/internal/has_custom_parser.hpp new file mode 100644 index 00000000..40b5720d --- /dev/null +++ b/include/rfl/internal/has_custom_parser.hpp @@ -0,0 +1,23 @@ +#ifndef RFL_INTERNAL_HASCUSTOMPARSER_HPP_ +#define RFL_INTERNAL_HASCUSTOMPARSER_HPP_ + +#include + +#include "../parsing/Parser.hpp" + +namespace rfl::internal { + +template +concept has_custom_parser = requires( + const T& _t, const typename parsing::Parser< + R, W, T, ProcessorsType>::CustomParserHelperStruct& _h) { + { + std::remove_cvref_t::from_class(_t) + } -> std::same_as>; + + { _h.to_class() } -> std::same_as; +}; + +} // namespace rfl::internal + +#endif diff --git a/include/rfl/parsing/CustomParser.hpp b/include/rfl/parsing/CustomParser.hpp index 200c0ff9..34a9d561 100644 --- a/include/rfl/parsing/CustomParser.hpp +++ b/include/rfl/parsing/CustomParser.hpp @@ -16,6 +16,8 @@ namespace parsing { template struct CustomParser { + using CustomParserHelperStruct = std::remove_cvref_t; + static Result read(const R& _r, const auto& _var) noexcept { const auto to_class = [](auto&& _h) -> Result { try { @@ -32,21 +34,22 @@ struct CustomParser { return Error(e.what()); } }; - return Parser::read(_r, _var).and_then( - to_class); + return Parser::read(_r, + _var) + .and_then(to_class); } template static auto write(const W& _w, const OriginalClass& _p, const P& _parent) noexcept { - Parser::write( + Parser::write( _w, HelperStruct::from_class(_p), _parent); } static schema::Type to_schema( std::map* _definitions) { - return Parser, - ProcessorsType>::to_schema(_definitions); + return Parser::to_schema( + _definitions); } }; diff --git a/include/rfl/parsing/Parser_default.hpp b/include/rfl/parsing/Parser_default.hpp index 7c56a5b4..8ff5b1a5 100644 --- a/include/rfl/parsing/Parser_default.hpp +++ b/include/rfl/parsing/Parser_default.hpp @@ -32,7 +32,7 @@ #include "schemaful/IsSchemafulReader.hpp" #include "schemaful/IsSchemafulWriter.hpp" -namespace rfl ::parsing { +namespace rfl::parsing { /// Default case - anything that cannot be explicitly matched. template From 8e44af24b6aba69307eb4b59d37542877f8dbab7 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sat, 4 Jan 2025 12:24:36 +0100 Subject: [PATCH 40/57] Added test for test_size --- tests/capnproto/test_size.cpp | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/tests/capnproto/test_size.cpp b/tests/capnproto/test_size.cpp index 139a4515..e85870e4 100644 --- a/tests/capnproto/test_size.cpp +++ b/tests/capnproto/test_size.cpp @@ -17,23 +17,21 @@ struct Person { }; TEST(capnproto, test_size) { - // TODO - /* const auto bart = Person{ + const auto bart = Person{ .first_name = "Bart", .last_name = "Simpson", .birthday = "1987-04-19"}; - const auto lisa = Person{ - .first_name = "Lisa", .last_name = "Simpson", .birthday = "1987-04-19"}; + const auto lisa = Person{ + .first_name = "Lisa", .last_name = "Simpson", .birthday = "1987-04-19"}; - const auto maggie = Person{ - .first_name = "Maggie", .last_name = "Simpson", .birthday = - "1987-04-19"}; + const auto maggie = Person{ + .first_name = "Maggie", .last_name = "Simpson", .birthday = "1987-04-19"}; - const auto homer = - Person{.first_name = "Homer", - .last_name = "Simpson", - .birthday = "1987-04-19", - .children = std::vector({bart, lisa, maggie})}; + const auto homer = + Person{.first_name = "Homer", + .last_name = "Simpson", + .birthday = "1987-04-19", + .children = std::vector({bart, lisa, maggie})}; - write_and_read(homer);*/ + write_and_read(homer); } } // namespace test_size From b842e40a1201c93b7b1f1e8a850f54926620a64f Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sat, 4 Jan 2025 12:27:33 +0100 Subject: [PATCH 41/57] Made sure the last test passes --- tests/capnproto/test_custom_class4.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/capnproto/test_custom_class4.cpp b/tests/capnproto/test_custom_class4.cpp index 71fa16fc..688d877c 100644 --- a/tests/capnproto/test_custom_class4.cpp +++ b/tests/capnproto/test_custom_class4.cpp @@ -37,6 +37,11 @@ struct PersonImpl { .last_name = rfl::make_box(*_p.last_name()), .age = _p.age()}; } + + Person to_class() const { + return Person(first_name.value(), std::move(last_name.value()), + age.value()); + } }; } // namespace test_custom_class4 From b3b64d37cac66cfc3478a5b00fe15f03a6f5c667 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sat, 4 Jan 2025 12:35:03 +0100 Subject: [PATCH 42/57] Added missing include --- include/rfl/parsing/CustomParser.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/rfl/parsing/CustomParser.hpp b/include/rfl/parsing/CustomParser.hpp index 34a9d561..88f8be09 100644 --- a/include/rfl/parsing/CustomParser.hpp +++ b/include/rfl/parsing/CustomParser.hpp @@ -1,6 +1,7 @@ #ifndef RFL_PARSING_CUSTOMPARSER_HPP_ #define RFL_PARSING_CUSTOMPARSER_HPP_ +#include #include #include From 66659b75e5bc98591e1a350d361654e2537c3b03 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sat, 4 Jan 2025 14:00:22 +0100 Subject: [PATCH 43/57] Added save_load --- .gitignore | 1 + include/rfl/capnproto.hpp | 4 +- tests/capnproto/test_save_load.cpp | 61 ++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 tests/capnproto/test_save_load.cpp diff --git a/.gitignore b/.gitignore index eb6cda5f..b9205018 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ # Output files *.avro *.bson +*.capnproto *.cbor *.json *.fb diff --git a/include/rfl/capnproto.hpp b/include/rfl/capnproto.hpp index 3a3e9bc8..6895378e 100644 --- a/include/rfl/capnproto.hpp +++ b/include/rfl/capnproto.hpp @@ -6,9 +6,9 @@ #include "capnproto/Reader.hpp" #include "capnproto/Schema.hpp" #include "capnproto/Writer.hpp" -// #include "capnproto/load.hpp" +#include "capnproto/load.hpp" #include "capnproto/read.hpp" -// #include "capnproto/save.hpp" +#include "capnproto/save.hpp" #include "capnproto/to_schema.hpp" #include "capnproto/write.hpp" diff --git a/tests/capnproto/test_save_load.cpp b/tests/capnproto/test_save_load.cpp new file mode 100644 index 00000000..a64a1527 --- /dev/null +++ b/tests/capnproto/test_save_load.cpp @@ -0,0 +1,61 @@ +#include + +#include +#include +#include +#include +#include +#include + +namespace test_save_load { + +using Age = rfl::Validator, rfl::Maximum<130>>>; + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name; + rfl::Timestamp<"%Y-%m-%d"> birthday; + Age age; + rfl::Email email; + std::vector children; +}; + +TEST(capnproto, test_save_load) { + const auto bart = Person{.first_name = "Bart", + .last_name = "Simpson", + .birthday = "1987-04-19", + .age = 10, + .email = "bart@simpson.com", + .children = std::vector()}; + + const auto lisa = Person{.first_name = "Lisa", + .last_name = "Simpson", + .birthday = "1987-04-19", + .age = 8, + .email = "lisa@simpson.com"}; + + const auto maggie = Person{.first_name = "Maggie", + .last_name = "Simpson", + .birthday = "1987-04-19", + .age = 0, + .email = "maggie@simpson.com"}; + + const auto homer1 = + Person{.first_name = "Homer", + .last_name = "Simpson", + .birthday = "1987-04-19", + .age = 45, + .email = "homer@simpson.com", + .children = std::vector({bart, lisa, maggie})}; + + rfl::capnproto::save("homer.capnproto", homer1); + + const auto homer2 = rfl::capnproto::load("homer.capnproto").value(); + + const auto string1 = rfl::capnproto::write(homer1); + const auto string2 = rfl::capnproto::write(homer2); + + EXPECT_EQ(string1, string2); +} +} // namespace test_save_load From c32523b62067d374d86eb874361aee34a74b881a Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sat, 4 Jan 2025 14:08:23 +0100 Subject: [PATCH 44/57] Make sure the avro schema is only generated once --- include/rfl/avro/to_schema.hpp | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/include/rfl/avro/to_schema.hpp b/include/rfl/avro/to_schema.hpp index 1eb04dfb..855fd80f 100644 --- a/include/rfl/avro/to_schema.hpp +++ b/include/rfl/avro/to_schema.hpp @@ -22,13 +22,28 @@ namespace rfl::avro { std::string to_json_representation( const parsing::schema::Definition& internal_schema); +/// This ensures that the schema is only generated once. +template +struct SchemaHolder { + static SchemaHolder make() noexcept { + const auto internal_schema = + parsing::schema::make>(); + const auto json_str = to_json_representation(internal_schema); + return SchemaHolder{Schema::from_json(json_str)}; + } + + rfl::Result> schema_; +}; + +template +static const SchemaHolder schema_holder = + SchemaHolder::make(); + /// Returns the Avro schema for a class. template -Schema to_schema() noexcept { - const auto internal_schema = - parsing::schema::make>(); - const auto json_str = to_json_representation(internal_schema); - return std::move(Schema::from_json(json_str).value()); +Schema to_schema() { + return schema_holder.schema_.value(); } } // namespace rfl::avro From d6b3e9cc59a0b7d0f21d3e7cb009f6f30b09ef8a Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sat, 4 Jan 2025 15:11:41 +0100 Subject: [PATCH 45/57] Revert "Make sure the avro schema is only generated once" This reverts commit c32523b62067d374d86eb874361aee34a74b881a. --- include/rfl/avro/to_schema.hpp | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/include/rfl/avro/to_schema.hpp b/include/rfl/avro/to_schema.hpp index 855fd80f..1eb04dfb 100644 --- a/include/rfl/avro/to_schema.hpp +++ b/include/rfl/avro/to_schema.hpp @@ -22,28 +22,13 @@ namespace rfl::avro { std::string to_json_representation( const parsing::schema::Definition& internal_schema); -/// This ensures that the schema is only generated once. -template -struct SchemaHolder { - static SchemaHolder make() noexcept { - const auto internal_schema = - parsing::schema::make>(); - const auto json_str = to_json_representation(internal_schema); - return SchemaHolder{Schema::from_json(json_str)}; - } - - rfl::Result> schema_; -}; - -template -static const SchemaHolder schema_holder = - SchemaHolder::make(); - /// Returns the Avro schema for a class. template -Schema to_schema() { - return schema_holder.schema_.value(); +Schema to_schema() noexcept { + const auto internal_schema = + parsing::schema::make>(); + const auto json_str = to_json_representation(internal_schema); + return std::move(Schema::from_json(json_str).value()); } } // namespace rfl::avro From ce6baa83328c27c7ee79fcc01df82d96c800e96d Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sat, 4 Jan 2025 21:11:56 +0100 Subject: [PATCH 46/57] Added support for enums --- include/rfl/capnproto/is_named_type.hpp | 3 ++- .../rfl/capnproto/schema/CapnProtoTypes.hpp | 6 +++-- include/rfl/capnproto/schema/Type.hpp | 9 ++++++-- src/rfl/capnproto/Type.cpp | 12 +++++++++- src/rfl/capnproto/to_schema.cpp | 23 ++++++++++++++----- 5 files changed, 41 insertions(+), 12 deletions(-) diff --git a/include/rfl/capnproto/is_named_type.hpp b/include/rfl/capnproto/is_named_type.hpp index 0f7971c7..c95377c3 100644 --- a/include/rfl/capnproto/is_named_type.hpp +++ b/include/rfl/capnproto/is_named_type.hpp @@ -8,7 +8,8 @@ inline bool is_named_type(const parsing::schema::Type& _type) { using T = std::remove_cvref_t; return std::is_same() || std::is_same() || - std::is_same(); + std::is_same() || + std::is_same(); }); } diff --git a/include/rfl/capnproto/schema/CapnProtoTypes.hpp b/include/rfl/capnproto/schema/CapnProtoTypes.hpp index 9c71bda2..5a5515f4 100644 --- a/include/rfl/capnproto/schema/CapnProtoTypes.hpp +++ b/include/rfl/capnproto/schema/CapnProtoTypes.hpp @@ -10,8 +10,9 @@ namespace rfl::capnproto::schema { struct CapnProtoTypes { bool has_maps_ = false; std::map structs_; - std::map unions_; + std::map enums_; std::map tuples_; + std::map unions_; }; const char* MAP_DEFINITION = R"( @@ -38,7 +39,8 @@ std::ostream& operator<<(std::ostream& _os, const CapnProtoTypes& _cnp_types) { if (_cnp_types.has_maps_) { _os << MAP_DEFINITION << std::endl << std::endl; } - _os << _cnp_types.structs_ << _cnp_types.unions_ << _cnp_types.tuples_; + _os << _cnp_types.structs_ << _cnp_types.enums_ << _cnp_types.tuples_ + << _cnp_types.unions_; return _os; } diff --git a/include/rfl/capnproto/schema/Type.hpp b/include/rfl/capnproto/schema/Type.hpp index 40758d2b..12bf3be6 100644 --- a/include/rfl/capnproto/schema/Type.hpp +++ b/include/rfl/capnproto/schema/Type.hpp @@ -43,6 +43,11 @@ struct Type { struct Text {}; + struct Enum { + std::string name; + std::vector fields; + }; + struct List { rfl::Ref type; }; @@ -67,8 +72,8 @@ struct Type { using ReflectionType = rfl::Variant; + UInt64, Float32, Float64, Data, Enum, List, Map, Reference, + Struct, Text, Union>; const auto& reflection() const { return value; } diff --git a/src/rfl/capnproto/Type.cpp b/src/rfl/capnproto/Type.cpp index 4e1f655f..90ad9c0b 100644 --- a/src/rfl/capnproto/Type.cpp +++ b/src/rfl/capnproto/Type.cpp @@ -86,7 +86,8 @@ void handle_fields_in_structs_or_unions(const auto& _struct_or_union, Type Type::with_name(const std::string& _name) const { const auto set_name = [&](const auto& _v) -> ReflectionType { using T = std::remove_cvref_t; - if constexpr (std::is_same() || std::is_same()) { + if constexpr (std::is_same() || std::is_same() || + std::is_same()) { auto v_with_name = _v; v_with_name.name = _name; return v_with_name; @@ -149,6 +150,15 @@ std::ostream& operator<<(std::ostream& _os, const Type::Data&) { return _os << "Data"; } +std::ostream& operator<<(std::ostream& _os, const Type::Enum& _e) { + _os << "enum " << _e.name << " {" << std::endl; + for (size_t i = 0; i < _e.fields.size(); ++i) { + _os << " " << internal::strings::to_camel_case(_e.fields[i]) << " @" << i + << ";" << std::endl; + } + return _os << "}" << std::endl; +} + std::ostream& operator<<(std::ostream& _os, const Type::List& _l) { return _os << "List(" << *_l.type << ")"; } diff --git a/src/rfl/capnproto/to_schema.cpp b/src/rfl/capnproto/to_schema.cpp index 63f13550..3057a60e 100644 --- a/src/rfl/capnproto/to_schema.cpp +++ b/src/rfl/capnproto/to_schema.cpp @@ -69,6 +69,21 @@ schema::Type any_of_to_capnproto_schema_type( } } +schema::Type literal_to_capnproto_schema_type( + const parsing::schema::Type::Literal& _literal, + const std::map& _definitions, + const Parent _parent, schema::CapnProtoTypes* _cnp_types) { + const auto enum_schema = schema::Type::Enum{.fields = _literal.values_}; + if (_parent == Parent::is_top_level) { + return schema::Type{.value = enum_schema}; + } else { + const auto name = + std::string("Enum") + std::to_string(_cnp_types->enums_.size() + 1); + _cnp_types->enums_[name] = schema::Type{.value = enum_schema}; + return schema::Type{.value = schema::Type::Reference{name}}; + } +} + schema::Type object_to_capnproto_schema_type( const parsing::schema::Type::Object& _obj, const std::map& _definitions, @@ -176,12 +191,8 @@ schema::Type type_to_capnproto_schema_type( _cnp_types))}}; } else if constexpr (std::is_same()) { - // TODO: As enum - return schema::Type{.value = schema::Type::Text{}}; - /*return schema::Type{ - .value = schema::Type::Enum{.name = std::string("unnamed_") + - std::to_string(++(*_cnp_types)), - .symbols = _t.values_}};*/ + return literal_to_capnproto_schema_type(_t, _definitions, _parent, + _cnp_types); } else if constexpr (std::is_same()) { return object_to_capnproto_schema_type(_t, _definitions, _parent, From 8d0a3663278ccf1740908f6f750804738bd5410d Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 5 Jan 2025 21:31:03 +0100 Subject: [PATCH 47/57] Make sure enums are properly supported too --- include/rfl/capnproto/Reader.hpp | 6 ++++-- include/rfl/capnproto/Writer.hpp | 9 +++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/include/rfl/capnproto/Reader.hpp b/include/rfl/capnproto/Reader.hpp index d7d904b8..238ec7a2 100644 --- a/include/rfl/capnproto/Reader.hpp +++ b/include/rfl/capnproto/Reader.hpp @@ -93,9 +93,11 @@ class Reader { "float or double."); } - // TODO: read as enum } else if constexpr (internal::is_literal_v) { - return to_basic_type(_var).and_then(T::from_string); + if (type != capnp::DynamicValue::ENUM) { + return rfl::Error("Could not cast to an enum."); + } + return T::from_value(_var.val_.as().getRaw()); } else { static_assert(rfl::always_false_v, "Unsupported type."); diff --git a/include/rfl/capnproto/Writer.hpp b/include/rfl/capnproto/Writer.hpp index a32a148d..d57abda7 100644 --- a/include/rfl/capnproto/Writer.hpp +++ b/include/rfl/capnproto/Writer.hpp @@ -173,8 +173,7 @@ class Writer { _parent->val_.set(_parent->ix_++, static_cast(_var)); } else if constexpr (internal::is_literal_v) { - // TODO: Transform to enum - return add_value_to_array(_var.str(), _parent); + return add_value_to_array(_var.value(), _parent); } else { static_assert(rfl::always_false_v, "Unsupported type."); @@ -207,8 +206,7 @@ class Writer { _parent->val_.set(_name.data(), static_cast(_var)); } else if constexpr (internal::is_literal_v) { - // TODO: Transform to enum - return add_value_to_object(_name, _var.str(), _parent); + return add_value_to_object(_name, _var.value(), _parent); } else { static_assert(rfl::always_false_v, "Unsupported type."); @@ -231,8 +229,7 @@ class Writer { _parent->val_.set(field, static_cast(_var)); } else if constexpr (internal::is_literal_v) { - // TODO: Transform to enum - return add_value_to_union(_index, _var.str(), _parent); + return add_value_to_union(_index, _var.value(), _parent); } else { static_assert(rfl::always_false_v, "Unsupported type."); From dc12e364b65d2603c6ca6b699c33df4ec714a2be Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 5 Jan 2025 21:36:43 +0100 Subject: [PATCH 48/57] Added Cap'n Proto to the Github Action pipelines --- .github/workflows/linux.yaml | 2 +- .github/workflows/macos-clang.yaml | 2 +- .github/workflows/windows-msvc.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linux.yaml b/.github/workflows/linux.yaml index e36545b7..865c4fb1 100644 --- a/.github/workflows/linux.yaml +++ b/.github/workflows/linux.yaml @@ -66,7 +66,7 @@ jobs: sudo ln -s $(which ccache) /usr/local/bin/$CC sudo ln -s $(which ccache) /usr/local/bin/$CXX $CXX --version - cmake -S . -B build -G Ninja -DREFLECTCPP_BUILD_TESTS=ON -DREFLECTCPP_BUILD_BENCHMARKS=ON -DREFLECTCPP_BSON=ON -DREFLECTCPP_CBOR=ON -DREFLECTCPP_FLEXBUFFERS=ON -DREFLECTCPP_MSGPACK=ON -DREFLECTCPP_TOML=ON -DREFLECTCPP_XML=ON -DREFLECTCPP_YAML=ON -DCMAKE_BUILD_TYPE=Release + cmake -S . -B build -G Ninja -DREFLECTCPP_BUILD_TESTS=ON -DREFLECTCPP_BUILD_BENCHMARKS=ON -DREFLECTCPP_BSON=ON -DREFLECTCPP_CAPNPROTO=ON -DREFLECTCPP_CBOR=ON -DREFLECTCPP_FLEXBUFFERS=ON -DREFLECTCPP_MSGPACK=ON -DREFLECTCPP_TOML=ON -DREFLECTCPP_XML=ON -DREFLECTCPP_YAML=ON -DCMAKE_BUILD_TYPE=Release cmake --build build - name: Run tests run: | diff --git a/.github/workflows/macos-clang.yaml b/.github/workflows/macos-clang.yaml index e12e7028..c3787f13 100644 --- a/.github/workflows/macos-clang.yaml +++ b/.github/workflows/macos-clang.yaml @@ -55,7 +55,7 @@ jobs: export CMAKE_GENERATOR=Ninja fi $CXX --version - cmake -S . -B build -DREFLECTCPP_BUILD_TESTS=ON -DREFLECTCPP_BUILD_BENCHMARKS=ON -DREFLECTCPP_BSON=ON -DREFLECTCPP_CBOR=ON -DREFLECTCPP_FLEXBUFFERS=ON -DREFLECTCPP_MSGPACK=ON -DREFLECTCPP_TOML=ON -DREFLECTCPP_XML=ON -DREFLECTCPP_YAML=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ + cmake -S . -B build -DREFLECTCPP_BUILD_TESTS=ON -DREFLECTCPP_BUILD_BENCHMARKS=ON -DREFLECTCPP_BSON=ON -DREFLECTCPP_CAPNPROTO=ON -DREFLECTCPP_CBOR=ON -DREFLECTCPP_FLEXBUFFERS=ON -DREFLECTCPP_MSGPACK=ON -DREFLECTCPP_TOML=ON -DREFLECTCPP_XML=ON -DREFLECTCPP_YAML=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ cmake --build build -j 4 - name: Run tests run: | diff --git a/.github/workflows/windows-msvc.yaml b/.github/workflows/windows-msvc.yaml index 2bf17440..b1406f66 100644 --- a/.github/workflows/windows-msvc.yaml +++ b/.github/workflows/windows-msvc.yaml @@ -24,7 +24,7 @@ jobs: - uses: lukka/run-vcpkg@v11 - name: Compile run: | - cmake -S . -B build -DREFLECTCPP_BUILD_TESTS=ON -DREFLECTCPP_BUILD_BENCHMARKS=ON -DREFLECTCPP_BSON=ON -DREFLECTCPP_CBOR=ON -DREFLECTCPP_FLEXBUFFERS=ON -DREFLECTCPP_MSGPACK=ON -DREFLECTCPP_XML=ON -DREFLECTCPP_TOML=ON -DREFLECTCPP_YAML=ON -DCMAKE_BUILD_TYPE=Release + cmake -S . -B build -DREFLECTCPP_BUILD_TESTS=ON -DREFLECTCPP_BUILD_BENCHMARKS=ON -DREFLECTCPP_BSON=ON -DREFLECTCPP_CAPNPROTO=ON -DREFLECTCPP_CBOR=ON -DREFLECTCPP_FLEXBUFFERS=ON -DREFLECTCPP_MSGPACK=ON -DREFLECTCPP_XML=ON -DREFLECTCPP_TOML=ON -DREFLECTCPP_YAML=ON -DCMAKE_BUILD_TYPE=Release cmake --build build --config Release -j4 - name: Run tests run: | From 80b91c8400b1e43b3585efc0c0d22791da6dd40b Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Mon, 6 Jan 2025 11:44:34 +0100 Subject: [PATCH 49/57] Added support for bytestrings --- include/rfl/capnproto/Reader.hpp | 21 +++++++++++---------- include/rfl/capnproto/Writer.hpp | 21 +++++++++++++++++++++ tests/capnproto/test_bytestring.cpp | 21 +++++++++++++++++++++ tests/capnproto/write_and_read.hpp | 4 ---- 4 files changed, 53 insertions(+), 14 deletions(-) create mode 100644 tests/capnproto/test_bytestring.cpp diff --git a/include/rfl/capnproto/Reader.hpp b/include/rfl/capnproto/Reader.hpp index 238ec7a2..d8d10971 100644 --- a/include/rfl/capnproto/Reader.hpp +++ b/include/rfl/capnproto/Reader.hpp @@ -14,6 +14,7 @@ #include "../Result.hpp" #include "../always_false.hpp" #include "../internal/is_literal.hpp" +#include "../internal/ptr_cast.hpp" namespace rfl::capnproto { @@ -59,16 +60,16 @@ class Reader { return Error("Could not cast to string."); } return std::string(_var.val_.as().cStr()); - // TODO - /*} else if constexpr (std::is_same, - rfl::Bytestring>()) { - const void* ptr = nullptr; - size_t size = 0; - const auto err = capnproto_value_get_bytes(_var.val_, &ptr, &size); - if (err) { - return Error("Could not cast to bytestring."); - } - return rfl::Bytestring(static_cast(ptr), size - 1);*/ + + } else if constexpr (std::is_same, + rfl::Bytestring>()) { + if (type != capnp::DynamicValue::DATA) { + return Error("Could not cast to bytestring."); + } + const auto data = _var.val_.as(); + return rfl::Bytestring(internal::ptr_cast(data.begin()), + data.size()); + } else if constexpr (std::is_same, bool>()) { if (type != capnp::DynamicValue::BOOL) { return rfl::Error("Could not cast to boolean."); diff --git a/include/rfl/capnproto/Writer.hpp b/include/rfl/capnproto/Writer.hpp index d57abda7..32c2e58a 100644 --- a/include/rfl/capnproto/Writer.hpp +++ b/include/rfl/capnproto/Writer.hpp @@ -2,6 +2,7 @@ #define RFL_CAPNPROTO_WRITER_HPP_ #include +#include #include #include @@ -21,6 +22,7 @@ #include "../Result.hpp" #include "../always_false.hpp" #include "../internal/is_literal.hpp" +#include "../internal/ptr_cast.hpp" namespace rfl::capnproto { @@ -165,6 +167,12 @@ class Writer { if constexpr (std::is_same, std::string>()) { _parent->val_.set(_parent->ix_++, _var.c_str()); + } else if constexpr (std::is_same, + rfl::Bytestring>()) { + const auto array_ptr = kj::ArrayPtr( + internal::ptr_cast(_var.data()), _var.size()); + _parent->val_.set(_parent->ix_++, capnp::Data::Reader(array_ptr)); + } else if constexpr (std::is_floating_point>() || std::is_same, bool>()) { _parent->val_.set(_parent->ix_++, _var); @@ -198,6 +206,12 @@ class Writer { if constexpr (std::is_same, std::string>()) { _parent->val_.set(_name.data(), _var.c_str()); + } else if constexpr (std::is_same, + rfl::Bytestring>()) { + const auto array_ptr = kj::ArrayPtr( + internal::ptr_cast(_var.data()), _var.size()); + _parent->val_.set(_name.data(), capnp::Data::Reader(array_ptr)); + } else if constexpr (std::is_floating_point>() || std::is_same, bool>()) { _parent->val_.set(_name.data(), _var); @@ -218,9 +232,16 @@ class Writer { OutputVarType add_value_to_union(const size_t _index, const T& _var, OutputUnionType* _parent) const noexcept { const auto field = _parent->val_.getSchema().getFields()[_index]; + if constexpr (std::is_same, std::string>()) { _parent->val_.set(field, _var.c_str()); + } else if constexpr (std::is_same, + rfl::Bytestring>()) { + const auto array_ptr = kj::ArrayPtr( + internal::ptr_cast(_var.data()), _var.size()); + _parent->val_.set(field, capnp::Data::Reader(array_ptr)); + } else if constexpr (std::is_floating_point>() || std::is_same, bool>()) { _parent->val_.set(field, _var); diff --git a/tests/capnproto/test_bytestring.cpp b/tests/capnproto/test_bytestring.cpp new file mode 100644 index 00000000..43fa5c9b --- /dev/null +++ b/tests/capnproto/test_bytestring.cpp @@ -0,0 +1,21 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_bytestring { + +struct TestStruct { + rfl::Bytestring bytestring; +}; + +TEST(capnproto, test_bytestring) { + const auto test = + TestStruct{.bytestring = rfl::Bytestring({std::byte{13}, std::byte{14}, + std::byte{15}, std::byte{16}})}; + + write_and_read(test); +} +} // namespace test_bytestring diff --git a/tests/capnproto/write_and_read.hpp b/tests/capnproto/write_and_read.hpp index 671daec0..81692be4 100644 --- a/tests/capnproto/write_and_read.hpp +++ b/tests/capnproto/write_and_read.hpp @@ -5,7 +5,6 @@ #include #include -#include #include template @@ -17,8 +16,5 @@ void write_and_read(const auto& _struct) { << res.error().value().what(); const auto serialized2 = rfl::capnproto::write(res.value()); EXPECT_EQ(serialized1, serialized2); - - // For temporary testing only, remove later. - std::cout << rfl::json::write(res.value()) << std::endl; } #endif From c0ac3cef5a8f3e7ba2d1bc34f0a2a01223e2a814 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Mon, 6 Jan 2025 11:59:09 +0100 Subject: [PATCH 50/57] Don't use local bindings --- src/rfl/capnproto/Type.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rfl/capnproto/Type.cpp b/src/rfl/capnproto/Type.cpp index 90ad9c0b..0ddf11a6 100644 --- a/src/rfl/capnproto/Type.cpp +++ b/src/rfl/capnproto/Type.cpp @@ -40,7 +40,7 @@ void handle_fields_in_structs_or_unions(const auto& _struct_or_union, const size_t _indent, const std::string& _namespace, std::ostream* _os, size_t* _ix) { - for (const auto& [name, type] : _struct_or_union.fields) { + for (const auto [name, type] : _struct_or_union.fields) { // Because of the way Cap'n proto handles unions, we need a special case for // them. type.reflection().visit([&](const auto& _r) { From da6117452c29764f70b689eab5335f953fc08f24 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Mon, 6 Jan 2025 12:06:43 +0100 Subject: [PATCH 51/57] Don't pass by reference --- src/rfl/capnproto/Type.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rfl/capnproto/Type.cpp b/src/rfl/capnproto/Type.cpp index 0ddf11a6..abcbc1dd 100644 --- a/src/rfl/capnproto/Type.cpp +++ b/src/rfl/capnproto/Type.cpp @@ -40,17 +40,17 @@ void handle_fields_in_structs_or_unions(const auto& _struct_or_union, const size_t _indent, const std::string& _namespace, std::ostream* _os, size_t* _ix) { - for (const auto [name, type] : _struct_or_union.fields) { + for (const auto& [name, type] : _struct_or_union.fields) { // Because of the way Cap'n proto handles unions, we need a special case for // them. - type.reflection().visit([&](const auto& _r) { + type.reflection().visit([=](const auto& _r) { using R = std::remove_cvref_t; if constexpr (std::is_same()) { // Special case: Union field. *_os << std::string(_indent * 2, ' ') << name << " :union {" << std::endl; for (size_t i = 0; i < _r.fields.size(); ++i) { - _r.fields[i].second.reflection().visit([&](const auto& _union_field) { + _r.fields[i].second.reflection().visit([=](const auto& _union_field) { using U = std::remove_cvref_t; if constexpr (std::is_same()) { *_os << std::string(_indent * 2 + 2, ' ') From f05323f21b07d5dfc565ef816a6cc639b73bdb29 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Mon, 6 Jan 2025 12:15:12 +0100 Subject: [PATCH 52/57] Don't use structured bindings --- src/rfl/capnproto/Type.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/rfl/capnproto/Type.cpp b/src/rfl/capnproto/Type.cpp index abcbc1dd..de6592fe 100644 --- a/src/rfl/capnproto/Type.cpp +++ b/src/rfl/capnproto/Type.cpp @@ -40,17 +40,18 @@ void handle_fields_in_structs_or_unions(const auto& _struct_or_union, const size_t _indent, const std::string& _namespace, std::ostream* _os, size_t* _ix) { - for (const auto& [name, type] : _struct_or_union.fields) { + for (const auto& pair : _struct_or_union.fields) { + const auto name = pair.first; // Because of the way Cap'n proto handles unions, we need a special case for // them. - type.reflection().visit([=](const auto& _r) { + pair.second.reflection().visit([&](const auto& _r) { using R = std::remove_cvref_t; if constexpr (std::is_same()) { // Special case: Union field. *_os << std::string(_indent * 2, ' ') << name << " :union {" << std::endl; for (size_t i = 0; i < _r.fields.size(); ++i) { - _r.fields[i].second.reflection().visit([=](const auto& _union_field) { + _r.fields[i].second.reflection().visit([&](const auto& _union_field) { using U = std::remove_cvref_t; if constexpr (std::is_same()) { *_os << std::string(_indent * 2 + 2, ' ') From 13799e74155abd18cb7a2e86f20e45941133d39e Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Mon, 6 Jan 2025 13:39:21 +0100 Subject: [PATCH 53/57] Added documentation for Cap'n Proto --- README.md | 3 +- docs/supported_formats/avro.md | 2 +- docs/supported_formats/capnproto.md | 140 ++++++++++++++++++++++++++++ mkdocs.yaml | 1 + tests/README.md | 3 +- 5 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 docs/supported_formats/capnproto.md diff --git a/README.md b/README.md index 0223d5aa..4359c06d 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,7 @@ The following table lists the serialization formats currently supported by refle | JSON | [yyjson](https://github.com/ibireme/yyjson) | >= 0.8.0 | MIT | out-of-the-box support, included in this repository | | Avro | [avro-c](https://avro.apache.org/docs/1.11.1/api/c/) | >= 1.11.3 | Apache 2.0 | Schemaful binary format | | BSON | [libbson](https://github.com/mongodb/mongo-c-driver) | >= 1.25.1 | Apache 2.0 | JSON-like binary format | +| Cap'n Proto | [capnproto](https://capnproto.org) | >= 1.0.2 | MIT | Schemaful binary format | | CBOR | [tinycbor](https://github.com/intel/tinycbor) | >= 0.6.0 | MIT | JSON-like binary format | | flexbuffers | [flatbuffers](https://github.com/google/flatbuffers) | >= 23.5.26 | Apache 2.0 | Schema-less version of flatbuffers, binary format | | msgpack | [msgpack-c](https://github.com/msgpack/msgpack-c) | >= 6.0.0 | BSL 1.0 | JSON-like binary format | @@ -512,7 +513,7 @@ In addition, it supports the following custom containers: - `rfl::Binary`: Used to express numbers in binary format. - `rfl::Box`: Similar to `std::unique_ptr`, but (almost) guaranteed to never be null. -- `rfl::Bytestring`: An alias for `std::basic_string`. Supported by BSON, CBOR, flexbuffers, msgpack and UBJSON. +- `rfl::Bytestring`: An alias for `std::basic_string`. Supported by Avro, BSON, Cap'n Proto, CBOR, flexbuffers, msgpack and UBJSON. - `rfl::Generic`: A catch-all type that can represent (almost) anything. - `rfl::Hex`: Used to express numbers in hex format. - `rfl::Literal`: An explicitly enumerated string. diff --git a/docs/supported_formats/avro.md b/docs/supported_formats/avro.md index 4ceeeff0..65a92a9b 100644 --- a/docs/supported_formats/avro.md +++ b/docs/supported_formats/avro.md @@ -36,7 +36,7 @@ However, Avro is a schemaful format, so before you serialize or deserialize, you have to declare a schema. In the two function calls above, this is abstracted away. -But if you are repeated serializing or deserializing the same struct, +But if you are repeatedly serializing or deserializing the same struct, it is more efficient to generate the schema explicitly: ```cpp diff --git a/docs/supported_formats/capnproto.md b/docs/supported_formats/capnproto.md new file mode 100644 index 00000000..e83f18c0 --- /dev/null +++ b/docs/supported_formats/capnproto.md @@ -0,0 +1,140 @@ +# Cap'n Proto + +For Cap'n Proto support, you must also include the header `` and link to the capnproto library (https://capnproto.org). +Furthermore, when compiling reflect-cpp, you need to pass `-DREFLECTCPP_CAPNPROTO=ON` to cmake. + +Cap'n Proto is a schemaful binary format. This sets it apart from most other formats supported by reflect-cpp, which are schemaless. + +## Reading and writing + +Suppose you have a struct like this: + +```cpp +struct Person { + std::string first_name; + std::string last_name; + rfl::Timestamp<"%Y-%m-%d"> birthday; + std::vector children; +}; +``` + +A `Person` struct can be serialized to a bytes vector like this: + +```cpp +const auto person = Person{...}; +const std::vector bytes = rfl::capnproto::write(person); +``` + +You can parse bytes like this: + +```cpp +const rfl::Result result = rfl::capnproto::read(bytes); +``` + +## The schema + +However, Cap'n Proto is a schemaful format, so before you serialize or +deserialize, you have to declare a schema. In the two function calls +above, this is abstracted away. + +If you want to, you can pass the schema explicitly, but it will not +yield any performance gains, because the schemata are always created +upfront: + +```cpp +const auto schema = rfl::capnproto::to_schema(); + +const auto person = Person{...}; +const std::vector bytes = rfl::capnproto::write(person, schema); + +const rfl::Result result = rfl::capnproto::read(bytes, schema); +``` + +Cap'n Proto schemas are created using a schema language. You can +retrieve the schema like this: + +```cpp +schema.str(); +``` + +In this case, the resulting schema representation looks like this: + +``` +@0xdbb9ad1f14bf0b36; + +struct Person { + firstName @0 :Text; + lastName @1 :Text; + birthday @2 :Text; + children @3 :List(Person); +} +``` + +## Loading and saving + +You can also load and save to disc using a very similar syntax: + +```cpp +const rfl::Result result = rfl::capnproto::load("/path/to/file.capnproto"); + +const auto person = Person{...}; +rfl::capnproto::save("/path/to/file.capnproto", person); +``` + +## Reading from and writing into streams + +You can also read from and write into any `std::istream` and `std::ostream` respectively. + +```cpp +const rfl::Result result = rfl::capnproto::read(my_istream); + +const auto person = Person{...}; +rfl::capnproto::write(person, my_ostream); +``` + +Note that `std::cout` is also an ostream, so this works as well: + +```cpp +rfl::capnproto::write(person, std::cout) << std::endl; +``` + +(Since Cap'n Proto is a binary format, the readability of this will be limited, but it might be useful for debugging). + +## Custom constructors + +One of the great things about C++ is that it gives you control over +when and how you code is compiled. + +For large and complex systems of structs, it is often a good idea to split up +your code into smaller compilation units. You can do so using custom constructors. + +For the Cap'n Proto format, these must be a static function on your struct or class called +`from_capnproto` that take a `rfl::capnproto::Reader::InputVarType` as input and return +the class or the class wrapped in `rfl::Result`. + +In your header file you can write something like this: + +```cpp +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name; + rfl::Timestamp<"%Y-%m-%d"> birthday; + + using InputVarType = typename rfl::capnproto::Reader::InputVarType; + static rfl::Result from_capnproto(const InputVarType& _obj); +}; +``` + +And in your source file, you implement `from_capnproto` as follows: + +```cpp +rfl::Result Person::from_capnproto(const InputVarType& _obj) { + const auto from_nt = [](auto&& _nt) { + return rfl::from_named_tuple(std::move(_nt)); + }; + return rfl::capnproto::read>(_obj) + .transform(from_nt); +} +``` + +This will force the compiler to only compile the Cap'n Proto parsing when the source file is compiled. diff --git a/mkdocs.yaml b/mkdocs.yaml index 46beabe5..cd3ee8be 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -94,6 +94,7 @@ nav: - Supported Formats: - Avro: supported_formats/avro.md - BSON: supported_formats/bson.md + - Cap'n Proto: supported_formats/capnproto.md - CBOR: supported_formats/cbor.md - FlexBuffers: supported_formats/flexbuffers.md - JSON: supported_formats/json.md diff --git a/tests/README.md b/tests/README.md index 3da3b66b..67ccb3b8 100644 --- a/tests/README.md +++ b/tests/README.md @@ -32,7 +32,7 @@ To run the tests, do the following: To compile the tests with serialization formats other than JSON, do the following: ```bash -cmake -S . -B build -DREFLECTCPP_BUILD_TESTS=ON -DREFLECTCPP_AVRO=ON -DREFLECTCPP_BSON=ON -DREFLECTCPP_CBOR=ON -DREFLECTCPP_FLEXBUFFERS=ON -DREFLECTCPP_MSGPACK=ON -DREFLECTCPP_XML=ON -DREFLECTCPP_TOML=ON -DREFLECTCPP_UBJSON=ON -DREFLECTCPP_YAML=ON -DCMAKE_BUILD_TYPE=Release +cmake -S . -B build -DREFLECTCPP_BUILD_TESTS=ON -DREFLECTCPP_AVRO=ON -DREFLECTCPP_BSON=ON -DREFLECTCPP_CAPNPROTO=ON -DREFLECTCPP_CBOR=ON -DREFLECTCPP_FLEXBUFFERS=ON -DREFLECTCPP_MSGPACK=ON -DREFLECTCPP_XML=ON -DREFLECTCPP_TOML=ON -DREFLECTCPP_UBJSON=ON -DREFLECTCPP_YAML=ON -DCMAKE_BUILD_TYPE=Release cmake --build build -j 4 # gcc, clang cmake --build build --config Release -j 4 # MSVC ``` @@ -42,6 +42,7 @@ To run the tests, do the following: ``` ./build/tests/avro/reflect-cpp-avro-tests ./build/tests/bson/reflect-cpp-bson-tests +./build/tests/capnproto/reflect-cpp-capnproto-tests ./build/tests/cbor/reflect-cpp-cbor-tests ./build/tests/flexbuffers/reflect-cpp-flexbuffers-tests ./build/tests/msgpack/reflect-cpp-msgpack-tests From b0cc5520166e1d23e12d93313da18679d838511d Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Mon, 6 Jan 2025 13:57:50 +0100 Subject: [PATCH 54/57] Added an explanatory note --- docs/supported_formats/capnproto.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/supported_formats/capnproto.md b/docs/supported_formats/capnproto.md index e83f18c0..9e7e962e 100644 --- a/docs/supported_formats/capnproto.md +++ b/docs/supported_formats/capnproto.md @@ -1,7 +1,8 @@ # Cap'n Proto For Cap'n Proto support, you must also include the header `` and link to the capnproto library (https://capnproto.org). -Furthermore, when compiling reflect-cpp, you need to pass `-DREFLECTCPP_CAPNPROTO=ON` to cmake. +Furthermore, when compiling reflect-cpp, you need to pass `-DREFLECTCPP_CAPNPROTO=ON` to cmake. If you are using vcpkg or Conan, there +should be an appropriate feature (vcpkg) or options that will abstract this away for you. Cap'n Proto is a schemaful binary format. This sets it apart from most other formats supported by reflect-cpp, which are schemaless. From 32868dab0b9d18667249c5c6e5841b33b2638fa7 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Mon, 6 Jan 2025 13:58:01 +0100 Subject: [PATCH 55/57] Added Cap'n Proto to the Conanfile --- conanfile.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/conanfile.py b/conanfile.py index 8b3272e8..dde33e86 100644 --- a/conanfile.py +++ b/conanfile.py @@ -23,6 +23,7 @@ class ReflectCppConan(ConanFile): "reflection", "serialization", "memory", + "Cap'n Proto", "cbor", "flatbuffers", "json", @@ -37,6 +38,7 @@ class ReflectCppConan(ConanFile): options = { "shared": [True, False], "fPIC": [True, False], + "with_capnproto": [True, False], "with_cbor": [True, False], "with_flatbuffers": [True, False], "with_msgpack": [True, False], @@ -48,6 +50,7 @@ class ReflectCppConan(ConanFile): default_options = { "shared": False, "fPIC": True, + "with_capnproto": False, "with_cbor": False, "with_flatbuffers": False, "with_msgpack": False, @@ -68,6 +71,8 @@ def configure(self): def requirements(self): self.requires("ctre/3.9.0", transitive_headers=True) self.requires("yyjson/0.10.0", transitive_headers=True) + if self.options.with_capnproto: + self.requires("capnproto/1.1.0", transitive_headers=True) if self.options.with_cbor: self.requires("tinycbor/0.6.0", transitive_headers=True) if self.options.with_flatbuffers: @@ -105,6 +110,7 @@ def generate(self): tc.cache_variables["REFLECTCPP_BUILD_SHARED"] = self.options.shared tc.cache_variables["REFLECTCPP_USE_BUNDLED_DEPENDENCIES"] = False tc.cache_variables["REFLECTCPP_USE_VCPKG"] = False + tc.cache_variables["REFLECTCPP_CAPNPROTO"] = self.options.with_capnproto tc.cache_variables["REFLECTCPP_CBOR"] = self.options.with_cbor tc.cache_variables["REFLECTCPP_FLEXBUFFERS"] = self.options.with_flatbuffers tc.cache_variables["REFLECTCPP_MSGPACK"] = self.options.with_msgpack From 83e7a6364856658183cd184939c54301df40b108 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Mon, 6 Jan 2025 14:10:14 +0100 Subject: [PATCH 56/57] Added the option to the test pipelines --- .github/workflows/linux.yaml | 2 +- .github/workflows/macos-clang.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linux.yaml b/.github/workflows/linux.yaml index 865c4fb1..0098ec5d 100644 --- a/.github/workflows/linux.yaml +++ b/.github/workflows/linux.yaml @@ -53,7 +53,7 @@ jobs: pipx install conan conan profile detect - name: Make sure the library compiles with Conan - run: conan build . --build=missing -s compiler.cppstd=gnu20 -o *:with_cbor=True -o *:with_flatbuffers=True -o *:with_msgpack=True -o *:with_toml=True -o *:with_ubjson=True -o *:with_xml=True -o *:with_yaml=True + run: conan build . --build=missing -s compiler.cppstd=gnu20 -o *:with_capnproto=True -o *:with_cbor=True -o *:with_flatbuffers=True -o *:with_msgpack=True -o *:with_toml=True -o *:with_ubjson=True -o *:with_xml=True -o *:with_yaml=True - name: Compile run: | if [[ "${{ matrix.compiler }}" == "llvm" ]]; then diff --git a/.github/workflows/macos-clang.yaml b/.github/workflows/macos-clang.yaml index c3787f13..af073036 100644 --- a/.github/workflows/macos-clang.yaml +++ b/.github/workflows/macos-clang.yaml @@ -44,7 +44,7 @@ jobs: env: CC: clang CXX: clang++ - run: conan build . --build=missing -s compiler.cppstd=gnu20 -o *:with_cbor=True -o *:with_flatbuffers=True -o *:with_msgpack=True -o *:with_toml=True -o *:with_ubjson=True -o *:with_xml=True -o *:with_yaml=True + run: conan build . --build=missing -s compiler.cppstd=gnu20 -o *:with_capnproto=True -o *:with_cbor=True -o *:with_flatbuffers=True -o *:with_msgpack=True -o *:with_toml=True -o *:with_ubjson=True -o *:with_xml=True -o *:with_yaml=True - name: Compile env: CC: clang From 227339b40e904c61b6686767f89fbede06782030 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Mon, 6 Jan 2025 14:43:12 +0100 Subject: [PATCH 57/57] Do not print the schema --- include/rfl/capnproto/to_schema.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/rfl/capnproto/to_schema.hpp b/include/rfl/capnproto/to_schema.hpp index 5f64ab7d..24d66448 100644 --- a/include/rfl/capnproto/to_schema.hpp +++ b/include/rfl/capnproto/to_schema.hpp @@ -31,7 +31,6 @@ struct SchemaHolder { parsing::schema::make>(); const auto str = to_string_representation(internal_schema); - std::cout << str << std::endl; return SchemaHolder{Schema::from_string(str)}; }