view dep/semver/semver.hpp @ 285:65df2813d0de

dep/animone: header cleanup
author Paper <paper@paper.us.eu.org>
date Wed, 08 May 2024 16:43:32 -0400
parents b9f111d84d95
children
line wrap: on
line source

//          _____                            _   _
//         / ____|                          | | (_)
//        | (___   ___ _ __ ___   __ _ _ __ | |_ _  ___
//         \___ \ / _ \ '_ ` _ \ / _` | '_ \| __| |/ __|
//         ____) |  __/ | | | | | (_| | | | | |_| | (__
//        |_____/ \___|_| |_| |_|\__,_|_| |_|\__|_|\___|
// __      __           _             _                _____
// \ \    / /          (_)           (_)              / ____|_     _
//  \ \  / /__ _ __ ___ _  ___  _ __  _ _ __   __ _  | |   _| |_ _| |_
//   \ \/ / _ \ '__/ __| |/ _ \| '_ \| | '_ \ / _` | | |  |_   _|_   _|
//    \  /  __/ |  \__ \ | (_) | | | | | | | | (_| | | |____|_|   |_|
//     \/ \___|_|  |___/_|\___/|_| |_|_|_| |_|\__, |  \_____|
// https://github.com/Neargye/semver           __/ |
// version 0.3.0                              |___/
//
// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
// SPDX-License-Identifier: MIT
// Copyright (c) 2018 - 2021 Daniil Goncharov <neargye@gmail.com>.
// Copyright (c) 2020 - 2021 Alexander Gorbunov <naratzul@gmail.com>.
//
// Permission is hereby  granted, free of charge, to any  person obtaining a copy
// of this software and associated  documentation files (the "Software"), to deal
// in the Software  without restriction, including without  limitation the rights
// to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
// copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
// IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
// FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
// AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
// LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#ifndef NEARGYE_SEMANTIC_VERSIONING_HPP
#define NEARGYE_SEMANTIC_VERSIONING_HPP

#define SEMVER_VERSION_MAJOR 0
#define SEMVER_VERSION_MINOR 3
#define SEMVER_VERSION_PATCH 0

#include <cstddef>
#include <cstdint>
#include <iosfwd>
#include <limits>
#include <optional>
#include <string>
#include <string_view>
#if __has_include(<charconv>)
#include <charconv>
#else
#include <system_error>
#endif

#if defined(SEMVER_CONFIG_FILE)
#include SEMVER_CONFIG_FILE
#endif

#if defined(SEMVER_THROW)
// define SEMVER_THROW(msg) to override semver throw behavior.
#elif defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
#  include <stdexcept>
#  define SEMVER_THROW(msg) (throw std::invalid_argument{msg})
#else
#  include <cassert>
#  include <cstdlib>
#  define SEMVER_THROW(msg) (assert(!msg), std::abort())
#endif

#if defined(__clang__)
#  pragma clang diagnostic push
#  pragma clang diagnostic ignored "-Wmissing-braces" // Ignore warning: suggest braces around initialization of subobject 'return {first, std::errc::invalid_argument};'.
#endif

#if __cpp_impl_three_way_comparison >= 201907L
#include <compare>
#endif


namespace semver {

enum struct prerelease : std::uint8_t {
  alpha = 0,
  beta = 1,
  rc = 2,
  none = 3
};

#if __has_include(<charconv>)
struct from_chars_result : std::from_chars_result {
  [[nodiscard]] constexpr operator bool() const noexcept { return ec == std::errc{}; }
};

struct to_chars_result : std::to_chars_result {
  [[nodiscard]] constexpr operator bool() const noexcept { return ec == std::errc{}; }
};
#else
struct from_chars_result {
  const char* ptr;
  std::errc ec;

  [[nodiscard]] constexpr operator bool() const noexcept { return ec == std::errc{}; }
};

struct to_chars_result {
  char* ptr;
  std::errc ec;

