aboutsummaryrefslogtreecommitdiffstats
path: root/nihil.ucl/object.ccm
blob: 93dc4db0733a5ba412828960a4cf94542183666d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// This source code is released into the public domain.
module;

// A UCL object.  The object is immutable and internally refcounted, so it
// may be copied as needed.

#include <ucl.h>

export module nihil.ucl:object;

import nihil.std;
import :type;

namespace nihil::ucl {

//***********************************************************************
// The basic object type.

// Ref the UCL object when creating an object.
export inline constexpr struct ref_t
{
} ref;

// Don't ref the UCL object.
export inline constexpr struct noref_t
{
} noref;

export struct object
{
	static constexpr object_type ucl_type = object_type::object;

	// Create an object from an existing ucl_object_t.  The first argument
	// determines whether we ref the object or not.

	object(ref_t, ::ucl_object_t const *object)
		: m_object(::ucl_object_ref(object))
	{
	}

	object(noref_t, ::ucl_object_t *object)
		: m_object(object)
	{
	}

	// Free our object on destruction.
	virtual ~object()
	{
		if (m_object != nullptr)
			::ucl_object_unref(m_object);
	}

	// Movable.
	object(object &&other) noexcept
		: m_object(std::exchange(other.m_object, nullptr))
	{
	}

	auto operator=(this object &self, object &&other) noexcept -> object &
	{
		if (&self != &other)
			self.m_object = std::exchange(other.m_object, nullptr);
		return self; // NOLINT
	}

	// Copyable.  Note that this copies the entire UCL object.

	object(object const &other)
		: m_object([&] {
			auto *uobj = ::ucl_object_copy(other.get_ucl_object());
			if (uobj == nullptr)
				throw std::runtime_error("failed to copy UCL object");
			return uobj;
		}())
	{
	}

	auto operator=(this object &self, object const &other) -> object &
	{
		if (&self != &other)
			self = object(other);
		return self; // NOLINT
	}

	// Increase the refcount of this object.
	[[nodiscard]] auto ref(this object const &self) -> object
	{
		return {nihil::ucl::ref, self.get_ucl_object()};
	}

	// Return the type of this object.
	[[nodiscard]] auto type(this object const &self) -> object_type
	{
		auto utype = ::ucl_object_type(self.get_ucl_object());
		return static_cast<object_type>(utype);
	}

	// Return the underlying object.
	[[nodiscard]] auto get_ucl_object(this object &self) -> ::ucl_object_t *
	{
		if (self.m_object == nullptr)
			throw std::logic_error("attempt to access empty UCL object");
		return self.m_object;
	}

	[[nodiscard]] auto get_ucl_object(this object const &self) -> ::ucl_object_t const *
	{
		if (self.m_object == nullptr)
			throw std::logic_error("attempt to access empty UCL object");
		return self.m_object;
	}

	// Return the key of this object.
	[[nodiscard]] auto key(this object const &self) -> std::string_view
	{
		auto        dlen = std::size_t{};
		auto const *dptr = ::ucl_object_keyl(self.get_ucl_object(), &dlen);
		return {dptr, dlen};
	}

protected:
	friend auto swap(object &a, object &b) noexcept -> void
	{
		std::swap(a.m_object, b.m_object);
	}

	// Helper to validate the type of a UCL object.  Throws type_mismatch if the
	// type doesn't match, or else returns the pointer.
	[[nodiscard]] static auto ensure_ucl_type(::ucl_object_t const *uobj, object_type type)
		-> ::ucl_object_t const *
	{
		if (static_cast<object_type>(::ucl_object_type(uobj)) != type)
			throw type_mismatch(type, static_cast<object_type>(::ucl_object_type(uobj)));
		return uobj;
	}

	[[nodiscard]] static auto ensure_ucl_type(::ucl_object_t *uobj, object_type type)
		-> ::ucl_object_t *
	{
		if (static_cast<object_type>(::ucl_object_type(uobj)) != type)
			throw type_mismatch(type, static_cast<object_type>(::ucl_object_type(uobj)));
		return uobj;
	}

private:
	// The object we're wrapping.
	::ucl_object_t *m_object = nullptr;

	//
	// Object comparison.
	//

	[[nodiscard]] friend auto
	operator<=>(object const &lhs, object const &rhs) -> std::strong_ordering
	{
		auto cmp = ::ucl_object_compare(lhs.get_ucl_object(), rhs.get_ucl_object());

		if (cmp < 0)
			return std::strong_ordering::less;
		else if (cmp > 0)
			return std::strong_ordering::greater;
		else
			return std::strong_ordering::equal;
	}

	[[nodiscard]] friend auto operator==(object const &lhs, object const &rhs) -> bool
	{
		return (lhs <=> rhs) == std::strong_ordering::equal;
	}
};

} // namespace nihil::ucl