semver.cxx 1.2025.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +123 -0
- package/package.json +26 -0
- package/semver.hpp +1053 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018 - 2025 Daniil Goncharov
|
|
4
|
+
Copyright (c) 2020 - 2025 Alexander Gorbunov
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
[](https://github.com/Neargye/semver/releases)
|
|
2
|
+
[](https://github.com/microsoft/vcpkg/tree/master/ports/neargye-semver)
|
|
3
|
+
[](https://conan.io/center/recipes/neargye-semver)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
C++ library compare and manipulate versions are available as extensions to the `<major>.<minor>.<patch>-<prerelease_tag>+<build_metadata>` format complying with [Semantic Versioning 2.0.0](https://semver.org)
|
|
7
|
+
|
|
8
|
+
## [Features & Examples](https://github.com/Neargye/semver/blob/master/example/)
|
|
9
|
+
|
|
10
|
+
* Parse
|
|
11
|
+
|
|
12
|
+
```cpp
|
|
13
|
+
semver::version v1;
|
|
14
|
+
if (semver::parse("1.4.3", v1)) {
|
|
15
|
+
const int patch = v1.patch(); // 3
|
|
16
|
+
// do something...
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
semver::version v2;
|
|
20
|
+
if (semver::parse("1.2.4-alpha.10")) {
|
|
21
|
+
const std::string prerelease_tag = v2.prerelease_tag() // alpha.10
|
|
22
|
+
// do something...
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
* Сomparison
|
|
27
|
+
|
|
28
|
+
```cpp
|
|
29
|
+
assert(v1 != v2);
|
|
30
|
+
assert(v1 > v2);
|
|
31
|
+
assert(v1 >= v2);
|
|
32
|
+
assert(v2 < v1);
|
|
33
|
+
assert(v2 <= v1);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
* Validate
|
|
37
|
+
|
|
38
|
+
```cpp
|
|
39
|
+
const bool result = semver::valid("1.2.3-alpha+build");
|
|
40
|
+
assert(result);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
* Range matching
|
|
44
|
+
|
|
45
|
+
```cpp
|
|
46
|
+
semver::range_set range;
|
|
47
|
+
if (semver::parse(">=1.0.0 <2.0.0 || >3.2.1", range)) {
|
|
48
|
+
semver::version version;
|
|
49
|
+
if (semver::parse("1.2.3", version)) {
|
|
50
|
+
assert(range.contains(version));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Check the *examples* folder to see more various usage examples
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
Run:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
$ npm i semver.cxx
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
And then include `semver.hpp` as follows:
|
|
66
|
+
|
|
67
|
+
```cxx
|
|
68
|
+
// main.cxx
|
|
69
|
+
#include <semver.hpp>
|
|
70
|
+
|
|
71
|
+
int main() { /* ... */ }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Finally, compile while adding the path `node_modules/semver.cxx` to your compiler's include paths.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
$ clang++ -std=c++20 -I./node_modules/semver.cxx main.cxx # or, use g++
|
|
78
|
+
$ g++ -std=c++20 -I./node_modules/semver.cxx main.cxx
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
You may also use a simpler approach with the [cpoach](https://www.npmjs.com/package/cpoach.sh) tool, which automatically adds the necessary include paths of all the installed dependencies for your project.
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
$ cpoach clang++ -std=c++20 main.cxx # or, use g++
|
|
85
|
+
$ cpoach g++ -std=c++20 main.cxx
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
Alternatively, you should add required file [semver.hpp](semver.hpp).
|
|
91
|
+
|
|
92
|
+
If you are using [vcpkg](https://github.com/Microsoft/vcpkg/) on your project for external dependencies, then you can use the [neargye-semver](https://github.com/microsoft/vcpkg/tree/master/ports/neargye-semver).
|
|
93
|
+
|
|
94
|
+
If you are using [Conan](https://www.conan.io/) to manage your dependencies, merely add `neargye-semver/x.y.z` to your conan's requires, where `x.y.z` is the release version you want to use.
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
Alternatively, you can use something like [CPM](https://github.com/TheLartians/CPM) which is based on CMake's `Fetch_Content` module.
|
|
98
|
+
|
|
99
|
+
```cmake
|
|
100
|
+
CPMAddPackage(
|
|
101
|
+
NAME semver
|
|
102
|
+
GITHUB_REPOSITORY Neargye/semver
|
|
103
|
+
GIT_TAG x.y.z # Where `x.y.z` is the release version you want to use.
|
|
104
|
+
)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Compiler compatibility
|
|
108
|
+
|
|
109
|
+
* Clang/LLVM >= 5
|
|
110
|
+
* MSVC++ >= 14.11 / Visual Studio >= 2017
|
|
111
|
+
* Xcode >= 10
|
|
112
|
+
* GCC >= 7
|
|
113
|
+
|
|
114
|
+
## Licensed under the [MIT License](LICENSE)
|
|
115
|
+
|
|
116
|
+
<br>
|
|
117
|
+
<br>
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
[](https://wolfram77.github.io)<br>
|
|
121
|
+
[](https://github.com/Neargye/semver)
|
|
122
|
+
[](https://nodef.github.io)
|
|
123
|
+

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