  [[nodiscard]] constexpr operator bool() const noexcept { return ec == std::errc{}; }
};
#endif

// Max version string length = 5(<major>) + 1(.) + 5(<minor>) + 1(.) + 5(<patch>) + 1(-) + 5(<prerelease>) + 1(.) + 5(<prereleaseversion>) = 29.
inline constexpr auto max_version_string_length = std::size_t{29};

namespace detail {

inline constexpr auto alpha = std::string_view{"alpha", 5};
inline constexpr auto beta  = std::string_view{"beta", 4};
inline constexpr auto rc    = std::string_view{"rc", 2};

// Min version string length = 1(<major>) + 1(.) + 1(<minor>) + 1(.) + 1(<patch>) = 5.
inline constexpr auto min_version_string_length = 5;

constexpr char to_lower(char c) noexcept {
  return (c >= 'A' && c <= 'Z') ? static_cast<char>(c + ('a' - 'A')) : c;
}

constexpr bool is_digit(char c) noexcept {
  return c >= '0' && c <= '9';
}

constexpr bool is_space(char c) noexcept {
  return c == ' ';
}

constexpr bool is_operator(char c) noexcept {
  return c == '<' || c == '>' || c == '=';
}

constexpr bool is_dot(char c) noexcept {
  return c == '.';
}

constexpr bool is_logical_or(char c) noexcept {
  return c == '|';
}

constexpr bool is_hyphen(char c) noexcept {
  return c == '-';
}

constexpr bool is_letter(char c) noexcept {
  return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}

constexpr std::uint16_t to_digit(char c) noexcept {
  return static_cast<std::uint16_t>(c - '0');
}

constexpr std::uint8_t length(std::uint16_t x) noexcept {
  if (x < 10) {
    return 1;
  }
  if (x < 100) {
    return 2;
  }
  if (x < 1000) {
    return 3;
  }
  if (x < 10000) {
    return 4;
  }
  return 5;
}

constexpr std::uint8_t length(prerelease t) noexcept {
  if (t == prerelease::alpha) {
    return static_cast<std::uint8_t>(alpha.length());
  } else if (t == prerelease::beta) {
    return static_cast<std::uint8_t>(beta.length());
  } else if (t == prerelease::rc) {
    return static_cast<std::uint8_t>(rc.length());
  }

  return 0;
}

constexpr bool equals(const char* first, const char* last, std::string_view str) noexcept {
  for (std::size_t i = 0; first != last && i < str.length(); ++i, ++first) {
    if (to_lower(*first) != to_lower(str[i])) {
      return false;
    }
  }

  return true;
}

constexpr char* to_chars(char* str, std::uint16_t x, bool dot = true) noexcept {
  do {
    *(--str) = static_cast<char>('0' + (x % 10));
    x /= 10;
  } while (x != 0);

  if (dot) {
    *(--str) = '.';
  }

  return str;
}

constexpr char* to_chars(char* str, prerelease t) noexcept {
  const auto p = t == prerelease::alpha
                     ? alpha
                     : t == prerelease::beta
                           ? beta
                           : t == prerelease::rc
                                 ? rc
                                 : std::string_view{};

  if (p.size() > 0) {
    for (auto it = p.rbegin(); it != p.rend(); ++it) {
      *(--str) = *it;
    }
    *(--str) = '-';
  }

  return str;
}

constexpr const char* from_chars(const char* first, const char* last, std::uint16_t& d) noexcept {
  if (first != last && is_digit(*first)) {
    std::int32_t t = 0;
    for (; first != last && is_digit(*first); ++first) {
      t = t * 10 + to_digit(*first);
    }
    if (t <= (std::numeric_limits<std::uint16_t>::max)()) {
      d = static_cast<std::uint16_t>(t);
      return first;
    }
  }

  return nullptr;
}

constexpr const char* from_chars(const char* first, const char* last, std::optional<std::uint16_t>& d) noexcept {
  if (first != last && is_digit(*first)) {
    std::int32_t t = 0;
    for (; first != last && is_digit(*first); ++first) {
      t = t * 10 + to_digit(*first);
    }
    if (t <= (std::numeric_limits<std::uint16_t>::max)()) {
      d = static_cast<std::uint16_t>(t);
      return first;
    }
  }

  return nullptr;
}

constexpr const char* from_chars(const char* first, const char* last, prerelease& p) noexcept {
  if (is_hyphen(*first)) {
    ++first;
  }

  if (equals(first, last, alpha)) {
    p = prerelease::alpha;
    return first + alpha.length();
  } else if (equals(first, last, beta)) {
    p = prerelease::beta;
    return first + beta.length();
  } else if (equals(first, last, rc)) {
    p = prerelease::rc;
    return first + rc.length();
  }

  return nullptr;
}

constexpr bool check_delimiter(const char* first, const char* last, char d) noexcept {
  return first != last && first != nullptr && *first == d;
}

template <typename T, typename = void>
struct resize_uninitialized {
  static auto resize(T& str, std::size_t size) -> std::void_t<decltype(str.resize(size))> {
    str.resize(size);
  }
};

template <typename T>
struct resize_uninitialized<T, std::void_t<decltype(std::declval<T>().__resize_default_init(42))>> {
  static void resize(T& str, std::size_t size) {
    str.__resize_default_init(size);
  }
};

} // namespace semver::detail

struct version {
  std::uint16_t major             = 0;
  std::uint16_t minor             = 1;
  std::uint16_t patch             = 0;
  prerelease prerelease_type      = prerelease::none;
  std::optional<std::uint16_t> prerelease_number = std::nullopt;

