// Licensed under the Boost License . // SPDX-License-Identifier: BSL-1.0 #pragma once #include #include #include #include #include #include #include namespace tser{ //implementation details for C++20 is_detected namespace detail { struct ns { ~ns() = delete; ns(ns const&) = delete; }; template class Op, class... Args> struct detector { using value_t = std::false_type; using type = Default; }; template class Op, class... Args> struct detector>, Op, Args...> { using value_t = std::true_type; using type = Op; }; template struct is_array : std::is_array {}; template class TArray, typename T, size_t N> struct is_array> : std::true_type {}; constexpr size_t n_args(char const* c, size_t nargs = 1) { for (; *c; ++c) if (*c == ',') ++nargs; return nargs; } constexpr size_t str_size(char const* c, size_t strSize = 1) { for (; *c; ++c) ++strSize; return strSize; } } // we need a bunch of template metaprogramming for being able to differentiate between different types template class Op, class... Args> constexpr bool is_detected_v = detail::detector::value_t::value; class BinaryArchive; template using has_begin_t = decltype(*std::begin(std::declval())); template using has_members_t = decltype(std::declval().members()); template using has_smaller_t = decltype(std::declval() < std::declval()); template using has_equal_t = decltype(std::declval() == std::declval()); template using has_nequal_t = decltype(std::declval() != std::declval()); template using has_outstream_op_t = decltype(std::declval() << std::declval()); template using has_tuple_t = std::tuple_element_t<0, T>; template using has_optional_t = decltype(std::declval().has_value()); template using has_element_t = typename T::element_type; template using has_mapped_t = typename T::mapped_type; template using has_custom_save_t = decltype(std::declval().save(std::declval())); template using has_free_save_t = decltype(std::declval() << std::declval()); template constexpr bool is_container_v = is_detected_v; template constexpr bool is_tuple_v = is_detected_v; template constexpr bool is_tser_t_v = is_detected_v; template constexpr bool is_pointer_like_v = std::is_pointer_v || is_detected_v || is_detected_v; //implementation of the recursive json printing template constexpr inline decltype(auto) print(std::ostream& os, T&& val) { using V = std::decay_t; if constexpr (std::is_constructible_v || std::is_same_v) { os << "\"" << val << "\""; } else if constexpr (is_container_v) { size_t i = 0; os << "\n["; for (auto& elem : val) { os << (i++ == 0 ? "" : ",") << tser::print(os, elem); } os << "]\n"; } else if constexpr (is_tser_t_v && !is_detected_v) { auto pMem = [&](auto& ... memberVal) { size_t i = 0; (((os << (i != 0 ? ", " : "") << '\"'), os << V::_memberNames[i++] << "\" : " << tser::print(os, memberVal)), ...); }; os << "{ \"" << V::_typeName << "\": {"; std::apply(pMem, val.members()); os << "}}\n"; } else if constexpr (std::is_enum_v &&! is_detected_v) { os << tser::print(os, static_cast>(val)); } else if constexpr (is_tuple_v && !is_detected_v) { std::apply([&](auto& ... t) { int i = 0; os << "{"; (((i++ != 0 ? os << ", " : os), tser::print(os, t)), ...); // WTF os << "}"; }, val); } else if constexpr (is_pointer_like_v) { os << (val ? (os << (tser::print(os, *val)), "") : "null"); } else { os << val; } return ""; } class BinaryArchive { std::string m_bytes = std::string(1024, '\0'); size_t m_bufferSize = 0, m_readOffset = 0; public: explicit BinaryArchive(const size_t initialSize = 1024) : m_bytes(initialSize, '\0') {} template explicit BinaryArchive(const T& t) { save(t); } template void save(const T& t) { if constexpr (is_detected_v) { operator<<(t,*this); } else if constexpr (is_detected_v) { t.save(*this); } else if constexpr(is_tser_t_v) { std::apply([&](auto& ... mVal) { (save(mVal), ...); }, t.members()); } else if constexpr(is_tuple_v) { std::apply([&](auto& ... tVal) { (save(tVal), ...); }, t); } else if constexpr (is_pointer_like_v) { save(static_cast(t)); if (t) save(*t); } else if constexpr (is_container_v) { if constexpr (!detail::is_array::value) { save(t.size()); } for (auto& val : t) save(val); } else { if (m_bufferSize + sizeof(T) + sizeof(T) / 4 > m_bytes.size()) { m_bytes.resize((m_bufferSize + sizeof(T)) * 2); } std::memcpy(m_bytes.data() + m_bufferSize, std::addressof(t), sizeof(T)); m_bufferSize += sizeof(T); } } template void load(T& t) { using V = std::decay_t; if constexpr (is_detected_v) { operator>>(t, *this); } else if constexpr (is_detected_v) { t.load(*this); } else if constexpr (is_tser_t_v) { std::apply([&](auto& ... mVal) { (load(mVal), ...); }, t.members()); } else if constexpr (is_tuple_v) { std::apply([&](auto& ... tVal) { (load(tVal), ...); }, t); } else if constexpr (is_pointer_like_v) { if constexpr (std::is_pointer_v) { t = load() ? (t = new std::remove_pointer_t(), load(*t), t) : nullptr; } else if constexpr (is_detected_v) { t = load() ? T(load()) : T(); } else { //smart pointer t = T(load*>()); } } else if constexpr (is_container_v) { if constexpr (!detail::is_array::value) { const auto size = load(); using VT = typename V::value_type; for (size_t i = 0; i < size; ++i) { if constexpr (!is_detected_v) { t.insert(t.end(), load()); } else { //we have to special case map, because of the const key t.emplace(VT{ load(), load() }); } } } else { for (auto& val : t) load(val); } } else { std::memcpy(&t, m_bytes.data() + m_readOffset, sizeof(T)); m_readOffset += sizeof(T); } } template T load() { std::remove_const_t t{}; load(t); return t; } template friend BinaryArchive& operator<<(BinaryArchive& ba, const T& t) { ba.save(t); return ba; } template friend BinaryArchive& operator>>(BinaryArchive& ba, T& t) { ba.load(t); return ba; } void reset() { m_bufferSize = 0; m_readOffset = 0; } void initialize(std::string_view str) { m_bytes = str; m_bufferSize = str.size(); m_readOffset = 0; } std::string_view get_buffer() const { return std::string_view(m_bytes.data(), m_bufferSize); } }; template std::conditional_t, const Base, Base>& base(Derived* thisPtr) { return *thisPtr; } template auto load(std::string_view encoded) { BinaryArchive ba(encoded); return ba.load(); } } //this macro defines printing, serialisation and comparision operators (==,!=,<) for custom types #define DEFINE_SERIALIZABLE(Type, ...) \ inline decltype(auto) members() const { return std::tie(__VA_ARGS__); } \ inline decltype(auto) members() { return std::tie(__VA_ARGS__); } \ static constexpr std::array _memberNameData = [](){ \ std::array chars{'\0'}; size_t _idx = 0; constexpr auto* ini(#__VA_ARGS__); \ for (char const* _c = ini; *_c; ++_c, ++_idx) if(*_c != ',' && *_c != ' ') chars[_idx] = *_c; return chars;}(); \ static constexpr const char* _typeName = #Type; \ static constexpr std::array _memberNames = \ [](){ std::array out{ }; \ for(size_t _i = 0, nArgs = 0; nArgs < tser::detail::n_args(#__VA_ARGS__) ; ++_i) { \ while(Type::_memberNameData[_i] == '\0') _i++; out[nArgs++] = &Type::_memberNameData[_i]; \ while(Type::_memberNameData[++_i] != '\0'); } return out;}();\ template && !tser::is_detected_v, int> = 0>\ friend std::ostream& operator<<(std::ostream& os, const OT& t) { tser::print(os, t); return os; }