100 for (
hilet& token : _tokens) {
124 for (
hilet& token : _tokens) {
125 r += token.debug_u32string();
154 if (_tokens.
empty() or not _tokens.
front().is_text()) {
157 auto r = _tokens.
front().u32string();
158 if (_tokens.
size() >= 2 and _tokens[1].is_any_directory()) {
192 [[nodiscard]] std::filesystem::path
base_path() const noexcept
195 if (
auto i = text.rfind(
'/'); i == std::u32string::npos) {
210 [[nodiscard]]
constexpr bool matches(std::u32string_view str)
const noexcept
212 auto first = _tokens.
cbegin();
213 auto last = _tokens.
cend();
216 if (not matches_strip(first, last, str)) {
220 }
else if (first == last) {
227 return matches(first, last, str);
237 return matches(std::u32string_view{str});
245 [[nodiscard]]
constexpr bool matches(
char32_t const *str)
const noexcept
247 return matches(std::u32string_view{str});
255 [[nodiscard]]
constexpr bool matches(std::string_view str)
const noexcept
267 return matches(std::string_view{str});
275 [[nodiscard]]
constexpr bool matches(
char const *str)
const noexcept
277 return matches(std::string_view{str});
285 [[nodiscard]]
bool matches(std::filesystem::path
const& path)
const noexcept
287 return matches(path.generic_u32string());
291 enum class match_result_type { fail, success, unchecked };
302 constexpr token_type(token_type
const&)
noexcept =
default;
303 constexpr token_type(token_type&&) noexcept = default;
304 constexpr token_type& operator=(token_type const&) noexcept = default;
305 constexpr token_type& operator=(token_type&&) noexcept = default;
307 constexpr token_type(
text_type&& rhs) noexcept : _value(
std::move(rhs)) {}
308 constexpr token_type(character_class_type&& rhs) noexcept : _value(
std::move(rhs)) {}
309 constexpr token_type(alternation_type&& rhs) noexcept : _value(
std::move(rhs)) {}
310 constexpr token_type(any_character_type&& rhs) noexcept : _value(
std::move(rhs)) {}
311 constexpr token_type(any_text_type&& rhs) noexcept : _value(
std::move(rhs)) {}
312 constexpr token_type(any_directory_type&& rhs) noexcept : _value(
std::move(rhs)) {}
314 [[nodiscard]]
constexpr bool is_text() const noexcept
316 return std::holds_alternative<text_type>(_value);
319 [[nodiscard]]
constexpr bool is_any_directory() const noexcept
321 return std::holds_alternative<any_directory_type>(_value);
325 [[nodiscard]]
constexpr match_result_type strip(std::u32string_view& str)
const noexcept
327 constexpr bool Right = not Left;
329 if (
hilet text_ptr = std::get_if<text_type>(&_value)) {
330 if (Left and str.starts_with(*text_ptr)) {
331 str.remove_prefix(text_ptr->size());
332 return match_result_type::success;
334 }
else if (Right and str.ends_with(*text_ptr)) {
335 str.remove_suffix(text_ptr->size());
336 return match_result_type::success;
339 return match_result_type::fail;
342 }
else if (
hilet character_class_ptr = std::get_if<character_class_type>(&_value)) {
344 return match_result_type::fail;
346 hilet c = Left ? str.front() : str.back();
347 for (
hilet[first_char, last_char] : *character_class_ptr) {
348 if (c >= first_char and c <= last_char) {
349 if constexpr (Left) {
350 str.remove_prefix(1);
352 str.remove_suffix(1);
354 return match_result_type::success;
357 return match_result_type::fail;
359 }
else if (std::holds_alternative<any_character_type>(_value)) {
361 return match_result_type::fail;
363 if constexpr (Left) {
364 str.remove_prefix(1);
366 str.remove_suffix(1);
368 return match_result_type::success;
371 return match_result_type::unchecked;
375 [[nodiscard]]
constexpr match_result_type matches(std::u32string_view& str,
size_t iteration)
const noexcept
377 if (
hilet text_ptr = std::get_if<text_type>(&_value)) {
378 if (iteration != 0) {
379 return match_result_type::fail;
381 }
else if (str.starts_with(*text_ptr)) {
382 str.remove_prefix(text_ptr->size());
383 return match_result_type::success;
386 return match_result_type::fail;
389 }
else if (
hilet character_class_ptr = std::get_if<character_class_type>(&_value)) {
390 if (iteration != 0 or str.empty()) {
391 return match_result_type::fail;
394 for (
hilet[first_char, last_char] : *character_class_ptr) {
395 if (c >= first_char and c <= last_char) {
396 str.remove_prefix(1);
397 return match_result_type::success;
400 return match_result_type::fail;
403 }
else if (
hilet alternation_ptr = std::get_if<alternation_type>(&_value)) {
404 if (iteration >= alternation_ptr->size()) {
405 return match_result_type::fail;
406 }
else if (str.starts_with((*alternation_ptr)[iteration])) {
407 str.remove_prefix((*alternation_ptr)[iteration].size());
408 return match_result_type::success;
410 return match_result_type::unchecked;
413 }
else if (std::holds_alternative<any_character_type>(_value)) {
414 if (iteration != 0 or str.empty()) {
415 return match_result_type::fail;
417 str.remove_prefix(1);
418 return match_result_type::success;
421 }
else if (std::holds_alternative<any_text_type>(_value)) {
422 if (iteration > str.size() or iteration > str.find(
'/')) {
423 return match_result_type::fail;
425 str.remove_prefix(iteration);
426 return match_result_type::success;
429 }
else if (std::holds_alternative<any_directory_type>(_value)) {
430 if (str.empty() or str.front() !=
'/') {
431 return match_result_type::fail;
433 for (
auto i = 0_uz; i != std::u32string_view::npos; i = str.find(
'/', i + 1)) {
434 if (iteration-- == 0) {
435 str.remove_prefix(i + 1);
436 return match_result_type::success;
439 return match_result_type::fail;
451 if (
auto text_ptr = std::get_if<text_type>(&_value)) {
454 }
else if (
auto character_class_ptr = std::get_if<character_class_type>(&_value)) {
456 for (
hilet[first_char, last_char] : *character_class_ptr) {
457 if (first_char == last_char) {
467 }
else if (
auto alternation_ptr = std::get_if<alternation_type>(&_value)) {
469 for (
hilet& text : *alternation_ptr) {
477 }
else if (std::holds_alternative<any_character_type>(_value)) {
480 }
else if (std::holds_alternative<any_text_type>(_value)) {
483 }
else if (std::holds_alternative<any_directory_type>(_value)) {
493 [[nodiscard]]
constexpr std::u32string debug_u32string() const noexcept
497 if (
auto text_ptr = std::get_if<text_type>(&_value)) {
502 }
else if (
auto character_class_ptr = std::get_if<character_class_type>(&_value)) {
504 for (
hilet[first_char, last_char] : *character_class_ptr) {
505 if (first_char == last_char) {
515 }
else if (
auto alternation_ptr = std::get_if<alternation_type>(&_value)) {
517 for (
hilet& text : *alternation_ptr) {
525 }
else if (std::holds_alternative<any_character_type>(_value)) {
528 }
else if (std::holds_alternative<any_text_type>(_value)) {
531 }
else if (std::holds_alternative<any_directory_type>(_value)) {
542 using variant_type = std::
543 variant<text_type, character_class_type, alternation_type, any_character_type, any_text_type, any_directory_type>;
549 using const_iterator = tokens_type::const_iterator;
553 [[nodiscard]]
constexpr static token_type make_text(token_type::text_type&& rhs)
noexcept
558 [[nodiscard]]
constexpr static token_type make_alternation(token_type::alternation_type&& rhs)
noexcept
563 [[nodiscard]]
constexpr static token_type make_character_class(token_type::character_class_type&& rhs)
noexcept
568 [[nodiscard]]
constexpr static token_type make_any_character() noexcept
570 return token_type{token_type::any_character_type{}};
573 [[nodiscard]]
constexpr static token_type make_any_text() noexcept
575 return token_type{token_type::any_text_type{}};
578 [[nodiscard]]
constexpr static token_type make_any_directory() noexcept
580 return token_type{token_type::any_directory_type{}};
583 [[nodiscard]]
constexpr static tokens_type parse(
auto first,
auto last)
585#define HI_GLOB_APPEND_TEXT() \
587 if (not text.empty()) { \
588 r.emplace_back(make_text(std::move(text))); \
593 enum class state_type { idle, star, slash, slash_star, slash_star_star, bracket, bracket_range, brace };
594 using enum state_type;
596 static_assert(std::is_same_v<std::decay_t<
decltype(*first)>,
char32_t>);
598 auto r = tokens_type{};
601 auto text = token_type::text_type{};
602 auto alternation = token_type::alternation_type{};
603 auto character_class = token_type::character_class_type{};
615 HI_GLOB_APPEND_TEXT();
616 r.push_back(make_any_character());
622 HI_GLOB_APPEND_TEXT();
626 HI_GLOB_APPEND_TEXT();
636 throw parse_error(
"Double ** is only allowed between slashes, like /**/.");
638 HI_GLOB_APPEND_TEXT();
639 r.push_back(make_any_text());
657 state = slash_star_star;
660 HI_GLOB_APPEND_TEXT();
661 r.push_back(make_any_text());
667 case slash_star_star:
669 HI_GLOB_APPEND_TEXT();
670 r.push_back(make_any_directory());
673 throw parse_error(
"Double ** is only allowed between slashes, like /**/.");
679 if (character_class.empty()) {
680 character_class.emplace_back(c, c);
682 state = bracket_range;
684 }
else if (c == U
']') {
685 r.push_back(make_character_class(
std::move(character_class)));
686 character_class.clear();
689 character_class.emplace_back(c, c);
695 throw parse_error(
"Double '--' is not allowed inside a character class, i.e. between '[' and ']'.");
696 }
else if (c == U
']') {
697 character_class.emplace_back(U
'-', U
'-');
698 r.push_back(make_character_class(
std::move(character_class)));
699 character_class.clear();
702 character_class.back().second =
c;
709 if (not text.empty()) {
713 r.push_back(make_alternation(
std::move(alternation)));
716 }
else if (c == U
',') {
733 HI_GLOB_APPEND_TEXT();
737 HI_GLOB_APPEND_TEXT();
738 r.push_back(make_any_text());
743 HI_GLOB_APPEND_TEXT();
748 HI_GLOB_APPEND_TEXT();
749 r.push_back(make_any_text());
752 case slash_star_star:
753 HI_GLOB_APPEND_TEXT();
754 r.push_back(make_any_directory());
758 throw parse_error(
"Unclosed bracket '[' found in glob pattern.");
761 throw parse_error(
"Unclosed bracket '[' found in glob pattern.");
764 throw parse_error(
"Unclosed brace '{' found in glob pattern.");
769#undef HI_GLOB_APPEND_TEXT
772 [[nodiscard]]
constexpr static tokens_type parse(
auto&& range)
774 return parse(std::ranges::begin(range), std::ranges::end(range));
778 [[nodiscard]]
constexpr static bool
779 matches_strip(const_iterator& first, const_iterator& last, std::u32string_view& str)
noexcept
781 while (first != last) {
782 hilet it = Left ? first : last - 1;
783 switch (it->strip<Left>(str)) {
784 case match_result_type::fail:
786 case match_result_type::unchecked:
788 case match_result_type::success:
789 if constexpr (Left) {
802 [[nodiscard]]
constexpr static bool
803 matches_strip(const_iterator& first, const_iterator& last, std::u32string_view& str)
noexcept
805 return matches_strip<true>(first, last, str) and matches_strip<false>(first, last, str);
808 [[nodiscard]]
constexpr bool matches(const_iterator it, const_iterator last, std::u32string_view original)
const noexcept
810 hi_axiom(it != last);
812 struct stack_element {
813 std::u32string_view str;
820 stack.emplace_back(original, 0);
822 auto [str, iteration] = stack.back();
824 switch (it->matches(str, iteration)) {
825 case match_result_type::success:
826 if (it + 1 == last) {
834 ++stack.back().iteration;
839 stack.emplace_back(str, 0);
844 case match_result_type::unchecked:
846 ++stack.back().iteration;
849 case match_result_type::fail:
851 if (stack.size() == 1) {
858 ++stack.back().iteration;