From ef8f1ea7a769ec696a97bda1c0e8ab8304309a30 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Wed, 7 Oct 2026 08:25:46 +0900 Subject: [PATCH] Rewrite `char_` with constexpr-friendly implementation --- include/iris/x4/char/any_char.hpp | 24 +- include/iris/x4/char/char_class.hpp | 10 +- include/iris/x4/char/char_parser.hpp | 44 ++-- include/iris/x4/char/char_set.hpp | 102 ++++---- include/iris/x4/char/detail/basic_chset.hpp | 232 ------------------ include/iris/x4/char/detail/char_range.hpp | 43 ---- .../x4/char/detail/char_range_functions.hpp | 109 -------- .../iris/x4/char/detail/char_range_run.hpp | 178 -------------- include/iris/x4/char/detail/check_char.hpp | 43 ++++ include/iris/x4/char/detail/chset.hpp | 134 ++++++++++ include/iris/x4/char/literal_char.hpp | 22 +- include/iris/x4/char/unicode_char_class.hpp | 8 +- include/iris/x4/char_string_literal.hpp | 82 ++++--- include/iris/x4/string/literal_string.hpp | 11 +- test/x4/char.cpp | 230 ++++------------- test/x4/container_support.cpp | 1 + 16 files changed, 397 insertions(+), 876 deletions(-) delete mode 100644 include/iris/x4/char/detail/basic_chset.hpp delete mode 100644 include/iris/x4/char/detail/char_range.hpp delete mode 100644 include/iris/x4/char/detail/char_range_functions.hpp delete mode 100644 include/iris/x4/char/detail/char_range_run.hpp create mode 100644 include/iris/x4/char/detail/check_char.hpp create mode 100644 include/iris/x4/char/detail/chset.hpp diff --git a/include/iris/x4/char/any_char.hpp b/include/iris/x4/char/any_char.hpp index 63f8da02a..e6eac72de 100644 --- a/include/iris/x4/char/any_char.hpp +++ b/include/iris/x4/char/any_char.hpp @@ -34,29 +34,29 @@ struct any_char : char_parser, Encoding> template CharT> [[nodiscard]] static constexpr literal_char - operator()(CharT ch) noexcept + operator()(CharT ch) { - return {ch}; + return literal_char{ch}; } template CharT> [[nodiscard]] static constexpr literal_char - operator()(CharT const (&ch)[2]) noexcept + operator()(CharT const (&ch)[2]) { - return {ch[0]}; + return literal_char{ch[0]}; } template CharT, std::size_t N> - [[nodiscard]] static constexpr char_set - operator()(CharT const (&ch)[N]) + [[nodiscard]] static constexpr char_set> + operator()(CharT const (&str)[N]) { - static_assert(N >= 3); - return char_set{ch}; + static_assert(N >= 3, "x4::char_: the character set is empty"); + return char_set>{std::basic_string_view(str, N - 1)}; } template CharT> [[nodiscard]] static constexpr char_range - operator()(CharT from, CharT to) noexcept + operator()(CharT from, CharT to) { return {from, to}; } @@ -64,12 +64,6 @@ struct any_char : char_parser, Encoding> template static constexpr void operator()(From const (&)[FromN], To const (&)[ToN]) = delete; // Use single character literal to define character range - - [[nodiscard]] static char_set - operator()(std::basic_string_view sv) - { - return char_set{std::move(sv)}; - } }; } // iris::x4 diff --git a/include/iris/x4/char/char_class.hpp b/include/iris/x4/char/char_class.hpp index 4208eeb50..dcc31eef9 100644 --- a/include/iris/x4/char/char_class.hpp +++ b/include/iris/x4/char/char_class.hpp @@ -73,12 +73,15 @@ struct char_class_parser : char_parser, Encodin using encoding_type = Encoding; using tag = Tag; using char_type = Encoding::char_type; + using classify_type = Encoding::classify_type; using attribute_type = char_type; + static constexpr bool has_attribute = true; [[nodiscard]] static constexpr bool - test(Encoding::classify_type const classify_ch) noexcept + test(std::same_as auto const ch) noexcept { + auto const classify_ch = static_cast(ch); return encoding_type::ischar(classify_ch) && detail::char_class_base::is( tag{}, @@ -87,8 +90,9 @@ struct char_class_parser : char_parser, Encodin } [[nodiscard]] static constexpr bool - test(Encoding::classify_type const classify_ch, auto const& ctx) noexcept + test(std::same_as auto const ch, auto const& ctx) noexcept { + auto const classify_ch = static_cast(ch); return encoding_type::ischar(classify_ch) && detail::char_class_base::is( x4::get_case_compare(ctx).get_char_class_tag(tag{}), @@ -172,7 +176,7 @@ constexpr void builtin_skip_over(It& first, Se const& last) noexcept using Encoding = char_encoding_for; using Parser = char_class_parser; - while (first != last && Parser::test(static_cast(*first))) { + while (first != last && Parser::test(*first)) { ++first; } } diff --git a/include/iris/x4/char/char_parser.hpp b/include/iris/x4/char/char_parser.hpp index be06f7608..6bcf1452d 100644 --- a/include/iris/x4/char/char_parser.hpp +++ b/include/iris/x4/char/char_parser.hpp @@ -72,8 +72,8 @@ struct char_parser : parser> private: template - static constexpr bool has_static_test = requires(classify_type classify_ch, Context const& ctx) { - { Derived::test(classify_ch, ctx) } -> std::same_as; + static constexpr bool has_static_test = requires(char_type const ch, Context const& ctx) { + { Derived::test(ch, ctx) } -> std::same_as; }; public: @@ -85,16 +85,18 @@ struct char_parser : parser> static_assert(!CharIncompatibleWith, char_type>, "Mixing incompatible char types is not allowed"); static_assert(!CharLike || !CharIncompatibleWith, "Mixing incompatible char types is not allowed"); - auto it = first; - x4::skip_over(it, last, ctx); - - if (it != last && Derived::test(static_cast(*it), ctx)) { - if constexpr (x4::has_attribute_v) { - x4::write_attribute(attr, std::iter_value_t{*it}); + auto local_it = first; + x4::skip_over(local_it, last, ctx); + + if (local_it != last) { + if (auto const ch = *local_it; Derived::test(ch, ctx)) { + if constexpr (x4::has_attribute_v) { + x4::write_attribute(attr, ch); + } + ++local_it; + first = local_it; + return true; } - ++it; - first = it; - return true; } return false; } @@ -107,16 +109,18 @@ struct char_parser : parser> static_assert(!CharIncompatibleWith, char_type>, "Mixing incompatible char types is not allowed"); static_assert(!CharLike || !CharIncompatibleWith, "Mixing incompatible char types is not allowed"); - auto it = first; - x4::skip_over(it, last, ctx); - - if (it != last && self.test(static_cast(*it), ctx)) { - if constexpr (x4::has_attribute_v) { - x4::write_attribute(attr, std::iter_value_t{*it}); + auto local_it = first; + x4::skip_over(local_it, last, ctx); + + if (local_it != last) { + if (auto const ch = *local_it; self.test(ch, ctx)) { + if constexpr (x4::has_attribute_v) { + x4::write_attribute(attr, ch); + } + ++local_it; + first = local_it; + return true; } - ++it; - first = it; - return true; } return false; } diff --git a/include/iris/x4/char/char_set.hpp b/include/iris/x4/char/char_set.hpp index d3406123f..e0eab4a19 100644 --- a/include/iris/x4/char/char_set.hpp +++ b/include/iris/x4/char/char_set.hpp @@ -11,7 +11,8 @@ ==============================================================================*/ #include -#include +#include +#include #include #include @@ -19,83 +20,74 @@ #include #include -#include #include +#include // IWYU pragma: keep + namespace iris::x4 { // Parser for a character range -template +template struct char_range : char_parser, Encoding> { - using char_type = Encoding::char_type; + static_assert(X4Attribute); + using encoding_type = Encoding; + using char_type = Encoding::char_type; + using classify_type = Encoding::classify_type; using attribute_type = Attr; static constexpr bool has_attribute = !std::is_same_v; - constexpr char_range(char_type from_, char_type to_) noexcept - : from(from_), to(to_) - {} + constexpr char_range(std::same_as auto const from, std::same_as auto const to) + : from_(static_cast(from)), to_(static_cast(to)) + { + detail::check_char_range(from, to); + } - template - requires (std::is_convertible_v, char_type>) - [[nodiscard]] constexpr bool test(Char ch_, Context const& ctx) const noexcept + template + [[nodiscard]] constexpr bool test(std::same_as auto const ch, Context const& ctx) const noexcept { - char_type ch = static_cast(ch_); // optimize for token based parsing - static_assert(noexcept(x4::get_case_compare(ctx)(ch, from))); + auto const classify_ch = static_cast(ch); + static_assert(noexcept(x4::get_case_compare(ctx)(classify_ch, from_))); - return x4::get_case_compare(ctx)(ch, from) >= 0 - && x4::get_case_compare(ctx)(ch , to) <= 0; + return x4::get_case_compare(ctx)(classify_ch, from_) >= 0 && + x4::get_case_compare(ctx)(classify_ch, to_) <= 0; } - char_type from, to; - [[nodiscard]] std::string get_x4_info() const { // TODO: escape return std::string("char_(\"") - + iris::unicode::transcode(typename Encoding::string_type(1, this->from)) - + '-' + iris::unicode::transcode(typename Encoding::string_type(1, this->to)) + + iris::unicode::transcode(typename Encoding::string_type(1, static_cast(from_))) + + '-' + iris::unicode::transcode(typename Encoding::string_type(1, static_cast(to_))) + "\")"; } + +private: + classify_type from_, to_; }; // Parser for a character set -template -struct char_set : char_parser, Encoding> +template +struct char_set : char_parser, Encoding> { using char_type = Encoding::char_type; + using classify_type = Encoding::classify_type; using encoding_type = Encoding; using attribute_type = Attr; static constexpr bool has_attribute = !std::is_same_v; - constexpr explicit char_set(std::basic_string_view const str) + constexpr explicit char_set(std::basic_string_view const definition) { - for (auto definition = std::ranges::begin(str); definition != std::ranges::end(str);) { - auto const ch = *definition; - auto next_definition = std::next(definition); - if (next_definition == std::ranges::end(str)) { - chset_.set(ch); - break; - } - - auto next_ch = *next_definition; - if (next_ch == detail::char_tokens::hyphen) { - next_definition = std::next(next_definition); - if (next_definition == std::ranges::end(str)) { - chset_.set(ch); - chset_.set(detail::char_tokens::hyphen); - break; - } - chset_.set(ch, *next_definition); - - } else { - chset_.set(ch); - } - - definition = next_definition; + for (std::size_t i = 0; i < definition.size();) { + bool const is_range = i + 2 < definition.size() && definition[i + 1] == hyphen; + char_type const from = definition[i]; + char_type const to = definition[is_range ? i + 2 : i]; + detail::check_char_range(from, to); + chset_.set(static_cast(from), static_cast(to)); + i += is_range ? 3 : 1; } } @@ -109,11 +101,29 @@ struct char_set : char_parser, Encoding> [[nodiscard]] std::string get_x4_info() const { // TODO: escape - return "char-set"; // TODO: make more user-friendly + typename Encoding::string_type definition; + bool has_hyphen = false; + chset_.for_each_range([&](classify_type const first, classify_type const last) { + if (first == last && static_cast(first) == hyphen) { + has_hyphen = true; + return; + } + definition += static_cast(first); + if (first != last) { + definition += hyphen; + definition += static_cast(last); + } + }); + if (has_hyphen) { + definition += hyphen; + } + return "char_(\"" + iris::unicode::transcode(definition) + "\")"; } private: - detail::basic_chset chset_; + static constexpr char_type hyphen = detail::char_tokens::hyphen; + + Chset chset_; }; } // iris::x4 diff --git a/include/iris/x4/char/detail/basic_chset.hpp b/include/iris/x4/char/detail/basic_chset.hpp deleted file mode 100644 index f64ef9e3e..000000000 --- a/include/iris/x4/char/detail/basic_chset.hpp +++ /dev/null @@ -1,232 +0,0 @@ -#ifndef IRIS_ZZ_X4_CHAR_DETAIL_BASIC_CHSET_HPP -#define IRIS_ZZ_X4_CHAR_DETAIL_BASIC_CHSET_HPP - -/*============================================================================= - Copyright (c) 2001-2011 Joel de Guzman - Copyright (c) 2001-2009 Daniel Nuffer - Copyright (c) 2025 Nana Sakisaka - Copyright (c) 2026 The Iris Project Contributors - - Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -=============================================================================*/ - -#include - -#include -#include - -namespace iris::x4::detail { - -// basic character set implementation using `char_range_run` -template -struct basic_chset -{ - [[nodiscard]] constexpr bool - test(CharT v) const noexcept - { - return rr.test(v); - } - - constexpr void - set(CharT from, CharT to) - { - rr.set(char_range(from, to)); - } - - constexpr void - set(CharT c) - { - rr.set(char_range(c, c)); - } - - constexpr void - clear(CharT from, CharT to) - { - rr.clear(char_range(from, to)); - } - - constexpr void - clear(CharT c) - { - rr.clear(char_range(c, c)); - } - - constexpr void - clear() noexcept - { - rr.clear(); - } - - constexpr void - inverse() - { - basic_chset inv; - inv.set( - (std::numeric_limits::min)(), - (std::numeric_limits::max)() - ); - inv -= *this; - swap(inv); - } - - constexpr void - swap(basic_chset& x) noexcept - { - rr.swap(x.rr); - } - - - constexpr basic_chset& - operator|=(basic_chset const& x) - { - using const_iterator = typename char_range_run::const_iterator; - for (const_iterator iter = x.rr.begin(); iter != x.rr.end(); ++iter) { - rr.set(*iter); - } - return *this; - } - - constexpr basic_chset& - operator&=(basic_chset const& x) - { - basic_chset inv; - inv.set( - (std::numeric_limits::min)(), - (std::numeric_limits::max)() - ); - inv -= x; - *this -= inv; - return *this; - } - - constexpr basic_chset& - operator-=(basic_chset const& x) - { - using const_iterator = typename char_range_run::const_iterator; - for (const_iterator iter = x.rr.begin(); iter != x.rr.end(); ++iter) { - rr.clear(*iter); - } - return *this; - } - - constexpr basic_chset& - operator^=(basic_chset const& x) - { - basic_chset bma = x; - bma -= *this; - *this -= x; - *this |= bma; - return *this; - } - -private: - char_range_run rr; -}; - -#if (CHAR_BIT == 8) - -// Specializations for 8 bit chars using std::bitset -template -struct basic_chset_8bit -{ - [[nodiscard]] constexpr bool - test(CharT v) const noexcept - { - return bset.test(static_cast(v)); - } - - constexpr void - set(CharT from, CharT to) noexcept - { - for (int i = from; i <= to; ++i) - bset.set(static_cast(i)); - } - - constexpr void - set(CharT c) noexcept - { - bset.set(static_cast(c)); - } - - constexpr void - clear(CharT from, CharT to) noexcept - { - for (int i = from; i <= to; ++i) - bset.reset(static_cast(i)); - } - - constexpr void - clear(CharT c) noexcept - { - bset.reset(static_cast(c)); - } - - constexpr void - clear() noexcept - { - bset.reset(); - } - - constexpr void - inverse() noexcept - { - bset.flip(); - } - - constexpr void - swap(basic_chset_8bit& x) noexcept - { - std::swap(bset, x.bset); - } - - constexpr basic_chset_8bit& - operator|=(basic_chset_8bit const& x) noexcept - { - bset |= x.bset; - return *this; - } - - constexpr basic_chset_8bit& - operator&=(basic_chset_8bit const& x) noexcept - { - bset &= x.bset; - return *this; - } - - constexpr basic_chset_8bit& - operator-=(basic_chset_8bit const& x) noexcept - { - bset &= ~x.bset; - return *this; - } - - constexpr basic_chset_8bit& - operator^=(basic_chset_8bit const& x) noexcept - { - bset ^= x.bset; - return *this; - } - -private: - std::bitset<256> bset; -}; - -template<> -struct basic_chset - : basic_chset_8bit {}; - -template<> -struct basic_chset - : basic_chset_8bit {}; - -template<> -struct basic_chset - : basic_chset_8bit {}; - -#endif // #if (CHAR_BIT == 8) - -} // iris::x4::detail - -#endif - diff --git a/include/iris/x4/char/detail/char_range.hpp b/include/iris/x4/char/detail/char_range.hpp deleted file mode 100644 index 6b1551ff7..000000000 --- a/include/iris/x4/char/detail/char_range.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef IRIS_ZZ_X4_CHAR_DETAIL_CHAR_RANGE_HPP -#define IRIS_ZZ_X4_CHAR_DETAIL_CHAR_RANGE_HPP - -/*============================================================================= - Copyright (c) 2001-2011 Joel de Guzman - Copyright (c) 2025 Nana Sakisaka - Copyright (c) 2026 The Iris Project Contributors - - Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -==============================================================================*/ - -#include -#include - -namespace iris::x4::detail { - -// A closed range [first, last] -template -struct char_range -{ - static_assert(std::is_default_constructible_v); - static_assert(std::is_copy_constructible_v && std::is_copy_assignable_v); - - using value_type = CharT; - - constexpr char_range() noexcept(std::is_nothrow_default_constructible_v) = default; - - template - requires std::is_constructible_v && std::is_constructible_v - constexpr char_range(A&& first, B&& last) - noexcept(std::is_nothrow_constructible_v && std::is_nothrow_constructible_v) - : first(std::forward(first)) - , last(std::forward(last)) - {} - - CharT first{}; - CharT last{}; -}; - -} // iris::x4::detail - -#endif diff --git a/include/iris/x4/char/detail/char_range_functions.hpp b/include/iris/x4/char/detail/char_range_functions.hpp deleted file mode 100644 index 8dd238c0d..000000000 --- a/include/iris/x4/char/detail/char_range_functions.hpp +++ /dev/null @@ -1,109 +0,0 @@ -#ifndef IRIS_ZZ_X4_CHAR_DETAIL_CHAR_RANGE_FUNCTIONS_HPP -#define IRIS_ZZ_X4_CHAR_DETAIL_CHAR_RANGE_FUNCTIONS_HPP - -/*============================================================================= - Copyright (c) 2001-2011 Joel de Guzman - Copyright (c) 2025 Nana Sakisaka - Copyright (c) 2026 The Iris Project Contributors - - Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -==============================================================================*/ - -#include - -#include -#include - -namespace iris::x4::detail { - -template -[[nodiscard]] constexpr bool -is_valid(char_range const& range) noexcept -{ - // test for valid ranges - return range.first <= range.last; -} - -template -[[nodiscard]] constexpr bool -includes(char_range const& range, char_range const& other) noexcept -{ - // see if two ranges intersect - return range.first <= other.first && range.last >= other.last; -} - -template -[[nodiscard]] constexpr bool -includes(char_range const& range, CharT val) noexcept -{ - // see if val is in range - return range.first <= val && range.last >= val; -} - -template -[[nodiscard]] constexpr bool -can_merge(char_range const& range, char_range const& other) noexcept -{ - // see if a 'range' overlaps, or is adjacent to - // another range 'other', so we can merge them - - using limits = std::numeric_limits; - - CharT const decr_first = - range.first == (limits::min)() - ? range.first : range.first-1; - - CharT const incr_last = - range.last == (limits::max)() - ? range.last : range.last+1; - - return decr_first <= other.last && incr_last >= other.first; -} - -template -constexpr void -merge(char_range& result, char_range const& other) noexcept -{ - // merge two ranges - if (result.first > other.first) { - result.first = other.first; - } - if (result.last < other.last) { - result.last = other.last; - } -} - -struct char_range_compare -{ - using is_transparent = int; - - template - [[nodiscard]] constexpr bool operator()(char_range const& x, CharT const y) const noexcept - { - return x.first < y; - } - - template - [[nodiscard]] constexpr bool operator()(CharT const x, char_range const& y) const noexcept - { - return x < y.first; - } - - template - [[nodiscard]] constexpr bool operator()(char_range const& x, char_range const& y) const noexcept - { - return x.first < y.first; - } - - // This overload is required to satsify `std::indirect_strict_weak_order` - template // hack: minimal constraint to avoid obvious mistakes - [[nodiscard]] constexpr bool operator()(CharT const x, CharT const y) const noexcept - { - return x == y; - } -}; - -} // iris::x4::detail - -#endif diff --git a/include/iris/x4/char/detail/char_range_run.hpp b/include/iris/x4/char/detail/char_range_run.hpp deleted file mode 100644 index abc0777c7..000000000 --- a/include/iris/x4/char/detail/char_range_run.hpp +++ /dev/null @@ -1,178 +0,0 @@ -#ifndef IRIS_ZZ_X4_CHAR_DETAIL_CHAR_RANGE_RUN_HPP -#define IRIS_ZZ_X4_CHAR_DETAIL_CHAR_RANGE_RUN_HPP - -/*============================================================================= - Copyright (c) 2001-2011 Joel de Guzman - Copyright (c) 2025 Nana Sakisaka - Copyright (c) 2026 The Iris Project Contributors - - Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -==============================================================================*/ - -#include -#include - -#include -#include -#include - -#include - -namespace iris::x4::detail { - -// An implementation of a sparse bit (boolean) set. The set uses -// a sorted vector of disjoint ranges. This class implements the -// bare minimum essentials from which the full range of set -// operators can be implemented. The set is constructed from -// ranges. Internally, adjacent or overlapping ranges are -// coalesced. -// -// range_runs are very space-economical in situations where there -// are lots of ranges and a few individual disjoint values. -// Searching is O(log n) where n is the number of ranges. -template -class char_range_run -{ -public: - using range_type = char_range; - using storage_type = std::vector; // TODO: use default_init_allocator as soon as constexpr placement new is available - - [[nodiscard]] static constexpr bool - try_merge(storage_type& run, typename storage_type::iterator iter, range_type const& range) - noexcept(std::is_nothrow_move_assignable_v) - { - // *iter intersects with or is adjacent to 'range'? - if (!detail::can_merge(*iter, range)) return false; - - // merge range and *iter - detail::merge(*iter, range); - - // collapse all subsequent ranges that can merge with *iter: - auto it = std::next(iter); - // 1. skip subsequent ranges completely included in *iter - while (it != run.end() && it->last <= iter->last) { - ++it; - } - // 2. collapse next range if adjacent or overlapping with *iter - if (it != run.end() && it->first-1 <= iter->last) { - iter->last = it->last; - ++it; - } - - // erase all ranges that were collapsed - run.erase(std::next(iter), it); - return true; - } - - [[nodiscard]] constexpr bool test(CharT val) const noexcept - { - if (run_.empty()) return false; - - // search the ranges for one that potentially includes val - auto const iter = std::ranges::upper_bound( - run_, val, - char_range_compare{} - ); - - // return true if *(iter-1) includes val - return iter != run_.begin() && detail::includes(*std::prev(iter), val); - } - - constexpr void swap(char_range_run& other) noexcept - { - run_.swap(other.run_); - } - - constexpr void set(range_type const& range) - { - assert(detail::is_valid(range)); - if (run_.empty()) { - // the vector is empty, insert 'range' - run_.emplace_back(range); - return; - } - - // search the ranges for one that potentially includes 'range' - auto iter = std::ranges::upper_bound(run_, range, char_range_compare{}); - - if (iter != run_.begin()) { - // if *(iter-1) includes 'range', return early - if (detail::includes(*std::prev(iter), range)) { - return; - } - - // if *(iter-1) can merge with 'range', merge them and return - if (this->try_merge(run_, std::prev(iter), range)) { - return; - } - } - - // if *iter can merge with with 'range', merge them - if (iter == run_.end() || !this->try_merge(run_, iter, range)) { - // no overlap, insert 'range' - run_.insert(iter, range); - } - } - - constexpr void clear(range_type const& range) - { - assert(detail::is_valid(range)); - if (run_.empty()) return; - - // search the ranges for one that potentially includes 'range' - auto iter = std::ranges::upper_bound(run_, range, char_range_compare{}); - - // 'range' starts with or after another range: - if (iter != run_.begin()) { - auto const left_iter = std::prev(iter); - - // 'range' starts after '*left_iter': - if (left_iter->first < range.first) { - // if 'range' is completely included inside '*left_iter': - // need to break it apart into two ranges (punch a hole), - if (left_iter->last > range.last) { - auto const last_save = left_iter->last; - left_iter->last = range.first-1; - run_.insert(iter, range_type(range.last+1, last_save)); - return; - } - // if 'range' contains 'left_iter->last': - // truncate '*left_iter' (clip its right) - if (left_iter->last >= range.first) { - left_iter->last = range.first-1; - } - } else { - // 'range' has the same left bound as '*left_iter': it - // must be removed or truncated by the code below - iter = left_iter; - } - } - - // remove or truncate subsequent ranges that overlap with 'range': - auto it = iter; - // 1. skip subsequent ranges completely included in 'range' - while (it != run_.end() && it->last <= range.last) { - ++it; - } - // 2. clip left of next range if overlapping with 'range' - if (it != run_.end() && it->first <= range.last) { - it->first = range.last+1; - } - - // erase all ranges that 'range' contained - run_.erase(iter, it); - } - - constexpr void clear() noexcept - { - run_.clear(); - } - -private: - storage_type run_; -}; - -} // iris::x4::detail - -#endif diff --git a/include/iris/x4/char/detail/check_char.hpp b/include/iris/x4/char/detail/check_char.hpp new file mode 100644 index 000000000..bb82dba37 --- /dev/null +++ b/include/iris/x4/char/detail/check_char.hpp @@ -0,0 +1,43 @@ +#ifndef IRIS_ZZ_X4_CHAR_DETAIL_CHECK_CHAR_HPP +#define IRIS_ZZ_X4_CHAR_DETAIL_CHECK_CHAR_HPP + +/*============================================================================= + Copyright (c) 2026 The Iris Project Contributors + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +==============================================================================*/ + +#include // IWYU pragma: keep + +#include + +#include +#include + +namespace iris::x4::detail { + +template +constexpr void check_char(std::same_as auto const ch) +{ + if (!Encoding::ischar(ch)) { + iris::throwf("The character is not valid in the encoding"); + } +} + +template +constexpr void check_char_range( + std::same_as auto const from, + std::same_as auto const to +) +{ + detail::check_char(from); + detail::check_char(to); + if (static_cast(from) > static_cast(to)) { + iris::throwf("The first character of a range is greater than the last character"); + } +} + +} // iris::x4::detail + +#endif diff --git a/include/iris/x4/char/detail/chset.hpp b/include/iris/x4/char/detail/chset.hpp new file mode 100644 index 000000000..1a1bba180 --- /dev/null +++ b/include/iris/x4/char/detail/chset.hpp @@ -0,0 +1,134 @@ +#ifndef IRIS_ZZ_X4_CHAR_DETAIL_CHSET_HPP +#define IRIS_ZZ_X4_CHAR_DETAIL_CHSET_HPP + +/*============================================================================= + Copyright (c) 2001-2011 Joel de Guzman + Copyright (c) 2001-2009 Daniel Nuffer + Copyright (c) 2025 Nana Sakisaka + Copyright (c) 2026 The Iris Project Contributors + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +=============================================================================*/ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include // IWYU pragma: keep + +namespace iris::x4::detail { + +// [first, last] +template +struct char_range +{ + ClassifyT first; + ClassifyT last; +}; + +template +[[nodiscard]] constexpr std::size_t +insert_char_range(char_range* const ranges, std::size_t const size, ClassifyT const from, ClassifyT const to) noexcept +{ + using limits = std::numeric_limits; + ClassifyT const lower = from == (limits::min)() ? from : static_cast(from - 1); + ClassifyT const upper = to == (limits::max)() ? to : static_cast(to + 1); + + // [merge_first, merge_last) overlap with or are adjacent to [from, to] + auto* const last = ranges + size; + auto* const merge_first = std::ranges::partition_point(ranges, last, [&](char_range const& range) { return range.last < lower; }); + auto* const merge_last = std::ranges::partition_point(merge_first, last, [&](char_range const& range) { return range.first <= upper; }); + + if (merge_first == merge_last) { + std::ranges::move_backward(merge_first, last, last + 1); + *merge_first = {from, to}; + return size + 1; + } + + merge_first->first = std::min(merge_first->first, from); + merge_first->last = std::max((merge_last - 1)->last, to); + std::ranges::move(merge_last, last, merge_first + 1); + return size - static_cast(merge_last - merge_first - 1); +} + +template +struct ranged_chset +{ + [[nodiscard]] constexpr bool test(ClassifyT const ch) const noexcept + { + auto const* const last = ranges_.data() + size_; + auto const* const it = std::ranges::partition_point(ranges_.data(), last, [&](char_range const& range) { return range.last < ch; }); + return it != last && it->first <= ch; + } + + constexpr void set(std::same_as auto const from, std::same_as auto const to) + { + if (size_ == Capacity) { + iris::throwf("the character set has no room for another range"); + } + size_ = detail::insert_char_range(ranges_.data(), size_, from, to); + } + + template + constexpr void for_each_range(F&& f) const + { + for (std::size_t i = 0; i < size_; ++i) { + f(ranges_[i].first, ranges_[i].last); + } + } + +private: + std::array, Capacity> ranges_{}; + std::size_t size_ = 0; +}; + +struct bitset_chset +{ + [[nodiscard]] constexpr bool test(unsigned char const ch) const noexcept + { + return bits_[ch]; + } + + constexpr void set(unsigned char const from, unsigned char const to) noexcept + { + for (unsigned ch = from; ch <= to; ++ch) { + bits_.set(ch); + } + } + + template + constexpr void for_each_range(F&& f) const + { + for (unsigned first = 0; first <= UCHAR_MAX; ++first) { + if (!bits_[first]) continue; + + unsigned last = first; + while (last < UCHAR_MAX && bits_[last + 1]) { + ++last; + } + f(static_cast(first), static_cast(last)); + first = last; + } + } + +private: + std::bitset bits_; +}; + +template +using chset_for = std::conditional_t< + std::is_same_v, + bitset_chset, + ranged_chset +>; + +} // iris::x4::detail + +#endif diff --git a/include/iris/x4/char/literal_char.hpp b/include/iris/x4/char/literal_char.hpp index aaf60dcd0..6de1a49e2 100644 --- a/include/iris/x4/char/literal_char.hpp +++ b/include/iris/x4/char/literal_char.hpp @@ -11,18 +11,21 @@ ==============================================================================*/ #include +#include #include #include -#include #include +#include namespace iris::x4 { -template +template struct literal_char : char_parser, Encoding> { + static_assert(X4Attribute); + using encoding_type = Encoding; using attribute_type = Attr; using char_type = Encoding::char_type; @@ -30,21 +33,18 @@ struct literal_char : char_parser, Encoding> static constexpr bool has_attribute = !std::is_same_v; - template - requires - (!std::is_same_v, literal_char>) && - std::convertible_to - constexpr /*explicit*/ literal_char(Char ch) noexcept + template Char> + constexpr explicit literal_char(Char const ch) : classify_ch_(static_cast(ch)) { - static_assert(std::same_as, "Mixing incompatible character types is not allowed"); + detail::check_char(ch); } [[nodiscard]] constexpr bool - test(classify_type const test_classify_ch, auto const& ctx) const noexcept + test(std::same_as auto const ch, auto const& ctx) const noexcept { - static_assert(noexcept(x4::get_case_compare(ctx)(classify_ch_, test_classify_ch))); - return x4::get_case_compare(ctx)(classify_ch_, test_classify_ch) == 0; + static_assert(noexcept(x4::get_case_compare(ctx)(classify_ch_, static_cast(ch)))); + return x4::get_case_compare(ctx)(classify_ch_, static_cast(ch)) == 0; } constexpr void diff --git a/include/iris/x4/char/unicode_char_class.hpp b/include/iris/x4/char/unicode_char_class.hpp index 74b57edae..9b31eb37a 100644 --- a/include/iris/x4/char/unicode_char_class.hpp +++ b/include/iris/x4/char/unicode_char_class.hpp @@ -15,6 +15,8 @@ #include #include +#include + namespace iris::x4 { namespace char_classes::unicode { @@ -539,16 +541,18 @@ struct unicode_char_class_base template struct unicode_char_class : char_parser, char_encoding::unicode> { - using encoding_type = char_encoding::unicode; using tag = Tag; + using encoding_type = char_encoding::unicode; using char_type = encoding_type::char_type; + using classify_type = encoding_type::classify_type; using attribute_type = char_type; static constexpr bool has_attribute = true; [[nodiscard]] static constexpr bool - test(char_encoding::unicode::classify_type const classify_ch, auto const& /* ctx */) noexcept + test(std::same_as auto const ch, auto const& /* ctx */) noexcept { + auto const classify_ch = static_cast(ch); static_assert(noexcept(encoding_type::ischar(classify_ch) && detail::unicode_char_class_base::is(tag{}, classify_ch))); return encoding_type::ischar(classify_ch) && detail::unicode_char_class_base::is(tag{}, classify_ch); } diff --git a/include/iris/x4/char_string_literal.hpp b/include/iris/x4/char_string_literal.hpp index bb686f39c..3fc9f7e71 100644 --- a/include/iris/x4/char_string_literal.hpp +++ b/include/iris/x4/char_string_literal.hpp @@ -44,9 +44,12 @@ struct char_parser_fn char_encoding_for, typename AttrSelectorTT::type > - operator()(CharT ch) noexcept + operator()(CharT ch) { - return {ch}; + return literal_char< + char_encoding_for, + typename AttrSelectorTT::type + >{ch}; } }; @@ -58,9 +61,12 @@ struct char_array_parser_fn char_encoding_for, typename AttrSelectorTT::type > - operator()(CharT const (&str)[2]) noexcept + operator()(CharT const (&str)[2]) { - return {str[0]}; + return literal_char< + char_encoding_for, + typename AttrSelectorTT::type + >{str[0]}; } template @@ -69,12 +75,16 @@ struct char_array_parser_fn char_encoding_for, typename AttrSelectorTT>::type > - operator()(CharT const (&str)[N]) noexcept + operator()(CharT const (&str)[N]) requires (N >= 3) && x4::detail::is_string_parser_sso_eligible { std::array arr; std::ranges::copy_n(str, N - 1, arr.data()); - return {arr}; + return literal_string< + std::array, + char_encoding_for, + typename AttrSelectorTT>::type + >{arr}; } template @@ -83,11 +93,15 @@ struct char_array_parser_fn char_encoding_for, typename AttrSelectorTT>::type > - operator()(CharT const (&str)[N]) noexcept + operator()(CharT const (&str)[N]) requires (N >= 3) && (!x4::detail::is_string_parser_sso_eligible) { static_assert(N >= 2); - return {std::basic_string_view(str, N - 1)}; + return literal_string< + std::basic_string_view, + char_encoding_for, + typename AttrSelectorTT>::type + >{std::basic_string_view(str, N - 1)}; } }; @@ -100,7 +114,7 @@ struct char_array_parser_fn char_encoding_for, std::basic_string > - operator()(CharT const (&str)[2]) noexcept + operator()(CharT const (&str)[2]) { return literal_string< std::array, @@ -115,12 +129,16 @@ struct char_array_parser_fn char_encoding_for, std::basic_string > - operator()(CharT const (&str)[N]) noexcept + operator()(CharT const (&str)[N]) requires (N >= 3) && x4::detail::is_string_parser_sso_eligible { std::array arr; std::ranges::copy_n(str, N - 1, arr.data()); - return {arr}; + return literal_string< + std::array, + char_encoding_for, + std::basic_string + >{arr}; } template @@ -129,29 +147,18 @@ struct char_array_parser_fn char_encoding_for, std::basic_string > - operator()(CharT const (&str)[N]) noexcept + operator()(CharT const (&str)[N]) requires (N >= 3) && (!x4::detail::is_string_parser_sso_eligible) { static_assert(N >= 2); - return {std::basic_string_view(str, N - 1)}; + return literal_string< + std::basic_string_view, + char_encoding_for, + std::basic_string + >{std::basic_string_view(str, N - 1)}; } }; -//template class AttrSelectorTT> -//struct char_pointer_parser_fn -//{ -// template -// [[nodiscard]] static constexpr literal_string< -// std::basic_string_view, -// char_encoding_for, -// typename AttrSelectorTT>::type -// > -// operator()(CharT const* s) noexcept -// { -// return {std::basic_string_view{s}}; -// } -//}; - template class AttrSelectorTT> struct string_parser_fn { @@ -172,7 +179,11 @@ struct string_parser_fn StringLikeT >) { - return {std::forward(str)}; + return literal_string< + std::basic_string>, + char_encoding_for>, + typename AttrSelectorTT>>::type + >{std::forward(str)}; } }; @@ -196,7 +207,11 @@ struct string_view_parser_fn StringLikeT >) { - return {std::forward(str)}; + return literal_string< + std::basic_string_view>, + char_encoding_for>, + typename AttrSelectorTT>>::type + >{std::forward(str)}; } }; @@ -215,11 +230,6 @@ struct as_parser : x4::detail::char_array_parser_fn {}; -//template -//struct as_parser -// : x4::detail::char_pointer_parser_fn -//{}; - template struct as_parser> : x4::detail::string_parser_fn @@ -239,7 +249,6 @@ template struct lit_string_fn : char_parser_fn , char_array_parser_fn - //, char_pointer_parser_fn , string_parser_fn , string_view_parser_fn { @@ -253,7 +262,6 @@ template<> struct lit_string_fn : char_parser_fn , char_array_parser_fn - //, char_pointer_parser_fn , string_parser_fn , string_view_parser_fn { diff --git a/include/iris/x4/string/literal_string.hpp b/include/iris/x4/string/literal_string.hpp index 293c9fa82..583e8a563 100644 --- a/include/iris/x4/string/literal_string.hpp +++ b/include/iris/x4/string/literal_string.hpp @@ -12,6 +12,8 @@ #include +#include + #include #include #include @@ -47,10 +49,13 @@ struct literal_string : parser> template requires std::is_constructible_v - constexpr /*explicit*/ literal_string(Args&&... args) - noexcept(std::is_nothrow_constructible_v) + constexpr explicit literal_string(Args&&... args) : str_(std::forward(args)...) - {} + { + for (char_type const ch : str_) { + detail::check_char(ch); + } + } template Se, class Context, X4Attribute ExposedAttr> [[nodiscard]] constexpr bool diff --git a/test/x4/char.cpp b/test/x4/char.cpp index 85be72e04..2057e7764 100644 --- a/test/x4/char.cpp +++ b/test/x4/char.cpp @@ -1,8 +1,4 @@ /*============================================================================= - Copyright (c) 2001-2015 Joel de Guzman - Copyright (c) 2001-2011 Hartmut Kaiser - Copyright (c) 2019 Christian Mazakas - Copyright (c) 2025 Nana Sakisaka Copyright (c) 2026 The Iris Project Contributors Distributed under the Boost Software License, Version 1.0. (See accompanying @@ -16,218 +12,98 @@ #include #include #include -#include -#include -#include +#include +#include #include -#include -#include +#include TEST_CASE("char") { - static_assert(x4::traits::X4Container); - static_assert(x4::CategorizedAttr); - - static_assert(!x4::traits::X4Container); - static_assert(x4::CategorizedAttr); - namespace standard = x4::standard; namespace standard_wide = x4::standard_wide; namespace unicode = x4::unicode; - { - std::string_view sv; - auto first = sv.begin(); - auto const last = sv.end(); - - constexpr auto parser = standard::char_('x'); - char ch{}; - (void)parser.parse(first, last, unused, ch); - - // Make sure this is static - (void)std::remove_const_t::parse(first, last, unused, ch); - } - { - std::u32string_view sv; - auto first = sv.begin(); - auto const last = sv.end(); - constexpr auto parser = unicode::char_(U'x'); - char32_t ch{}; - (void)parser.parse(first, last, unused, ch); - - // Make sure this is static - (void)std::remove_const_t::parse(first, last, unused, ch); - } - { using namespace x4::standard; IRIS_X4_ASSERT_CONSTEXPR_CTORS(char_); IRIS_X4_ASSERT_CONSTEXPR_CTORS(char_('x')); IRIS_X4_ASSERT_CONSTEXPR_CTORS(char_('a', 'z')); + IRIS_X4_ASSERT_CONSTEXPR_CTORS(char_("a-z")); IRIS_X4_ASSERT_CONSTEXPR_CTORS(~char_('x')); - IRIS_X4_ASSERT_CONSTEXPR_CTORS(~char_('a', 'z')); - IRIS_X4_ASSERT_CONSTEXPR_CTORS(~~char_('x')); - IRIS_X4_ASSERT_CONSTEXPR_CTORS(~~char_('a', 'z')); + CHECK(parse("x", char_)); CHECK(parse("x", 'x')); - CHECK(parse(L"x", L'x')); CHECK(!parse("y", 'x')); - CHECK(!parse(L"y", L'x')); - - CHECK(parse("x", char_)); CHECK(parse("x", char_('x'))); - CHECK(!parse("x", char_('y'))); - CHECK(parse("x", char_('a', 'z'))); - CHECK(!parse("x", char_('0', '9'))); + CHECK(!parse("y", char_('x'))); CHECK(parse("0", char_('0', '9'))); CHECK(parse("9", char_('0', '9'))); - CHECK(!parse("0", ~char_('0', '9'))); - CHECK(!parse("9", ~char_('0', '9'))); + CHECK(!parse("/", char_('0', '9'))); + CHECK(!parse(":", char_('0', '9'))); CHECK(!parse("x", ~char_)); CHECK(!parse("x", ~char_('x'))); - CHECK(parse(" ", ~char_('x'))); - CHECK(parse("X", ~char_('x'))); - CHECK(!parse("x", ~char_('b', 'y'))); - CHECK(parse("a", ~char_('b', 'y'))); - CHECK(parse("z", ~char_('b', 'y'))); - - CHECK(parse("x", ~~char_)); - CHECK(parse("x", ~~char_('x'))); - CHECK(!parse(" ", ~~char_('x'))); - CHECK(!parse("X", ~~char_('x'))); - CHECK(parse("x", ~~char_('b', 'y'))); - CHECK(!parse("a", ~~char_('b', 'y'))); - CHECK(!parse("z", ~~char_('b', 'y'))); - } - { - using namespace x4::standard_wide; - - IRIS_X4_ASSERT_CONSTEXPR_CTORS(char_); - IRIS_X4_ASSERT_CONSTEXPR_CTORS(char_(L'x')); - IRIS_X4_ASSERT_CONSTEXPR_CTORS(char_(L'a', L'z')); - IRIS_X4_ASSERT_CONSTEXPR_CTORS(~char_(L'x')); - IRIS_X4_ASSERT_CONSTEXPR_CTORS(~char_(L'a', L'z')); - IRIS_X4_ASSERT_CONSTEXPR_CTORS(~~char_(L'x')); - IRIS_X4_ASSERT_CONSTEXPR_CTORS(~~char_(L'a', L'z')); - - CHECK(parse(L"x", char_)); - CHECK(parse(L"x", char_(L'x'))); - CHECK(!parse(L"x", char_(L'y'))); - CHECK(parse(L"x", char_(L'a', L'z'))); - CHECK(!parse(L"x", char_(L'0', L'9'))); - - CHECK(!parse(L"x", ~char_)); - CHECK(!parse(L"x", ~char_(L'x'))); - CHECK(parse(L" ", ~char_(L'x'))); - CHECK(parse(L"X", ~char_(L'x'))); - CHECK(!parse(L"x", ~char_(L'b', L'y'))); - CHECK(parse(L"a", ~char_(L'b', L'y'))); - CHECK(parse(L"z", ~char_(L'b', L'y'))); - - CHECK(parse(L"x", ~~char_)); - CHECK(parse(L"x", ~~char_(L'x'))); - CHECK(!parse(L" ", ~~char_(L'x'))); - CHECK(!parse(L"X", ~~char_(L'x'))); - CHECK(parse(L"x", ~~char_(L'b', L'y'))); - CHECK(!parse(L"a", ~~char_(L'b', L'y'))); - CHECK(!parse(L"z", ~~char_(L'b', L'y'))); - } - - { - using namespace x4::standard; + CHECK(parse("y", ~char_('x'))); + CHECK(!parse("0", ~char_('0', '9'))); + CHECK(parse("/", ~char_('0', '9'))); - CHECK(parse(" x", 'x', space)); - CHECK(parse(" x", char_, space)); CHECK(parse(" x", char_('x'), space)); - CHECK(!parse(" x", char_('y'), space)); - CHECK(parse(" x", char_('a', 'z'), space)); - CHECK(!parse(" x", char_('0', '9'), space)); - } - { - using namespace x4::standard_wide; - - CHECK(parse(L" x", L'x', space)); - CHECK(parse(L" x", char_, space)); - CHECK(parse(L" x", char_(L'x'), space)); - CHECK(!parse(L" x", char_(L'y'), space)); - CHECK(parse(L" x", char_(L'a', L'z'), space)); - CHECK(!parse(L" x", char_(L'0', L'9'), space)); } - // unicode (normal ASCII) - { - using namespace x4::unicode; - - CHECK(parse(U"abcd", +char_(U"abcd"))); - CHECK(!parse(U"abcd", +char_(U"qwer"))); + STATIC_CHECK(std::same_as); + STATIC_CHECK(std::same_as); + STATIC_CHECK(std::same_as, decltype(standard::char_('x'))>); - auto const sub_delims = char_(U"!$&'()*+,;="); + STATIC_CHECK(requires(std::string_view::iterator it, char ch) { + std::remove_const_t::parse(it, it, unused, ch); + std::remove_const_t::parse(it, it, unused, ch); + }); - auto const delims = std::vector{ - U"!", U"$", U"&", U"'", U"(", U")", U"*", U"+", U",", U";", U"=" - }; - - auto const matched_all_sub_delims = - std::ranges::all_of(delims, [&](auto const delim) -> bool { - return parse(delim, sub_delims).completed(); - }); - - CHECK(matched_all_sub_delims); - } - - // unicode (escaped Unicode char literals) { - using namespace x4::unicode; - - auto const chars = char_(U"\u0024\u00a2\u0939\u20ac\U00010348"); + constexpr auto set = unicode::char_(U"-a-c-e0-9"); + STATIC_CHECK(set.test(U'a', unused) && set.test(U'c', unused) && !set.test(U'd', unused)); + STATIC_CHECK(set.test(U'0', unused) && set.test(U'9', unused) && !set.test(U'/', unused) && !set.test(U':', unused)); + STATIC_CHECK(set.test(U'-', unused) && set.test(U'e', unused) && unicode::char_(U"a-").test(U'-', unused)); - auto const test_strings = std::vector{ - U"\u0024", U"\u00a2", U"\u0939", U"\u20ac", U"\U00010348" - }; - - auto const bad_test_strings = std::vector{ - U"a", U"B", U"c", U"\u0409" - }; + STATIC_CHECK(unicode::char_(U"a\0b").test(U'\0', unused) && unicode::char_(U"a\0b").test(U'b', unused)); + STATIC_CHECK(!unicode::char_(U"ab").test(U'\0', unused)); - auto const all_matched = - std::ranges::all_of(test_strings, [&](auto const test_str) -> bool { - return parse(test_str, chars).completed(); - }); + STATIC_CHECK(standard::char_("\x7f-\x80").test('\x80', unused)); + STATIC_CHECK(standard::char_('\x7f', '\x80').test('\x80', unused)); - auto const none_matched = - std::ranges::all_of(bad_test_strings, [&](auto const bad_test_str) -> bool { - return !parse(bad_test_str, chars).completed(); - }); + CHECK(x4::what(unicode::char_(U"x-zda-cb")) == R"(char_("a-dx-z"))"); + CHECK(x4::what(unicode::char_(U"acegb-f")) == R"(char_("a-g"))"); + CHECK(x4::what(standard::char_("acegb-f")) == R"(char_("a-g"))"); + CHECK(x4::what(standard::char_("+/-")) == R"(char_("+/-"))"); - CHECK(all_matched); - CHECK(none_matched); - } - - // single char strings - { - CHECK(parse("x", "x")); - CHECK(parse(L"x", L"x")); - CHECK(parse("x", standard::char_("x"))); - CHECK(parse(L"x", standard_wide::char_(L"x"))); - - CHECK(parse("x", standard::char_('a', 'z'))); - CHECK(parse(L"x", standard_wide::char_(L'a', L'z'))); + char32_t ch{}; + CHECK(x4::parse(std::u32string_view(U"q"), unicode::char_(U"a-z"), ch)); + CHECK(ch == U'q'); } - // chsets { - CHECK(parse("x", standard::char_("a-z"))); - CHECK(!parse("1", standard::char_("a-z"))); - CHECK(parse("1", standard::char_("a-z0-9"))); - - CHECK(parse(L"x", standard_wide::char_(L"a-z"))); - CHECK(!parse(L"1", standard_wide::char_(L"a-z"))); - CHECK(parse(L"1", standard_wide::char_(L"a-z0-9"))); - - CHECK(parse("x", standard::char_(std::string("a-z0-9")))); + auto const is_rejected = [](auto const make_parser) { + try { + (void)make_parser(); + } + catch (std::invalid_argument const&) { + return true; + } + return false; + }; + CHECK(is_rejected([] { return standard_wide::char_(L"a-cz-x"); })); + CHECK(is_rejected([] { return standard_wide::char_(L'z', L'x'); })); + CHECK(is_rejected([] { return unicode::char_(U"a\x110000"); })); + CHECK(is_rejected([] { return unicode::char_(U'a', U'\x110000'); })); + CHECK(is_rejected([] { return unicode::char_(U'\x110000'); })); + CHECK(is_rejected([] { return x4::lit(U'\x110000'); })); + CHECK(is_rejected([] { return x4::lit(U"\x110000"); })); + CHECK(is_rejected([] { return x4::string(U"\x110000"); })); + CHECK(is_rejected([] { return x4::lit(U"a\x110000"); })); + CHECK(is_rejected([] { return x4::lit(U"abcdefg\x110000"); })); + CHECK(!is_rejected([] { return unicode::char_(U'\0', U'\x10FFFF'); })); } } diff --git a/test/x4/container_support.cpp b/test/x4/container_support.cpp index c5bcd6225..d0ba3ee16 100644 --- a/test/x4/container_support.cpp +++ b/test/x4/container_support.cpp @@ -408,6 +408,7 @@ TEST_CASE("container_support") // ------------------------------------------------------------------ STATIC_CHECK(X4Container); + STATIC_CHECK(!X4Container); STATIC_CHECK(X4Container>); STATIC_CHECK(X4Container>); STATIC_CHECK(X4Container>);