Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmake/third_party.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ find_package(ctre REQUIRED)
find_package(magic_enum REQUIRED)
find_package(Microsoft.GSL REQUIRED)
find_package(RapidJSON REQUIRED)
find_package(ryml REQUIRED)

if(WIN32)
find_package(wil REQUIRED)
Expand Down
1 change: 1 addition & 0 deletions conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class Morpheus(ConanFile):
"magic_enum/0.9.5",
"ms-gsl/4.0.0",
"rapidjson/cci.20230929",
"rapidyaml/0.5.0",
"range-v3/0.12.0",
"scnlib/2.0.2",
#"zlib/1.2.12" # xapian-core/1.4.19' requires 'zlib/1.2.12' while 'boost/1.81.0' requires 'zlib/1.2.13'. To fix this conflict you need to override the package 'zlib' in your root package.
Expand Down
3 changes: 3 additions & 0 deletions libraries/core/src/morpheus/core/serialisation/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ target_sources(MorpheusCore
text_writer.hpp
write_serialiser.hpp
write_serialiser_decl.hpp
yaml_reader.hpp
yaml_writer.hpp
PRIVATE
exceptions.cpp
json_reader.cpp
Expand All @@ -24,6 +26,7 @@ target_sources(MorpheusCore
target_link_libraries(MorpheusCore
PUBLIC
rapidjson
ryml::ryml # https://conan.io/center/recipes/rapidyaml?version=0.5.0
)

add_subdirectory(adapters)
Expand Down
5 changes: 5 additions & 0 deletions libraries/core/src/morpheus/core/serialisation/exceptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,9 @@ void throwJsonException(std::string_view message)
throw boost::enable_error_info(JsonException(std::string(message))) << ExceptionInfo(MORPHEUS_CURRENT_STACKTRACE);
}

void throwYamlException(std::string_view message)
{
throw boost::enable_error_info(YamlException(std::string(message))) << ExceptionInfo(MORPHEUS_CURRENT_STACKTRACE);
}

} // namespace morpheus::serialisation
11 changes: 11 additions & 0 deletions libraries/core/src/morpheus/core/serialisation/exceptions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,21 @@ class JsonException : public std::runtime_error
using std::runtime_error::runtime_error;
};

/// \class YamlException
/// Exception type to be thrown for errors when parsing YAML.
class YamlException : public std::runtime_error
{
public:
using std::runtime_error::runtime_error;
};

/// Throws a std::runtime_error derived binary exception with the attached message.
MORPHEUSCORE_EXPORT [[noreturn]] MORPHEUS_FUNCTION_COLD void throwBinaryException(std::string_view message);

/// Throws a std::runtime_error derived Json exception with the attached message.
MORPHEUSCORE_EXPORT [[noreturn]] MORPHEUS_FUNCTION_COLD void throwJsonException(std::string_view message);

/// Throws a std::runtime_error derived Yaml exception with the attached message.
MORPHEUSCORE_EXPORT [[noreturn]] MORPHEUS_FUNCTION_COLD void throwYamlException(std::string_view message);

} // namespace morpheus::serialisation
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include "morpheus/core/serialisation/serialise.hpp"
#include "morpheus/core/serialisation/read_serialiser.hpp"
#include "morpheus/core/serialisation/write_serialiser.hpp"
#include "morpheus/core/serialisation/yaml_writer.hpp"
#include "morpheus/core/serialisation/yaml_reader.hpp"

namespace morpheus::serialisation
{
Expand All @@ -18,4 +20,6 @@ using BinaryReadSerialiser = ReadSerialiser<BinaryReader>;
using JsonWriteSerialiser = WriteSerialiser<JsonWriter>;
using JsonReadSerialiser = ReadSerialiser<JsonReader>;

using YamlWriteSerialiser = WriteSerialiser<YamlWriter>;
using YamlReadSerialiser = ReadSerialiser<YamlReader>;
}
179 changes: 179 additions & 0 deletions libraries/core/src/morpheus/core/serialisation/yaml_reader.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
#pragma once

#include "morpheus/core/base/assert.hpp"
#include "morpheus/core/base/cold.hpp"
#include "morpheus/core/functional/overload.hpp"
#include "morpheus/core/memory/polymorphic_value.hpp"
#include "morpheus/core/serialisation/exceptions.hpp"

#include <boost/numeric/conversion/cast.hpp>

//#include <rapidjson/istreamwrapper.h>
//#include <rapidjson/reader.h>
#include <ryml_all.hpp> // details around which header to include are mentioned at
// https://github.com/biojppm/rapidyaml/blob/master/samples/quickstart.cpp


