10#if __has_include(<charconv>)
13#include <system_error>
16#include "plg/config.hpp"
17#include "plg/hash.hpp"
18#include "plg/string.hpp"
19#include "plg/vector.hpp"
21#ifndef PLUGIFY_VECTOR_NO_STD_FORMAT
22#include "plg/format.hpp"
28 template <
typename T,
typename =
void>
30 constexpr static auto resize(T& str, std::size_t size) -> std::void_t<
decltype(str.resize(size))> {
37 constexpr static void resize(T& str, std::size_t size) {
38 str.__resize_default_init(size);
42 template <
typename Int>
43 constexpr std::size_t length(
Int n)
noexcept {
52 template <
typename OutputIt,
typename Int>
53 constexpr OutputIt to_chars(OutputIt dest, Int n)
noexcept {
55 *(--dest) =
static_cast<char>(
'0' + (n % 10));
61 enum struct prerelease_identifier_type {
67 prerelease_identifier_type type;
75 template <
typename I1 =
int,
typename I2 =
int,
typename I3 =
int>
81 using trivially_relocatable = std::conditional_t<
94 constexpr I1 major()
const noexcept {
return major_; }
95 constexpr I2 minor()
const noexcept {
return minor_; }
96 constexpr I3 patch()
const noexcept {
return patch_; }
98 constexpr const string& prerelease_tag()
const {
return prerelease_tag_; }
99 constexpr const string& build_metadata()
const {
return build_metadata_; }
101 constexpr string to_string()
const;
107 string prerelease_tag_;
108 string build_metadata_;
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);
118 constexpr void clear()
noexcept {
123 prerelease_tag_.clear();
124 prerelease_identifiers_.clear();
125 build_metadata_.clear();
129 template <
typename I1,
typename I2,
typename I3>
134 auto* it = result.end();
135 if (!build_metadata_.empty()) {
136 it = std::copy_backward(build_metadata_.begin(), build_metadata_.end(), it);
140 if (!prerelease_tag_.empty()) {
141 it = std::copy_backward(prerelease_tag_.begin(), prerelease_tag_.end(), it);
145 it = detail::to_chars(it, patch_);
148 it = detail::to_chars(it, minor_);
151 it = detail::to_chars(it, major_);
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{}; }
165 [[
nodiscard]]
constexpr operator bool()
const noexcept {
return ec == std::errc{}; }
169 enum class version_compare_option : std::uint8_t {
175 constexpr from_chars_result success(
const char* ptr)
noexcept {
176 return from_chars_result{ ptr, std::errc{} };
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 };
183 constexpr bool is_digit(
char c)
noexcept {
184 return c >=
'0' && c <=
'9';
187 constexpr bool is_letter(
char c)
noexcept {
188 return (c >=
'A' && c <=
'Z') || (c >=
'a' && c <=
'z');
191 constexpr std::uint8_t to_digit(
char c)
noexcept {
192 return static_cast<std::uint8_t
>(c -
'0');
195 constexpr char to_char(
int i)
noexcept {
196 return '0' + (char)i;
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>)
203 else if constexpr (std::is_signed_v<T>)
204 return t < 0 || std::make_unsigned_t<T>(t) < u;
206 return u >= 0 && t < std::make_unsigned_t<U>(u);
209 template<
class T,
class U>
210 constexpr bool cmp_less_equal(T t, U u)
noexcept {
211 return !cmp_less(u, t);
214 template<
class T,
class U>
215 constexpr bool cmp_greater_equal(T t, U u)
noexcept {
216 return !cmp_less(t, u);
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());
224 constexpr int compare(std::string_view lhs, std::string_view rhs) {
225 return lhs.compare(rhs);
228 constexpr int compare_numerically(std::string_view lhs, std::string_view rhs) {
231 if (lhs.size() != rhs.size()) {
232 return static_cast<int>(lhs.size() - rhs.size());
235 for (std::size_t i = 0; i < lhs.size(); ++i) {
236 int a = lhs[i] -
'0';
237 int b = rhs[i] -
'0';
246 enum class token_type : std::uint8_t {
258 enum class range_operator : std::uint8_t {
267 using value_t = std::variant<std::monostate, std::uint8_t, char, range_operator>;
278 constexpr void push(
const token&
token)
noexcept {
279 tokens_.push_back(
token);
282 constexpr token advance()
noexcept {
288 constexpr token peek(std::size_t
k = 0)
const noexcept {
289 return get(current_ +
k);
292 constexpr token previous()
const noexcept {
293 return get(current_ - 1);
296 constexpr bool advance_if_match(
token&
token, token_type type)
noexcept {
297 if (get(current_).type != type) {
305 constexpr bool advance_if_match(token_type type)
noexcept {
307 return advance_if_match(
token, type);
310 constexpr bool consume(token_type type)
noexcept {
311 return advance().type == type;
314 constexpr bool check(token_type type)
const noexcept {
315 return peek().type == type;
319 std::size_t current_ = 0;
322 constexpr token get(std::size_t
i)
const noexcept {
341 token_stream.push({ token_type::eol, {}, text_.data() + text_.size() });
347 std::string_view text_;
348 std::size_t current_pos_;
351 const char c = advance();
355 add_token(
stream, token_type::space);
358 add_token(
stream, token_type::dot);
361 add_token(
stream, token_type::hyphen);
364 add_token(
stream, token_type::plus);
367 if (advance_if_match(
'|')) {
368 add_token(
stream, token_type::logical_or);
371 return failure(get_prev_symbol());
373 add_token(
stream, token_type::range_operator, advance_if_match(
'=') ? range_operator::less_or_equal : range_operator::less);
376 add_token(
stream, token_type::range_operator, advance_if_match(
'=') ? range_operator::greater_or_equal : range_operator::greater);
379 add_token(
stream, token_type::range_operator, range_operator::equal);
383 add_token(
stream, token_type::digit, to_digit(
c));
386 else if (is_letter(
c)) {
387 add_token(
stream, token_type::letter,
c);
390 return failure(get_prev_symbol());
393 return success(get_prev_symbol());
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});
401 constexpr char advance()
noexcept {
402 char c = text_[current_pos_];
407 constexpr bool advance_if_match(
char c)
noexcept {
412 if (text_[current_pos_] !=
c) {
421 constexpr const char* get_prev_symbol()
const noexcept {
422 return text_.data() + current_pos_ - 1;
425 constexpr bool is_eol()
const noexcept {
return current_pos_ >= text_.size(); }
430 template <
typename I1,
typename I2,
typename I3>
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());
436 const std::size_t count = std::min(
lhs.prerelease_identifiers_.size(), rhs.prerelease_identifiers_.size());
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]);
445 return static_cast<int>(
lhs.prerelease_identifiers_.size()) -
static_cast<int>(rhs.prerelease_identifiers_.size());
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);
456 return lhs.type == prerelease_identifier_type::alphanumeric ? 1 : -1;
465 template <
typename I1,
typename I2,
typename I3>
474 if (!stream_.consume(token_type::dot)) {
475 return failure(stream_.previous().lexeme);
483 if (!stream_.consume(token_type::dot)) {
484 return failure(stream_.previous().lexeme);
492 if (stream_.advance_if_match(token_type::hyphen)) {
493 result = parse_prerelease_tag(
out.prerelease_tag_,
out.prerelease_identifiers_);
499 if (stream_.advance_if_match(token_type::plus)) {
500 result = parse_build_metadata(
out.build_metadata_);
513 template <
typename Int>
517 if (!is_digit(
token)) {
518 return failure(
token.lexeme);
526 return success(stream_.peek().lexeme);
529 while (stream_.advance_if_match(
token, token_type::digit)) {
533 if (detail::number_in_range<Int>(
result)) {
535 return success(stream_.peek().lexeme);
538 return failure(
token.lexeme, std::errc::result_out_of_range);
550 if (
const auto res = parse_prerelease_identifier(identifier); !
res) {
554 result.append(identifier);
557 }
while (stream_.advance_if_match(token_type::dot));
560 return success(stream_.peek().lexeme);
572 if (
const auto res = parse_build_identifier(identifier); !
res) {
576 result.append(identifier);
577 }
while (stream_.advance_if_match(token_type::dot));
580 return success(stream_.peek().lexeme);
588 switch (
token.type) {
589 case token_type::hyphen:
592 case token_type::letter:
595 case token_type::digit:
597 const auto digit = std::get<std::uint8_t>(
token.value);
606 if (
result.empty() && is_leading_zero(digit)) {
607 return failure(
token.lexeme);
610 result.push_back(to_char(digit));
614 return failure(
token.lexeme);
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));
619 return success(stream_.peek().lexeme);
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;
638 switch (
token.type) {
639 case token_type::hyphen:
642 case token_type::letter:
645 case token_type::digit:
647 const auto digit = std::get<std::uint8_t>(
token.value);
648 result.push_back(to_char(digit));
652 return failure(
token.lexeme);
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));
657 return success(stream_.peek().lexeme);
660 constexpr bool is_leading_zero(
int digit)
noexcept {
672 if (!is_alphanumeric(
token)) {
678 if (is_digit(
token)) {
688 constexpr bool is_digit(
const token&
token)
const noexcept {
689 return token.type == token_type::digit;
692 constexpr bool is_eol(
const token&
token)
const noexcept {
693 return token.type == token_type::eol;
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;
701 template <
typename I1,
typename I2,
typename I3>
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();
713 result = lhs.minor() - rhs.minor();
718 result = lhs.patch() - rhs.patch();
723 if (compare_option == version_compare_option::include_prerelease) {
724 result = detail::compare_prerelease(lhs, rhs);
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);
738 result = version_parser{ token_stream }.parse(out);
743 if (!token_stream.consume(token_type::eol)) {
744 return failure(token_stream.previous().lexeme);
747 return success(token_stream.previous().lexeme);
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;
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;
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;
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;
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;
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;
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);
786 return std::strong_ordering::equal;
788 return std::strong_ordering::greater;
789 return std::strong_ordering::less;
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);
797 constexpr bool valid(std::string_view str) {
799 return detail::parse(str, v);
803 template <
typename I1,
typename I2,
typename I3>
804 class range_comparator {
806 constexpr range_comparator(
const version<I1, I2, I3>& v, range_operator op) noexcept : v_(v), op_(op) {}
808 constexpr bool contains(
const version<I1, I2, I3>& other)
const noexcept {
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;
824 constexpr const version<I1, I2, I3>& get_version() const noexcept {
return v_; }
826 constexpr range_operator get_operator() const noexcept {
return op_; }
828 constexpr string to_string()
const {
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;
837 result += v_.to_string();
842 version<I1, I2, I3> v_;
848 template <
typename I1,
typename I2,
typename I3>
851 friend class detail::range_parser;
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)) {
860 return std::all_of(ranges_comparators_.begin(), ranges_comparators_.end(), [&](
const auto& ranges_comparator) {
861 return ranges_comparator.contains(v);
865 constexpr auto begin() const noexcept {
866 return ranges_comparators_.begin();
869 constexpr auto end() const noexcept {
870 return ranges_comparators_.end();
873 constexpr std::size_t size() const noexcept {
874 return ranges_comparators_.size();
877 constexpr bool empty() const noexcept {
878 return ranges_comparators_.empty();
881 constexpr string to_string()
const {
882 return join(ranges_comparators_,
" ");
886 vector<range_comparator<I1, I2, I3>> ranges_comparators_;
888 constexpr bool match_at_least_one_comparator_with_prerelease(
const version<I1, I2, I3>& v)
const noexcept {
889 if (v.prerelease_tag().empty()) {
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;
902 template <
typename I1 =
int,
typename I2 =
int,
typename I3 =
int>
905 friend class detail::range_parser;
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);
913 constexpr auto begin()
const noexcept {
914 return ranges_.begin();
917 constexpr auto end()
const noexcept {
918 return ranges_.end();
921 constexpr std::size_t size()
const noexcept {
922 return ranges_.size();
925 constexpr bool empty()
const noexcept {
926 return ranges_.empty();
929 constexpr string to_string()
const {
930 return join(ranges_,
" ");
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;
948 detail::range<I1, I2, I3> range;
949 if (
const auto res = parse_range(range); !res) {
953 ranges.push_back(range);
956 }
while (stream_.advance_if_match(token_type::logical_or));
958 out.ranges_ = std::move(ranges);
960 return success(stream_.peek().lexeme);
964 token_stream stream_;
966 template <
typename I1,
typename I2,
typename I3>
967 constexpr from_chars_result parse_range(detail::range<I1, I2, I3>& out)
noexcept {
971 if (
const auto res = parse_range_comparator(out.ranges_comparators_); !res) {
977 }
while (stream_.check(token_type::range_operator) || stream_.check(token_type::digit));
979 return success(stream_.peek().lexeme);
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;
986 if (stream_.advance_if_match(token, token_type::range_operator)) {
987 op = std::get<range_operator>(token.value);
992 version<I1, I2, I3> ver;
993 version_parser parser{ stream_ };
994 if (
const auto res = parser.parse(ver); !res) {
998 out.emplace_back(ver, op);
999 return success(stream_.peek().lexeme);
1002 constexpr void skip_whitespaces() noexcept {
1003 while (stream_.advance_if_match(token_type::space)) {
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);
1019 return detail::range_parser{ std::move(token_stream) }.parse(out);
1023#ifndef PLUGIFY_VECTOR_NO_STD_HASH
1026 template<
typename I1,
typename I2,
typename I3>
1027 struct hash<plg::version<I1, I2, I3>> {
1029 std::size_t seed = 0;
1030 plg::hash_combine_all(seed,
1034 ver.prerelease_tag(),
1035 ver.build_metadata());
1042#ifndef PLUGIFY_VECTOR_NO_STD_FORMAT
1044#ifdef FMT_HEADER_ONLY
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) {
1055 template<
class FormatContext>
1057 return std::format_to(ctx.out(),
"{}", ver.to_string());
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) {
1066 template<
class FormatContext>
1068 return std::format_to(ctx.out(),
"{}", ver.to_string());
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) {
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());
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) {
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());