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.
Files changed (4) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +123 -0
  3. package/package.json +26 -0
  4. 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
+ [![Github releases](https://img.shields.io/github/release/Neargye/semver.svg)](https://github.com/Neargye/semver/releases)
2
+ [![Vcpkg package](https://img.shields.io/badge/Vcpkg-package-blueviolet)](https://github.com/microsoft/vcpkg/tree/master/ports/neargye-semver)
3
+ [![Conan package](https://img.shields.io/badge/Conan-package-blueviolet)](https://conan.io/center/recipes/neargye-semver)
4
+ [![License](https://img.shields.io/github/license/Neargye/semver.svg)](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://raw.githubusercontent.com/qb40/designs/gh-pages/0/image/11.png)](https://wolfram77.github.io)<br>
121
+ [![SRC](https://img.shields.io/badge/src-repo-green?logo=Org)](https://github.com/Neargye/semver)
122
+ [![ORG](https://img.shields.io/badge/org-nodef-green?logo=Org)](https://nodef.github.io)
123
+ ![](https://ga-beacon.deno.dev/G-RC63DPBH3P:SH3Eq-NoQ9mwgYeHWxu7cw/github.com/nodef/semver.cxx)
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