#include <magic_enum/magic_enum.hpp>

#include <concepts>
#include <cstddef>
#include <cstdint>
#include <istream>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <tuple>
#include <variant>
#include <vector>

namespace morpheus::serialisation
{

/// \class YamlReader
/// Read in objects from an underlying yaml representation.
class MORPHEUSCORE_EXPORT YamlReader
{
enum class FundamentalType : std::uint32_t
{
Boolean,
Int64,
Uint64,
Float,
Double,
String
};

public:
using OwnedStream = memory::polymorphic_value<std::istream>;



static constexpr bool canBeTextual() { return true; }

/// \copydoc morpheus::serialisation::concepts::ReaderArchtype::isTextual()
static constexpr bool isTextual() { return true; }

/// Yaml reader take in a stream of yaml to extract data members from.
/// \param[in] stream Stream used to read in the yaml source. This must outlive the reader as its held by referece.
explicit YamlReader(OwnedStream stream, bool validate = true);
explicit YamlReader(YamlReader const& rhs);
~YamlReader();

/// \copydoc morpheus::serialisation::concepts::ReaderArchtype::beginComposite()
void beginComposite();

/// \copydoc morpheus::serialisation::concepts::ReaderArchtype::endComposite()
void endComposite();

/// \copydoc morpheus::serialisation::concepts::ReaderArchtype::beginValue()
void beginValue(std::string_view const key);

/// \copydoc morpheus::serialisation::concepts::ReaderArchtype::endValue()
void endValue();

/// \copydoc morpheus::serialisation::concepts::ReaderArchtype::beginSequence()
std::optional<std::size_t> beginSequence();

/// \copydoc morpheus::serialisation::concepts::ReaderArchtype::endSequence()
void endSequence();

/// \copydoc morpheus::serialisation::concepts::ReaderArchtype::beginNullable()
bool beginNullable();

/// \copydoc morpheus::serialisation::concepts::ReaderArchtype::endNullable()
void endNullable();

// clang-format off
/// Read a boolean from the serialisation.
template <typename T>
requires std::is_same_v<T, bool>
T read()
{
auto const [event, next] = getNext();
MORPHEUS_ASSERT(next->index() == 0);
return std::get<T>(*next);
}

/// Reads a integral type from the serialisation.
template <std::integral Interger>
requires(not std::is_same_v<bool, Interger>)
Interger read()
{
auto const [event, next] = getNext();
return std::visit(functional::Overload{
[](std::integral auto const value) { return boost::numeric_cast<Interger>(value); },
[](auto const) -> Interger { throwYamlException("Unable to convert to integral representation"); }
}, *next);
}

/// Reads a float or double type from the serialisation.
template <std::floating_point Float>
Float read()
{
auto const [event, next] = getNext();
return std::visit(functional::Overload {
[](std::integral auto const value) { return boost::numeric_cast<Float>(value); },
[](std::floating_point auto const value)
{
if (std::isinf(value)) [[unlikely]]
{
if (value > 0)
return std::numeric_limits<Float>::infinity();
else
return -std::numeric_limits<Float>::infinity();
}
return boost::numeric_cast<Float>(value);
},
[](auto const) -> Float { throwYamlException("Unable to convert to floating point representation"); }
}, *next);
}

/// Reads a string type from the serialisation.
template <typename T>
requires std::is_same_v<T, std::string>
T read()
{
auto const [event, next] = getNext();
MORPHEUS_ASSERT(next->index() == magic_enum::enum_integer(FundamentalType::String));
return std::get<T>(*next);
}

template <typename T>
requires std::is_same_v<T, std::vector<std::byte>>
T read()
{
return {};
}
// clang-format on

private:
enum class Event : std::uint32_t
{
BeginComposite,
EndComposite,
BeginValue,
Value,
EndValue,
BeginSequence,
EndSequence,
};

friend struct YamlExtracter;
using FundamentalValue = std::variant<bool, std::int64_t, std::uint64_t, float, double, std::string>;
using PossibleValue = std::optional<FundamentalValue>;
using EventValue = std::tuple<Event, PossibleValue>;

[[nodiscard]] EventValue getNext();

memory::polymorphic_value<std::istream> mSourceStream; /// Owned input stream containing the Yaml source.

rapidjson::IStreamWrapper mStream;
rapidjson::Reader mYamlReader;
std::unique_ptr<struct YamlExtracter> mExtractor;
bool mValidate = true;
};

} // namespace morpheus::serialisation
103 changes: 103 additions & 0 deletions libraries/core/src/morpheus/core/serialisation/yaml_writer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#pragma once

