LeviLamina
Loading...
Searching...
No Matches
Version.h
1#pragma once
2
3#include <algorithm>
4#include <charconv>
5#include <compare>
6#include <concepts>
7#include <cstddef>
8#include <cstdint>
9#include <limits>
10#include <optional>
11#include <stdexcept>
12#include <string>
13#include <string_view>
14#include <system_error>
15#include <type_traits>
16#include <utility>
17#include <variant>
18#include <vector>
19
20#include "ll/api/reflection/ReflectionError.h"
21#include "ll/api/utils/HashUtils.h"
22
23#include "fmt/core.h"
24
25namespace ll::data {
26
27namespace detail {
28
29struct from_chars_result : std::from_chars_result {
30 [[nodiscard]] constexpr operator bool() const noexcept { return ec == std::errc{}; }
31 constexpr void value() const {
32 if (ec != std::errc{}) {
33 throw std::system_error{std::make_error_code(ec)};
34 }
35 }
36};
37
38constexpr inline auto min_version_string_length = 5;
39
40constexpr bool is_digit(char c) noexcept { return c >= '0' && c <= '9'; }
41
42constexpr bool is_letter(char c) noexcept { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); }
43
44constexpr bool is_identifier_char(char c) noexcept { return is_digit(c) || is_letter(c) || c == '-'; }
45
46constexpr std::uint16_t to_digit(char c) noexcept { return static_cast<std::uint16_t>(c - '0'); }
47
48constexpr void append_number(std::string& output, std::uint16_t value) {
49 char digits[5];
50 size_t size{};
51 do {
52 digits[size++] = static_cast<char>('0' + value % 10);
53 value = static_cast<std::uint16_t>(value / 10);
54 } while (value != 0);
55 while (size != 0) {
56 output.push_back(digits[--size]);
57 }
58}
59
60constexpr from_chars_result from_chars(char const* first, char const* last, std::uint16_t& value) noexcept {
61 if (first == nullptr || last == nullptr || first >= last || !is_digit(*first)) {
62 return {first, std::errc::invalid_argument};
63 }
64 std::uint32_t parsed = 0;
65 for (; first != last && is_digit(*first); ++first) {
66 parsed = parsed * 10 + to_digit(*first);
67 if (parsed > (std::numeric_limits<std::uint16_t>::max)()) {
68 return {first, std::errc::result_out_of_range};
69 }
70 }
71 value = static_cast<std::uint16_t>(parsed);
72 return {first, std::errc{}};
73}
74
75constexpr from_chars_result
76parse_numeric_identifier(char const* first, char const* last, std::uint16_t& value) noexcept {
77 if (first == nullptr || last == nullptr || first >= last || (last - first > 1 && *first == '0')) {
78 return {first, std::errc::invalid_argument};
79 }
80 auto result = from_chars(first, last, value);
81 if (!result || result.ptr != last) {
82 return {result.ptr, result.ec == std::errc{} ? std::errc::invalid_argument : result.ec};
83 }
84 return result;
85}
86
87constexpr bool check_delimiter(char const* first, char const* last, char delimiter) noexcept {
88 return first != nullptr && first != last && *first == delimiter;
89}
90
91constexpr from_chars_result validate_identifiers(char const* first, char const* last) noexcept {
92 if (first == nullptr || last == nullptr || first >= last) {
93 return {first, std::errc::invalid_argument};
94 }
95 auto identifierBegin = first;
96 for (auto current = first; current != last; ++current) {
97 if (*current == '.') {
98 if (identifierBegin == current) {
99 return {current, std::errc::invalid_argument};
100 }
101 identifierBegin = current + 1;
102 } else if (!is_identifier_char(*current)) {
103 return {current, std::errc::invalid_argument};
104 }
105 }
106 if (identifierBegin == last) {
107 return {last, std::errc::invalid_argument};
108 }
109 return {last, std::errc{}};
110}
111
112} // namespace detail
113
114struct PreRelease {
115 std::vector<std::variant<std::string, std::uint16_t>> values;
116
117 constexpr PreRelease() = default;
118 constexpr ~PreRelease() = default;
119 constexpr explicit PreRelease(std::string_view str) { from_string(str); }
120
121 constexpr std::strong_ordering operator<=>(PreRelease const& other) const noexcept {
122 for (std::size_t i = 0; i < std::min(values.size(), other.values.size()); ++i) {
123 if (std::holds_alternative<std::string>(values[i])) {
124 if (std::holds_alternative<std::string>(other.values[i])) {
125 if (std::get<std::string>(values[i]) != std::get<std::string>(other.values[i])) {
126 return std::get<std::string>(values[i]) <=> std::get<std::string>(other.values[i]);
127 }
128 } else {
129 return std::strong_ordering::greater;
130 }
131 } else if (std::holds_alternative<std::string>(other.values[i])) {
132 return std::strong_ordering::less;
133 } else if (std::get<std::uint16_t>(values[i]) != std::get<std::uint16_t>(other.values[i])) {
134 return std::get<std::uint16_t>(values[i]) <=> std::get<std::uint16_t>(other.values[i]);
135 }
136 }
137 return values.size() <=> other.values.size();
138 }
139
140 constexpr bool operator==(PreRelease const& other) const noexcept = default;
141
142 constexpr detail::from_chars_result from_chars(char const* first, char const* last) noexcept {
143 if (first == nullptr || last == nullptr || first >= last) {
144 return {first, std::errc::invalid_argument};
145 }
146
147 std::vector<std::variant<std::string, std::uint16_t>> parsed;
148 auto current = first;
149 while (current != last && *current != '+') {
150 auto identifierBegin = current;
151 while (current != last && *current != '.' && *current != '+') {
152 if (!detail::is_identifier_char(*current)) {
153 return {current, std::errc::invalid_argument};
154 }
155 ++current;
156 }
157 if (identifierBegin == current) {
158 return {current, std::errc::invalid_argument};
159 }
160
161 bool numeric = std::all_of(identifierBegin, current, detail::is_digit);
162 if (numeric) {
163 std::uint16_t value{};
164 auto result = detail::parse_numeric_identifier(identifierBegin, current, value);
165 if (!result) {
166 return result;
167 }
168 parsed.emplace_back(value);
169 } else {
170 parsed.emplace_back(std::string{identifierBegin, current});
171 }
172
173 if (current != last && *current == '.') {
174 ++current;
175 if (current == last || *current == '+' || *current == '.') {
176 return {current, std::errc::invalid_argument};
177 }
178 }
179 }
180
181 values = std::move(parsed);
182 return {current, std::errc{}};
183 }
184
185 [[nodiscard]] constexpr detail::from_chars_result from_string_noexcept(std::string_view str) noexcept {
186 auto result = from_chars(str.data(), str.data() + str.length());
187 if (result && result.ptr != str.data() + str.length()) {
188 return {result.ptr, std::errc::invalid_argument};
189 }
190 return result;
191 }
192
193 constexpr PreRelease& from_string(std::string_view str) {
194 from_string_noexcept(str).value();
195 return *this;
196 }
197
198 [[nodiscard]] constexpr std::string to_string() const {
199 std::string str;
200 for (auto const& value : values) {
201 if (!str.empty()) {
202 str += '.';
203 }
204 if (std::holds_alternative<std::string>(value)) {
205 str += std::get<std::string>(value);
206 } else {
207 detail::append_number(str, std::get<std::uint16_t>(value));
208 }
209 }
210 return str;
211 }
212};
213
214struct Version {
215 std::uint16_t major = 0;
216 std::uint16_t minor = 1;
217 std::uint16_t patch = 0;
218 std::optional<PreRelease> preRelease;
219 std::optional<std::string> build;
220
221 constexpr Version() = default;
222 constexpr ~Version() = default;
223
224 constexpr Version(
225 std::uint16_t mj,
226 std::uint16_t mn,
227 std::uint16_t pt,
228 std::optional<PreRelease> prt = {},
229 std::optional<std::string> bu = {}
230 ) noexcept
231 : major{mj},
232 minor{mn},
233 patch{pt},
234 preRelease{std::move(prt)},
235 build{std::move(bu)} {}
236
237 constexpr Version(
238 std::uint16_t mj,
239 std::uint16_t mn,
240 std::uint16_t pt,
241 std::string_view prt,
242 std::optional<std::string> bu = {}
243 )
244 : major{mj},
245 minor{mn},
246 patch{pt},
247 preRelease{PreRelease{prt}},
248 build{std::move(bu)} {}
249
250 explicit constexpr Version(std::string_view str) : Version() { from_string(str); }
251
252 [[nodiscard]] constexpr detail::from_chars_result from_chars(char const* first, char const* last) noexcept {
253 if (first == nullptr || last == nullptr || first >= last
254 || (last - first) < detail::min_version_string_length) {
255 return {first, std::errc::invalid_argument};
256 }
257
258 auto parseCorePart = [last](char const*& current, std::uint16_t& value) -> detail::from_chars_result {
259 auto begin = current;
260 auto result = detail::from_chars(current, last, value);
261 if (!result) {
262 return result;
263 }
264 if (result.ptr - begin > 1 && *begin == '0') {
265 return {begin, std::errc::invalid_argument};
266 }
267 current = result.ptr;
268 return result;
269 };
270
271 auto current = first;
272 std::uint16_t parsedMajor{};
273 std::uint16_t parsedMinor{};
274 std::uint16_t parsedPatch{};
275
276 if (auto result = parseCorePart(current, parsedMajor); !result) {
277 return result;
278 }
279 if (!detail::check_delimiter(current, last, '.')) {
280 return {current, std::errc::invalid_argument};
281 }
282 ++current;
283 if (auto result = parseCorePart(current, parsedMinor); !result) {
284 return result;
285 }
286 if (!detail::check_delimiter(current, last, '.')) {
287 return {current, std::errc::invalid_argument};
288 }
289 ++current;
290 if (auto result = parseCorePart(current, parsedPatch); !result) {
291 return result;
292 }
293
294 std::optional<PreRelease> parsedPreRelease;
295 std::optional<std::string> parsedBuild;
296 if (detail::check_delimiter(current, last, '-')) {
297 PreRelease preRelease;
298 auto result = preRelease.from_chars(++current, last);
299 if (!result) {
300 return result;
301 }
302 parsedPreRelease = std::move(preRelease);
303 current = result.ptr;
304 }
305 if (detail::check_delimiter(current, last, '+')) {
306 auto buildBegin = ++current;
307 auto result = detail::validate_identifiers(buildBegin, last);
308 if (!result) {
309 return result;
310 }
311 parsedBuild = std::string{buildBegin, last};
312 current = last;
313 }
314 if (current != last) {
315 return {current, std::errc::invalid_argument};
316 }
317
318 major = parsedMajor;
319 minor = parsedMinor;
320 patch = parsedPatch;
321 preRelease = std::move(parsedPreRelease);
322 build = std::move(parsedBuild);
323 return {current, std::errc{}};
324 }
325
326 [[nodiscard]] constexpr detail::from_chars_result from_string_noexcept(std::string_view str) noexcept {
327 return from_chars(str.data(), str.data() + str.length());
328 }
329
330 constexpr Version& from_string(std::string_view str) {
331 from_string_noexcept(str).value();
332 return *this;
333 }
334
335 [[nodiscard]] constexpr std::string to_string() const {
336 std::string str;
337 detail::append_number(str, major);
338 str += '.';
339 detail::append_number(str, minor);
340 str += '.';
341 detail::append_number(str, patch);
342 if (preRelease) {
343 str += '-';
344 str += preRelease->to_string();
345 }
346 if (build) {
347 str += '+';
348 str += *build;
349 }
350 return str;
351 }
352
353 [[nodiscard]] constexpr std::strong_ordering operator<=>(Version const& other) const noexcept {
354 if (major != other.major) {
355 return major <=> other.major;
356 }
357 if (minor != other.minor) {
358 return minor <=> other.minor;
359 }
360 if (patch != other.patch) {
361 return patch <=> other.patch;
362 }
363 if (preRelease) {
364 if (other.preRelease) {
365 return *preRelease <=> *other.preRelease;
366 }
367 return std::strong_ordering::less;
368 }
369 if (other.preRelease) {
370 return std::strong_ordering::greater;
371 }
372 return std::strong_ordering::equal;
373 }
374
375 [[nodiscard]] constexpr bool operator==(Version const& other) const noexcept {
376 return *this <=> other == std::strong_ordering::equal;
377 }
378
379 [[nodiscard]] constexpr bool isIdenticalTo(Version const& other) const noexcept {
380 return *this == other && build == other.build;
381 }
382
383 [[nodiscard]] [[maybe_unused]] static constexpr bool valid(std::string_view str) noexcept {
384 return Version{}.from_string_noexcept(str);
385 }
386};
387
388template <class J, class T>
389[[nodiscard]] inline Expected<J> serialize(T&& ver) noexcept
390 requires(std::same_as<std::remove_cvref_t<T>, Version>)
391try {
392 return ver.to_string();
393} catch (...) {
394 return makeExceptionError();
395}
396
397template <class T, class J>
398[[nodiscard]] inline Expected<> deserialize(T& ver, J const& j) noexcept
399 requires(std::same_as<T, Version>)
400{
401 if (!j.is_string()) {
402 return reflection::makeDeserStringTypeError();
403 }
404 if (auto result = ver.from_string_noexcept((std::string const&)j); result) {
405 return {};
406 } else {
407 return makeErrorCodeError(result.ec);
408 }
409}
410
411namespace literals {
412[[nodiscard]] constexpr Version operator""_version(char const* str, std::size_t length) {
413 return Version{
414 std::string_view{str, length}
415 };
416}
417} // namespace literals
418
419} // namespace ll::data
420
421namespace std {
422template <>
423struct hash<ll::data::PreRelease> {
424 size_t operator()(ll::data::PreRelease const& preRelease) const noexcept {
425 return ll::hash_utils::HashCombiner{preRelease.values.size()}.addRange(preRelease.values);
426 }
427};
428
429template <>
430struct hash<ll::data::Version> {
431 size_t operator()(ll::data::Version const& version) const noexcept {
432 return ll::hash_utils::HashCombiner{version.major}
433 .add(version.minor)
434 .add(version.patch)
435 .add(version.preRelease);
436 }
437};
438} // namespace std
STL namespace.
Definition Version.h:114
Definition Version.h:214