diff --git a/include/boost/spirit/home/x3/auxiliary/guard.hpp b/include/boost/spirit/home/x3/auxiliary/guard.hpp index 982980ff8f..35ff6a0eef 100644 --- a/include/boost/spirit/home/x3/auxiliary/guard.hpp +++ b/include/boost/spirit/home/x3/auxiliary/guard.hpp @@ -1,5 +1,7 @@ /*============================================================================= Copyright (c) 2001-2014 Joel de Guzman + Copyright (c) 2017 wanghan02 + Copyright (c) 2024 Nana Sakisaka 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) @@ -8,7 +10,8 @@ #define BOOST_SPIRIT_X3_GUARD_FERBRUARY_02_2013_0649PM #include -#include +#include +#include namespace boost { namespace spirit { namespace x3 { @@ -17,7 +20,7 @@ namespace boost { namespace spirit { namespace x3 fail , retry , accept - , rethrow + , rethrow // see BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE for alternative behaviors }; template @@ -36,30 +39,48 @@ namespace boost { namespace spirit { namespace x3 { for (;;) { + Iterator i = first; + + #if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE try + #endif { - Iterator i = first; - bool r = this->subject.parse(i, last, context, rcontext, attr); - if (r) + if (this->subject.parse(i, last, context, rcontext, attr)) + { first = i; - return r; + return true; + } } - catch (expectation_failure const& x) - { + + #if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE + catch (expectation_failure const& x) { + #else + if (has_expectation_failure(context)) { + auto& x = get_expectation_failure(context); + #endif + // X3 developer note: don't forget to sync this implementation with x3::detail::rule_parser switch (handler(first, last, x, context)) { case error_handler_result::fail: + clear_expectation_failure(context); return false; + case error_handler_result::retry: continue; + case error_handler_result::accept: return true; + case error_handler_result::rethrow: + #if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE throw; + #else + return false; // TODO: design decision required + #endif } } + return false; } - return false; } Handler handler; diff --git a/include/boost/spirit/home/x3/core/proxy.hpp b/include/boost/spirit/home/x3/core/proxy.hpp index c9cc114876..c4384b3ca2 100644 --- a/include/boost/spirit/home/x3/core/proxy.hpp +++ b/include/boost/spirit/home/x3/core/proxy.hpp @@ -1,5 +1,7 @@ /*============================================================================= Copyright (c) 2001-2014 Joel de Guzman + Copyright (c) 2017 wanghan02 + Copyright (c) 2024 Nana Sakisaka 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) @@ -7,6 +9,7 @@ #if !defined(BOOST_SPIRIT_X3_PROXY_FEBRUARY_1_2013_0211PM) #define BOOST_SPIRIT_X3_PROXY_FEBRUARY_1_2013_0211PM +#include #include #include #include @@ -29,7 +32,7 @@ namespace boost { namespace spirit { namespace x3 , Context const& context, RuleContext& rcontext, Attribute& attr, Category) const { this->subject.parse(first, last, context, rcontext, attr); - return true; + return !has_expectation_failure(context); } // Main entry point. diff --git a/include/boost/spirit/home/x3/core/skip_over.hpp b/include/boost/spirit/home/x3/core/skip_over.hpp index 66e4c4a94f..b3202b4e75 100644 --- a/include/boost/spirit/home/x3/core/skip_over.hpp +++ b/include/boost/spirit/home/x3/core/skip_over.hpp @@ -1,5 +1,7 @@ /*============================================================================= Copyright (c) 2001-2014 Joel de Guzman + Copyright (c) 2017 wanghan02 + Copyright (c) 2024 Nana Sakisaka 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) @@ -7,6 +9,7 @@ #if !defined(BOOST_SPIRIT_X3_SKIP_APRIL_16_2006_0625PM) #define BOOST_SPIRIT_X3_SKIP_APRIL_16_2006_0625PM +#include #include #include #include @@ -15,6 +18,7 @@ #include #include #include +#include namespace boost { namespace spirit { namespace x3 { @@ -41,7 +45,7 @@ namespace boost { namespace spirit { namespace x3 struct is_unused_skipper> : mpl::true_ {}; - template <> + template <> struct is_unused_skipper : mpl::true_ {}; @@ -58,29 +62,85 @@ namespace boost { namespace spirit { namespace x3 return unused_skipper.skipper; } - template + template inline void skip_over( - Iterator& first, Iterator const& last, Skipper const& skipper) + Iterator& first, Iterator const& last, Context& context, Skipper const& skipper) { + #if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE + boost::ignore_unused(context); while (skipper.parse(first, last, unused, unused, unused)) - /***/; + /* loop */; + #else + if constexpr (std::is_same_v, unused_type>) + { + // The context given by parent was truly `unused_type`. + // There exists only one such case in core; that is + // `x3::phrase_parse(...)` which creates a fresh context + // for the (post)skipper. + // + // In that case, it is perfectly fine to pass `unused` + // because the skipper should have been wrapped + // like `x3::with(failure)[skipper]`. + // (Note that we have plenty of static_asserts in other + // locations to detect the absence of the context.) + // + // If we encounter this branch in any other situations, + // that should be a BUG of `expectation_failure` logic. + + while (skipper.parse(first, last, unused, unused, unused)) + /* loop */; + } + else + { + // In order to cut the template instantiation chain, + // we must *forget* the original context at least once + // during the (recursive) invocation of skippers. + // + // Traditionally, implementation detail of `skip_over` + // was disposing the context because we can clearly assume + // that any 'context,' including those provided by users, + // is semantically meaningless as long as we're just + // *skipping* iterators. As you can see in the other branch, + // `unused` was passed for that purpose. + // + // However, we need to do a quite different thing when the + // non-throwing expectation_failure mode is enabled. + // + // Since the reference bound to `x3::expectation_failure_tag` is + // provided by the user in the first place, if we do forget it + // then it will be impossible to resurrect the value afterwards. + // It will also be problematic for `skip_over` itself because the + // underlying skipper may (or may not) raise an expectation failure. + // In traditional mode, the error was thrown by a C++ exception. + // But how can we propagate that error without throwing? + // + // For this reason we're going to cherry-pick the reference + // and repack it into a brand new context. + + auto const local_ctx = make_context( + x3::get(context)); + + while (skipper.parse(first, last, local_ctx, unused, unused)) + /* loop */; + } + #endif } - template - inline void skip_over(Iterator&, Iterator const&, unused_type) + template + inline void skip_over(Iterator&, Iterator const&, Context&, unused_type) { } - template + template inline void skip_over( - Iterator&, Iterator const&, unused_skipper const&) + Iterator&, Iterator const&, Context&, unused_skipper const&) { } } // this tag is used to find the skipper from the context struct skipper_tag; - + template struct has_skipper : mpl::not_ inline void skip_over( - Iterator& first, Iterator const& last, Context const& context) + Iterator& first, Iterator const& last, Context& context) { - detail::skip_over(first, last, x3::get(context)); + detail::skip_over(first, last, context, x3::get(context)); } }}} diff --git a/include/boost/spirit/home/x3/directive/confix.hpp b/include/boost/spirit/home/x3/directive/confix.hpp index 8734fe11c8..79d7fb1fa0 100644 --- a/include/boost/spirit/home/x3/directive/confix.hpp +++ b/include/boost/spirit/home/x3/directive/confix.hpp @@ -1,6 +1,8 @@ /*============================================================================= Copyright (c) 2009 Chris Hoeppler Copyright (c) 2014 Lee Clagett + Copyright (c) 2017 wanghan02 + Copyright (c) 2024 Nana Sakisaka 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) @@ -10,6 +12,7 @@ #define BOOST_SPIRIT_X3_CONFIX_MAY_30_2014_1819PM #include +#include namespace boost { namespace spirit { namespace x3 { @@ -43,6 +46,14 @@ namespace boost { namespace spirit { namespace x3 this->subject.parse(first, last, context, rcontext, attr) && postfix.parse(first, last, context, rcontext, unused))) { + #if !BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE + if (has_expectation_failure(context)) + { + // don't rollback iterator (mimicking exception-like behavior) + return false; + } + #endif + first = save; return false; } diff --git a/include/boost/spirit/home/x3/directive/expect.hpp b/include/boost/spirit/home/x3/directive/expect.hpp index c39f887e30..c0f8ecb6f6 100644 --- a/include/boost/spirit/home/x3/directive/expect.hpp +++ b/include/boost/spirit/home/x3/directive/expect.hpp @@ -1,5 +1,7 @@ /*============================================================================= Copyright (c) 2001-2014 Joel de Guzman + Copyright (c) 2017 wanghan02 + Copyright (c) 2024 Nana Sakisaka 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) @@ -8,35 +10,12 @@ #define BOOST_SPIRIT_X3_EXPECT_MARCH_16_2012_1024PM #include +#include #include #include -#include // for BOOST_SYMBOL_VISIBLE -#include -#include - namespace boost { namespace spirit { namespace x3 { - template - struct BOOST_SYMBOL_VISIBLE expectation_failure : std::runtime_error - { - public: - - expectation_failure(Iterator where, std::string const& which) - : std::runtime_error("boost::spirit::x3::expectation_failure") - , where_(where), which_(which) - {} - ~expectation_failure() {} - - std::string which() const { return which_; } - Iterator const& where() const { return where_; } - - private: - - Iterator where_; - std::string which_; - }; - template struct expect_directive : unary_parser> { @@ -51,13 +30,20 @@ namespace boost { namespace spirit { namespace x3 bool parse(Iterator& first, Iterator const& last , Context const& context, RContext& rcontext, Attribute& attr) const { - bool r = this->subject.parse(first, last, context, rcontext, attr); + bool const r = this->subject.parse(first, last, context, rcontext, attr); if (!r) { + #if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE boost::throw_exception( expectation_failure( first, what(this->subject))); + #else + if (!has_expectation_failure(context)) + { + set_expectation_failure(first, this->subject, context); + } + #endif } return r; } @@ -88,14 +74,21 @@ namespace boost { namespace spirit { namespace x3 { namespace detail , Iterator& first, Iterator const& last , Context const& context, RContext& rcontext, Attribute& attr) { - bool r = parse_into_container( + bool const r = parse_into_container( parser.subject, first, last, context, rcontext, attr); if (!r) { + #if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE boost::throw_exception( expectation_failure( first, what(parser.subject))); + #else + if (!has_expectation_failure(context)) + { + set_expectation_failure(first, parser.subject, context); + } + #endif } return r; } diff --git a/include/boost/spirit/home/x3/directive/matches.hpp b/include/boost/spirit/home/x3/directive/matches.hpp index 46428465ac..a94f7cfed1 100644 --- a/include/boost/spirit/home/x3/directive/matches.hpp +++ b/include/boost/spirit/home/x3/directive/matches.hpp @@ -1,6 +1,8 @@ /*============================================================================= Copyright (c) 2015 Mario Lang Copyright (c) 2001-2011 Hartmut Kaiser + Copyright (c) 2017 wanghan02 + Copyright (c) 2024 Nana Sakisaka 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) @@ -10,6 +12,7 @@ #include #include +#include #include namespace boost { namespace spirit { namespace x3 @@ -30,6 +33,11 @@ namespace boost { namespace spirit { namespace x3 { bool const result = this->subject.parse( first, last, context, rcontext, unused); + + #if !BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE + if (has_expectation_failure(context)) return false; + #endif + traits::move_to(result, attr); return true; } diff --git a/include/boost/spirit/home/x3/directive/repeat.hpp b/include/boost/spirit/home/x3/directive/repeat.hpp index 6deb2db303..d19ea6a521 100644 --- a/include/boost/spirit/home/x3/directive/repeat.hpp +++ b/include/boost/spirit/home/x3/directive/repeat.hpp @@ -2,6 +2,8 @@ Copyright (c) 2001-2011 Joel de Guzman Copyright (c) 2001-2011 Hartmut Kaiser Copyright (c) 2014 Thomas Bernard + Copyright (c) 2017 wanghan02 + Copyright (c) 2024 Nana Sakisaka 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) @@ -11,6 +13,7 @@ #include #include +#include namespace boost { namespace spirit { namespace x3 { namespace detail { @@ -83,7 +86,8 @@ namespace boost { namespace spirit { namespace x3 this->subject, first, last, context, rcontext, attr)) break; } - return true; + + return !has_expectation_failure(context); } RepeatCountLimit repeat_limit; diff --git a/include/boost/spirit/home/x3/directive/seek.hpp b/include/boost/spirit/home/x3/directive/seek.hpp index d78def1c2b..1584169fa9 100644 --- a/include/boost/spirit/home/x3/directive/seek.hpp +++ b/include/boost/spirit/home/x3/directive/seek.hpp @@ -1,6 +1,8 @@ /*============================================================================= Copyright (c) 2011 Jamboree Copyright (c) 2014 Lee Clagett + Copyright (c) 2017 wanghan02 + Copyright (c) 2024 Nana Sakisaka 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) @@ -9,6 +11,7 @@ #define BOOST_SPIRIT_X3_SEEK_APRIL_13_2014_1920PM #include +#include namespace boost { namespace spirit { namespace x3 { @@ -36,6 +39,13 @@ namespace boost { namespace spirit { namespace x3 return true; } + #if !BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE + if (has_expectation_failure(context)) + { + return false; + } + #endif + // fail only after subject fails & no input if (current == last) return false; diff --git a/include/boost/spirit/home/x3/directive/skip.hpp b/include/boost/spirit/home/x3/directive/skip.hpp index 21a7b27f41..462a781a84 100644 --- a/include/boost/spirit/home/x3/directive/skip.hpp +++ b/include/boost/spirit/home/x3/directive/skip.hpp @@ -1,6 +1,7 @@ /*============================================================================= Copyright (c) 2001-2014 Joel de Guzman Copyright (c) 2013 Agustin Berge + Copyright (c) 2024 Nana Sakisaka 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) @@ -10,6 +11,7 @@ #include #include +#include #include #include #include @@ -30,16 +32,22 @@ namespace boost { namespace spirit { namespace x3 , typename RContext, typename Attribute> typename disable_if, bool>::type parse(Iterator& first, Iterator const& last - , Context const& context, RContext& rcontext, Attribute& attr) const + , Context& context, RContext& rcontext, Attribute& attr) const { auto const& skipper = detail::get_unused_skipper(x3::get(context)); - return this->subject.parse( - first, last - , make_context(skipper, context) - , rcontext - , attr); + auto const local_ctx = make_context(skipper, context); + bool const r = this->subject.parse(first, last, local_ctx, rcontext, attr); + + #if !BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE + if (has_expectation_failure(local_ctx)) + { + set_expectation_failure(get_expectation_failure(local_ctx), context); + } + #endif + + return r; } template @@ -47,11 +55,7 @@ namespace boost { namespace spirit { namespace x3 parse(Iterator& first, Iterator const& last , Context const& context, RContext& rcontext, Attribute& attr) const { - return this->subject.parse( - first, last - , context - , rcontext - , attr); + return this->subject.parse(first, last, context, rcontext, attr); } }; @@ -67,16 +71,48 @@ namespace boost { namespace spirit { namespace x3 , skipper(skipper) {} - template + template + bool parse(Iterator& first, Iterator const& last + , unused_type const&, RContext& rcontext, Attribute& attr) const + { + #if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE + return this->subject.parse(first, last, make_context(skipper), rcontext, attr); + + #else + static_assert( + false, + "In `skip(subject).parse(...)`: " + "subject might raise an expectation failure, but there's no place to store the variable " + "since the parent context type is unused_type. " + "Perhaps you forgot: `x3::with(failure)[p]`"); + #endif + } + + template bool parse(Iterator& first, Iterator const& last , Context const& context, RContext& rcontext, Attribute& attr) const { - return this->subject.parse( - first, last - , make_context(skipper, context) - , rcontext - , attr); + #if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE + return this->subject.parse(first, last, make_context(skipper, context), rcontext, attr); + + #else + static_assert( + !std::is_same_v, unused_type>, + "Context type was not specified for x3::expectation_failure_tag. " + "You probably forgot: `x3::with(failure)[p]`. " + "Note that you must also bind the context to your skipper."); + + // This logic is heavily related to the instantiation chain; + // see `x3::skip_over` for details. + auto const local_ctx = make_context(skipper, context); + bool const r = this->subject.parse(first, last, local_ctx, rcontext, attr); + + if (has_expectation_failure(local_ctx)) + { + set_expectation_failure(get_expectation_failure(local_ctx), context); + } + return r; + #endif } Skipper const skipper; diff --git a/include/boost/spirit/home/x3/nonterminal/detail/rule.hpp b/include/boost/spirit/home/x3/nonterminal/detail/rule.hpp index 78e54746d7..b44a92400d 100644 --- a/include/boost/spirit/home/x3/nonterminal/detail/rule.hpp +++ b/include/boost/spirit/home/x3/nonterminal/detail/rule.hpp @@ -1,5 +1,7 @@ /*============================================================================= Copyright (c) 2001-2014 Joel de Guzman + Copyright (c) 2017 wanghan02 + Copyright (c) 2024 Nana Sakisaka 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) @@ -11,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -233,25 +236,45 @@ namespace boost { namespace spirit { namespace x3 { namespace detail { for (;;) { + #if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE try + #endif { - return parse_rhs_main( - rhs, first, last, context, rcontext, attr, mpl::false_()); + if (parse_rhs_main( + rhs, first, last, context, rcontext, attr, mpl::false_())) + { + return true; + } } - catch (expectation_failure const& x) - { + + #if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE + catch (expectation_failure const& x) { + #else + if (has_expectation_failure(context)) { + auto& x = get_expectation_failure(context); + #endif + // X3 developer note: don't forget to sync this implementation with x3::guard switch (ID().on_error(first, last, x, context)) { case error_handler_result::fail: + clear_expectation_failure(context); return false; + case error_handler_result::retry: continue; + case error_handler_result::accept: return true; + case error_handler_result::rethrow: + #if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE throw; + #else + return false; // TODO: design decision required + #endif } } + return false; } } diff --git a/include/boost/spirit/home/x3/operator/alternative.hpp b/include/boost/spirit/home/x3/operator/alternative.hpp index aeb6998ba4..40a313739f 100644 --- a/include/boost/spirit/home/x3/operator/alternative.hpp +++ b/include/boost/spirit/home/x3/operator/alternative.hpp @@ -1,5 +1,7 @@ /*============================================================================= Copyright (c) 2001-2014 Joel de Guzman + Copyright (c) 2017 wanghan02 + Copyright (c) 2024 Nana Sakisaka 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) @@ -8,6 +10,7 @@ #define BOOST_SPIRIT_X3_ALTERNATIVE_JAN_07_2013_1131AM #include +#include #include #include @@ -29,7 +32,8 @@ namespace boost { namespace spirit { namespace x3 , Context const& context, RContext& rcontext, unused_type) const { return this->left.parse(first, last, context, rcontext, unused) - || this->right.parse(first, last, context, rcontext, unused); + || (!has_expectation_failure(context) + && this->right.parse(first, last, context, rcontext, unused)); } template left, first, last, context, rcontext, attr) - || detail::parse_alternative(this->right, first, last, context, rcontext, attr); + || (!has_expectation_failure(context) + && detail::parse_alternative(this->right, first, last, context, rcontext, attr)); } }; diff --git a/include/boost/spirit/home/x3/operator/difference.hpp b/include/boost/spirit/home/x3/operator/difference.hpp index 36de0a1d84..ac1038f718 100644 --- a/include/boost/spirit/home/x3/operator/difference.hpp +++ b/include/boost/spirit/home/x3/operator/difference.hpp @@ -1,5 +1,7 @@ /*============================================================================= Copyright (c) 2001-2014 Joel de Guzman + Copyright (c) 2017 wanghan02 + Copyright (c) 2024 Nana Sakisaka 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) @@ -9,6 +11,7 @@ #include #include +#include #include namespace boost { namespace spirit { namespace x3 @@ -35,6 +38,20 @@ namespace boost { namespace spirit { namespace x3 first = start; return false; } + + // In case of `Left - expect[r]`, + // if Right yielded expectation error, + // the whole difference expression (*this) should also yield error. + // In other words, when the THROW macro was 1 (i.e. traditional behavior), + // Right should already have thrown an exception. + #if !BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE + if (has_expectation_failure(context)) + { + // don't rollback iterator (mimicking exception-like behavior) + return false; + } + #endif + // Right fails, now try Left return this->left.parse(first, last, context, rcontext, attr); } diff --git a/include/boost/spirit/home/x3/operator/kleene.hpp b/include/boost/spirit/home/x3/operator/kleene.hpp index aa2977001d..66130ab42c 100644 --- a/include/boost/spirit/home/x3/operator/kleene.hpp +++ b/include/boost/spirit/home/x3/operator/kleene.hpp @@ -1,6 +1,8 @@ /*============================================================================= Copyright (c) 2001-2014 Joel de Guzman Copyright (c) 2001-2011 Hartmut Kaiser + Copyright (c) 2017 wanghan02 + Copyright (c) 2024 Nana Sakisaka 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) @@ -11,6 +13,7 @@ #include #include #include +#include #include namespace boost { namespace spirit { namespace x3 @@ -32,7 +35,7 @@ namespace boost { namespace spirit { namespace x3 while (detail::parse_into_container( this->subject, first, last, context, rcontext, attr)) ; - return true; + return !has_expectation_failure(context); } }; diff --git a/include/boost/spirit/home/x3/operator/list.hpp b/include/boost/spirit/home/x3/operator/list.hpp index 6484e7108a..e6f7e2a6c1 100644 --- a/include/boost/spirit/home/x3/operator/list.hpp +++ b/include/boost/spirit/home/x3/operator/list.hpp @@ -1,6 +1,8 @@ /*============================================================================= Copyright (c) 2001-2014 Joel de Guzman Copyright (c) 2001-2011 Hartmut Kaiser + Copyright (c) 2017 wanghan02 + Copyright (c) 2024 Nana Sakisaka 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) @@ -11,6 +13,7 @@ #include #include #include +#include #include namespace boost { namespace spirit { namespace x3 @@ -42,7 +45,7 @@ namespace boost { namespace spirit { namespace x3 first = iter; } - return true; + return !has_expectation_failure(context); } }; diff --git a/include/boost/spirit/home/x3/operator/not_predicate.hpp b/include/boost/spirit/home/x3/operator/not_predicate.hpp index 071ebd682e..b43f40cb30 100644 --- a/include/boost/spirit/home/x3/operator/not_predicate.hpp +++ b/include/boost/spirit/home/x3/operator/not_predicate.hpp @@ -1,5 +1,7 @@ /*============================================================================= Copyright (c) 2001-2014 Joel de Guzman + Copyright (c) 2017 wanghan02 + Copyright (c) 2024 Nana Sakisaka 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) @@ -8,6 +10,7 @@ #define BOOST_SPIRIT_X3_NOT_PREDICATE_MARCH_23_2007_0618PM #include +#include namespace boost { namespace spirit { namespace x3 { @@ -28,7 +31,8 @@ namespace boost { namespace spirit { namespace x3 , Context const& context, RContext& rcontext, Attribute& /*attr*/) const { Iterator i = first; - return !this->subject.parse(i, last, context, rcontext, unused); + return !this->subject.parse(i, last, context, rcontext, unused) + && !has_expectation_failure(context); } }; diff --git a/include/boost/spirit/home/x3/operator/optional.hpp b/include/boost/spirit/home/x3/operator/optional.hpp index 257fd78cfb..3c7e2e0997 100644 --- a/include/boost/spirit/home/x3/operator/optional.hpp +++ b/include/boost/spirit/home/x3/operator/optional.hpp @@ -1,6 +1,8 @@ /*============================================================================= Copyright (c) 2001-2014 Joel de Guzman Copyright (c) 2001-2011 Hartmut Kaiser + Copyright (c) 2017 wanghan02 + Copyright (c) 2024 Nana Sakisaka 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) @@ -10,6 +12,7 @@ #include #include +#include #include #include #include @@ -38,7 +41,7 @@ namespace boost { namespace spirit { namespace x3 { detail::parse_into_container( this->subject, first, last, context, rcontext, attr); - return true; + return !has_expectation_failure(context); } // Attribute is an optional @@ -59,6 +62,9 @@ namespace boost { namespace spirit { namespace x3 { // assign the parsed value into our attribute x3::traits::move_to(val, attr); + + } else { + return !has_expectation_failure(context); } return true; } diff --git a/include/boost/spirit/home/x3/operator/plus.hpp b/include/boost/spirit/home/x3/operator/plus.hpp index 6227d6a6fa..7ceb23acd1 100644 --- a/include/boost/spirit/home/x3/operator/plus.hpp +++ b/include/boost/spirit/home/x3/operator/plus.hpp @@ -1,6 +1,8 @@ /*============================================================================= Copyright (c) 2001-2014 Joel de Guzman Copyright (c) 2001-2011 Hartmut Kaiser + Copyright (c) 2017 wanghan02 + Copyright (c) 2024 Nana Sakisaka 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) @@ -9,6 +11,7 @@ #define BOOST_SPIRIT_X3_PLUS_MARCH_13_2007_0127PM #include +#include #include #include #include @@ -36,7 +39,7 @@ namespace boost { namespace spirit { namespace x3 while (detail::parse_into_container( this->subject, first, last, context, rcontext, attr)) ; - return true; + return !has_expectation_failure(context); } }; diff --git a/include/boost/spirit/home/x3/operator/sequence.hpp b/include/boost/spirit/home/x3/operator/sequence.hpp index 6557827d6b..25fd4e8e2c 100644 --- a/include/boost/spirit/home/x3/operator/sequence.hpp +++ b/include/boost/spirit/home/x3/operator/sequence.hpp @@ -1,5 +1,7 @@ /*============================================================================= Copyright (c) 2001-2014 Joel de Guzman + Copyright (c) 2017 wanghan02 + Copyright (c) 2024 Nana Sakisaka 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) @@ -8,6 +10,7 @@ #define BOOST_SPIRIT_X3_SEQUENCE_JAN_06_2013_1015AM #include +#include #include #include #include @@ -29,10 +32,20 @@ namespace boost { namespace spirit { namespace x3 Iterator& first, Iterator const& last , Context const& context, RContext& rcontext, unused_type) const { - Iterator save = first; + Iterator const save = first; + if (this->left.parse(first, last, context, rcontext, unused) && this->right.parse(first, last, context, rcontext, unused)) return true; + + #if !BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE + if (has_expectation_failure(context)) + { + // don't rollback iterator (mimicking exception-like behavior) + return false; + } + #endif + first = save; return false; } diff --git a/include/boost/spirit/home/x3/support/expectation.hpp b/include/boost/spirit/home/x3/support/expectation.hpp new file mode 100644 index 0000000000..fbcef06f0c --- /dev/null +++ b/include/boost/spirit/home/x3/support/expectation.hpp @@ -0,0 +1,397 @@ +/*============================================================================= + Copyright (c) 2017 wanghan02 + Copyright (c) 2024 Nana Sakisaka + + 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(BOOST_SPIRIT_X3_SUPPORT_EXPECTATION_HPP) +#define BOOST_SPIRIT_X3_SUPPORT_EXPECTATION_HPP + +#if !defined(BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE) +# define BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE 1 +#endif + +#include // for BOOST_SYMBOL_VISIBLE, BOOST_ATTRIBUTE_NODISCARD +#include +#include +#include + +// We utilize `x3::traits::build_optional<...>` for customization point +// instead of directly wrapping `expectation_failure` with `boost::optional`. +// This would make it possible for the user to eliminate the usages of +// `boost::optional`, and use `std::optional` everywhere. +// +// Note that we are intentionally including this header regardless of +// the value of BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE, since the +// helper types defined in non-throwing version might still be required +// when the users benchmark their application just by switching the +// macro while keeping their implementation unmodified. +// +// This will make it possible for the users to unconditionally +// inject `x3::expectation_failure_optional` into their parser, +// safely assuming that the value is no-op in throwing mode. +#include + +// This is required for partial specialization of relevant helpers. +// TODO: Add a macro to discard all #includes of . +// (this is TODO because it requires changes in `optional_traits.hpp`.) +#include +#include + +#if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE // throwing mode +# define BOOST_SPIRIT_X3_EXPECTATION_FAILURE_API BOOST_SYMBOL_VISIBLE +# define BOOST_SPIRIT_X3_EXPECTATION_FAILURE_BASE : std::runtime_error +# include +# include + +#else // non-throwing mode +# define BOOST_SPIRIT_X3_EXPECTATION_FAILURE_API +# define BOOST_SPIRIT_X3_EXPECTATION_FAILURE_BASE +#endif + +#include +#include + + +namespace boost { namespace spirit { namespace x3 +{ + struct expectation_failure_tag; + + template + struct BOOST_SPIRIT_X3_EXPECTATION_FAILURE_API + expectation_failure BOOST_SPIRIT_X3_EXPECTATION_FAILURE_BASE + { + public: + expectation_failure(Iterator where, std::string const& which) + #if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE + : std::runtime_error("boost::spirit::x3::expectation_failure"), + #else + : + #endif + where_(where), which_(which) + {} + + BOOST_ATTRIBUTE_NODISCARD + constexpr Iterator const& where() const noexcept { return where_; } + + BOOST_ATTRIBUTE_NODISCARD + constexpr std::string const& which() const noexcept { return which_; } + + private: + Iterator where_; + std::string which_; + }; + + template + using expectation_failure_t = std::remove_cv_t(std::declval()))>>; + + template + using expectation_failure_optional = + typename traits::build_optional>::type; + + + // x3::where(x), x3::which(x) + // Convenient accessors for absorbing the variation of + // optional/reference_wrapper interfaces. Usage: +#if 0 + struct my_error_dispatcher + { + // Note that `Exception` can be any of + // - C++ exception (i.e. `expectation_failure : std::runtime_error`) + // - Optional (i.e. `optional>`) + // - Reference to optional (i.e. `reference_wrapper>>`) + + template + x3::error_handler_result on_error( + Iterator const& first, Iterator const& last, + Exception const& x, Context const& context + ) { + auto& my_error_handler = x3::get(context).get(); + my_error_handler.print("Error! Expecting " + x3::which(x) + " here:", x3::where(x)); + } + }; +#endif + + // beware ADL - we should avoid overgeneralization here. + + namespace expectation_failure_helpers + { + // bare type + template + BOOST_ATTRIBUTE_NODISCARD + constexpr decltype(auto) where(expectation_failure const& failure) noexcept { return failure.where(); } + + template + BOOST_ATTRIBUTE_NODISCARD + constexpr decltype(auto) which(expectation_failure const& failure) noexcept { return failure.which(); } + + // std::optional + template + BOOST_ATTRIBUTE_NODISCARD + constexpr decltype(auto) where(std::optional> const& failure) noexcept { return failure->where(); } + + template + BOOST_ATTRIBUTE_NODISCARD + constexpr decltype(auto) which(std::optional> const& failure) noexcept { return failure->which(); } + + // boost::optional + template + BOOST_ATTRIBUTE_NODISCARD + constexpr decltype(auto) where(boost::optional> const& failure) noexcept { return failure->where(); } + + template + BOOST_ATTRIBUTE_NODISCARD + constexpr decltype(auto) which(boost::optional> const& failure) noexcept { return failure->which(); } + + // std::optional + std::reference_wrapper + template + BOOST_ATTRIBUTE_NODISCARD + constexpr decltype(auto) where(std::reference_wrapper>> const& failure) noexcept { return failure.get()->where(); } + + template + BOOST_ATTRIBUTE_NODISCARD + constexpr decltype(auto) which(std::reference_wrapper>> const& failure) noexcept { return failure.get()->which(); } + + // boost::optional + std::reference_wrapper + template + BOOST_ATTRIBUTE_NODISCARD + constexpr decltype(auto) where(std::reference_wrapper>> const& failure) noexcept { return failure.get()->where(); } + + template + BOOST_ATTRIBUTE_NODISCARD + constexpr decltype(auto) which(std::reference_wrapper>> const& failure) noexcept { return failure.get()->which(); } + } // expectation_failure_helpers + + using expectation_failure_helpers::where; + using expectation_failure_helpers::which; + + +#if !BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE + namespace detail { + inline constexpr bool has_expectation_failure_impl(unused_type const&) noexcept = delete; + + inline constexpr bool has_expectation_failure_impl(bool& failure) noexcept { + return failure; + } + + template + constexpr bool has_expectation_failure_impl(std::optional> const& failure) noexcept + { + return failure.has_value(); + } + + template + constexpr bool has_expectation_failure_impl(boost::optional> const& failure) noexcept + { + return failure.has_value(); + } + + template + constexpr bool has_expectation_failure_impl(std::reference_wrapper const& ref) noexcept + { + return has_expectation_failure_impl(ref.get()); + } + + + template + constexpr void set_expectation_failure_impl(bool& failure, T&& value) + { + failure = std::forward(value); + } + + template + constexpr void set_expectation_failure_impl(std::optional>& failure, T&& value) + { + failure = std::forward(value); + } + + template + constexpr void set_expectation_failure_impl(boost::optional>& failure, T&& value) + { + failure = std::forward(value); + } + + template + constexpr void set_expectation_failure_impl(std::reference_wrapper& failure, T&& value) + { + set_expectation_failure_impl(failure.get(), std::forward(value)); + } + + + template + constexpr void clear_expectation_failure_impl(unused_type const&) noexcept = delete; + + template + constexpr void clear_expectation_failure_impl(bool& failure) noexcept + { + failure = false; + } + + template + constexpr void clear_expectation_failure_impl(std::optional>& failure) noexcept + { + failure.reset(); + } + + template + constexpr void clear_expectation_failure_impl(boost::optional>& failure) noexcept + { + failure.reset(); + } + + template + constexpr void clear_expectation_failure_impl(std::reference_wrapper& ref) noexcept + { + return clear_expectation_failure_impl(ref.get()); + } + } +#endif + + template + BOOST_ATTRIBUTE_NODISCARD + constexpr bool has_expectation_failure(Context const& context) noexcept { + #if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE + boost::ignore_unused(context); + return false; + #else + using T = expectation_failure_t; + static_assert( + !std::is_same_v, + "Context type was not specified for x3::expectation_failure_tag. " + "You probably forgot: `x3::with(failure)[p]`. " + "Note that you must also bind the context to your skipper." + ); + return detail::has_expectation_failure_impl( + x3::get(context)); + #endif + } + + // + // Creation of a brand new expectation_failure instance. + // This is the primary overload. + // + template + constexpr void set_expectation_failure( + Iterator const& where, + Subject const& subject, + Context const& context + ) { + #if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE + boost::ignore_unused(where, subject, context); + + #else + using T = expectation_failure_t; + static_assert( + !std::is_same_v, + "Context type was not specified for x3::expectation_failure_tag. " + "You probably forgot: `x3::with(failure)[p]`. " + "Note that you must also bind the context to your skipper." + ); + + if constexpr (std::is_same_v) + { + boost::ignore_unused(where, subject); + detail::set_expectation_failure_impl( + x3::get(context), + true); + } + else + { + detail::set_expectation_failure_impl( + x3::get(context), + expectation_failure(where, what(subject))); + } + #endif + } + + // + // Copy-assignment of existing expectation_failure instance. + // + // When you're in the situation where this functionality is + // *really* needed, it essentially means that you have + // multiple valid exceptions at the same time. + // + // There are only two decent situations that I can think of: + // + // (a) When you are writing a custom parser procedure with very specific characteristics: + // 1. You're forking a context. + // 2. Your parser class has delegated some process to child parser(s). + // 3. The child parser(s) have raised an exceptation_failure. + // 4. You need to propagate the failure back to the parent context. + // + // (b) When you truly need a nested exception. + // That is, you're trying to preserve a nested exception structure + // raised by nested directive: e.g. `x3::expect[x3::expect[p]]`. + // Note that all builtin primitives just save the first error, + // so this structure does not exist in core (as of now). + // + template + constexpr void set_expectation_failure( + AnyExpectationFailure const& existing_failure, + Context const& context + ) { + #if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE + boost::ignore_unused(existing_failure, context); + + #else + using T = expectation_failure_t; + static_assert( + !std::is_same_v, + "Context type was not specified for x3::expectation_failure_tag. " + "You probably forgot: `x3::with(failure)[p]`. " + "Note that you must also bind the context to your skipper." + ); + + static_assert( + std::is_assignable_v, + "previous/current expectation failure types should be compatible" + ); + + detail::set_expectation_failure_impl( + x3::get(context), existing_failure); + #endif + } + +#if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE + template + constexpr decltype(auto) get_expectation_failure(Context const&) = delete; + +#else + template + BOOST_ATTRIBUTE_NODISCARD + constexpr decltype(auto) get_expectation_failure(Context const& context) + { + using T = expectation_failure_t; + static_assert( + !std::is_same_v, + "Context type was not specified for x3::expectation_failure_tag. " + "You probably forgot: `x3::with(failure)[p]`. " + "Note that you must also bind the context to your skipper." + ); + + return x3::get(context); + } +#endif + + template + constexpr void clear_expectation_failure(Context const& context) noexcept + { + #if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE + boost::ignore_unused(context); + #else + using T = expectation_failure_t; + static_assert( + !std::is_same_v, + "Context type was not specified for x3::expectation_failure_tag. " + "You probably forgot: `x3::with(failure)[p]`. " + "Note that you must also bind the context to your skipper." + ); + detail::clear_expectation_failure_impl( + x3::get(context)); + #endif + } +}}} + +#endif diff --git a/test/x3/Jamfile b/test/x3/Jamfile index 9c8e80bda3..e5434e7a8f 100644 --- a/test/x3/Jamfile +++ b/test/x3/Jamfile @@ -78,7 +78,8 @@ run difference.cpp ; run eoi.cpp ; run eol.cpp ; run eps.cpp ; -run expect.cpp ; +run expect_throw.cpp ; +run expect_nothrow.cpp ; run extract_int.cpp ; run int1.cpp ; run kleene.cpp ; diff --git a/test/x3/expect.cpp b/test/x3/expect.cpp deleted file mode 100644 index acd202830d..0000000000 --- a/test/x3/expect.cpp +++ /dev/null @@ -1,153 +0,0 @@ -/*============================================================================= - Copyright (c) 2001-2013 Joel de Guzman - - 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 "test.hpp" - -int -main() -{ - using namespace boost::spirit; - using namespace boost::spirit::x3::ascii; - using boost::spirit::x3::lit; - using boost::spirit::x3::expect; - using spirit_test::test; - using spirit_test::test_attr; - using boost::spirit::x3::expectation_failure; - - BOOST_SPIRIT_ASSERT_CONSTEXPR_CTORS(expect['x']); - BOOST_SPIRIT_ASSERT_CONSTEXPR_CTORS(char_ > char_); - - { - try - { - BOOST_TEST((test("aa", char_ >> expect[char_]))); - BOOST_TEST((test("aaa", char_ >> expect[char_ >> char_('a')]))); - BOOST_TEST((test("xi", char_('x') >> expect[char_('i')]))); - BOOST_TEST((!test("xi", char_('y') >> expect[char_('o')]))); // should not throw! - BOOST_TEST((test("xin", char_('x') >> expect[char_('i') >> char_('n')]))); - BOOST_TEST((!test("xi", char_('x') >> expect[char_('o')]))); - } - catch (expectation_failure const& x) - { - BOOST_TEST_CSTR_EQ(x.which().c_str(), "'o'"); - BOOST_TEST_CSTR_EQ(x.where(), "i"); - } - } - - { - try - { - BOOST_TEST((test("aa", char_ > char_))); - BOOST_TEST((test("aaa", char_ > char_ > char_('a')))); - BOOST_TEST((test("xi", char_('x') > char_('i')))); - BOOST_TEST((!test("xi", char_('y') > char_('o')))); // should not throw! - BOOST_TEST((test("xin", char_('x') > char_('i') > char_('n')))); - BOOST_TEST((!test("xi", char_('x') > char_('o')))); - } - catch (expectation_failure const& x) - { - BOOST_TEST_CSTR_EQ(x.which().c_str(), "'o'"); - BOOST_TEST_CSTR_EQ(x.where(), "i"); - } - } - - { - try - { - BOOST_TEST((!test("ay:a", char_ > char_('x') >> ':' > 'a'))); - } - catch (expectation_failure const& x) - { -#ifndef BOOST_SPIRIT_X3_NO_RTTI - BOOST_TEST(x.which().find("sequence") != std::string::npos); -#else - BOOST_TEST_CSTR_EQ(x.which().c_str(), "undefined"); -#endif - BOOST_TEST_CSTR_EQ(x.where(), "y:a"); - } - } - -#if defined(BOOST_CLANG) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Woverloaded-shift-op-parentheses" -#endif - { // Test that attributes with > (sequences) work just like >> (sequences) - - using boost::fusion::vector; - using boost::fusion::at_c; - - { - vector attr; - BOOST_TEST((test_attr(" a\n b\n c", - char_ > char_ > char_, attr, space))); - BOOST_TEST((at_c<0>(attr) == 'a')); - BOOST_TEST((at_c<1>(attr) == 'b')); - BOOST_TEST((at_c<2>(attr) == 'c')); - } - - { - vector attr; - BOOST_TEST((test_attr(" a\n b\n c", - char_ > char_ >> char_, attr, space))); - BOOST_TEST((at_c<0>(attr) == 'a')); - BOOST_TEST((at_c<1>(attr) == 'b')); - BOOST_TEST((at_c<2>(attr) == 'c')); - } - - { - vector attr; - BOOST_TEST((test_attr(" a, b, c", - char_ >> ',' > char_ >> ',' > char_, attr, space))); - BOOST_TEST((at_c<0>(attr) == 'a')); - BOOST_TEST((at_c<1>(attr) == 'b')); - BOOST_TEST((at_c<2>(attr) == 'c')); - } - - { - std::string attr; - BOOST_TEST((test_attr("'azaaz'", - "'" > *(char_("a") | char_("z")) > "'", attr, space))); - BOOST_TEST(attr == "azaaz"); - } - } -#if defined(BOOST_CLANG) -#pragma clang diagnostic pop -#endif - - { - try - { - BOOST_TEST((test(" a a", char_ > char_, space))); - BOOST_TEST((test(" x i", char_('x') > char_('i'), space))); - BOOST_TEST((!test(" x i", char_('x') > char_('o'), space))); - } - catch (expectation_failure const& x) - { - BOOST_TEST_CSTR_EQ(x.which().c_str(), "'o'"); - BOOST_TEST_CSTR_EQ(x.where(), "i"); - } - } - - { - try - { - BOOST_TEST((test("bar", expect[lit("foo")]))); - } - catch (expectation_failure const& x) - { - BOOST_TEST_CSTR_EQ(x.which().c_str(), "\"foo\""); - BOOST_TEST_CSTR_EQ(x.where(), "bar"); - } - } - - return boost::report_errors(); -} diff --git a/test/x3/expect.ipp b/test/x3/expect.ipp new file mode 100644 index 0000000000..9398482161 --- /dev/null +++ b/test/x3/expect.ipp @@ -0,0 +1,798 @@ +/*============================================================================= + Copyright (c) 2001-2013 Joel de Guzman + Copyright (c) 2017 wanghan02 + Copyright (c) 2024 Nana Sakisaka + + 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 +#include +#include +#include +#include +#include + +#include "test.hpp" + +namespace x3 = boost::spirit::x3; + + +#if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE +# define TEST_SUCCESS_IMPL(tester, ...) \ + BOOST_TEST_NO_THROW({ \ + BOOST_TEST(tester(__VA_ARGS__)); \ + }) + +# define TEST_FAILURE_IMPL_4(tester, input, parser, catch_stmt) \ + BOOST_TEST_THROWS( \ + { \ + try \ + { \ + BOOST_TEST(tester(input, parser)); \ + } \ + catch (expectation_failure const& x) \ + { \ + catch_stmt \ + throw; \ + } \ + }, \ + expectation_failure \ + ) + +# define TEST_FAILURE_IMPL_5(tester, input, parser, arg0, catch_stmt) \ + BOOST_TEST_THROWS( \ + { \ + try \ + { \ + BOOST_TEST(tester(input, parser, arg0)); \ + } \ + catch (expectation_failure const& x) \ + { \ + catch_stmt \ + throw; \ + } \ + }, \ + expectation_failure \ + ) + +# define TEST_FAILURE_IMPL_6(tester, input, parser, arg0, arg1, catch_stmt) \ + BOOST_TEST_THROWS( \ + { \ + try \ + { \ + BOOST_TEST(tester(input, parser, arg0, arg1)); \ + } \ + catch (expectation_failure const& x) \ + { \ + catch_stmt \ + throw; \ + } \ + }, \ + expectation_failure \ + ) + +#else + +using expectation_failure_optional_t = x3::expectation_failure_optional; + +namespace detail +{ + template >> + decltype(auto) wrap_with_expectation_failure(expectation_failure_optional_t&, T& attr) + { + return attr; + } + + template + auto wrap_with_expectation_failure(expectation_failure_optional_t& xopt, T const& skipper) + { + return x3::with(xopt)[skipper]; + } +} // detail + +# define TEST_SUCCESS_IMPL_3(tester, input, parser) \ + BOOST_TEST_NO_THROW({ \ + expectation_failure_optional_t xopt; \ + BOOST_TEST(tester(input, with(xopt)[parser])); \ + BOOST_TEST(!xopt.has_value()); \ + }) + +# define TEST_SUCCESS_IMPL_4(tester, input, parser, arg0) \ + BOOST_TEST_NO_THROW({ \ + expectation_failure_optional_t xopt; \ + BOOST_TEST(tester( \ + input, \ + with(xopt)[parser], \ + detail::wrap_with_expectation_failure(xopt, arg0) \ + )); \ + BOOST_TEST(!xopt.has_value()); \ + }) + +# define TEST_SUCCESS_IMPL_5(tester, input, parser, arg0, arg1) \ + BOOST_TEST_NO_THROW({ \ + expectation_failure_optional_t xopt; \ + BOOST_TEST(tester( \ + input, \ + with(xopt)[parser], \ + detail::wrap_with_expectation_failure(xopt, arg0), \ + detail::wrap_with_expectation_failure(xopt, arg1) \ + )); \ + BOOST_TEST(!xopt.has_value()); \ + }) + +# define TEST_SUCCESS_IMPL(...) BOOST_PP_EXPAND(BOOST_PP_OVERLOAD(TEST_SUCCESS_IMPL_, __VA_ARGS__) (__VA_ARGS__)) + + +# define TEST_FAILURE_IMPL_4(tester, input, parser, catch_stmt) \ + BOOST_TEST_NO_THROW( \ + { \ + expectation_failure_optional_t xopt; \ + do \ + { \ + if (!BOOST_TEST(tester(input, with(xopt)[parser]))) break; \ + if (!BOOST_TEST(xopt.has_value())) break; \ + auto const& x = *xopt; \ + catch_stmt \ + } while (false); \ + } \ + ) + +# define TEST_FAILURE_IMPL_5(tester, input, parser, arg0, catch_stmt) \ + BOOST_TEST_NO_THROW( \ + { \ + expectation_failure_optional_t xopt; \ + do \ + { \ + if (!BOOST_TEST(tester( \ + input, \ + with(xopt)[parser], \ + detail::wrap_with_expectation_failure(xopt, arg0))) \ + ) break; \ + \ + if (!BOOST_TEST(xopt.has_value())) break; \ + auto const& x = *xopt; \ + catch_stmt \ + } while (false); \ + } \ + ) + +# define TEST_FAILURE_IMPL_6(tester, input, parser, arg0, arg1, catch_stmt) \ + BOOST_TEST_NO_THROW( \ + { \ + expectation_failure_optional_t xopt; \ + do \ + { \ + if (!BOOST_TEST(tester( \ + input, \ + with(xopt)[parser], \ + detail::wrap_with_expectation_failure(xopt, arg0), \ + detail::wrap_with_expectation_failure(xopt, arg1))) \ + ) break; \ + \ + if (!BOOST_TEST(xopt.has_value())) break; \ + auto const& x = *xopt; \ + catch_stmt \ + } while (false); \ + } \ + ) +#endif + +#define TEST_FAILURE_IMPL(...) BOOST_PP_EXPAND(BOOST_PP_OVERLOAD(TEST_FAILURE_IMPL_, __VA_ARGS__) (__VA_ARGS__)) + +// Comments below are intentionally written verbosely +// to provide human-friendly intellisense tooltip for testers + +// expect_throw: parser = ok, exception = none +// expect_nothrow: parser = ok, exception = none, expectation_failure = none +#define TEST_SUCCESS_PASS(...) TEST_SUCCESS_IMPL(test, __VA_ARGS__) + +// expect_throw: parser = ok, exception = none +// expect_nothrow: parser = ok, exception = none, expectation_failure = none +#define TEST_ATTR_SUCCESS_PASS(...) TEST_SUCCESS_IMPL(test_attr, __VA_ARGS__) + +// expect_throw: parser = fail, exception = none +// expect_nothrow: parser = fail, exception = none, expectation_failure = none +#define TEST_SUCCESS_FAIL(...) TEST_SUCCESS_IMPL(!test, __VA_ARGS__) + +// expect_throw: parser = fail, exception = none +// expect_nothrow: parser = fail, exception = none, expectation_failure = none +#define TEST_ATTR_SUCCESS_FAIL(...) TEST_SUCCESS_IMPL(!test_attr, __VA_ARGS__) + +// expect_throw: parser = fail, exception = thrown +// expect_nothrow: parser = fail, exception = none, expectation_failure = yes +#define TEST_FAILURE(...) TEST_FAILURE_IMPL(!test, __VA_ARGS__) + +// expect_throw: parser = fail, exception = thrown +// expect_nothrow: parser = fail, exception = none, expectation_failure = yes +#define TEST_ATTR_FAILURE(...) TEST_FAILURE_IMPL(!test_attr, __VA_ARGS__) + + +// For testers; development QOL purpose only. + +#define DEBUG_PRINT(x) \ + do { \ + std::cout << "----------------------------------\n"; \ + std::cout << "which: " << x.which() << "\n"; \ + std::cout << "where: " << x.where() << "\n"; \ + std::cout << "----------------------------------\n"; \ + BOOST_TEST(!"remove DEBUG_PRINT before commit!"); \ + } while (false) + + +int +main() +{ + using namespace std::string_view_literals; + + using x3::standard::alpha; + using x3::standard::digit; + using x3::standard::space; + using x3::standard::blank; + using x3::standard::char_; + using x3::standard::string; + using x3::standard::lit; + + // using x3::lit; + using x3::expect; + using x3::lexeme; + using x3::no_case; + using x3::no_skip; + using x3::omit; + using x3::raw; + using x3::skip; + using x3::seek; + using x3::repeat; + using x3::matches; + using x3::eps; + using x3::eoi; + using x3::eol; + using x3::attr; + using x3::dword; + using x3::int_; + using x3::symbols; + using x3::confix; + using x3::with; + using x3::expectation_failure; + using x3::expectation_failure_tag; + + using boost::fusion::vector; + using boost::fusion::at_c; + + using spirit_test::test; + using spirit_test::test_attr; + + BOOST_SPIRIT_ASSERT_CONSTEXPR_CTORS(expect['x']); + BOOST_SPIRIT_ASSERT_CONSTEXPR_CTORS(char_ > char_); + + { + TEST_SUCCESS_PASS("aa", char_ >> expect[char_]); + TEST_SUCCESS_PASS("aaa", char_ >> expect[char_ >> char_('a')]); + TEST_SUCCESS_PASS("xi", char_('x') >> expect[char_('i')]); + TEST_SUCCESS_FAIL("xi", char_('y') >> expect[char_('o')]); // should not throw! + TEST_SUCCESS_PASS("xin", char_('x') >> expect[char_('i') >> char_('n')]); + + TEST_FAILURE("xi", char_('x') >> expect[char_('o')], { + BOOST_TEST(x.which() == "'o'"sv); + BOOST_TEST(x.where() == "i"sv); + }); + } + + { + TEST_SUCCESS_PASS("aa", char_ > char_); + TEST_SUCCESS_PASS("aaa", char_ > char_ > char_('a')); + TEST_SUCCESS_PASS("xi", char_('x') > char_('i')); + TEST_SUCCESS_FAIL("xi", char_('y') > char_('o')); // should not throw! + TEST_SUCCESS_PASS("xin", char_('x') > char_('i') > char_('n')); + + TEST_FAILURE("xi", char_('x') > char_('o'), + { + BOOST_TEST(x.which() == "'o'"sv); + BOOST_TEST(x.where() == "i"sv); + }); + } + + { + #ifndef BOOST_SPIRIT_X3_NO_RTTI + TEST_FAILURE("ay:a", char_ > char_('x') >> ':' > 'a', + { + BOOST_TEST(x.which().find("sequence") != std::string::npos); + BOOST_TEST(x.where() == "y:a"sv); + }); + #else + TEST_FAILURE("ay:a", char_ > char_('x') >> ':' > 'a', + { + BOOST_TEST(x.which() == "undefined"sv); + BOOST_TEST(x.where() == "y:a"sv); + }); + #endif + } + +#if defined(BOOST_CLANG) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Woverloaded-shift-op-parentheses" +#endif + + // Test that attributes with > (sequences) work just like >> (sequences) + { + vector attr; + TEST_ATTR_SUCCESS_PASS(" a\n b\n c", char_ > char_ > char_, attr, space); + BOOST_TEST((at_c<0>(attr) == 'a')); + BOOST_TEST((at_c<1>(attr) == 'b')); + BOOST_TEST((at_c<2>(attr) == 'c')); + } + + { + vector attr; + TEST_ATTR_SUCCESS_PASS(" a\n b\n c", char_ > char_ >> char_, attr, space); + BOOST_TEST((at_c<0>(attr) == 'a')); + BOOST_TEST((at_c<1>(attr) == 'b')); + BOOST_TEST((at_c<2>(attr) == 'c')); + } + + { + vector attr; + TEST_ATTR_SUCCESS_PASS(" a, b, c", char_ >> ',' > char_ >> ',' > char_, attr, space); + BOOST_TEST((at_c<0>(attr) == 'a')); + BOOST_TEST((at_c<1>(attr) == 'b')); + BOOST_TEST((at_c<2>(attr) == 'c')); + } + + { + std::string attr; + TEST_ATTR_SUCCESS_PASS("'azaaz'", "'" > *(char_("a") | char_("z")) > "'", attr, space); + BOOST_TEST(attr == "azaaz"); + } + +#if defined(BOOST_CLANG) +#pragma clang diagnostic pop +#endif + + { + TEST_SUCCESS_PASS(" a a", char_ > char_, space); + TEST_SUCCESS_PASS(" x i", char_('x') > char_('i'), space); + + TEST_FAILURE(" x i", char_('x') > char_('o'), space, { + BOOST_TEST(x.which() == "'o'"sv); + BOOST_TEST(x.where() == "i"sv); + }); + } + + { + TEST_FAILURE("bar", expect[lit("foo")], + { + BOOST_TEST(x.which() == "\"foo\""sv); + BOOST_TEST(x.where() == "bar"sv); + }); + } + + + // skipper + { + TEST_FAILURE("accb", repeat(7)[alpha], (lit('a') > 'b') | (lit('c') > 'd'), { + BOOST_TEST(x.which() == "'b'"sv); + BOOST_TEST(x.where() == "ccb"sv); + }); + } + + // + // ********* Developers note ********** + // + // As of now (see `git blame`), get_info is still not + // specialized for many of the X3 parsers so that the + // value of `expectation_failure<...>::which()` will be + // implementation-defined demangled string. + // Therefore it's essentially impossible to test them + // right now; further work must be done. + // + // Some specific situations are already been reported + // (e.g. https://github.com/boostorg/spirit/issues/777) + // but we really need to implement all specializations for + // X3's predefined parsers, not just the one reported above. + // + + + // sanity check: test expectation_failure propagation + // on custom skippers + { + TEST_SUCCESS_PASS("a..b", lit('a') >> 'b', lit('.') >> '.'); + TEST_SUCCESS_FAIL("a..b", lit('a') >> 'b', lit('.') >> 'z'); + + TEST_SUCCESS_PASS("a b", lit('a') >> 'b', blank); + TEST_SUCCESS_PASS("a..b", lit('a') >> 'b', +lit('.')); + TEST_SUCCESS_PASS("a..b", lit('a') >> 'b', lit('.') >> '.'); + + // if this test fails, there might be a problem in x3::skip_over + TEST_FAILURE ("a..b", lit('a') >> 'b', lit('.') >> expect[lit('z')], { + BOOST_TEST(x.which() == "'z'"sv); + BOOST_TEST(x.where() == ".b"sv); + }); + + // --------------------------------------------------------- + // skip(...) version of the code above + // we must test against semantically identical cases! + + TEST_SUCCESS_PASS("a..b", skip(lit('.') >> '.')[lit('a') >> 'b']); + TEST_SUCCESS_FAIL("a..b", skip(lit('.') >> 'z')[lit('a') >> 'b']); + + TEST_SUCCESS_PASS("a b", skip(blank)[lit('a') >> 'b']); + TEST_SUCCESS_PASS("a..b", skip(+lit('.'))[lit('a') >> 'b']); + TEST_SUCCESS_PASS("a..b", skip(lit('.') >> '.')[lit('a') >> 'b']); + + // if this test fails, there might be a problem in x3::skip_over and/or x3::skip_directive + TEST_FAILURE ("a..b", skip(lit('.') >> expect[lit('z')])[lit('a') >> 'b'], { + BOOST_TEST(x.which() == "'z'"sv); + BOOST_TEST(x.where() == ".b"sv); + }); + } + + // sanity check; test `post_skip` in `x3::phrase_parse(...)` + { + TEST_SUCCESS_PASS("a..b..", lit('a') >> 'b', lit('.') >> '.'); + TEST_SUCCESS_FAIL("a..b..", lit('a') >> 'z', lit('.') >> '.'); + TEST_SUCCESS_FAIL("a..b..", lit('a') >> 'b', lit('.') >> 'z'); + + // should fail in `post_skip` + TEST_SUCCESS_FAIL("a..b.z", lit('a') >> 'b', lit('.') >> '.'); + + // if this test fails, x3::skip_over is BUGGED when `post_skip` is run + TEST_FAILURE("a..b.z", lit('a') >> 'b', lit('.') > '.', { + BOOST_TEST(x.which() == "'.'"sv); + BOOST_TEST(x.where() == "z"sv); + }); + } + + // sequence + { + TEST_SUCCESS_PASS("ab", lit('a') >> 'b'); + TEST_SUCCESS_FAIL("ac", lit('a') >> 'b'); + + TEST_FAILURE("ac", lit('a') >> expect[lit('b')], { + BOOST_TEST(x.which() == "'b'"sv); + BOOST_TEST(x.where() == "c"sv); + }); + TEST_FAILURE("ac", lit('a') > lit('b'), { + BOOST_TEST(x.which() == "'b'"sv); + BOOST_TEST(x.where() == "c"sv); + }); + } + + // auxilary parsers + { + TEST_SUCCESS_PASS("a12", lit('a') > eps > +digit); + TEST_SUCCESS_PASS("a12", lit('a') > +digit > eoi); + TEST_SUCCESS_PASS("a12\n", lit('a') > +digit > eol); + + TEST_FAILURE("a12", lit('a') > eps(false) > +digit, { + BOOST_TEST(x.where() == "12"sv); + }); + TEST_FAILURE("a12", lit('a') > eoi > +digit, { + BOOST_TEST(x.where() == "12"sv); + }); + TEST_FAILURE("a12\n", lit('a') > eol > +digit, { + BOOST_TEST(x.where() == "12\n"sv); + }); + + int n = 0; + TEST_ATTR_SUCCESS_PASS("abc", lit("abc") > attr(12) > eoi, n); + BOOST_TEST(n == 12); + } + + // binary, numeric, char, string parsers + { + TEST_SUCCESS_PASS("12abcd", +digit > dword); + TEST_SUCCESS_PASS("abc12", +alpha > int_); + TEST_SUCCESS_PASS("12a", +digit > lit('a')); + + TEST_FAILURE("12abc", +digit > dword, { + BOOST_TEST(x.where() == "abc"sv); + }); + TEST_FAILURE("abc", +alpha > int_, { + BOOST_TEST(x.where() == ""sv); + }); + TEST_FAILURE("12a", +digit > lit('b'), { + BOOST_TEST(x.which() == "'b'"sv); + BOOST_TEST(x.where() == "a"sv); + }); + + symbols<> s; + s.add("cat"); + TEST_SUCCESS_PASS("12cat", +digit > s); + TEST_FAILURE("12dog", +digit > s, { + BOOST_TEST(x.where() == "dog"sv); + }); + } + + // confix + { + TEST_SUCCESS_PASS("[12cat]", confix('[', ']')[+digit > lit("cat")]); + TEST_FAILURE("[12dog]", confix('[', ']')[+digit > lit("cat")], { + BOOST_TEST(x.which() == "\"cat\""sv); + BOOST_TEST(x.where() == "dog]"sv); + }); + } + + // expect + { + TEST_SUCCESS_PASS("abc", lit('a') >> expect[lit('b') >> 'c']); + TEST_FAILURE("abc", lit('a') >> expect[lit('b') >> 'd'], { + BOOST_TEST(x.where() == "bc"sv); + }); + TEST_FAILURE("abc", lit('a') >> expect[lit('b') > 'd'], { + BOOST_TEST(x.which() == "'d'"sv); + BOOST_TEST(x.where() == "c"sv); + }); + } + + // lexeme + { + TEST_SUCCESS_PASS("12 ab", int_ >> lexeme[lit('a') > 'b'], space); + TEST_FAILURE("12 a b", int_ >> lexeme[lit('a') > 'b'], space, { + BOOST_TEST(x.which() == "'b'"sv); + BOOST_TEST(x.where() == " b"sv); + }); + } + + // matches + { + TEST_SUCCESS_PASS("ab", matches[lit('a') >> 'b']); + TEST_SUCCESS_PASS("ac", matches[lit('a') >> 'b'] >> "ac"); + TEST_SUCCESS_PASS("ab", matches[lit('a') > 'b']); + TEST_FAILURE("ac", matches[lit('a') > 'b'] >> "ac", { + BOOST_TEST(x.which() == "'b'"sv); + BOOST_TEST(x.where() == "c"sv); + }); + + bool attr = false; + TEST_ATTR_SUCCESS_PASS("ab", matches[lit('a') > 'b'], attr); + BOOST_TEST(attr == true); + } + + // no_case + { + TEST_SUCCESS_PASS("12 aB", int_ >> no_case[lit('a') > 'b'], space); + TEST_FAILURE("12 aB", int_ >> no_case[lit('a') > 'c'], space, { + BOOST_TEST(x.which() == "'c'"sv); + BOOST_TEST(x.where() == "B"sv); + }); + } + + // no_skip + { + TEST_SUCCESS_PASS("12 3ab", int_ >> int_ >> no_skip[lit('a') > 'b'], space); + TEST_FAILURE("12 3ab", int_ >> int_ >> no_skip[lit('a') > 'c'], space, { + BOOST_TEST(x.which() == "'c'"sv); + BOOST_TEST(x.where() == "b"sv); + }); + } + + // skip + { + TEST_SUCCESS_PASS("ab[]c[]d", skip(lit('[') > ']')[+alpha]); + TEST_FAILURE("ab[]c[5]d", skip(lit('[') > ']')[+alpha], { + BOOST_TEST(x.which() == "']'"sv); + BOOST_TEST(x.where() == "5]d"sv); + }); + + TEST_SUCCESS_PASS("a1[]b2c3[]d4", skip(lit('[') > ']')[+(alpha > digit)]); + TEST_FAILURE("a1[]b2c3[]d", skip(lit('[') > ']')[+(alpha > digit)], { + BOOST_TEST(x.where() == ""sv); + }); + + TEST_FAILURE("a b c", lit('a') > 'c', space, { + BOOST_TEST(x.which() == "'c'"sv); + BOOST_TEST(x.where() == "b c"sv); + }); + + + { + std::string s; + TEST_ATTR_FAILURE("a b c d", skip(space)[*char_ > lit('z')], s, { + BOOST_TEST(x.which() == "'z'"sv); + BOOST_TEST(x.where() == ""sv); + }); + } + + { + std::string s; + TEST_ATTR_SUCCESS_PASS("a b\n c\n d", char_('a') > char_('b') > skip(space)[char_('c') > char_('d')], s, blank); + BOOST_TEST(s == "abcd"); + } + { + std::string s; + TEST_ATTR_FAILURE("a b\n c\n d", char_('a') > char_('z') > skip(space)[char_('c') > char_('d')], s, blank, { + BOOST_TEST(x.which() == "'z'"sv); + BOOST_TEST(x.where() == "b\n c\n d"sv); + }); + } + { + std::string s; + TEST_ATTR_FAILURE("a b\n c\n d", char_('a') > char_('b') > skip(space)[char_('z') > char_('d')], s, blank, { + BOOST_TEST(x.where() == "\n c\n d"sv); + }); + } + { + std::string s; + TEST_ATTR_FAILURE("a b\n c\n d", char_('a') > char_('b') > skip(space)[char_('c') > char_('z')], s, blank, { + BOOST_TEST(x.which() == "'z'"sv); + BOOST_TEST(x.where() == "d"sv); + }); + } + + // reskip + { + std::string s; + TEST_ATTR_SUCCESS_PASS("a b c d", char_('a') > char_('b') > no_skip[lit(' ') > char_('c') > skip[char_('d')]], s, blank); + BOOST_TEST(s == "abcd"); + } + { + std::string s; + TEST_ATTR_FAILURE("a b c d", char_('a') > char_('b') > no_skip[lit(' ') > char_('c') > skip[char_('z')]], s, blank, { + BOOST_TEST(x.where() == "d"sv); + }); + } + + // reskip with expectation failure context propagation + { + std::string s; + TEST_ATTR_SUCCESS_PASS("a b c d e", char_('a') > char_('b') > no_skip[lit(' ') > char_('c') > skip[char_('d') > char_('e')]], s, blank); + BOOST_TEST(s == "abcde"); + } + { + std::string s; + TEST_ATTR_FAILURE("a b c d e", char_('a') > char_('b') > no_skip[lit(' ') > char_('c') > skip[char_('z') > char_('e')]], s, blank, { + BOOST_TEST(x.where() == " d e"sv); + }); + } + { + std::string s; + TEST_ATTR_FAILURE("a b c d e", char_('a') > char_('b') > no_skip[lit(' ') > char_('c') > skip[char_('d') > char_('z')]], s, blank, { + BOOST_TEST(x.which() == "'z'"sv); + BOOST_TEST(x.where() == "e"sv); + }); + } + } + + // omit + { + TEST_SUCCESS_PASS("ab", omit[lit('a') > 'b']); + TEST_FAILURE("ab", omit[lit('a') > 'c'], { + BOOST_TEST(x.which() == "'c'"sv); + BOOST_TEST(x.where() == "b"sv); + }); + } + + // raw + { + TEST_SUCCESS_PASS("ab", raw[lit('a') > 'b']); + TEST_FAILURE("ab", raw[lit('a') > 'c'], { + BOOST_TEST(x.which() == "'c'"sv); + BOOST_TEST(x.where() == "b"sv); + }); + } + + // repeat + { + TEST_SUCCESS_PASS("ababac", repeat(1, 3)[lit('a') >> 'b'] >> "ac" | +alpha); + + TEST_FAILURE("ababac", repeat(1, 3)[lit('a') > 'b'] | +alpha, { + BOOST_TEST(x.which() == "'b'"sv); + BOOST_TEST(x.where() == "c"sv); + }); + TEST_FAILURE("acab", repeat(2, 3)[lit('a') > 'b'] | +alpha, { + BOOST_TEST(x.which() == "'b'"sv); + BOOST_TEST(x.where() == "cab"sv); + }); + + TEST_SUCCESS_PASS("bcab", repeat(2, 3)[lit('a') > 'b'] | +alpha); + } + + // seek + { + TEST_SUCCESS_PASS("a1b1c1", seek[lit('c') > '1']); + TEST_FAILURE("a1b1c2c1", seek[lit('c') > '1'], { + BOOST_TEST(x.which() == "'1'"sv); + BOOST_TEST(x.where() == "2c1"sv); + }); + } + + // alternative + { + TEST_SUCCESS_PASS("ac", lit('a') >> 'b' | "ac"); + TEST_SUCCESS_PASS("ac", lit('a') >> 'b' | lit('a') >> 'd' | "ac"); + + TEST_FAILURE("ac", (lit('a') > 'b') | "ac", { + BOOST_TEST(x.which() == "'b'"sv); + BOOST_TEST(x.where() == "c"sv); + }); + TEST_FAILURE("ac", lit('a') >> 'b' | (lit('a') > 'd') | "ac", { + BOOST_TEST(x.which() == "'d'"sv); + BOOST_TEST(x.where() == "c"sv); + }); + } + + // predicate + { + TEST_SUCCESS_PASS("abc", lit('a') >> &(lit('b') > 'c') >> "bc"); + TEST_FAILURE("abc", lit('a') >> &(lit('b') > 'd') >> "bc", { + BOOST_TEST(x.which() == "'d'"sv); + BOOST_TEST(x.where() == "c"sv); + }); + } + + // difference + { + TEST_SUCCESS_PASS("bcac", *(char_ - (lit('a') >> 'b'))); + TEST_SUCCESS_PASS("bcab", *(char_ - (lit('a') > 'b')) >> "ab"); + TEST_FAILURE("bcac", *(char_ - (lit('a') > 'b')) >> "ab", { + BOOST_TEST(x.which() == "'b'"sv); + BOOST_TEST(x.where() == "c"sv); + }); + } + + // kleene + { + TEST_SUCCESS_PASS("abac", *(lit('a') >> 'b') >> "ac"); + TEST_SUCCESS_PASS("abbc", *(lit('a') > 'b') >> "bc"); + TEST_FAILURE("abac", *(lit('a') > 'b') >> "ac", { + BOOST_TEST(x.which() == "'b'"sv); + BOOST_TEST(x.where() == "c"sv); + }); + } + + // list + { + TEST_SUCCESS_PASS("ab::ab::ac", (lit('a') >> 'b') % (lit(':') >> ':') >> "::ac"); + TEST_SUCCESS_PASS("ab::ab:ac", (lit('a') > 'b') % (lit(':') >> ':') >> ":ac"); + + TEST_FAILURE("ab::ab::ac", (lit('a') > 'b') % (lit(':') >> ':') >> "::ac", { + BOOST_TEST(x.which() == "'b'"sv); + BOOST_TEST(x.where() == "c"sv); + }); + TEST_FAILURE("ab::ab:ab", (lit('a') >> 'b') % (lit(':') > ':') >> ":ab", { + BOOST_TEST(x.which() == "':'"sv); + BOOST_TEST(x.where() == "ab"sv); + }); + } + + // not + { + TEST_SUCCESS_PASS("[ac]", lit('[') >> !(lit('a') >> 'b') >> +alpha >> ']'); + TEST_SUCCESS_PASS("[bc]", lit('[') >> !(lit('a') > 'b') >> +alpha >> ']'); + TEST_FAILURE("[ac]", lit('[') >> !(lit('a') > 'b') >> +alpha >> ']', { + BOOST_TEST(x.which() == "'b'"sv); + BOOST_TEST(x.where() == "c]"sv); + }); + } + + // optional + { + TEST_SUCCESS_PASS("ac", -(lit('a') >> 'b') >> "ac"); + TEST_SUCCESS_PASS("ab", -(lit('a') > 'b')); + TEST_FAILURE("ac", -(lit('a') > 'b') >> "ac", { + BOOST_TEST(x.which() == "'b'"sv); + BOOST_TEST(x.where() == "c"sv); + }); + } + + // plus + { + TEST_SUCCESS_PASS("abac", +(lit('a') >> 'b') >> "ac"); + TEST_SUCCESS_PASS("abbc", +(lit('a') > 'b') >> "bc"); + TEST_FAILURE("abac", +(lit('a') > 'b') >> "ac", { + BOOST_TEST(x.which() == "'b'"sv); + BOOST_TEST(x.where() == "c"sv); + }); + } + + return boost::report_errors(); +} diff --git a/test/x3/expect_nothrow.cpp b/test/x3/expect_nothrow.cpp new file mode 100644 index 0000000000..419aa43d4c --- /dev/null +++ b/test/x3/expect_nothrow.cpp @@ -0,0 +1,8 @@ +/*============================================================================= + Copyright (c) 2024 Nana Sakisaka + + 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) +=============================================================================*/ +#define BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE 0 +#include "expect.ipp" diff --git a/test/x3/expect_throw.cpp b/test/x3/expect_throw.cpp new file mode 100644 index 0000000000..73474c5fa4 --- /dev/null +++ b/test/x3/expect_throw.cpp @@ -0,0 +1,8 @@ +/*============================================================================= + Copyright (c) 2024 Nana Sakisaka + + 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) +=============================================================================*/ +#define BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE 1 +#include "expect.ipp"