#include "morpheus/core/base/platform.hpp"

//#include <rapidjson/writer.h>
//#include <rapidjson/ostreamwrapper.h>

#include <ryml_all.hpp> // details around which header to include are mentioned at
// https://github.com/biojppm/rapidyaml/blob/master/samples/quickstart.cpp


#include <cstddef>
#include <cstdint>
#include <ostream>
#include <optional>
#include <span>
#include <string_view>

namespace morpheus::serialisation
{

/// \class YamlWriter
/// Implementes the concept Writer for a streaming YAML writer which writes item by item to the output stream.
class MORPHEUSCORE_EXPORT YamlWriter
{
public:
static constexpr bool canBeTextual() { return true; }

/// \copydoc morpheus::serialisation::concepts::WriterArchtype::isTextual()
static constexpr bool isTextual() { return true; }

explicit YamlWriter(std::ostream& stream);

/// \copydoc morpheus::serialisation::concepts::WriterArchtype::beginComposite()
void beginComposite();

/// \copydoc morpheus::serialisation::concepts::WriterArchtype::endComposite()
void endComposite();

/// \copydoc morpheus::serialisation::concepts::WriterArchtype::beginValue()
void beginValue(std::string_view const key);

/// \copydoc morpheus::serialisation::concepts::WriterArchtype::endValue()
void endValue();

/// \copydoc morpheus::serialisation::concepts::WriterArchtype::beginSequence()
void beginSequence(std::optional<std::size_t> size = std::nullopt);

/// \copydoc morpheus::serialisation::concepts::WriterArchtype::endSequence()
void endSequence();

/// \copydoc morpheus::serialisation::concepts::WriterArchtype::beginNullable()
void beginNullable(bool const null);

/// \copydoc morpheus::serialisation::concepts::WriterArchtype::endNullable()
void endNullable();

/// Write a boolean to the serialisation.
void write(bool const value);
/// Write a 8-bit unsigned integer to the serialisation.
void write(std::uint8_t const value);
/// Write a 8-bit integer to the serialisation.
void write(std::int8_t const value);
/// Write a 16-bit unsigned integer to the serialisation.
void write(std::uint16_t const value);
/// Write a 16-bit integer to the serialisation.
void write(std::int16_t const value);
/// Write a 32-bit unsigned integer to the serialisation.
void write(std::uint32_t const value);
/// Write a 32-bit integer to the serialisation.
void write(std::int32_t const value);
/// Write a 64-bit unsigned integer to the serialisation.
void write(std::uint64_t const value);
/// Write a 64-bit integer to the serialisation.
void write(std::int64_t const value);
/// Write a float to the serialisation.
void write(float const value);
/// Write a double to the serialisation.
void write(double const value);
/// \copydoc morpheus::serialisation::concepts::WriterArchtype::write(std::string_view const)
void write(std::string_view const value);
/// \copydoc morpheus::serialisation::concepts::WriterArchtype::write(std::span<std::byte> const)
void write(std::span<std::byte const> const value);
/// Write a string literal to the serialisation.
template <std::size_t N> void write(const char(&str)[N]) { write(std::string_view(str, N-1)); }

private:
// TODO completely rework this section for ryml calls instead of rapidjson
template<typename OutputStream>
using RapidYamlWriter = rapidjson::Writer<
OutputStream,
rapidjson::UTF8<>,
rapidjson::UTF8<>,
rapidjson::CrtAllocator,
(rapidjson::kWriteDefaultFlags | rapidjson::kWriteNanAndInfFlag)
>;

rapidjson::OStreamWrapper mStream;
RapidYamlWriter<rapidjson::OStreamWrapper> mYamlWriter;
};


}
2 changes: 2 additions & 0 deletions libraries/core/tests/serialisation/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ target_sources(MorpheusCoreTests
exceptions.tests.cpp
json_reader.tests.cpp
json_writer.tests.cpp
yaml_reader.tests.cpp
yaml_writer.tests.cpp
read_serialiser.tests.cpp
write_serialiser.tests.cpp
)
Expand Down
1 change: 1 addition & 0 deletions libraries/core/tests/serialisation/exceptions.tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ TEST_CASE("Serialisatoin exception helpers", "[morpheus.serialisation.exception.
{
REQUIRE_THROWS_AS(throwBinaryException("Test binary exception"), BinaryException);
REQUIRE_THROWS_AS(throwJsonException("Tesst Json exception"), JsonException);
REQUIRE_THROWS_AS(throwYamlException("Test Yaml exception"), YamlException);
}


Expand Down
Loading