  constexpr version(std::uint16_t mj,
                    std::uint16_t mn,
                    std::uint16_t pt,
                    prerelease prt = prerelease::none,
                    std::optional<std::uint16_t> prn = std::nullopt) noexcept
      : major{mj},
        minor{mn},
        patch{pt},
        prerelease_type{prt},
        prerelease_number{prt == prerelease::none ? std::nullopt : prn} {
  }

    constexpr version(std::uint16_t mj,
                    std::uint16_t mn,
                    std::uint16_t pt,
                    prerelease prt,
                    std::uint16_t prn) noexcept
      : major{mj},
        minor{mn},
        patch{pt},
        prerelease_type{prt},
        prerelease_number{prt == prerelease::none ? std::nullopt : std::make_optional<std::uint16_t>(prn)} {
  }

  explicit constexpr version(std::string_view str) : version(0, 0, 0, prerelease::none, std::nullopt) {
    from_string(str);
  }

  constexpr version() = default; // https://semver.org/#how-should-i-deal-with-revisions-in-the-0yz-initial-development-phase

  constexpr version(const version&) = default;

  constexpr version(version&&) = default;

  ~version() = default;

  version& operator=(const version&) = default;

  version& operator=(version&&) = default;

  [[nodiscard]] constexpr from_chars_result from_chars(const char* first, const char* last) noexcept {
    if (first == nullptr || last == nullptr || (last - first) < detail::min_version_string_length) {
      return {first, std::errc::invalid_argument};
    }

    auto next = first;
    if (next = detail::from_chars(next, last, major); detail::check_delimiter(next, last, '.')) {
      if (next = detail::from_chars(++next, last, minor); detail::check_delimiter(next, last, '.')) {
        if (next = detail::from_chars(++next, last, patch); next == last) {
          prerelease_type = prerelease::none;
          prerelease_number = {};
          return {next, std::errc{}};
        } else if (detail::check_delimiter(next, last, '-')) {
          if (next = detail::from_chars(next, last, prerelease_type); next == last) {
            prerelease_number = {};
            return {next, std::errc{}};
          } else if (detail::check_delimiter(next, last, '.')) {
            if (next = detail::from_chars(++next, last, prerelease_number); next == last) {
              return {next, std::errc{}};
            }
          }
        }
      }
    }

    return {first, std::errc::invalid_argument};
  }

  [[nodiscard]] constexpr to_chars_result to_chars(char* first, char* last) const noexcept {
    const auto length = string_length();
    if (first == nullptr || last == nullptr || (last - first) < length) {
      return {last, std::errc::value_too_large};
    }

    auto next = first + length;
    if (prerelease_type != prerelease::none) {
      if (prerelease_number.has_value()) {
        next = detail::to_chars(next, prerelease_number.value());
      }
      next = detail::to_chars(next, prerelease_type);
    }
    next = detail::to_chars(next, patch);
    next = detail::to_chars(next, minor);
    next = detail::to_chars(next, major, false);

    return {first + length, std::errc{}};
  }

  [[nodiscard]] constexpr bool from_string_noexcept(std::string_view str) noexcept {
    return from_chars(str.data(), str.data() + str.length());
  }

  constexpr version& from_string(std::string_view str) {
    if (!from_string_noexcept(str)) {
      SEMVER_THROW("semver::version::from_string invalid version.");
    }

    return *this;
  }

  [[nodiscard]] std::string to_string() const {
    auto str = std::string{};
    detail::resize_uninitialized<std::string>::resize(str, string_length());
    if (!to_chars(str.data(), str.data() + str.length())) {
      SEMVER_THROW("semver::version::to_string invalid version.");
    }

    return str;
  }

