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/Expected.h"
21#include "ll/api/reflection/Reflection.h"
22#include "ll/api/utils/HashUtils.h"
23
24#include "fmt/core.h"
25
26namespace ll::data {
27
28namespace detail {
29
30struct from_chars_result : std::from_chars_result {
31 [[nodiscard]] constexpr operator bool() const noexcept { return ec == std::errc{}; }
32 constexpr void value() const {
33 if (ec != std::errc{}) {
34 throw std::system_error{std::make_error_code(ec)};
35 }
36 }
37};
38
39constexpr inline auto min_version_string_length = 5;
40
41constexpr bool is_digit(char c) noexcept { return c >= '0' && c <= '9'; }
42
43constexpr bool is_letter(char c) noexcept { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); }
44
45constexpr bool is_identifier_char(char c) noexcept { return is_digit(c) || is_letter(c) || c == '-'; }
46
47constexpr uint16 to_digit(char c) noexcept { return static_cast<uint16>(c - '0'); }
48
49constexpr void append_number(std::string& output, uint16 value) {
50 char digits[5];
51 size_t size{};
52 do {
53 digits[size++] = static_cast<char>('0' + value % 10);
54 value = static_cast<uint16>(value / 10);
55 } while (value != 0);
56 while (size != 0) {
57 output.push_back(digits[--size]);
58 }
59}
60
61constexpr from_chars_result from_chars(char const* first, char const* last, uint16& value) noexcept {
62 if (first == nullptr || last == nullptr || first >= last || !is_digit(*first)) {
63 return {first, std::errc::invalid_argument};
64 }
65 uint32 parsed = 0;
66 for (; first != last && is_digit(*first); ++first) {
67 parsed = parsed * 10 + to_digit(*first);
68 if (parsed > (std::numeric_limits<uint16>::max)()) {
69 return {first, std::errc::result_out_of_range};
70 }
71 }
72 value = static_cast<uint16>(parsed);
73 return {first, std::errc{}};
74}
75
76constexpr from_chars_result parse_numeric_identifier(char const* first, char const* last, uint16& 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, uint16>> 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<uint16>(values[i]) != std::get<uint16>(other.values[i])) {
134 return std::get<uint16>(values[i]) <=> std::get<uint16>(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, uint16>> 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 uint16 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<uint16>(value));
208 }
209 }
210 return str;
211 }
212};
213
214struct Version {
215 uint16 major = 0;
216 uint16 minor = 1;
217 uint16 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 uint16 mj,
226 uint16 mn,
227 uint16 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(uint16 mj, uint16 mn, uint16 pt, std::string_view prt, std::optional<std::string> bu = {})
238 : major{mj},
239 minor{mn},
240 patch{pt},
241 preRelease{PreRelease{prt}},
242 build{std::move(bu)} {}
243
244 explicit constexpr Version(std::string_view str) : Version() { from_string(str); }
245
246 [[nodiscard]] constexpr detail::from_chars_result from_chars(char const* first, char const* last) noexcept {
247 if (first == nullptr || last == nullptr || first >= last
248 || (last - first) < detail::min_version_string_length) {
249 return {first, std::errc::invalid_argument};
250 }
251
252 auto parseCorePart = [last](char const*& current, uint16& value) -> detail::from_chars_result {
253 auto begin = current;
254 auto result = detail::from_chars(current, last, value);
255 if (!result) {
256 return result;
257 }
258 if (result.ptr - begin > 1 && *begin == '0') {
259 return {begin, std::errc::invalid_argument};
260 }
261 current = result.ptr;
262 return result;
263 };
264
265 auto current = first;
266 uint16 parsedMajor{};
267 uint16 parsedMinor{};
268 uint16 parsedPatch{};
269
270 if (auto result = parseCorePart(current, parsedMajor); !result) {
271 return result;
272 }
273 if (!detail::check_delimiter(current, last, '.')) {
274 return {current, std::errc::invalid_argument};
275 }
276 ++current;
277 if (auto result = parseCorePart(current, parsedMinor); !result) {
278 return result;
279 }
280 if (!detail::check_delimiter(current, last, '.')) {
281 return {current, std::errc::invalid_argument};
282 }
283 ++current;
284 if (auto result = parseCorePart(current, parsedPatch); !result) {
285 return result;
286 }
287
288 std::optional<PreRelease> parsedPreRelease;
289 std::optional<std::string> parsedBuild;
290 if (detail::check_delimiter(current, last, '-')) {
291 PreRelease preRelease;
292 auto result = preRelease.from_chars(++current, last);
293 if (!result) {
294 return result;
295 }
296 parsedPreRelease = std::move(preRelease);
297 current = result.ptr;
298 }
299 if (detail::check_delimiter(current, last, '+')) {
300 auto buildBegin = ++current;
301 auto result = detail::validate_identifiers(buildBegin, last);
302 if (!result) {
303 return result;
304 }
305 parsedBuild = std::string{buildBegin, last};
306 current = last;
307 }
308 if (current != last) {
309 return {current, std::errc::invalid_argument};
310 }
311
312 major = parsedMajor;
313 minor = parsedMinor;
314 patch = parsedPatch;
315 preRelease = std::move(parsedPreRelease);
316 build = std::move(parsedBuild);
317 return {current, std::errc{}};
318 }
319
320 [[nodiscard]] constexpr detail::from_chars_result from_string_noexcept(std::string_view str) noexcept {
321 return from_chars(str.data(), str.data() + str.length());
322 }
323
324 constexpr Version& from_string(std::string_view str) {
325 from_string_noexcept(str).value();
326 return *this;
327 }
328
329 [[nodiscard]] constexpr std::string to_string() const {
330 std::string str;
331 detail::append_number(str, major);
332 str += '.';
333 detail::append_number(str, minor);
334 str += '.';
335 detail::append_number(str, patch);
336 if (preRelease) {
337 str += '-';
338 str += preRelease->to_string();
339 }
340 if (build) {
341 str += '+';
342 str += *build;
343 }
344 return str;
345 }
346
347 [[nodiscard]] constexpr std::strong_ordering operator<=>(Version const& other) const noexcept {
348 if (major != other.major) {
349 return major <=> other.major;
350 }
351 if (minor != other.minor) {
352 return minor <=> other.minor;
353 }
354 if (patch != other.patch) {
355 return patch <=> other.patch;
356 }
357 if (preRelease) {
358 if (other.preRelease) {
359 return *preRelease <=> *other.preRelease;
360 }
361 return std::strong_ordering::less;
362 }
363 if (other.preRelease) {
364 return std::strong_ordering::greater;
365 }
366 return std::strong_ordering::equal;
367 }
368
369 [[nodiscard]] constexpr bool operator==(Version const& other) const noexcept {
370 return *this <=> other == std::strong_ordering::equal;
371 }
372
373 [[nodiscard]] constexpr bool isIdenticalTo(Version const& other) const noexcept {
374 return *this == other && build == other.build;
375 }
376
377 [[nodiscard]] [[maybe_unused]] static constexpr bool valid(std::string_view str) noexcept {
378 return Version{}.from_string_noexcept(str);
379 }
380};
381
382namespace literals {
383[[nodiscard]] constexpr Version operator""_version(char const* str, std::size_t length) {
384 return Version{
385 std::string_view{str, length}
386 };
387}
388} // namespace literals
389
390} // namespace ll::data
391
392template <>
394 static std::string to_string(data::Version const& t) { return t.to_string(); }
395 static ll::Expected<data::Version> from_string(std::string_view s) {
396 if (ll::data::Version ver; auto res = ver.from_string_noexcept(s)) {
397 return ver;
398 } else {
399 return makeErrorCodeError(res.ec);
400 }
401 }
402
403 template <typename J>
404 static ll::Expected<> serialize(data::Version const& t, J& j) {
405 j = t.to_string();
406 return {};
407 }
408 template <typename J>
409 static ll::Expected<data::Version> deserialize(J const& j) {
410 if (!j.is_string()) return makeDeserStringTypeError();
411 return from_string(std::string_view{j});
412 }
413};
414
415namespace std {
416template <>
417struct hash<ll::data::PreRelease> {
418 size_t operator()(ll::data::PreRelease const& preRelease) const noexcept {
419 return ll::hash_utils::HashCombiner{preRelease.values.size()}.addRange(preRelease.values);
420 }
421};
422
423template <>
424struct hash<ll::data::Version> {
425 size_t operator()(ll::data::Version const& version) const noexcept {
426 return ll::hash_utils::HashCombiner{version.major}
427 .add(version.minor)
428 .add(version.patch)
429 .add(version.preRelease);
430 }
431};
432} // namespace std
STL namespace.
Definition Version.h:114
Definition Version.h:214
Definition Serializer.h:9