diff --git a/include/iris/x4/auxiliary/attr.hpp b/include/iris/x4/auxiliary/attr.hpp index 03dbb06b5..5e1b31fea 100644 --- a/include/iris/x4/auxiliary/attr.hpp +++ b/include/iris/x4/auxiliary/attr.hpp @@ -14,9 +14,9 @@ #include #include +#include #include -#include #include #include @@ -104,7 +104,7 @@ struct attr_parser : parser> namespace detail { -template +template using string_array_attr_parser_t = attr_parser< std::basic_string>>, std::basic_string_view>> @@ -112,7 +112,7 @@ using string_array_attr_parser_t = attr_parser< } // detail -template +template attr_parser(R const&) -> attr_parser< std::basic_string>>, std::basic_string_view>> @@ -141,7 +141,7 @@ struct attr_gen return attr_parser>{std::forward(value)}; } - template + template [[nodiscard]] static constexpr string_array_attr_parser_t operator()(R&& value) noexcept(std::is_nothrow_constructible_v, R>) diff --git a/include/iris/x4/char/any_char.hpp b/include/iris/x4/char/any_char.hpp index 6719ea0a9..72af3998a 100644 --- a/include/iris/x4/char/any_char.hpp +++ b/include/iris/x4/char/any_char.hpp @@ -10,9 +10,9 @@ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ==============================================================================*/ +#include #include #include -#include namespace iris::x4 { @@ -43,7 +43,7 @@ struct any_char : char_parser> return {ch}; } - template CharT> + template CharT> static constexpr void operator()(CharT) = delete; // Mixing incompatible char types is not allowed [[nodiscard]] static constexpr literal_char @@ -59,7 +59,7 @@ struct any_char : char_parser> return char_set{ch}; } - template CharT, std::size_t N> + template CharT, std::size_t N> static constexpr void operator()(CharT const (&)[N]) = delete; // Mixing incompatible char types is not allowed @@ -71,7 +71,7 @@ struct any_char : char_parser> } template - requires traits::CharIncompatibleWith || traits::CharIncompatibleWith + requires CharIncompatibleWith || CharIncompatibleWith static constexpr void operator()(From, To) = delete; // Mixing incompatible char types is not allowed template diff --git a/include/iris/x4/char/char.hpp b/include/iris/x4/char/char.hpp index 7674ed135..867959364 100644 --- a/include/iris/x4/char/char.hpp +++ b/include/iris/x4/char/char.hpp @@ -10,9 +10,9 @@ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ==============================================================================*/ +#include #include #include -#include #include @@ -39,7 +39,7 @@ lit(char ch) noexcept } [[nodiscard]] constexpr literal_char -lit(traits::X4VagueArrayOf2Chars auto const& ch) noexcept +lit(X4VagueArrayOf2Chars auto const& ch) noexcept { return {ch[0]}; } @@ -51,8 +51,8 @@ lit(traits::X4VagueArrayOf2Chars auto const& ch) noexcept // If you still see errors after the inclusion, that might be due to // mixing incompatible string literals. Don't do that. -constexpr void lit(traits::CharIncompatibleWith auto const*) = delete; // Mixing incompatible character types is not allowed -constexpr void lit(traits::CharIncompatibleWith auto) = delete; // Mixing incompatible character types is not allowed +constexpr void lit(CharIncompatibleWith auto const*) = delete; // Mixing incompatible character types is not allowed +constexpr void lit(CharIncompatibleWith auto) = delete; // Mixing incompatible character types is not allowed } // standard @@ -76,15 +76,15 @@ lit(wchar_t ch) noexcept } [[nodiscard]] constexpr literal_char -lit(traits::X4VagueArrayOf2Chars auto const& ch) noexcept +lit(X4VagueArrayOf2Chars auto const& ch) noexcept { return {ch[0]}; } } // helpers -constexpr void lit(traits::CharIncompatibleWith auto const*) = delete; // Mixing incompatible character types is not allowed -constexpr void lit(traits::CharIncompatibleWith auto) = delete; // Mixing incompatible character types is not allowed +constexpr void lit(CharIncompatibleWith auto const*) = delete; // Mixing incompatible character types is not allowed +constexpr void lit(CharIncompatibleWith auto) = delete; // Mixing incompatible character types is not allowed } // standard_wide @@ -108,15 +108,15 @@ lit(char32_t ch) noexcept } [[nodiscard]] constexpr literal_char -lit(traits::X4VagueArrayOf2Chars auto const& ch) noexcept +lit(X4VagueArrayOf2Chars auto const& ch) noexcept { return {ch[0]}; } } // helpers -constexpr void lit(traits::CharIncompatibleWith auto const*) = delete; // Mixing incompatible character types is not allowed -constexpr void lit(traits::CharIncompatibleWith auto) = delete; // Mixing incompatible character types is not allowed +constexpr void lit(CharIncompatibleWith auto const*) = delete; // Mixing incompatible character types is not allowed +constexpr void lit(CharIncompatibleWith auto) = delete; // Mixing incompatible character types is not allowed } // unicode @@ -143,7 +143,7 @@ using x4::standard_wide::lit; namespace unicode { using x4::unicode::char_; using x4::unicode::lit; -} // standard_wide +} // unicode #endif using x4::char_; // TODO: make `any_parser` encoding-agnostic @@ -178,10 +178,24 @@ struct as_parser return {ch}; } }; -#endif +#endif // IRIS_X4_NO_STANDARD_WIDE + +#ifdef IRIS_X4_UNICODE +template<> +struct as_parser +{ + using type = literal_char; + using value_type = type; + + [[nodiscard]] static constexpr type call(char32_t ch) noexcept + { + return {ch}; + } +}; +#endif // IRIS_X4_UNICODE template<> -struct as_parser +struct as_parser { using type = literal_char; using value_type = type; @@ -194,7 +208,7 @@ struct as_parser #ifndef IRIS_X4_NO_STANDARD_WIDE template<> -struct as_parser +struct as_parser { using type = literal_char; using value_type = type; @@ -206,6 +220,20 @@ struct as_parser }; #endif // IRIS_X4_NO_STANDARD_WIDE +#ifdef IRIS_X4_UNICODE +template<> +struct as_parser +{ + using type = literal_char; + using value_type = type; + + [[nodiscard]] static constexpr type call(char32_t const ch[]) noexcept + { + return {ch[0]}; + } +}; +#endif // IRIS_X4_UNICODE + } // extension } // iris::x4 diff --git a/include/iris/x4/char/char_class.hpp b/include/iris/x4/char/char_class.hpp index 925522667..4df79cc2e 100644 --- a/include/iris/x4/char/char_class.hpp +++ b/include/iris/x4/char/char_class.hpp @@ -11,6 +11,7 @@ ==============================================================================*/ #include +#include #include #include @@ -18,7 +19,7 @@ #include -#include +#include #include @@ -183,7 +184,7 @@ template Se> constexpr void builtin_skip_over(It& first, Se const& last) noexcept { using CharT = std::remove_cvref_t>; - static_assert(traits::CharLike); + static_assert(CharLike); using Encoding = traits::char_encoding_for; using Parser = char_class_parser; diff --git a/include/iris/x4/char/char_parser.hpp b/include/iris/x4/char/char_parser.hpp index 53b196006..393242cd0 100644 --- a/include/iris/x4/char/char_parser.hpp +++ b/include/iris/x4/char/char_parser.hpp @@ -13,8 +13,7 @@ #include #include #include - -#include +#include #include #include @@ -47,8 +46,8 @@ struct char_parser : parser noexcept(++first) ) { - static_assert(std::same_as, char_type>, "Mixing incompatible char types is not allowed"); - static_assert(!traits::CharLike || std::same_as, "Mixing incompatible char types is not allowed"); + static_assert(!CharIncompatibleWith, char_type>, "Mixing incompatible char types is not allowed"); + static_assert(!CharLike || !CharIncompatibleWith, "Mixing incompatible char types is not allowed"); x4::skip_over(first, last, ctx); @@ -72,8 +71,8 @@ struct char_parser : parser noexcept(++first) ) { - static_assert(std::same_as, char_type>, "Mixing incompatible char types is not allowed"); - static_assert(!traits::CharLike || std::same_as, "Mixing incompatible char types is not allowed"); + static_assert(!CharIncompatibleWith, char_type>, "Mixing incompatible char types is not allowed"); + static_assert(!CharLike || !CharIncompatibleWith, "Mixing incompatible char types is not allowed"); x4::skip_over(first, last, ctx); diff --git a/include/iris/x4/core/char_traits.hpp b/include/iris/x4/core/char_traits.hpp new file mode 100644 index 000000000..19ad29650 --- /dev/null +++ b/include/iris/x4/core/char_traits.hpp @@ -0,0 +1,60 @@ +#ifndef IRIS_ZZ_X4_CORE_CHAR_TRAITS_HPP +#define IRIS_ZZ_X4_CORE_CHAR_TRAITS_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 + +#include +#include + +namespace iris::x4 { + +template +concept CharLike = + std::same_as, char> || + std::same_as, wchar_t> || + std::same_as, char8_t> || + std::same_as, char16_t> || + std::same_as, char32_t>; + +template +concept CharArray = + std::is_array_v> && + CharLike>>; + +// Spirit has historically converted "c" to 'c'. +// +// While we think it's still useful to retain the conversion, +// we need to avoid further conversion to `std::basic_string_view`, +// which leads to performance overhead. This trait enables +// detection of such arrays. +// +// Note that the status quo introduces ambiguity in determining +// {'c', '\0'} and {'c', 'd'}, but we're not aware of any practical +// usage of non-null-terminated character array in the context of +// DSL on parser combinator. +// +// However, if compelling use cases emerge, we may revise these +// semantics. Versioned as `X4` for forward compatibility. +template +concept X4VagueArrayOf2Chars = + std::same_as>, CharT> && + std::is_bounded_array_v> && + std::extent_v> == 2; + +// Mixing incompatible character types is semantically wrong. +// Don't do that. It may even lead to security vulnerabilities. +template +concept CharIncompatibleWith = + CharLike && + !std::same_as; + +} // iris::x4 + +#endif diff --git a/include/iris/x4/core/move_to.hpp b/include/iris/x4/core/move_to.hpp index 24612e6c1..bb1c7589a 100644 --- a/include/iris/x4/core/move_to.hpp +++ b/include/iris/x4/core/move_to.hpp @@ -12,7 +12,8 @@ =============================================================================*/ #include -#include + +#include #include #include @@ -74,7 +75,7 @@ constexpr void move_to(T&, T&) noexcept ); static_assert( - false, + !std::is_lvalue_reference_v, "lvalue reference detected on the `src` argument of `x4::move_to`. " "The caller is definitely lacking `std::move` or `std::forward`. If you " "intend to *copy* the mutable value, apply `x4::move_to(std::as_const(attr_), attr)`." @@ -134,6 +135,8 @@ move_to(Source&& src, Dest& dest) { static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); static_assert(std::is_assignable_v); + static_assert((!CharLike && !CharLike>) || !CharIncompatibleWith>, "Mixing incompatible char types is not allowed"); + dest = std::forward(src); } @@ -206,6 +209,13 @@ move_to(It first, Se last, Dest& dest) { static_assert(!std::same_as, unused_type>); static_assert(!std::same_as, unused_container_type>); + static_assert( + // If either `It` or `Dest` is relevant to any character type, + (!CharLike>> && !CharLike::type>>) || + // ... then do the check below: + !CharIncompatibleWith>, std::remove_cvref_t::type>>, + "Mixing incompatible char types is not allowed" + ); if constexpr (!is_ttp_specialization_of_v) { if (!traits::is_empty(dest)) { diff --git a/include/iris/x4/core/string_traits.hpp b/include/iris/x4/core/string_traits.hpp new file mode 100644 index 000000000..5952626d8 --- /dev/null +++ b/include/iris/x4/core/string_traits.hpp @@ -0,0 +1,50 @@ +#ifndef IRIS_ZZ_X4_CORE_STRING_TRAITS_HPP +#define IRIS_ZZ_X4_CORE_STRING_TRAITS_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 + +#include + +#include +#include +#include +#include + +namespace iris::x4 { + +// Mixing incompatible character types is semantically wrong. +// Don't do that. It may even lead to security vulnerabilities. +template +concept StringLikeIncompatibleWith = + CharLike>>> && + !std::convertible_to>; + +// Main utility to guide `char_`, `lit` and `string` to be +// resolved into either `x4::literal_char` or `x4::literal_string`. +// +// This may also be used in other codes which require the same +// semantics. +template +concept CppStringLike = + // This avoids converting `CharT[2]` to `std::basic_string_view`. + (!X4VagueArrayOf2Chars) && + // All other types that are *naturally* convertible to `std::basic_string_view`. + std::convertible_to, std::basic_string_view>; + +template +using maybe_owning_string = std::conditional_t< + std::is_pointer_v>, + std::basic_string_view>>>, + std::remove_cvref_t +>; + +} // iris::x4 + +#endif diff --git a/include/iris/x4/debug/print_attribute.hpp b/include/iris/x4/debug/print_attribute.hpp index cd5507cdc..ee71588ce 100644 --- a/include/iris/x4/debug/print_attribute.hpp +++ b/include/iris/x4/debug/print_attribute.hpp @@ -25,6 +25,7 @@ #include #include +#include #include @@ -144,7 +145,7 @@ inline void print_chars(std::ostream& os, char32_t const ch) } template Se> -void print_chars(std::ostream& os, It const it, Se const se, std::size_t const max_code_points) +void print_chars(std::ostream& os, It it, Se const se, std::size_t const max_code_points) { iris::unicode::code_point_iterator code_point_it{it, it, se}; iris::unicode::code_point_iterator const code_point_se{se, it, se}; @@ -241,7 +242,7 @@ struct print_attribute_debug static void call(std::ostream& out, T_ const& val) { if constexpr (iris::StringLike) { - out << std::basic_string_view{val}; + out << iris::unicode::transcode_ref(std::basic_string_view{val}); } else { out << '['; diff --git a/include/iris/x4/numeric/bool.hpp b/include/iris/x4/numeric/bool.hpp index c170d8acf..07261a751 100644 --- a/include/iris/x4/numeric/bool.hpp +++ b/include/iris/x4/numeric/bool.hpp @@ -17,6 +17,8 @@ #include +#include + #include #include #include @@ -25,6 +27,23 @@ namespace iris::x4 { +template +struct bool_token; + +template<> +struct bool_token +{ + static constexpr auto true_ = "true"; + static constexpr auto false_ = "false"; +}; + +template<> +struct bool_token +{ + static constexpr auto true_ = U"true"; + static constexpr auto false_ = U"false"; +}; + // Default boolean policies template struct bool_policies @@ -34,8 +53,8 @@ struct bool_policies parse_true(It& first, Se const& last, Attr& attr_, CaseCompare const& compare) noexcept(noexcept(x4::move_to(T(true), attr_))) { - using namespace std::string_view_literals; - if (detail::string_parse("true"sv, first, last, unused_container, compare)) { + using CharT = std::remove_const_t>; + if (detail::string_parse(std::basic_string_view{bool_token::true_}, first, last, unused_container, compare)) { x4::move_to(T(true), attr_); return true; } @@ -47,8 +66,8 @@ struct bool_policies parse_false(It& first, Se const& last, Attr& attr_, CaseCompare const& compare) noexcept(noexcept(x4::move_to(T(false), attr_))) { - using namespace std::string_view_literals; - if (detail::string_parse("false"sv, first, last, unused_container, compare)) { + using CharT = std::remove_const_t>; + if (detail::string_parse(std::basic_string_view{bool_token::false_}, first, last, unused_container, compare)) { x4::move_to(T(false), attr_); return true; } @@ -56,28 +75,13 @@ struct bool_policies } }; -namespace detail { - -template -concept BoolPolicy = requires( - typename Encoding::char_type const*& first, typename Encoding::char_type const* const& last, - unused_type const& ctx, case_compare const& compare -) { - { Policy::parse_true(first, last, ctx, compare) } -> std::same_as; - { Policy::parse_false(first, last, ctx, compare) } -> std::same_as; -}; - -} // detail - -template> -struct bool_parser : parser> +template> +struct bool_parser : parser> { static_assert(X4Attribute); static_assert(std::default_initializable); - static_assert(detail::BoolPolicy); using attribute_type = T; - using encoding = Encoding; using policy_type = Policy; static constexpr bool has_attribute = true; @@ -94,13 +98,15 @@ struct bool_parser : parser> parse(It& first, Se const& last, Context const& ctx, U& attr) noexcept( noexcept(x4::skip_over(first, last, ctx)) && - noexcept(Policy::parse_true(first, last, attr, get_case_compare(ctx))) && - noexcept(Policy::parse_false(first, last, attr, get_case_compare(ctx))) + noexcept(Policy::parse_true(first, last, attr, x4::get_case_compare>>>(ctx))) && + noexcept(Policy::parse_false(first, last, attr, x4::get_case_compare>>>(ctx))) ) { x4::skip_over(first, last, ctx); - return Policy::parse_true(first, last, attr, get_case_compare(ctx)) - || Policy::parse_false(first, last, attr, get_case_compare(ctx)); + + auto const& compare = x4::get_case_compare>>>(ctx); + return Policy::parse_true(first, last, attr, compare) + || Policy::parse_false(first, last, attr, compare); } template Se, class Context, class Attr> @@ -115,7 +121,7 @@ struct bool_parser : parser> static_assert(X4NonUnusedAttribute); // this case is called when Attribute is not T - T attr_; + T attr_{}; if (bool_parser::parse(first, last, ctx, attr_)) { x4::move_to(std::move(attr_), attr); return true; @@ -129,15 +135,13 @@ struct bool_parser : parser> } }; -template> -struct literal_bool_parser : parser> +template> +struct literal_bool_parser : parser> { static_assert(X4Attribute); static_assert(std::default_initializable); - static_assert(detail::BoolPolicy); using attribute_type = T; - using encoding = Encoding; static constexpr bool has_attribute = true; @@ -161,13 +165,15 @@ struct literal_bool_parser : parser> parse(It& first, Se const& last, Context const& ctx, U& attr) const noexcept( noexcept(x4::skip_over(first, last, ctx)) && - noexcept(Policy::parse_true(first, last, attr, x4::get_case_compare(ctx))) && - noexcept(Policy::parse_false(first, last, attr, x4::get_case_compare(ctx))) + noexcept(Policy::parse_true(first, last, attr, x4::get_case_compare>>>(ctx))) && + noexcept(Policy::parse_false(first, last, attr, x4::get_case_compare>>>(ctx))) ) { x4::skip_over(first, last, ctx); - return (expected_bool_ && Policy::parse_true(first, last, attr, x4::get_case_compare(ctx))) - || (!expected_bool_ && Policy::parse_false(first, last, attr, x4::get_case_compare(ctx))); + + auto const& compare = x4::get_case_compare>>>(ctx); + return (expected_bool_ && Policy::parse_true(first, last, attr, compare)) + || (!expected_bool_ && Policy::parse_false(first, last, attr, compare)); } template Se, class Context, class Attr> @@ -182,7 +188,7 @@ struct literal_bool_parser : parser> static_assert(X4NonUnusedAttribute); // this case is called when Attribute is not T - T attr_; + T attr_{}; if (literal_bool_parser::parse(first, last, ctx, attr_)) { x4::move_to(std::move(attr_), attr); return true; @@ -199,54 +205,18 @@ struct literal_bool_parser : parser> T expected_bool_; // TODO: remove this runtime param; make this CTP }; -namespace standard { - -[[maybe_unused]] inline constexpr bool_parser bool_{}; -[[maybe_unused]] inline constexpr literal_bool_parser true_{true}; -[[maybe_unused]] inline constexpr literal_bool_parser false_{false}; - -} // standard - -namespace parsers::standard { - -using x4::standard::bool_; -using x4::standard::true_; -using x4::standard::false_; - -} // parsers::standard - -#ifndef IRIS_X4_NO_STANDARD_WIDE -namespace standard_wide { - -[[maybe_unused]] inline constexpr bool_parser bool_{}; -[[maybe_unused]] inline constexpr literal_bool_parser true_{true}; -[[maybe_unused]] inline constexpr literal_bool_parser false_{false}; - -} // standard_wide - -namespace parsers::standard_wide { - -using x4::standard_wide::bool_; -using x4::standard_wide::true_; -using x4::standard_wide::false_; - -} // parsers::standard_wide -#endif - -// TODO: unicode bool parser - -using standard::bool_; -using standard::true_; -using standard::false_; - namespace parsers { -using x4::standard::bool_; -using x4::standard::true_; -using x4::standard::false_; +[[maybe_unused]] inline constexpr bool_parser bool_{}; +[[maybe_unused]] inline constexpr literal_bool_parser true_{true}; +[[maybe_unused]] inline constexpr literal_bool_parser false_{false}; } // parsers +using parsers::bool_; +using parsers::true_; +using parsers::false_; + } // iris::x4 #endif diff --git a/include/iris/x4/parse.hpp b/include/iris/x4/parse.hpp index 2d9180170..abdbf885c 100644 --- a/include/iris/x4/parse.hpp +++ b/include/iris/x4/parse.hpp @@ -16,11 +16,10 @@ #include #include #include +#include #include // for builtin skipper -#include - #include #include @@ -159,14 +158,14 @@ concept X4RangeParseSkipper = >; template - requires (!traits::CharArray) + requires (!CharArray) [[nodiscard]] static constexpr decltype(auto) as_parse_range(R const& range) noexcept { return range; } template - requires traits::CharArray + requires CharArray [[nodiscard]] static constexpr auto as_parse_range(R const& str) noexcept(noexcept(std::basic_string_view{str})) { diff --git a/include/iris/x4/parse_result.hpp b/include/iris/x4/parse_result.hpp index 480988e4a..8feb694cc 100644 --- a/include/iris/x4/parse_result.hpp +++ b/include/iris/x4/parse_result.hpp @@ -11,7 +11,7 @@ #include #include -#include +#include #include #include @@ -95,7 +95,7 @@ struct parse_result_for_impl }; template - requires traits::CharArray + requires CharArray struct parse_result_for_impl { using string_view_type = std::basic_string_view< @@ -116,7 +116,7 @@ struct range_parse_parser_impl }; template - requires traits::CharArray + requires CharArray struct range_parse_parser_impl { using string_view_type = std::basic_string_view< diff --git a/include/iris/x4/string/detail/string_parse.hpp b/include/iris/x4/string/detail/string_parse.hpp index 945a6a732..e985172c8 100644 --- a/include/iris/x4/string/detail/string_parse.hpp +++ b/include/iris/x4/string/detail/string_parse.hpp @@ -11,7 +11,7 @@ ==============================================================================*/ #include -#include +#include #include #include @@ -33,7 +33,7 @@ string_parse( using synthesized_value_type = traits::synthesized_value_t; static_assert(std::same_as, traits::container_attr>); using value_type = traits::container_value::type; - static_assert(!traits::CharLike || std::same_as, "Mixing incompatible char types is not allowed"); + static_assert(!CharLike || !CharIncompatibleWith, "Mixing incompatible char types is not allowed"); It it = first; auto stri = str.begin(); @@ -80,7 +80,7 @@ string_parse( using synthesized_value_type = traits::synthesized_value_t; static_assert(std::same_as, traits::container_attr>); using value_type = traits::container_value::type; - static_assert(!traits::CharLike || std::same_as, "Mixing incompatible char types is not allowed"); + static_assert(!CharLike || !CharIncompatibleWith, "Mixing incompatible char types is not allowed"); auto uc_it = ucstr.begin(); auto uc_last = ucstr.end(); diff --git a/include/iris/x4/string/string.hpp b/include/iris/x4/string/string.hpp index 4ea10a291..73d0934e3 100644 --- a/include/iris/x4/string/string.hpp +++ b/include/iris/x4/string/string.hpp @@ -10,9 +10,11 @@ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ==============================================================================*/ -#include +#include +#include + #include // required for "c" -> 'c' optimization -#include +#include #include @@ -35,10 +37,10 @@ namespace standard { inline namespace helpers { -template T> -[[nodiscard]] constexpr literal_string, char_encoding::standard> +template T> +[[nodiscard]] constexpr literal_string, char_encoding::standard> string(T&& string_like) - noexcept(std::is_nothrow_constructible_v, char_encoding::standard>, T>) + noexcept(std::is_nothrow_constructible_v, char_encoding::standard>, T>) { return {std::forward(string_like)}; } @@ -52,15 +54,15 @@ string(char ch) noexcept // Optimize `literal_string{"c"}` into `literal_char{'c'}` [[nodiscard]] constexpr literal_char> -string(traits::X4VagueArrayOf2Chars auto const& ch) noexcept +string(X4VagueArrayOf2Chars auto const& ch) noexcept { return {ch[0]}; } -template T> -[[nodiscard]] constexpr literal_string, char_encoding::standard, unused_type> +template T> +[[nodiscard]] constexpr literal_string, char_encoding::standard, unused_type> lit(T&& string_like) - noexcept(std::is_nothrow_constructible_v, char_encoding::standard, unused_type>, T>) + noexcept(std::is_nothrow_constructible_v, char_encoding::standard, unused_type>, T>) { return {std::forward(string_like)}; } @@ -68,7 +70,7 @@ lit(T&& string_like) } // helpers template - requires traits::CharIncompatibleWith || traits::StringLikeIncompatibleWith + requires CharIncompatibleWith || StringLikeIncompatibleWith constexpr void string(T&&) = delete; // Mixing incompatible character types is not allowed } // standard @@ -81,10 +83,10 @@ namespace standard_wide { inline namespace helpers { -template T> -[[nodiscard]] constexpr literal_string, char_encoding::standard_wide> +template T> +[[nodiscard]] constexpr literal_string, char_encoding::standard_wide> string(T&& string_like) - noexcept(std::is_nothrow_constructible_v, char_encoding::standard_wide>, T>) + noexcept(std::is_nothrow_constructible_v, char_encoding::standard_wide>, T>) { return {std::forward(string_like)}; } @@ -98,15 +100,15 @@ string(wchar_t ch) noexcept // Optimize `literal_string{L"c"}` into `literal_char{L'c'}` [[nodiscard]] constexpr literal_char> -string(traits::X4VagueArrayOf2Chars auto const& ch) noexcept +string(X4VagueArrayOf2Chars auto const& ch) noexcept { return {ch[0]}; } -template T> -[[nodiscard]] constexpr literal_string, char_encoding::standard_wide, unused_type> +template T> +[[nodiscard]] constexpr literal_string, char_encoding::standard_wide, unused_type> lit(T&& string_like) - noexcept(std::is_nothrow_constructible_v, char_encoding::standard_wide, unused_type>, T>) + noexcept(std::is_nothrow_constructible_v, char_encoding::standard_wide, unused_type>, T>) { return {std::forward(string_like)}; } @@ -114,7 +116,7 @@ lit(T&& string_like) } // helpers template - requires traits::CharIncompatibleWith || traits::StringLikeIncompatibleWith + requires CharIncompatibleWith || StringLikeIncompatibleWith constexpr void string(T&&) = delete; // Mixing incompatible character types is not allowed } // standard_wide @@ -129,10 +131,10 @@ namespace unicode { inline namespace helpers { // TODO: add `char8_t` and `char16_t` overloads -template T> -[[nodiscard]] constexpr literal_string, char_encoding::unicode> +template T> +[[nodiscard]] constexpr literal_string, char_encoding::unicode> string(T&& string_like) - noexcept(std::is_nothrow_constructible_v, char_encoding::unicode>, T>) + noexcept(std::is_nothrow_constructible_v, char_encoding::unicode>, T>) { return {std::forward(string_like)}; } @@ -146,15 +148,15 @@ string(char32_t ch) noexcept // Optimize `literal_string{U"c"}` into `literal_char{U'c'}` [[nodiscard]] constexpr literal_char> -string(traits::X4VagueArrayOf2Chars auto const& ch) noexcept +string(X4VagueArrayOf2Chars auto const& ch) noexcept { return {ch[0]}; } -template T> -[[nodiscard]] constexpr literal_string, char_encoding::unicode, unused_type> +template T> +[[nodiscard]] constexpr literal_string, char_encoding::unicode, unused_type> lit(T&& string_like) - noexcept(std::is_nothrow_constructible_v, char_encoding::unicode, unused_type>, T>) + noexcept(std::is_nothrow_constructible_v, char_encoding::unicode, unused_type>, T>) { return {std::forward(string_like)}; } @@ -162,7 +164,7 @@ lit(T&& string_like) } // helpers template - requires traits::CharIncompatibleWith || traits::StringLikeIncompatibleWith + requires CharIncompatibleWith || StringLikeIncompatibleWith constexpr void string(T&&) = delete; // Mixing incompatible character types is not allowed } // unicode @@ -201,7 +203,7 @@ using x4::lit; namespace extension { -template +template struct as_parser { using type = literal_string, traits::char_encoding_for, unused_type>; @@ -213,10 +215,10 @@ struct as_parser } }; -template +template struct as_parser : as_parser {}; -template +template struct as_parser { using type = literal_string, traits::char_encoding_for, unused_type>; @@ -228,7 +230,7 @@ struct as_parser } }; -template +template struct as_parser> { using type = literal_string, traits::char_encoding_for, unused_type>; @@ -242,7 +244,7 @@ struct as_parser> } }; -template +template struct as_parser> { using type = literal_string, traits::char_encoding_for, unused_type>; diff --git a/include/iris/x4/symbols.hpp b/include/iris/x4/symbols.hpp index ab325d5af..73a1ef2f8 100644 --- a/include/iris/x4/symbols.hpp +++ b/include/iris/x4/symbols.hpp @@ -17,8 +17,6 @@ #include #include -#include - #include #include @@ -312,14 +310,14 @@ struct symbols_parser_impl : parser [[nodiscard]] constexpr value_type* find_impl(Iterator begin, Iterator end) noexcept { value_type* r = lookup->find(begin, end, case_compare()); - return begin == end ? r : 0; + return begin == end ? r : nullptr; } template [[nodiscard]] constexpr value_type const* find_impl(Iterator begin, Iterator end) const noexcept { value_type const* r = lookup->find(begin, end, case_compare()); - return begin == end ? r : 0; + return begin == end ? r : nullptr; } std::conditional_t, std::unique_ptr> lookup; diff --git a/include/iris/x4/traits/char_encoding_traits.hpp b/include/iris/x4/traits/char_encoding_traits.hpp index ff14fe981..cc8ac0adc 100644 --- a/include/iris/x4/traits/char_encoding_traits.hpp +++ b/include/iris/x4/traits/char_encoding_traits.hpp @@ -23,6 +23,20 @@ namespace iris::x4::traits { namespace detail { +template struct char_encoding_for_impl; +template<> struct char_encoding_for_impl { using type = char_encoding::standard; }; + +#ifndef IRIS_X4_NO_STANDARD_WIDE +template<> struct char_encoding_for_impl { using type = char_encoding::standard_wide; }; +#endif + +#ifdef IRIS_X4_UNICODE +template<> struct char_encoding_for_impl { using type = char_encoding::unicode; }; +template<> struct char_encoding_for_impl { using type = char_encoding::unicode; }; +template<> struct char_encoding_for_impl { using type = char_encoding::unicode; }; +#endif + + template struct char_encoding_traits_impl { @@ -45,6 +59,11 @@ struct char_encoding_traits_impl } // detail + +template +using char_encoding_for = typename detail::char_encoding_for_impl::type; + + template struct char_encoding_traits; diff --git a/include/iris/x4/traits/string_traits.hpp b/include/iris/x4/traits/string_traits.hpp deleted file mode 100644 index 639741877..000000000 --- a/include/iris/x4/traits/string_traits.hpp +++ /dev/null @@ -1,138 +0,0 @@ -#ifndef IRIS_ZZ_X4_TRAITS_STRING_TRAITS_HPP -#define IRIS_ZZ_X4_TRAITS_STRING_TRAITS_HPP - -/*============================================================================= - Copyright (c) 2001-2014 Joel de Guzman - Copyright (c) 2001-2011 Hartmut Kaiser - Copyright (c) 2010 Bryce Lelbach - 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) -================================================_==============================*/ - -#if defined(IRIS_UNICODE) && !defined(IRIS_X4_UNICODE) -# error "`IRIS_UNICODE` has no effect on X4. #define `IRIS_X4_UNICODE`" -#endif - -#if defined(IRIS_X3_UNICODE) && !defined(IRIS_X4_UNICODE) -# error "`IRIS_X3_UNICODE` has no effect on X4. #define `IRIS_X4_UNICODE`" -#endif - -#if defined(IRIS_NO_STANDARD_WIDE) && !defined(IRIS_X4_NO_STANDARD_WIDE) -# error "`IRIS_NO_STANDARD_WIDE` has no effect on X4. #define `IRIS_X4_NO_STANDARD_WIDE`" -#endif - -#if defined(IRIS_X3_NO_STANDARD_WIDE) && !defined(IRIS_X4_NO_STANDARD_WIDE) -# error "`IRIS_X3_NO_STANDARD_WIDE` has no effect on X4. #define `IRIS_X4_NO_STANDARD_WIDE`" -#endif - -#include -#include -#include -#include - -namespace iris::x4::char_encoding { - -struct standard; - -#ifndef IRIS_X4_NO_STANDARD_WIDE -struct standard_wide; -#endif - -#ifdef IRIS_X4_UNICODE -struct unicode; -#endif - -} // iris::x4::char_encoding - -namespace iris::x4::traits { - -template -concept CharLike = - std::same_as, char> || - std::same_as, wchar_t> || - std::same_as, char8_t> || - std::same_as, char16_t> || - std::same_as, char32_t>; - -template -concept CharArray = - std::is_array_v> && - CharLike>>; - -// Spirit has historically converted "c" to 'c'. -// -// While we think it's still useful to retain the conversion, -// we need to avoid further conversion to `std::basic_string_view`, -// which leads to performance overhead. This trait enables -// detection of such arrays. -// -// Note that the status quo introduces ambiguity in determining -// {'c', '\0'} and {'c', 'd'}, but we're not aware of any practical -// usage of non-null-terminated character array in the context of -// DSL on parser combinator. -// -// However, if compelling use cases emerge, we may revise these -// semantics. Versioned as `X4` for forward compatibility. -template -concept X4VagueArrayOf2Chars = - std::same_as>, CharT> && - std::is_bounded_array_v> && - std::extent_v> == 2; - -// Main utility to guide `char_`, `lit` and `string` to be -// resolved into either `x4::literal_char` or `x4::literal_string`. -// -// This may also be used in other codes which require the same -// semantics. -template -concept CppStringLike = - // This avoids converting `CharT[2]` to `std::basic_string_view`. - (!X4VagueArrayOf2Chars) && - // All other types that are *naturally* convertible to `std::basic_string_view`. - std::convertible_to, std::basic_string_view>; - -// Mixing incompatible character types is semantically wrong. -// Don't do that. It may even lead to security vulnerabilities. -template -concept CharIncompatibleWith = - CharLike && - !std::same_as; - -// Mixing incompatible character types is semantically wrong. -// Don't do that. It may even lead to security vulnerabilities. -template -concept StringLikeIncompatibleWith = - CharLike>>> && - !std::convertible_to>; - -namespace detail { - template struct char_encoding_for_impl; - template<> struct char_encoding_for_impl { using type = char_encoding::standard; }; - -#ifndef IRIS_X4_NO_STANDARD_WIDE - template<> struct char_encoding_for_impl { using type = char_encoding::standard_wide; }; -#endif - -#ifdef IRIS_X4_UNICODE - template<> struct char_encoding_for_impl { using type = char_encoding::unicode; }; - template<> struct char_encoding_for_impl { using type = char_encoding::unicode; }; - template<> struct char_encoding_for_impl { using type = char_encoding::unicode; }; -#endif -} // detail - -template -using maybe_owning_string = std::conditional_t< - std::is_pointer_v>, - std::basic_string_view>>>, - std::remove_cvref_t ->; - -template -using char_encoding_for = typename detail::char_encoding_for_impl::type; - -} // iris::x4::traits - -#endif diff --git a/modules/iris b/modules/iris index 8104bf726..8dfa73a34 160000 --- a/modules/iris +++ b/modules/iris @@ -1 +1 @@ -Subproject commit 8104bf726122384bea8e18feac2d956ffa26bf7f +Subproject commit 8dfa73a34bbb707b7f7ef4ddef77f810a6d15a23 diff --git a/test/x4/bool.cpp b/test/x4/bool.cpp index f46949379..12b2fb927 100644 --- a/test/x4/bool.cpp +++ b/test/x4/bool.cpp @@ -46,12 +46,6 @@ TEST_CASE("bool") using x4::false_; using x4::no_case; - static_assert(x4::detail::BoolPolicy, x4::char_encoding::standard>); - static_assert(x4::detail::BoolPolicy, x4::char_encoding::standard_wide>); - static_assert(x4::detail::BoolPolicy, x4::char_encoding::unicode>); - - static_assert(x4::detail::BoolPolicy); - { IRIS_X4_ASSERT_CONSTEXPR_CTORS(bool_); @@ -91,7 +85,7 @@ TEST_CASE("bool") } { - using backwards_bool_type = x4::bool_parser; + using backwards_bool_type = x4::bool_parser; constexpr backwards_bool_type backwards_bool{}; IRIS_X4_ASSERT_CONSTEXPR_CTORS(backwards_bool); @@ -124,7 +118,7 @@ TEST_CASE("bool") bool b; }; - using bool_test_type = x4::bool_parser; + using bool_test_type = x4::bool_parser; constexpr bool_test_type test_bool{}; IRIS_X4_ASSERT_CONSTEXPR_CTORS(test_bool);