  [[nodiscard]] constexpr std::uint8_t string_length() const noexcept {
    // (<major>) + 1(.) + (<minor>) + 1(.) + (<patch>)
    auto length = detail::length(major) + detail::length(minor) + detail::length(patch) + 2;
    if (prerelease_type != prerelease::none) {
      // + 1(-) + (<prerelease>)
      length += detail::length(prerelease_type) + 1;
      if (prerelease_number.has_value()) {
        // + 1(.) + (<prereleaseversion>)
        length += detail::length(prerelease_number.value()) + 1;
      }
    }

    return static_cast<std::uint8_t>(length);
  }

  [[nodiscard]] constexpr int compare(const version& other) const noexcept {
    if (major != other.major) {
      return major - other.major;
    }

    if (minor != other.minor) {
      return minor - other.minor;
    }

    if (patch != other.patch) {
      return patch - other.patch;
    }

    if (prerelease_type != other.prerelease_type) {
      return static_cast<std::uint8_t>(prerelease_type) - static_cast<std::uint8_t>(other.prerelease_type);
    }

    if (prerelease_number.has_value()) {
      if (other.prerelease_number.has_value()) {
        return prerelease_number.value() - other.prerelease_number.value();
      }
      return 1;
    } else if (other.prerelease_number.has_value()) {
      return -1;
    }

    return 0;
  }
};

[[nodiscard]] constexpr bool operator==(const version& lhs, const version& rhs) noexcept {
  return lhs.compare(rhs) == 0;
}

[[nodiscard]] constexpr bool operator!=(const version& lhs, const version& rhs) noexcept {
  return lhs.compare(rhs) != 0;
}

[[nodiscard]] constexpr bool operator>(const version& lhs, const version& rhs) noexcept {
  return lhs.compare(rhs) > 0;
}

[[nodiscard]] constexpr bool operator>=(const version& lhs, const version& rhs) noexcept {
  return lhs.compare(rhs) >= 0;
}

[[nodiscard]] constexpr bool operator<(const version& lhs, const version& rhs) noexcept {
  return lhs.compare(rhs) < 0;
}

[[nodiscard]] constexpr bool operator<=(const version& lhs, const version& rhs) noexcept {
  return lhs.compare(rhs) <= 0;
}

#if __cpp_impl_three_way_comparison >= 201907L
[[nodiscard]] constexpr std::strong_ordering operator<=>(const version& lhs, const version& rhs) {
  int compare = lhs.compare(rhs);
  if ( compare == 0 )
    return std::strong_ordering::equal;
  if ( compare > 0 )
    return std::strong_ordering::greater;
  return std::strong_ordering::less;
}
#endif

[[nodiscard]] constexpr version operator""_version(const char* str, std::size_t length) {
  return version{std::string_view{str, length}};
}

[[nodiscard]] constexpr bool valid(std::string_view str) noexcept {
  return version{}.from_string_noexcept(str);
}

[[nodiscard]] constexpr from_chars_result from_chars(const char* first, const char* last, version& v) noexcept {
  return v.from_chars(first, last);
}

[[nodiscard]] constexpr to_chars_result to_chars(char* first, char* last, const version& v) noexcept {
  return v.to_chars(first, last);
}

[[nodiscard]] constexpr std::optional<version> from_string_noexcept(std::string_view str) noexcept {
  if (version v{}; v.from_string_noexcept(str)) {
    return v;
  }

  return std::nullopt;
}

[[nodiscard]] constexpr version from_string(std::string_view str) {
  return version{str};
}

[[nodiscard]] inline std::string to_string(const version& v) {
  return v.to_string();
}

template <typename Char, typename Traits>
inline std::basic_ostream<Char, Traits>& operator<<(std::basic_ostream<Char, Traits>& os, const version& v) {
  for (const auto c : v.to_string()) {
    os.put(c);
  }

  return os;
}

inline namespace comparators {

enum struct comparators_option : std::uint8_t {
  exclude_prerelease,
  include_prerelease
};

[[nodiscard]] constexpr int compare(const version& lhs, const version& rhs, comparators_option option = comparators_option::include_prerelease) noexcept {
  if (option == comparators_option::exclude_prerelease) {
    return version{lhs.major, lhs.minor, lhs.patch}.compare(version{rhs.major, rhs.minor, rhs.patch});
  }
  return lhs.compare(rhs);
}

[[nodiscard]] constexpr bool equal_to(const version& lhs, const version& rhs, comparators_option option = comparators_option::include_prerelease) noexcept {
  return compare(lhs, rhs, option) == 0;
}

[[nodiscard]] constexpr bool not_equal_to(const version& lhs, const version& rhs, comparators_option option = comparators_option::include_prerelease) noexcept {
  return compare(lhs, rhs, option) != 0;
}

[[nodiscard]] constexpr bool greater(const version& lhs, const version& rhs, comparators_option option = comparators_option::include_prerelease) noexcept {
  return compare(lhs, rhs, option) > 0;
}

[[nodiscard]] constexpr bool greater_equal(const version& lhs, const version& rhs, comparators_option option = comparators_option::include_prerelease) noexcept {
  return compare(lhs, rhs, option) >= 0;
}

[[nodiscard]] constexpr bool less(const version& lhs, const version& rhs, comparators_option option = comparators_option::include_prerelease) noexcept {
  return compare(lhs, rhs, option) < 0;
}

[[nodiscard]] constexpr bool less_equal(const version& lhs, const version& rhs, comparators_option option = comparators_option::include_prerelease) noexcept {
  return compare(lhs, rhs, option) <= 0;
}

} // namespace semver::comparators

namespace range {

namespace detail {

using namespace semver::detail;

class range {
 public:
  constexpr explicit range(std::string_view str) noexcept : parser{str} {}

