99 for (
hilet& token : _tokens) {
123 for (
hilet& token : _tokens) {
124 r += token.debug_u32string();
153 if (_tokens.
empty() or not _tokens.
front().is_text()) {
156 auto r = _tokens.
front().u32string();
157 if (_tokens.
size() >= 2 and _tokens[1].is_any_directory()) {
191 [[nodiscard]] std::filesystem::path
base_path() const noexcept
194 if (
auto i = text.rfind(
'/'); i == std::u32string::npos) {
209 [[nodiscard]]
constexpr bool matches(std::u32string_view str)
const noexcept
211 auto first = _tokens.
cbegin();
212 auto last = _tokens.
cend();
215 if (not matches_strip(first, last, str)) {
219 }
else if (first == last) {
226 return matches(first, last, str);
236 return matches(std::u32string_view{str});
244 [[nodiscard]]
constexpr bool matches(
char32_t const *str)
const noexcept
246 return matches(std::u32string_view{str});
254 [[nodiscard]]
constexpr bool matches(std::string_view str)
const noexcept
266 return matches(std::string_view{str});
274 [[nodiscard]]
constexpr bool matches(
char const *str)
const noexcept
276 return matches(std::string_view{str});
284 [[nodiscard]]
bool matches(std::filesystem::path
const& path)
const noexcept
286 return matches(path.generic_u32string());
290 enum class match_result_type { fail, success, unchecked };
301 constexpr token_type(token_type
const&)
noexcept =
default;
302 constexpr token_type(token_type&&) noexcept = default;
303 constexpr token_type& operator=(token_type const&) noexcept = default;
304 constexpr token_type& operator=(token_type&&) noexcept = default;
306 constexpr token_type(
text_type&& rhs) noexcept : _value(
std::move(rhs)) {}
307 constexpr token_type(character_class_type&& rhs) noexcept : _value(
std::move(rhs)) {}
308 constexpr token_type(alternation_type&& rhs) noexcept : _value(
std::move(rhs)) {}
309 constexpr token_type(any_character_type&& rhs) noexcept : _value(
std::move(rhs)) {}
310 constexpr token_type(any_text_type&& rhs) noexcept : _value(
std::move(rhs)) {}
311 constexpr token_type(any_directory_type&& rhs) noexcept : _value(
std::move(rhs)) {}
313 [[nodiscard]]
constexpr bool is_text() const noexcept
315 return std::holds_alternative<text_type>(_value);
318 [[nodiscard]]
constexpr bool is_any_directory() const noexcept
320 return std::holds_alternative<any_directory_type>(_value);
324 [[nodiscard]]
constexpr match_result_type strip(std::u32string_view& str)
const noexcept
326 constexpr bool Right = not Left;
328 if (
hilet text_ptr = std::get_if<text_type>(&_value)) {
329 if (Left and str.starts_with(*text_ptr)) {
330 str.remove_prefix(text_ptr->size());
331 return match_result_type::success;
333 }
else if (Right and str.ends_with(*text_ptr)) {
334 str.remove_suffix(text_ptr->size());
335 return match_result_type::success;
338 return match_result_type::fail;
341 }
else if (
hilet character_class_ptr = std::get_if<character_class_type>(&_value)) {
343 return match_result_type::fail;
345 hilet c = Left ? str.front() : str.back();
346 for (
hilet[first_char, last_char] : *character_class_ptr) {
347 if (c >= first_char and c <= last_char) {
348 if constexpr (Left) {
349 str.remove_prefix(1);
351 str.remove_suffix(1);
353 return match_result_type::success;
356 return match_result_type::fail;
358 }
else if (std::holds_alternative<any_character_type>(_value)) {
360 return match_result_type::fail;
362 if constexpr (Left) {
363 str.remove_prefix(1);
365 str.remove_suffix(1);
367 return match_result_type::success;
370 return match_result_type::unchecked;
374 [[nodiscard]]
constexpr match_result_type matches(std::u32string_view& str,
size_t iteration)
const noexcept
376 if (
hilet text_ptr = std::get_if<text_type>(&_value)) {
377 if (iteration != 0) {
378 return match_result_type::fail;
380 }
else if (str.starts_with(*text_ptr)) {
381 str.remove_prefix(text_ptr->size());
382 return match_result_type::success;
385 return match_result_type::fail;
388 }
else if (
hilet character_class_ptr = std::get_if<character_class_type>(&_value)) {
389 if (iteration != 0 or str.empty()) {
390 return match_result_type::fail;
393 for (
hilet[first_char, last_char] : *character_class_ptr) {
394 if (c >= first_char and c <= last_char) {
395 str.remove_prefix(1);
396 return match_result_type::success;
399 return match_result_type::fail;
402 }
else if (
hilet alternation_ptr = std::get_if<alternation_type>(&_value)) {
403 if (iteration >= alternation_ptr->size()) {
404 return match_result_type::fail;
405 }
else if (str.starts_with((*alternation_ptr)[iteration])) {
406 str.remove_prefix((*alternation_ptr)[iteration].size());
407 return match_result_type::success;
409 return match_result_type::unchecked;
412 }
else if (std::holds_alternative<any_character_type>(_value)) {
413 if (iteration != 0 or str.empty()) {
414 return match_result_type::fail;
416 str.remove_prefix(1);
417 return match_result_type::success;
420 }
else if (std::holds_alternative<any_text_type>(_value)) {
421 if (iteration > str.size() or iteration > str.find(
'/')) {
422 return match_result_type::fail;
424 str.remove_prefix(iteration);
425 return match_result_type::success;
428 }
else if (std::holds_alternative<any_directory_type>(_value)) {
429 if (str.empty() or str.front() !=
'/') {
430 return match_result_type::fail;
432 for (
auto i = 0_uz; i != std::u32string_view::npos; i = str.find(
'/', i + 1)) {
433 if (iteration-- == 0) {
434 str.remove_prefix(i + 1);
435 return match_result_type::success;
438 return match_result_type::fail;
450 if (
auto text_ptr = std::get_if<text_type>(&_value)) {
453 }
else if (
auto character_class_ptr = std::get_if<character_class_type>(&_value)) {
455 for (
hilet[first_char, last_char] : *character_class_ptr) {
456 if (first_char == last_char) {
466 }
else if (
auto alternation_ptr = std::get_if<alternation_type>(&_value)) {
468 for (
hilet& text : *alternation_ptr) {
476 }
else if (std::holds_alternative<any_character_type>(_value)) {
479 }
else if (std::holds_alternative<any_text_type>(_value)) {
482 }
else if (std::holds_alternative<any_directory_type>(_value)) {
492 [[nodiscard]]
constexpr std::u32string debug_u32string() const noexcept
496 if (
auto text_ptr = std::get_if<text_type>(&_value)) {
501 }
else if (
auto character_class_ptr = std::get_if<character_class_type>(&_value)) {
503 for (
hilet[first_char, last_char] : *character_class_ptr) {
504 if (first_char == last_char) {
514 }
else if (
auto alternation_ptr = std::get_if<alternation_type>(&_value)) {
516 for (
hilet& text : *alternation_ptr) {
524 }
else if (std::holds_alternative<any_character_type>(_value)) {
527 }
else if (std::holds_alternative<any_text_type>(_value)) {
530 }
else if (std::holds_alternative<any_directory_type>(_value)) {
541 using variant_type = std::
542 variant<text_type, character_class_type, alternation_type, any_character_type, any_text_type, any_directory_type>;
548 using const_iterator = tokens_type::const_iterator;
552 [[nodiscard]]
constexpr static token_type make_text(token_type::text_type&& rhs)
noexcept
557 [[nodiscard]]
constexpr static token_type make_alternation(token_type::alternation_type&& rhs)
noexcept
562 [[nodiscard]]
constexpr static token_type make_character_class(token_type::character_class_type&& rhs)
noexcept
567 [[nodiscard]]
constexpr static token_type make_any_character() noexcept
569 return token_type{token_type::any_character_type{}};
572 [[nodiscard]]
constexpr static token_type make_any_text() noexcept
574 return token_type{token_type::any_text_type{}};
577 [[nodiscard]]
constexpr static token_type make_any_directory() noexcept
579 return token_type{token_type::any_directory_type{}};
582 [[nodiscard]]
constexpr static tokens_type parse(
auto first,
auto last)
584#define HI_GLOB_APPEND_TEXT() \
586 if (not text.empty()) { \
587 r.emplace_back(make_text(std::move(text))); \
592 enum class state_type { idle, star, slash, slash_star, slash_star_star, bracket, bracket_range, brace };
593 using enum state_type;
595 static_assert(std::is_same_v<std::decay_t<
decltype(*first)>,
char32_t>);
597 auto r = tokens_type{};
600 auto text = token_type::text_type{};
601 auto alternation = token_type::alternation_type{};
602 auto character_class = token_type::character_class_type{};
614 HI_GLOB_APPEND_TEXT();
615 r.push_back(make_any_character());
621 HI_GLOB_APPEND_TEXT();
625 HI_GLOB_APPEND_TEXT();
635 throw parse_error(
"Double ** is only allowed between slashes, like /**/.");
637 HI_GLOB_APPEND_TEXT();
638 r.push_back(make_any_text());
656 state = slash_star_star;
659 HI_GLOB_APPEND_TEXT();
660 r.push_back(make_any_text());
666 case slash_star_star:
668 HI_GLOB_APPEND_TEXT();
669 r.push_back(make_any_directory());
672 throw parse_error(
"Double ** is only allowed between slashes, like /**/.");
678 if (character_class.empty()) {
679 character_class.emplace_back(c, c);
681 state = bracket_range;
683 }
else if (c == U
']') {
684 r.push_back(make_character_class(
std::move(character_class)));
685 character_class.clear();
688 character_class.emplace_back(c, c);
694 throw parse_error(
"Double '--' is not allowed inside a character class, i.e. between '[' and ']'.");
695 }
else if (c == U
']') {
696 character_class.emplace_back(U
'-', U
'-');
697 r.push_back(make_character_class(
std::move(character_class)));
698 character_class.clear();
701 character_class.back().second =
c;
708 if (not text.empty()) {
712 r.push_back(make_alternation(
std::move(alternation)));
715 }
else if (c == U
',') {
732 HI_GLOB_APPEND_TEXT();
736 HI_GLOB_APPEND_TEXT();
737 r.push_back(make_any_text());
742 HI_GLOB_APPEND_TEXT();
747 HI_GLOB_APPEND_TEXT();
748 r.push_back(make_any_text());
751 case slash_star_star:
752 HI_GLOB_APPEND_TEXT();
753 r.push_back(make_any_directory());
757 throw parse_error(
"Unclosed bracket '[' found in glob pattern.");
760 throw parse_error(
"Unclosed bracket '[' found in glob pattern.");
763 throw parse_error(
"Unclosed brace '{' found in glob pattern.");
768#undef HI_GLOB_APPEND_TEXT
771 [[nodiscard]]
constexpr static tokens_type parse(
auto&& range)
773 return parse(std::ranges::begin(range), std::ranges::end(range));
777 [[nodiscard]]
constexpr static bool
778 matches_strip(const_iterator& first, const_iterator & last, std::u32string_view& str)
noexcept
780 while (first != last) {
781 hilet it = Left ? first : last - 1;
782 switch (it->strip<Left>(str)) {
783 case match_result_type::fail:
785 case match_result_type::unchecked:
787 case match_result_type::success:
788 if constexpr (Left) {
801 [[nodiscard]]
constexpr static bool
802 matches_strip(const_iterator& first, const_iterator& last, std::u32string_view& str)
noexcept
804 return matches_strip<true>(first, last, str) and matches_strip<false>(first, last, str);
807 [[nodiscard]]
constexpr bool matches(const_iterator it, const_iterator last, std::u32string_view original)
const noexcept
811 struct stack_element {
812 std::u32string_view str;
819 stack.emplace_back(original, 0);
821 auto [str, iteration] = stack.back();
823 switch (it->matches(str, iteration)) {
824 case match_result_type::success:
825 if (it + 1 == last) {
833 ++stack.back().iteration;
838 stack.emplace_back(str, 0);
843 case match_result_type::unchecked:
845 ++stack.back().iteration;
848 case match_result_type::fail:
850 if (stack.size() == 1) {
857 ++stack.back().iteration;