plugify 1.2.8
Loading...
Searching...
No Matches
version.hpp
1#pragma once
2
3#include <algorithm>
4#include <cstddef>
5#include <cstdint>
6#include <iosfwd>
7#include <limits>
8#include <optional>
9#include <variant>
10#if __has_include(<charconv>)
11#include <charconv>
12#else
13#include <system_error>
14#endif
15
16#include "plg/config.hpp"
17#include "plg/hash.hpp"
18#include "plg/string.hpp"
19#include "plg/vector.hpp"
20
21#ifndef PLUGIFY_VECTOR_NO_STD_FORMAT
22#include "plg/format.hpp"
23#endif
24
25// from https://github.com/Neargye/semver
26namespace plg {
27 namespace detail {
28 template <typename T, typename = void>
30 constexpr static auto resize(T& str, std::size_t size) -> std::void_t<decltype(str.resize(size))> {
31 str.resize(size);
32 }
33 };
34
35 template <typename T>
36 struct resize_uninitialized<T, std::void_t<decltype(std::declval<T>().__resize_default_init(42))>> {
37 constexpr static void resize(T& str, std::size_t size) {
38 str.__resize_default_init(size);
39 }
40 };
41
42 template <typename Int>
43 constexpr std::size_t length(Int n) noexcept {
44 std::size_t digits = 0;
45 do {
46 digits++;
47 n /= 10;
48 } while (n != 0);
49 return digits;
50 }
51
52 template <typename OutputIt, typename Int>
53 constexpr OutputIt to_chars(OutputIt dest, Int n) noexcept {
54 do {
55 *(--dest) = static_cast<char>('0' + (n % 10));
56 n /= 10;
57 } while (n != 0);
58 return dest;
59 }
60
61 enum struct prerelease_identifier_type {
62 numeric,
63 alphanumeric
64 };
65
67 prerelease_identifier_type type;
68 string identifier;
69 };
70
71 class version_parser;
73 } // namespace detail
74
75 template <typename I1 = int, typename I2 = int, typename I3 = int>
76 class version {
77 friend class detail::version_parser;
79
80 public:
81 using trivially_relocatable = std::conditional_t<
85
86 constexpr version() = default; // https://semver.org/#how-should-i-deal-with-revisions-in-the-0yz-initial-development-phase
87 constexpr version(const version&) = default;
88 constexpr version(version&&) = default;
89 constexpr ~version() = default;
90
91 constexpr version& operator=(const version&) = default;
92 constexpr version& operator=(version&&) = default;
93
94 constexpr I1 major() const noexcept { return major_; }
95 constexpr I2 minor() const noexcept { return minor_; }
96 constexpr I3 patch() const noexcept { return patch_; }
97
98 constexpr const string& prerelease_tag() const { return prerelease_tag_; }
99 constexpr const string& build_metadata() const { return build_metadata_; }
100
101 constexpr string to_string() const;
102
103 private:
104 I1 major_ = 0;
105 I2 minor_ = 1;
106 I3 patch_ = 0;
107 string prerelease_tag_;
108 string build_metadata_;
109
110 vector<detail::prerelease_identifier> prerelease_identifiers_;
111
112 constexpr std::size_t length() const noexcept {
113 return detail::length(major_) + detail::length(minor_) + detail::length(patch_) + 2
114 + (prerelease_tag_.empty() ? 0 : prerelease_tag_.length() + 1)
115 + (build_metadata_.empty() ? 0 : build_metadata_.length() + 1);
116 }
117
118 constexpr void clear() noexcept {
119 major_ = 0;
120 minor_ = 1;
121 patch_ = 0;
122
123 prerelease_tag_.clear();
124 prerelease_identifiers_.clear();
125 build_metadata_.clear();
126 }
127 };
128
129 template <typename I1, typename I2, typename I3>
130 constexpr string version<I1, I2, I3>::to_string() const {
131 string result;
132 detail::resize_uninitialized<string>{}.resize(result, length());
133
134 auto* it = result.end();
135 if (!build_metadata_.empty()) {
136 it = std::copy_backward(build_metadata_.begin(), build_metadata_.end(), it);
137 *(--it) = '+';
138 }
139
140 if (!prerelease_tag_.empty()) {
141 it = std::copy_backward(prerelease_tag_.begin(), prerelease_tag_.end(), it);
142 *(--it) = '-';
143 }
144
145 it = detail::to_chars(it, patch_);
146 *(--it) = '.';
147
148 it = detail::to_chars(it, minor_);
149 *(--it) = '.';
150
151 it = detail::to_chars(it, major_);
152
153 return result;
154 }
155
156#if __has_include(<charconv>)
157 struct from_chars_result : std::from_chars_result {
158 [[nodiscard]] constexpr operator bool() const noexcept { return ec == std::errc{}; }
159 };
160#else
162 const char* ptr;
163 std::errc ec;
164
165 [[nodiscard]] constexpr operator bool() const noexcept { return ec == std::errc{}; }
166 };
167#endif
168
169 enum class version_compare_option : std::uint8_t {
170 exclude_prerelease,
171 include_prerelease
172 };
173
174 namespace detail {
175 constexpr from_chars_result success(const char* ptr) noexcept {
176 return from_chars_result{ ptr, std::errc{} };
177 }
178
179 constexpr from_chars_result failure(const char* ptr, std::errc error_code = std::errc::invalid_argument) noexcept {
180 return from_chars_result{ ptr, error_code };
181 }
182
183 constexpr bool is_digit(char c) noexcept {
184 return c >= '0' && c <= '9';
185 }
186
187 constexpr bool is_letter(char c) noexcept {
188 return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
189 }
190
191 constexpr std::uint8_t to_digit(char c) noexcept {
192 return static_cast<std::uint8_t>(c - '0');
193 }
194
195 constexpr char to_char(int i) noexcept {
196 return '0' + (char)i;
197 }
198
199 template<class T, class U>
200 constexpr bool cmp_less(T t, U u) noexcept {
201 if constexpr (std::is_signed_v<T> == std::is_signed_v<U>)
202 return t < u;
203 else if constexpr (std::is_signed_v<T>)
204 return t < 0 || std::make_unsigned_t<T>(t) < u;
205 else
206 return u >= 0 && t < std::make_unsigned_t<U>(u);
207 }
208
209 template<class T, class U>
210 constexpr bool cmp_less_equal(T t, U u) noexcept {
211 return !cmp_less(u, t);
212 }
213
214 template<class T, class U>
215 constexpr bool cmp_greater_equal(T t, U u) noexcept {
216 return !cmp_less(t, u);
217 }
218
219 template<typename R, typename T>
220 constexpr bool number_in_range(T t) noexcept {
221 return cmp_greater_equal(t, std::numeric_limits<R>::min()) && cmp_less_equal(t, std::numeric_limits<R>::max());
222 }
223
224 constexpr int compare(std::string_view lhs, std::string_view rhs) {
225 return lhs.compare(rhs);
226 }
227
228 constexpr int compare_numerically(std::string_view lhs, std::string_view rhs) {
229 // assume that strings don't have leading zeros (we've already checked it at parsing stage).
230
231 if (lhs.size() != rhs.size()) {
232 return static_cast<int>(lhs.size() - rhs.size());
233 }
234
235 for (std::size_t i = 0; i < lhs.size(); ++i) {
236 int a = lhs[i] - '0';
237 int b = rhs[i] - '0';
238 if (a != b) {
239 return a - b;
240 }
241 }
242
243 return 0;
244 }
245
246 enum class token_type : std::uint8_t {
247 eol,
248 space,
249 dot,
250 plus,
251 hyphen,
252 letter,
253 digit,
254 range_operator,
255 logical_or
256 };
257
258 enum class range_operator : std::uint8_t {
259 less,
260 less_or_equal,
261 greater,
262 greater_or_equal,
263 equal
264 };
265
266 struct token {
267 using value_t = std::variant<std::monostate, std::uint8_t, char, range_operator>;
268 token_type type;
269 value_t value;
270 const char* lexeme;
271 };
272
274 public:
275 constexpr token_stream() = default;
276 constexpr explicit token_stream(vector<token> tokens) noexcept : tokens_(std::move(tokens)) {}
277
278 constexpr void push(const token& token) noexcept {
279 tokens_.push_back(token);
280 }
281
282 constexpr token advance() noexcept {
283 const token token = get(current_);
284 ++current_;
285 return token;
286 }
287
288 constexpr token peek(std::size_t k = 0) const noexcept {
289 return get(current_ + k);
290 }
291
292 constexpr token previous() const noexcept {
293 return get(current_ - 1);
294 }
295
296 constexpr bool advance_if_match(token& token, token_type type) noexcept {
297 if (get(current_).type != type) {
298 return false;
299 }
300
301 token = advance();
302 return true;
303 }
304
305 constexpr bool advance_if_match(token_type type) noexcept {
306 token token;
307 return advance_if_match(token, type);
308 }
309
310 constexpr bool consume(token_type type) noexcept {
311 return advance().type == type;
312 }
313
314 constexpr bool check(token_type type) const noexcept {
315 return peek().type == type;
316 }
317
318 private:
319 std::size_t current_ = 0;
320 vector<token> tokens_;
321
322 constexpr token get(std::size_t i) const noexcept {
323 return tokens_[i];
324 }
325 };
326
327 class lexer {
328 public:
329 explicit constexpr lexer(std::string_view text) noexcept : text_{text}, current_pos_{0} {}
330
331 constexpr from_chars_result scan_tokens(token_stream& token_stream) noexcept {
332 from_chars_result result{ text_.data(), std::errc{} };
333
334 while (!is_eol()) {
335 result = scan_token(token_stream);
336 if (!result) {
337 return result;
338 }
339 }
340
341 token_stream.push({ token_type::eol, {}, text_.data() + text_.size() });
342
343 return result;
344 }
345
346 private:
347 std::string_view text_;
348 std::size_t current_pos_;
349
350 constexpr from_chars_result scan_token(token_stream& stream) noexcept {
351 const char c = advance();
352
353 switch (c) {
354 case ' ':
355 add_token(stream, token_type::space);
356 break;
357 case '.':
358 add_token(stream, token_type::dot);
359 break;
360 case '-':
361 add_token(stream, token_type::hyphen);
362 break;
363 case '+':
364 add_token(stream, token_type::plus);
365 break;
366 case '|':
367 if (advance_if_match('|')) {
368 add_token(stream, token_type::logical_or);
369 break;
370 }
371 return failure(get_prev_symbol());
372 case '<':
373 add_token(stream, token_type::range_operator, advance_if_match('=') ? range_operator::less_or_equal : range_operator::less);
374 break;
375 case '>':
376 add_token(stream, token_type::range_operator, advance_if_match('=') ? range_operator::greater_or_equal : range_operator::greater);
377 break;
378 case '=':
379 add_token(stream, token_type::range_operator, range_operator::equal);
380 break;
381 default:
382 if (is_digit(c)) {
383 add_token(stream, token_type::digit, to_digit(c));
384 break;
385 }
386 else if (is_letter(c)) {
387 add_token(stream, token_type::letter, c);
388 break;
389 }
390 return failure(get_prev_symbol());
391 }
392
393 return success(get_prev_symbol());
394 }
395
396 constexpr void add_token(token_stream& stream, token_type type, token::value_t value = {}) noexcept {
397 const char* lexeme = get_prev_symbol();
398 stream.push({ type, value, lexeme});
399 }
400
401 constexpr char advance() noexcept {
402 char c = text_[current_pos_];
403 current_pos_ += 1;
404 return c;
405 }
406
407 constexpr bool advance_if_match(char c) noexcept {
408 if (is_eol()) {
409 return false;
410 }
411
412 if (text_[current_pos_] != c) {
413 return false;
414 }
415
416 current_pos_ += 1;
417
418 return true;
419 }
420
421 constexpr const char* get_prev_symbol() const noexcept {
422 return text_.data() + current_pos_ - 1;
423 }
424
425 constexpr bool is_eol() const noexcept { return current_pos_ >= text_.size(); }
426 };
427
429 public:
430 template <typename I1, typename I2, typename I3>
431 [[nodiscard]] constexpr int compare(const version<I1, I2, I3>& lhs, const version<I1, I2, I3>& rhs) const noexcept {
432 if (lhs.prerelease_identifiers_.empty() != rhs.prerelease_identifiers_.empty()) {
433 return static_cast<int>(rhs.prerelease_identifiers_.size()) - static_cast<int>(lhs.prerelease_identifiers_.size());
434 }
435
436 const std::size_t count = std::min(lhs.prerelease_identifiers_.size(), rhs.prerelease_identifiers_.size());
437
438 for (std::size_t i = 0; i < count; ++i) {
439 const int compare_result = compare_identifier(lhs.prerelease_identifiers_[i], rhs.prerelease_identifiers_[i]);
440 if (compare_result != 0) {
441 return compare_result;
442 }
443 }
444
445 return static_cast<int>(lhs.prerelease_identifiers_.size()) - static_cast<int>(rhs.prerelease_identifiers_.size());
446 }
447
448 private:
449 [[nodiscard]] constexpr int compare_identifier(const prerelease_identifier& lhs, const prerelease_identifier& rhs) const noexcept {
450 if (lhs.type == prerelease_identifier_type::numeric && rhs.type == prerelease_identifier_type::numeric) {
451 return compare_numerically(lhs.identifier, rhs.identifier);
452 } else if (lhs.type == prerelease_identifier_type::alphanumeric && rhs.type == prerelease_identifier_type::alphanumeric) {
453 return detail::compare(lhs.identifier, rhs.identifier);
454 }
455
456 return lhs.type == prerelease_identifier_type::alphanumeric ? 1 : -1;
457 }
458 };
459
461 public:
462 constexpr explicit version_parser(token_stream& stream) : stream_{stream} {
463 }
464
465 template <typename I1, typename I2, typename I3>
466 constexpr from_chars_result parse(version<I1, I2, I3>& out) noexcept {
467 out.clear();
468
469 from_chars_result result = parse_number(out.major_);
470 if (!result) {
471 return result;
472 }
473
474 if (!stream_.consume(token_type::dot)) {
475 return failure(stream_.previous().lexeme);
476 }
477
478 result = parse_number(out.minor_);
479 if (!result) {
480 return result;
481 }
482
483 if (!stream_.consume(token_type::dot)) {
484 return failure(stream_.previous().lexeme);
485 }
486
487 result = parse_number(out.patch_);
488 if (!result) {
489 return result;
490 }
491
492 if (stream_.advance_if_match(token_type::hyphen)) {
493 result = parse_prerelease_tag(out.prerelease_tag_, out.prerelease_identifiers_);
494 if (!result) {
495 return result;
496 }
497 }
498
499 if (stream_.advance_if_match(token_type::plus)) {
500 result = parse_build_metadata(out.build_metadata_);
501 if (!result) {
502 return result;
503 }
504 }
505
506 return result;
507 }
508
509
510 private:
511 token_stream& stream_;
512
513 template <typename Int>
514 constexpr from_chars_result parse_number(Int& out) {
515 token token = stream_.advance();
516
517 if (!is_digit(token)) {
518 return failure(token.lexeme);
519 }
520
521 const auto first_digit = std::get<std::uint8_t>(token.value);
522 std::uint64_t result = first_digit;
523
524 if (first_digit == 0) {
525 out = static_cast<Int>(result);
526 return success(stream_.peek().lexeme);
527 }
528
529 while (stream_.advance_if_match(token, token_type::digit)) {
530 result = result * 10 + std::get<std::uint8_t>(token.value);
531 }
532
533 if (detail::number_in_range<Int>(result)) {
534 out = static_cast<Int>(result);
535 return success(stream_.peek().lexeme);
536 }
537
538 return failure(token.lexeme, std::errc::result_out_of_range);
539 }
540
541 constexpr from_chars_result parse_prerelease_tag(string& out, vector<detail::prerelease_identifier>& out_identifiers) {
542 string result;
543
544 do {
545 if (!result.empty()) {
546 result.push_back('.');
547 }
548
549 string identifier;
550 if (const auto res = parse_prerelease_identifier(identifier); !res) {
551 return res;
552 }
553
554 result.append(identifier);
555 out_identifiers.push_back(make_prerelease_identifier(identifier));
556
557 } while (stream_.advance_if_match(token_type::dot));
558
559 out = result;
560 return success(stream_.peek().lexeme);
561 }
562
563 constexpr from_chars_result parse_build_metadata(string& out) {
564 string result;
565
566 do {
567 if (!result.empty()) {
568 result.push_back('.');
569 }
570
571 string identifier;
572 if (const auto res = parse_build_identifier(identifier); !res) {
573 return res;
574 }
575
576 result.append(identifier);
577 } while (stream_.advance_if_match(token_type::dot));
578
579 out = result;
580 return success(stream_.peek().lexeme);
581 }
582
583 constexpr from_chars_result parse_prerelease_identifier(string& out) {
584 string result;
585 token token = stream_.advance();
586
587 do {
588 switch (token.type) {
589 case token_type::hyphen:
590 result.push_back('-');
591 break;
592 case token_type::letter:
593 result.push_back(std::get<char>(token.value));
594 break;
595 case token_type::digit:
596 {
597 const auto digit = std::get<std::uint8_t>(token.value);
598
599 // numerical prerelease identifier doesn't allow leading zero
600 // 1.2.3-1.alpha is valid,
601 // 1.2.3-01b is valid as well, but
602 // 1.2.3-01.alpha is not valid
603
604 // Only check for leading zero when digit is the first character of the
605 // prerelease identifier.
606 if (result.empty() && is_leading_zero(digit)) {
607 return failure(token.lexeme);
608 }
609
610 result.push_back(to_char(digit));
611 break;
612 }
613 default:
614 return failure(token.lexeme);
615 }
616 } while (stream_.advance_if_match(token, token_type::hyphen) || stream_.advance_if_match(token, token_type::letter) || stream_.advance_if_match(token, token_type::digit));
617
618 out = result;
619 return success(stream_.peek().lexeme);
620 }
621
622 constexpr detail::prerelease_identifier make_prerelease_identifier(const string& identifier) {
623 auto type = detail::prerelease_identifier_type::numeric;
624 for (char c : identifier) {
625 if (c == '-' || detail::is_letter(c)) {
626 type = detail::prerelease_identifier_type::alphanumeric;
627 break;
628 }
629 }
630 return detail::prerelease_identifier{ type, identifier };
631 }
632
633 constexpr from_chars_result parse_build_identifier(string& out) {
634 string result;
635 token token = stream_.advance();
636
637 do {
638 switch (token.type) {
639 case token_type::hyphen:
640 result.push_back('-');
641 break;
642 case token_type::letter:
643 result.push_back(std::get<char>(token.value));
644 break;
645 case token_type::digit:
646 {
647 const auto digit = std::get<std::uint8_t>(token.value);
648 result.push_back(to_char(digit));
649 break;
650 }
651 default:
652 return failure(token.lexeme);
653 }
654 } while (stream_.advance_if_match(token, token_type::hyphen) || stream_.advance_if_match(token, token_type::letter) || stream_.advance_if_match(token, token_type::digit));
655
656 out = result;
657 return success(stream_.peek().lexeme);
658 }
659
660 constexpr bool is_leading_zero(int digit) noexcept {
661 if (digit != 0) {
662 return false;
663 }
664
665 size_t k = 0;
666 int alpha_numerics = 0;
667 int digits = 0;
668
669 while (true) {
670 const token token = stream_.peek(k);
671
672 if (!is_alphanumeric(token)) {
673 break;
674 }
675
677
678 if (is_digit(token)) {
679 ++digits;
680 }
681
682 ++k;
683 }
684
685 return digits > 0 && digits == alpha_numerics;
686 }
687
688 constexpr bool is_digit(const token& token) const noexcept {
689 return token.type == token_type::digit;
690 }
691
692 constexpr bool is_eol(const token& token) const noexcept {
693 return token.type == token_type::eol;
694 }
695
696 constexpr bool is_alphanumeric(const token& token) const noexcept {
697 return token.type == token_type::hyphen || token.type == token_type::letter || token.type == token_type::digit;
698 }
699 };
700
701 template <typename I1, typename I2, typename I3>
702 constexpr int compare_prerelease(const version<I1, I2, I3>& lhs, const version<I1, I2, I3>& rhs) noexcept {
703 return prerelease_comparator{}.compare(lhs, rhs);
704 }
705
706 template <typename I1, typename I2, typename I3>
707 constexpr int compare_parsed(const version<I1, I2, I3>& lhs, const version<I1, I2, I3>& rhs, version_compare_option compare_option) {
708 int result = lhs.major() - rhs.major();
709 if (result != 0) {
710 return result;
711 }
712
713 result = lhs.minor() - rhs.minor();
714 if (result != 0) {
715 return result;
716 }
717
718 result = lhs.patch() - rhs.patch();
719 if (result != 0) {
720 return result;
721 }
722
723 if (compare_option == version_compare_option::include_prerelease) {
724 result = detail::compare_prerelease(lhs, rhs);
725 }
726
727 return result;
728 }
729
730 template <typename I1, typename I2, typename I3>
731 constexpr from_chars_result parse(std::string_view str, version<I1, I2, I3>& out) {
732 token_stream token_stream;
733 from_chars_result result = lexer{ str }.scan_tokens(token_stream);
734 if (!result) {
735 return result;
736 }
737
738 result = version_parser{ token_stream }.parse(out);
739 if (!result) {
740 return result;
741 }
742
743 if (!token_stream.consume(token_type::eol)) {
744 return failure(token_stream.previous().lexeme);
745 }
746
747 return success(token_stream.previous().lexeme);
748 }
749
750 } // namespace detail
751
752 template <typename I1, typename I2, typename I3>
753 [[nodiscard]] constexpr bool operator==(const version<I1, I2, I3>& lhs, const version<I1, I2, I3>& rhs) noexcept {
754 return detail::compare_parsed(lhs, rhs, version_compare_option::include_prerelease) == 0;
755 }
756
757 template <typename I1, typename I2, typename I3>
758 [[nodiscard]] constexpr bool operator!=(const version<I1, I2, I3>& lhs, const version<I1, I2, I3>& rhs) noexcept {
759 return detail::compare_parsed(lhs, rhs, version_compare_option::include_prerelease) != 0;
760 }
761
762 template <typename I1, typename I2, typename I3>
763 [[nodiscard]] constexpr bool operator>(const version<I1, I2, I3>& lhs, const version<I1, I2, I3>& rhs) noexcept {
764 return detail::compare_parsed(lhs, rhs, version_compare_option::include_prerelease) > 0;
765 }
766
767 template <typename I1, typename I2, typename I3>
768 [[nodiscard]] constexpr bool operator>=(const version<I1, I2, I3>& lhs, const version<I1, I2, I3>& rhs) noexcept {
769 return detail::compare_parsed(lhs, rhs, version_compare_option::include_prerelease) >= 0;
770 }
771
772 template <typename I1, typename I2, typename I3>
773 [[nodiscard]] constexpr bool operator<(const version<I1, I2, I3>& lhs, const version<I1, I2, I3>& rhs) noexcept {
774 return detail::compare_parsed(lhs, rhs, version_compare_option::include_prerelease) < 0;
775 }
776
777 template <typename I1, typename I2, typename I3>
778 [[nodiscard]] constexpr bool operator<=(const version<I1, I2, I3>& lhs, const version<I1, I2, I3>& rhs) noexcept {
779 return detail::compare_parsed(lhs, rhs, version_compare_option::include_prerelease) <= 0;
780 }
781
782 template <typename I1, typename I2, typename I3>
783 [[nodiscard]] constexpr std::strong_ordering operator<=>(const version<I1, I2, I3>& lhs, const version<I1, I2, I3>& rhs) {
784 int compare = detail::compare_parsed(lhs, rhs, version_compare_option::include_prerelease);
785 if (compare == 0)
786 return std::strong_ordering::equal;
787 if (compare > 0)
788 return std::strong_ordering::greater;
789 return std::strong_ordering::less;
790 }
791
792 template<typename I1, typename I2, typename I3>
793 constexpr from_chars_result parse(std::string_view str, version<I1, I2, I3>& output) {
794 return detail::parse(str, output);
795 }
796
797 constexpr bool valid(std::string_view str) {
798 version v{};
799 return detail::parse(str, v);
800 }
801
802 namespace detail {
803 template <typename I1, typename I2, typename I3>
804 class range_comparator {
805 public:
806 constexpr range_comparator(const version<I1, I2, I3>& v, range_operator op) noexcept : v_(v), op_(op) {}
807
808 constexpr bool contains(const version<I1, I2, I3>& other) const noexcept {
809 switch (op_) {
810 case range_operator::less:
811 return detail::compare_parsed(other, v_, version_compare_option::include_prerelease) < 0;
812 case range_operator::less_or_equal:
813 return detail::compare_parsed(other, v_, version_compare_option::include_prerelease) <= 0;
814 case range_operator::greater:
815 return detail::compare_parsed(other, v_, version_compare_option::include_prerelease) > 0;
816 case range_operator::greater_or_equal:
817 return detail::compare_parsed(other, v_, version_compare_option::include_prerelease) >= 0;
818 case range_operator::equal:
819 return detail::compare_parsed(other, v_, version_compare_option::include_prerelease) == 0;
820 }
821 return false;
822 }
823
824 constexpr const version<I1, I2, I3>& get_version() const noexcept { return v_; }
825
826 constexpr range_operator get_operator() const noexcept { return op_; }
827
828 constexpr string to_string() const {
829 string result;
830 switch (op_) {
831 case range_operator::less: result += "<"; break;
832 case range_operator::less_or_equal: result += "<="; break;
833 case range_operator::greater: result += ">"; break;
834 case range_operator::greater_or_equal: result += ">="; break;
835 case range_operator::equal: result += "="; break;
836 }
837 result += v_.to_string();
838 return result;
839 }
840
841 private:
842 version<I1, I2, I3> v_;
843 range_operator op_;
844 };
845
846 class range_parser;
847
848 template <typename I1, typename I2, typename I3>
849 class range {
850 public:
851 friend class detail::range_parser;
852
853 constexpr bool contains(const version<I1, I2, I3>& v, version_compare_option option) const noexcept {
854 if (option == version_compare_option::exclude_prerelease) {
855 if (!match_at_least_one_comparator_with_prerelease(v)) {
856 return false;
857 }
858 }
859
860 return std::all_of(ranges_comparators_.begin(), ranges_comparators_.end(), [&](const auto& ranges_comparator) {
861 return ranges_comparator.contains(v);
862 });
863 }
864
865 constexpr auto begin() const noexcept {
866 return ranges_comparators_.begin();
867 }
868
869 constexpr auto end() const noexcept {
870 return ranges_comparators_.end();
871 }
872
873 constexpr std::size_t size() const noexcept {
874 return ranges_comparators_.size();
875 }
876
877 constexpr bool empty() const noexcept {
878 return ranges_comparators_.empty();
879 }
880
881 constexpr string to_string() const {
882 return join(ranges_comparators_, " ");
883 }
884
885 private:
886 vector<range_comparator<I1, I2, I3>> ranges_comparators_;
887
888 constexpr bool match_at_least_one_comparator_with_prerelease(const version<I1, I2, I3>& v) const noexcept {
889 if (v.prerelease_tag().empty()) {
890 return true;
891 }
892
893 return std::any_of(ranges_comparators_.begin(), ranges_comparators_.end(), [&](const auto& ranges_comparator) {
894 const bool has_prerelease = !ranges_comparator.get_version().prerelease_tag().empty();
895 const bool equal_without_prerelease = detail::compare_parsed(v, ranges_comparator.get_version(), version_compare_option::exclude_prerelease) == 0;
896 return has_prerelease && equal_without_prerelease;
897 });
898 }
899 };
900 }
901
902 template <typename I1 = int, typename I2 = int, typename I3 = int>
903 class range_set {
904 public:
905 friend class detail::range_parser;
906
907 constexpr bool contains(const version<I1, I2, I3>& v, version_compare_option option = version_compare_option::exclude_prerelease) const noexcept {
908 return std::any_of(ranges_.begin(), ranges_.end(), [&](const auto& range) {
909 return range.contains(v, option);
910 });
911 }
912
913 constexpr auto begin() const noexcept {
914 return ranges_.begin();
915 }
916
917 constexpr auto end() const noexcept {
918 return ranges_.end();
919 }
920
921 constexpr std::size_t size() const noexcept {
922 return ranges_.size();
923 }
924
925 constexpr bool empty() const noexcept {
926 return ranges_.empty();
927 }
928
929 constexpr string to_string() const {
930 return join(ranges_, " ");
931 }
932
933 private:
935 };
936
937 namespace detail {
938 class range_parser {
939 public:
940 constexpr explicit range_parser(token_stream stream) noexcept : stream_(std::move(stream)) {}
941
942 template <typename I1, typename I2, typename I3>
943 constexpr from_chars_result parse(range_set<I1, I2, I3>& out) noexcept {
944 vector<range<I1, I2, I3>> ranges;
945
946 do {
947
948 detail::range<I1, I2, I3> range;
949 if (const auto res = parse_range(range); !res) {
950 return res;
951 }
952
953 ranges.push_back(range);
954 skip_whitespaces();
955
956 } while (stream_.advance_if_match(token_type::logical_or));
957
958 out.ranges_ = std::move(ranges);
959
960 return success(stream_.peek().lexeme);
961 }
962
963 private:
964 token_stream stream_;
965
966 template <typename I1, typename I2, typename I3>
967 constexpr from_chars_result parse_range(detail::range<I1, I2, I3>& out) noexcept {
968 do {
969 skip_whitespaces();
970
971 if (const auto res = parse_range_comparator(out.ranges_comparators_); !res) {
972 return res;
973 }
974
975 skip_whitespaces();
976
977 } while (stream_.check(token_type::range_operator) || stream_.check(token_type::digit));
978
979 return success(stream_.peek().lexeme);
980 }
981
982 template <typename I1, typename I2, typename I3>
983 constexpr from_chars_result parse_range_comparator(vector<detail::range_comparator<I1, I2, I3>>& out) noexcept {
984 range_operator op = range_operator::equal;
985 token token;
986 if (stream_.advance_if_match(token, token_type::range_operator)) {
987 op = std::get<range_operator>(token.value);
988 }
989
990 skip_whitespaces();
991
992 version<I1, I2, I3> ver;
993 version_parser parser{ stream_ };
994 if (const auto res = parser.parse(ver); !res) {
995 return res;
996 }
997
998 out.emplace_back(ver, op);
999 return success(stream_.peek().lexeme);
1000 }
1001
1002 constexpr void skip_whitespaces() noexcept {
1003 while (stream_.advance_if_match(token_type::space)) {
1004 ;
1005 }
1006 }
1007 };
1008 } // namespace detail
1009
1010
1011 template <typename I1, typename I2, typename I3>
1012 constexpr from_chars_result parse(std::string_view str, range_set<I1, I2, I3>& out) {
1013 detail::token_stream token_stream;
1014 const from_chars_result result = detail::lexer{ str }.scan_tokens(token_stream);
1015 if (!result) {
1016 return result;
1017 }
1018
1019 return detail::range_parser{ std::move(token_stream) }.parse(out);
1020 }
1021} // namespace plg
1022
1023#ifndef PLUGIFY_VECTOR_NO_STD_HASH
1024// hash support
1025namespace std {
1026 template<typename I1, typename I2, typename I3>
1027 struct hash<plg::version<I1, I2, I3>> {
1028 std::size_t operator()(const plg::version<I1, I2, I3>& ver) const noexcept {
1029 std::size_t seed = 0;
1030 plg::hash_combine_all(seed,
1031 ver.major(),
1032 ver.minor(),
1033 ver.patch(),
1034 ver.prerelease_tag(),
1035 ver.build_metadata());
1036 return seed;
1037 }
1038 };
1039}// namespace std
1040#endif // PLUGIFY_VECTOR_NO_STD_HASH
1041
1042#ifndef PLUGIFY_VECTOR_NO_STD_FORMAT
1043// format support
1044#ifdef FMT_HEADER_ONLY
1045namespace fmt {
1046#else
1047namespace std {
1048#endif
1049 template<typename I1, typename I2, typename I3>
1050 struct formatter<plg::version<I1, I2, I3>> {
1051 constexpr auto parse(std::format_parse_context& ctx) {
1052 return ctx.begin();
1053 }
1054
1055 template<class FormatContext>
1056 auto format(const plg::version<I1, I2, I3>& ver, FormatContext& ctx) const {
1057 return std::format_to(ctx.out(), "{}", ver.to_string());
1058 }
1059 };
1060 template<typename I1, typename I2, typename I3>
1061 struct formatter<plg::range_set<I1, I2, I3>> {
1062 constexpr auto parse(std::format_parse_context& ctx) {
1063 return ctx.begin();
1064 }
1065
1066 template<class FormatContext>
1067 auto format(const plg::range_set<I1, I2, I3>& ver, FormatContext& ctx) const {
1068 return std::format_to(ctx.out(), "{}", ver.to_string());
1069 }
1070 };
1071 template<typename I1, typename I2, typename I3>
1072 struct formatter<plg::detail::range<I1, I2, I3>> {
1073 constexpr auto parse(std::format_parse_context& ctx) {
1074 return ctx.begin();
1075 }
1076
1077 template<class FormatContext>
1078 auto format(const plg::detail::range<I1, I2, I3>& ver, FormatContext& ctx) const {
1079 return std::format_to(ctx.out(), "{}", ver.to_string());
1080 }
1081 };
1082 template<typename I1, typename I2, typename I3>
1083 struct formatter<plg::detail::range_comparator<I1, I2, I3>> {
1084 constexpr auto parse(std::format_parse_context& ctx) {
1085 return ctx.begin();
1086 }
1087
1088 template<class FormatContext>
1089 auto format(const plg::detail::range_comparator<I1, I2, I3>& ver, FormatContext& ctx) const {
1090 return std::format_to(ctx.out(), "{}", ver.to_string());
1091 }
1092 };
1093}// namespace std
1094#endif // PLUGIFY_VECTOR_NO_STD_FORMAT