  constexpr bool satisfies(const version& ver, bool include_prerelease) {
    const bool has_prerelease = ver.prerelease_type != prerelease::none;

    do {
      if (is_logical_or_token()) {
        parser.advance_token(range_token_type::logical_or);
      }

      bool contains = true;
      bool allow_compare = include_prerelease;

      while (is_operator_token() || is_number_token()) {
        const auto range = parser.parse_range();
        const bool equal_without_tags = equal_to(range.ver, ver, comparators_option::exclude_prerelease);

        if (has_prerelease && equal_without_tags) {
          allow_compare = true;
        }

        if (!range.satisfies(ver)) {
          contains = false;
          break;
        }
      }

      if (has_prerelease) {
        if (allow_compare && contains) {
          return true;
        }
      } else if (contains) {
        return true;
      }

    } while (is_logical_or_token());
    
    return false;
  }

private:
  enum struct range_operator : std::uint8_t {
    less,
    less_or_equal,
    greater,
    greater_or_equal,
    equal
  };

  struct range_comparator {
    range_operator op;
    version ver;

    constexpr bool satisfies(const version& version) const {
      switch (op) {
        case range_operator::equal:
          return version == ver;
        case range_operator::greater:
          return version > ver;
        case range_operator::greater_or_equal:
          return version >= ver;
        case range_operator::less:
          return version < ver;
        case range_operator::less_or_equal:
          return version <= ver;
        default:
          SEMVER_THROW("semver::range unexpected operator.");
      }
    }
  };

  enum struct range_token_type : std::uint8_t {
    none,
    number,
    range_operator,
    dot,
    logical_or,
    hyphen,
    prerelease,
    end_of_line
  };

  struct range_token {
    range_token_type type      = range_token_type::none;
    std::uint16_t number       = 0;
    range_operator op          = range_operator::equal;
    prerelease prerelease_type = prerelease::none;
  };

  struct range_lexer {
    std::string_view text;
    std::size_t pos;

    constexpr explicit range_lexer(std::string_view text) noexcept : text{text}, pos{0} {}

    constexpr range_token get_next_token() noexcept {
      while (!end_of_line()) {

        if (is_space(text[pos])) {
          advance(1);
          continue;
        }

        if (is_logical_or(text[pos])) {
          advance(2);
          return {range_token_type::logical_or};
        }

        if (is_operator(text[pos])) {
          const auto op = get_operator();
          return {range_token_type::range_operator, 0, op};
        }

        if (is_digit(text[pos])) {
          const auto number = get_number();
          return {range_token_type::number, number};
        }

        if (is_dot(text[pos])) {
          advance(1);
          return {range_token_type::dot};
        }

        if (is_hyphen(text[pos])) {
          advance(1);
          return {range_token_type::hyphen};
        }

        if (is_letter(text[pos])) {
          const auto prerelease = get_prerelease();
          return {range_token_type::prerelease, 0, range_operator::equal, prerelease};
        }
      }

      return {range_token_type::end_of_line};
    }

    constexpr bool end_of_line() const noexcept { return pos >= text.length(); }

    constexpr void advance(std::size_t i) noexcept {
      pos += i;
    }

