aboutsummaryrefslogtreecommitdiffstats
path: root/nihil.ucl/string.ccm
blob: 4b46e395aef662ed6620aeea4154158ff008da8f (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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
// This source code is released into the public domain.
module;

#include <ucl.h>

export module nihil.ucl:string;

import nihil.std;
import nihil.core;
import :object;
import :type;

namespace nihil::ucl {

export struct string final : object
{
	using contained_type = std::string_view;
	static constexpr object_type ucl_type = object_type::string;

	// string is a container of char
	using value_type = char const;
	using size_type = std::size_t;
	using difference_type = std::ptrdiff_t;
	using reference = value_type &;
	using pointer = value_type *;
	using iterator = pointer;

	// Create a new empty string.  Throws std::system_error on failure.
	string()
		: string(std::string_view(""))
	{
	}

	// Create a string from a value.  Throws std::system_error on failure.
	explicit string(std::string_view value)
		: string(noref, [&] {
			auto *uobj = ::ucl_object_fromstring_common(value.data(), value.size(),
		                                                    UCL_STRING_RAW);
			if (uobj == nullptr)
				throw std::system_error(std::make_error_code(sys_error()));
			return uobj;
		}())
	{
	}

	// Create a string from a C literal.  Throws std::system_error
	// on failure.
	explicit string(char const *value)
		: string(std::string_view(value))
	{
	}

	// Create a string from a contiguous range.  The range's value type
	// must be char.  Throws std::system_error on failure.
	template <std::ranges::contiguous_range Range>
	requires(!std::same_as<std::string_view, Range> &&
	         std::same_as<char, std::ranges::range_value_t<Range>>)
	explicit string(Range const &range)
		: string(std::string_view(std::ranges::begin(range), std::ranges::end(range)))
	{
	}

	// Create a string from a non-contiguous range.  This requires a
	// temporary value due to limitations of the UCL C API.
	template <std::ranges::range Range>
	requires(!std::ranges::contiguous_range<Range> &&
	         std::same_as<char, std::ranges::range_value_t<Range>>)
	explicit string(Range range)
		: string(std::string(std::from_range, std::forward<Range>(range)))
	{
	}

	// Create a string from an iterator pair.  The iterator's value type
	// must be char.  If the iterator pair is not contiguous, the value
	// will be copied to a temporary first.
	//
	// Throws std::system_error on failure.
	template <std::input_iterator Iterator>
	requires(std::same_as<char, std::iter_value_t<Iterator>>)
	string(Iterator first, Iterator last)
		: string(std::ranges::subrange(first, last))
	{
	}

	// Create a new string from a UCL object.  Throws type_mismatch
	// on failure.
	string(ref_t, ::ucl_object_t const *uobj)
		: object(nihil::ucl::ref, ensure_ucl_type(uobj, string::ucl_type))
	{
	}

	string(noref_t, ::ucl_object_t *uobj)
		: object(nihil::ucl::noref, ensure_ucl_type(uobj, string::ucl_type))
	{
	}

	// Return the value of this string.
	[[nodiscard]] auto value(this string const &self) -> contained_type
	{
		char const *dptr{};
		std::size_t dlen{};

		auto const *uobj = self.get_ucl_object();
		if (::ucl_object_tolstring_safe(uobj, &dptr, &dlen))
			return {dptr, dlen};

		throw std::runtime_error("ucl_object_tolstring_safe() failed");
	}

	// Return the size of this string.
	[[nodiscard]] auto size(this string const &self) -> size_type
	{
		return self.value().size();
	}

	// Test if this string is empty.
	[[nodiscard]] auto empty(this string const &self) -> bool
	{
		return self.size() == 0;
	}

	// Access this string's data
	[[nodiscard]] auto data(this string const &self) -> pointer
	{
		char const *dptr{};

		auto const *uobj = self.get_ucl_object();
		if (::ucl_object_tostring_safe(uobj, &dptr))
			return dptr;

		throw std::runtime_error("ucl_object_tostring_safe() failed");
	}

	// Iterator access
	[[nodiscard]] auto begin(this string const &self) -> iterator
	{
		return self.data();
	}

	[[nodiscard]] auto end(this string const &self) -> iterator
	{
		return self.data() + self.size();
	}

private:
	//
	// Comparison operators.
	//

	[[nodiscard]] friend auto operator==(string const &a, string const &b) -> bool
	{
		return a.value() == b.value();
	}

	[[nodiscard]] friend auto
	operator<=>(string const &a, string const &b) -> std::strong_ordering
	{
		return a.value() <=> b.value();
	}

	// For convenience, allow comparison with C++ strings without having to
	// construct a temporary UCL object.

	[[nodiscard]] friend auto operator==(string const &lhs, std::string_view rhs) -> bool
	{
		return lhs.value() == rhs;
	}

	[[nodiscard]] friend auto
	operator<=>(string const &lhs, std::string_view rhs) -> std::strong_ordering
	{
		return lhs.value() <=> rhs;
	}

	[[nodiscard]] friend auto operator==(string const &lhs, std::string const &rhs) -> bool
	{
		return lhs == std::string_view(rhs);
	}

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

	[[nodiscard]] friend auto operator==(string const &lhs, char const *rhs) -> bool
	{
		return lhs == std::string_view(rhs);
	}

	[[nodiscard]] friend auto
	operator<=>(string const &lhs, char const *rhs) -> std::strong_ordering
	{
		return lhs <=> std::string_view(rhs);
	}

	// Stream output.
	friend auto operator<<(std::ostream &strm, string const &s) -> std::ostream &
	{
		return strm << s.value();
	}
};

//
// String constructors.  These return an error instead of throwing.
//

// From string_view
export [[nodiscard]] auto make_string(std::string_view s) -> std::expected<string, error>
{
	auto *uobj = ::ucl_object_fromstring_common(s.data(), s.size(), UCL_STRING_RAW);

	if (uobj == nullptr)
		return std::unexpected(error(errc::failed_to_create_object, error(sys_error())));

	return string(noref, uobj);
}

// Empty string
export [[nodiscard]] auto make_string() -> std::expected<string, error>
{
	return make_string(std::string_view(""));
}

// From C literal
export [[nodiscard]] auto make_string(char const *s) -> std::expected<string, error>
{
	return make_string(std::string_view(s));
}

// From contiguous range
export template <std::ranges::contiguous_range Range>
requires(!std::same_as<std::string_view, Range> &&
         std::same_as<char, std::ranges::range_value_t<Range>>)
[[nodiscard]] auto make_string(Range &&range)
{
	return make_string(std::string_view(std::forward<Range>(range)));
}

// From non-contiguous range
export template <std::ranges::range Range>
requires(!std::ranges::contiguous_range<Range> &&
         std::same_as<char, std::ranges::range_value_t<Range>>)
[[nodiscard]] auto make_string(Range &&range)
{
	return make_string(std::string(std::from_range, std::forward<Range>(range)));
}

// From iterator pair
export template <std::input_iterator Iterator>
requires(std::same_as<char, std::iter_value_t<Iterator>>)
[[nodiscard]] auto make_string(Iterator first, Iterator last)
{
	return make_string(std::ranges::subrange(first, last));
}

/*
 * Print a string to a stream.
 */
export auto operator<<(std::ostream &, string const &) -> std::ostream &;

/*
 * Literal operator.
 */
inline namespace literals {
export constexpr auto operator""_ucl(char const *s, std::size_t n) -> string
{
	return string(std::string_view(s, n));
}
} // namespace literals

} // namespace nihil::ucl

namespace nihil {
inline namespace literals {
export using namespace ::nihil::ucl::literals;
}
} // namespace nihil

/*
 * std::formatter for a string.  This provides the same format operations
 * as std::formatter<std::string_view>.
 */
export template <>
struct std::formatter<nihil::ucl::string, char>
{
	std::formatter<std::string_view> base_formatter;

	template <class ParseContext>
	constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator
	{
		return base_formatter.parse(ctx);
	}

	template <class FmtContext>
	auto format(nihil::ucl::string const &o, FmtContext &ctx) const -> FmtContext::iterator
	{
		return base_formatter.format(o.value(), ctx);
	}
};