parselink-old/source/include/parselink/msgpack/object.h

260 lines
8.1 KiB
C++

//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Generic (non-owning) wrapper for MessagePack data.
//
// Emphasis on the "non-owning" aspect of these objects. They're intended for
// use in parsing contexts only where the lifetime of the backing data buffer
// exceeds the lifetime of the object instances themselves, so for strings,
// byte-strings, etc. an explicit copy must be made.
//
// TBD: How best to handle arrays and maps.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#ifndef msgpack_object_f43c22522692063f
#define msgpack_object_f43c22522692063f
#include "core/format.h"
#include <string_view>
#include <tl/expected.hpp>
#include <limits>
#include <memory>
#include <tuple>
#include <type_traits>
#include <variant>
#include <vector>
namespace msgpack {
// This API is _currently_ optimizing on the fact that most desktop/server
// platforms are running on 64-bit, but lengths are at most 32 bits. This means
// that a generic object type can utilize the remaining 32-bit padding for type
// information, resulting in an object that only occupies 16 bytes, rather than
// 24. This could be further optimized to include type-specific flags, such as
// whether the data referenced is heap-allocated/managed by the object, or if
// it's merely a view of some other data.
//
// On the flip side, on 32-bit platforms, we manage with just 12 bytes. There
// might be room for optimization on that front, but it's left unimplemented
// for now.
//
// Of course, this means custom code!
template <std::integral SizeType, typename E>
requires (sizeof(SizeType) + sizeof(std::underlying_type_t<E>) <= sizeof(uintptr_t))
class size_and_enum {
public:
using size_type = SizeType;
using enum_type = E;
using enum_int_type = std::underlying_type_t<E>;
constexpr static uintptr_t enum_mask = ((1 << (sizeof(enum_int_type) * 8)) - 1);
constexpr static uintptr_t size_shift = (sizeof(SizeType) * 8);
constexpr static uintptr_t size_mask = ~enum_mask;
// Constructors
constexpr size_and_enum() noexcept = default;
constexpr size_and_enum(size_type size, enum_type enum_value) noexcept {
set_both(size, enum_value);
}
// Accessors
constexpr auto get_size() const noexcept {
return static_cast<size_type>((value & size_mask) >> size_shift);
}
constexpr auto get_enum() const noexcept {
return static_cast<enum_type>((value & enum_mask));
}
constexpr auto rep() const noexcept { return value; }
// Mutators
constexpr auto set_size(size_type size) noexcept {
value = (static_cast<uintptr_t>(size) << size_shift) | (value & enum_mask);
}
constexpr auto set_enum(enum_type enum_value) noexcept {
value = (static_cast<uintptr_t>(enum_value) & enum_mask) | (value & size_mask);
}
constexpr auto set_both(size_type size, enum_type enum_value) noexcept {
value = (static_cast<uintptr_t>(size) << size_shift) |
(static_cast<uintptr_t>(enum_value) & enum_mask);
}
// Explicit conversion
constexpr explicit operator size_type() const noexcept {
return get_size();
}
constexpr explicit operator enum_type() const noexcept {
return get_enum();
}
private:
uintptr_t value{};
};
enum class object_type : std::uint8_t {
invalid,
unsigned_int,
signed_int,
string,
bytes,
nil,
boolean,
array,
map
};
template <size_t WordSize>
class object_base;
// TODO(ksassenrath): Move to own file.
enum class object_error {
wrong_type,
will_truncate
};
// Specialization for 64-bit systems.
template <>
class object_base<8> {
public:
object_base() noexcept = default;
object_base(object_base const& other) noexcept
: value_(other.value_)
, size_and_type_(other.size_and_type_) {}
template <std::integral T>
explicit object_base(T value) noexcept {
if constexpr (std::is_same_v<T, bool>) {
size_and_type_.set_enum(object_type::boolean);
value_.b = value;
} else if constexpr (std::is_signed_v<T>) {
size_and_type_.set_enum(object_type::signed_int);
value_.i = value;
} else {
size_and_type_.set_enum(object_type::unsigned_int);
value_.u = value;
}
}
template <std::convertible_to<std::string_view> T>
explicit object_base(T const& value) noexcept {
std::string_view sv(value);
size_and_type_.set_enum(object_type::string);
size_and_type_.set_size(sv.size());
value_.str = sv.data();
}
template <std::convertible_to<std::span<std::byte const>> T>
explicit object_base(T const& value) noexcept {
std::span<std::byte const> bv(value);
size_and_type_.set_enum(object_type::bytes);
size_and_type_.set_size(bv.size());
value_.bp = bv.data();
}
constexpr object_type type() const noexcept {
return size_and_type_.get_enum();
}
template <typename T>
constexpr tl::expected<T, object_error> get() const noexcept;
template<std::integral T>
constexpr tl::expected<T, object_error> get() const noexcept {
constexpr auto expected_type = std::is_same_v<T, bool> ?
object_type::boolean : std::is_signed_v<T> ?
object_type::signed_int : object_type::unsigned_int;
if (type() != expected_type) {
return tl::make_unexpected(object_error::wrong_type);
}
if constexpr (expected_type == object_type::boolean) {
return T(value_.b);
} else if constexpr (expected_type == object_type::signed_int) {
if (std::numeric_limits<T>::max() < value_.i ||
std::numeric_limits<T>::lowest() > value_.i) {
return tl::make_unexpected(object_error::will_truncate);
}
return T(value_.i);
} else {
if (std::numeric_limits<T>::max() < value_.u) {
return tl::make_unexpected(object_error::will_truncate);
}
return T(value_.u);
}
}
private:
union {
std::uint64_t u;
std::int64_t i;
char const* str;
std::byte const* bp;
bool b;
object_base* obj;
} value_;
size_and_enum<std::uint32_t, object_type> size_and_type_{};
};
template<>
inline tl::expected<std::string, object_error> object_base<8>::get()
const noexcept
{
if (type() != object_type::string) {
return tl::make_unexpected(object_error::wrong_type);
}
return std::string{value_.str, size_and_type_.get_size()};
}
template<>
constexpr tl::expected<std::string_view, object_error> object_base<8>::get()
const noexcept
{
if (type() != object_type::string) {
return tl::make_unexpected(object_error::wrong_type);
}
return std::string_view{value_.str, size_and_type_.get_size()};
}
template<>
inline tl::expected<std::vector<std::byte>, object_error> object_base<8>::get()
const noexcept
{
tl::expected<std::vector<std::byte>, object_error> result;
if (type() != object_type::bytes) {
result = tl::make_unexpected(object_error::wrong_type);
} else {
result = std::vector<std::byte>(value_.bp,
value_.bp + size_and_type_.get_size());
}
return result;
}
template<>
constexpr tl::expected<std::span<std::byte const>, object_error> object_base<8>::get() const noexcept
{
if (type() != object_type::bytes) {
return tl::make_unexpected(object_error::wrong_type);
}
return std::span<std::byte const>(value_.bp, size_and_type_.get_size());
}
using object = object_base<sizeof(void*)>;
} // namespace msgpack
#endif // msgpack_object_f43c22522692063f