    constexpr range_operator get_operator() noexcept {
      if (text[pos] == '<') {
        advance(1);
        if (text[pos] == '=') {
          advance(1);
          return range_operator::less_or_equal;
        }
        return range_operator::less;
      } else if (text[pos] == '>') {
        advance(1);
        if (text[pos] == '=') {
          advance(1);
          return range_operator::greater_or_equal;
        }
        return range_operator::greater;
      } else if (text[pos] == '=') {
        advance(1);
        return range_operator::equal;
      }

      return range_operator::equal;
    }

    constexpr std::uint16_t get_number() noexcept {
      const auto first = text.data() + pos;
      const auto last = text.data() + text.length();
      if (std::uint16_t n{}; from_chars(first, last, n) != nullptr) {
        advance(length(n));
        return n;
      }

      return 0;
    }

    constexpr prerelease get_prerelease() noexcept {
      const auto first = text.data() + pos;
      const auto last = text.data() + text.length();
      if (first > last) {
        advance(1);
        return prerelease::none;
      }

      if (prerelease p{}; from_chars(first, last, p) != nullptr) {
        advance(length(p));
        return p;
      }

      advance(1);

      return prerelease::none;
    }
  };

  struct range_parser {
    range_lexer lexer;
    range_token current_token;

    constexpr explicit range_parser(std::string_view str) : lexer{str}, current_token{range_token_type::none} {
      advance_token(range_token_type::none);
    }

    constexpr void advance_token(range_token_type token_type) {
      if (current_token.type != token_type) {
        SEMVER_THROW("semver::range unexpected token.");
      }
      current_token = lexer.get_next_token();
    }

    constexpr range_comparator parse_range() {
      if (current_token.type == range_token_type::number) {
        const auto version = parse_version();
        return {range_operator::equal, version};
      } else if (current_token.type == range_token_type::range_operator) {
        const auto range_operator = current_token.op;
        advance_token(range_token_type::range_operator);
        const auto version = parse_version();
        return {range_operator, version};
      }

      return {range_operator::equal, version{}};
    }

    constexpr version parse_version() {
      const auto major = parse_number();

      advance_token(range_token_type::dot);
      const auto minor = parse_number();

      advance_token(range_token_type::dot);
      const auto patch = parse_number();

      prerelease prerelease = prerelease::none;
      std::optional<std::uint16_t> prerelease_number = std::nullopt;

      if (current_token.type == range_token_type::hyphen) {
        advance_token(range_token_type::hyphen);
        prerelease = parse_prerelease();
        if (current_token.type == range_token_type::dot) {
          advance_token(range_token_type::dot);
          prerelease_number = parse_number();
        }
      }

      return {major, minor, patch, prerelease, prerelease_number};
    }

    constexpr std::uint16_t parse_number() {
      const auto token = current_token;
      advance_token(range_token_type::number);

      return token.number;
    }

    constexpr prerelease parse_prerelease() {
      const auto token = current_token;
      advance_token(range_token_type::prerelease);

      return token.prerelease_type;
    }
  };

  [[nodiscard]] constexpr bool is_logical_or_token() const noexcept {
    return parser.current_token.type == range_token_type::logical_or;
  }
  [[nodiscard]] constexpr bool is_operator_token() const noexcept {
    return parser.current_token.type == range_token_type::range_operator;
  }

  [[nodiscard]] constexpr bool is_number_token() const noexcept {
    return parser.current_token.type == range_token_type::number;
  }

  range_parser parser;
};

} // namespace semver::range::detail

enum struct satisfies_option : std::uint8_t {
  exclude_prerelease,
  include_prerelease
};

constexpr bool satisfies(const version& ver, std::string_view str, satisfies_option option = satisfies_option::exclude_prerelease) {
  switch (option) {
  case satisfies_option::exclude_prerelease:
    return detail::range{str}.satisfies(ver, false);
  case satisfies_option::include_prerelease:
    return detail::range{str}.satisfies(ver, true);
  default:
    SEMVER_THROW("semver::range unexpected satisfies_option.");
  }
}

} // namespace semver::range

// Version lib semver.
inline constexpr auto semver_version = version{SEMVER_VERSION_MAJOR, SEMVER_VERSION_MINOR, SEMVER_VERSION_PATCH};

} // namespace semver

#if defined(__clang__)
#  pragma clang diagnostic pop
#endif

#endif // NEARGYE_SEMANTIC_VERSIONING_HPP