260 lines
8.1 KiB
C++
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
|