From 88d9987167799e009427b8ec48154809d8db37dc Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Fri, 9 Aug 2024 19:39:03 +0900 Subject: [PATCH 1/3] Implement non-throwing version of x3::expect --- .../boost/spirit/home/x3/auxiliary/guard.hpp | 39 +- include/boost/spirit/home/x3/core/proxy.hpp | 5 +- .../boost/spirit/home/x3/core/skip_over.hpp | 82 +- .../boost/spirit/home/x3/directive/confix.hpp | 11 + .../boost/spirit/home/x3/directive/expect.hpp | 45 +- .../spirit/home/x3/directive/matches.hpp | 8 + .../boost/spirit/home/x3/directive/repeat.hpp | 6 +- .../boost/spirit/home/x3/directive/seek.hpp | 10 + .../boost/spirit/home/x3/directive/skip.hpp | 82 +- .../home/x3/nonterminal/detail/rule.hpp | 31 +- .../spirit/home/x3/operator/alternative.hpp | 9 +- .../spirit/home/x3/operator/difference.hpp | 17 + .../boost/spirit/home/x3/operator/kleene.hpp | 5 +- .../boost/spirit/home/x3/operator/list.hpp | 5 +- .../spirit/home/x3/operator/not_predicate.hpp | 6 +- .../spirit/home/x3/operator/optional.hpp | 8 +- .../boost/spirit/home/x3/operator/plus.hpp | 5 +- .../spirit/home/x3/operator/sequence.hpp | 15 +- .../spirit/home/x3/support/expectation.hpp | 397 +++++++++ test/x3/Jamfile | 3 +- test/x3/expect.cpp | 153 ---- test/x3/expect.ipp | 798 ++++++++++++++++++ test/x3/expect_nothrow.cpp | 8 + test/x3/expect_throw.cpp | 8 + 24 files changed, 1524 insertions(+), 232 deletions(-) create mode 100644 include/boost/spirit/home/x3/support/expectation.hpp delete mode 100644 test/x3/expect.cpp create mode 100644 test/x3/expect.ipp create mode 100644 test/x3/expect_nothrow.cpp create mode 100644 test/x3/expect_throw.cpp 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..8f7c6c7484 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,58 @@ 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 + { + // It is perfectly fine to omit the expectation_failure context + // even in non-throwing mode if and only if the skipper itself + // is expectation-less. + // + // For example: + // skip(a > b) [lit('foo')] + // skip(c >> d)[lit('foo')] + // `a > b` should require non-`unused_type` context, but + // `c >> d` should NOT require non-`unused_type` context + // + // However, it's impossible right now to detect whether + // `this->subject` actually is expectation-less, so we just + // call the parse function to see what will happen. If the + // subject turns out to lack the expectation context, + // static_assert will be engaged in other locations. + // + // Anyways, we don't need to repack the expectation context + // into our brand new skipper context, in contrast to the + // repacking process done in `x3::skip_over`. + return this->subject.parse(first, last, + make_context(skipper), rcontext, attr); + } + + 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" From cf643c13c048e0a0943e6abff138f9029bdb12bd Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Mon, 12 Aug 2024 23:14:19 +0900 Subject: [PATCH 2/3] Add documentation for non-throwing expectations --- doc/x3/spirit_x3.qbk | 2 + doc/x3/tutorial/error_handling.qbk | 12 ++- doc/x3/tutorial/non_throwing_expectations.qbk | 97 +++++++++++++++++++ 3 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 doc/x3/tutorial/non_throwing_expectations.qbk diff --git a/doc/x3/spirit_x3.qbk b/doc/x3/spirit_x3.qbk index 4c4edd3f14..5f03494262 100644 --- a/doc/x3/spirit_x3.qbk +++ b/doc/x3/spirit_x3.qbk @@ -1,6 +1,7 @@ [/============================================================================== Copyright (C) 2001-2015 Joel de Guzman Copyright (C) 2001-2011 Hartmut Kaiser + 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) @@ -237,6 +238,7 @@ Supported compilers will be: [include tutorial/annotation.qbk] [include tutorial/rexpr.qbk] [include tutorial/error_handling.qbk] +[include tutorial/non_throwing_expectations.qbk] [endsect] [section Quick Reference] diff --git a/doc/x3/tutorial/error_handling.qbk b/doc/x3/tutorial/error_handling.qbk index e08f610a21..9ad9328846 100644 --- a/doc/x3/tutorial/error_handling.qbk +++ b/doc/x3/tutorial/error_handling.qbk @@ -1,5 +1,6 @@ [/============================================================================== Copyright (C) 2001-2018 Joel de Guzman + 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) @@ -94,6 +95,10 @@ matches the input or an exception is emitted. Using on_error(), that exception can be handled by calling a handler with the context at which the parsing failed can be reported. +[tip Check the [link spirit_x3.tutorials.non_throwing_expectations Non-throwing Expectations Tutorial] + for information on switching to more efficient error handling without using C++ exceptions. +] + [heading on_error] `on_error` is the counterpart of `on_success`, as discussed in the @@ -104,6 +109,7 @@ that are executed by the parser when an __x3_expectation_failure__ is thrown via the expect operator or directive. `on_error` handlers have access to the iterators, the context and the exception that was thrown. +[#__tutorial_error_handling__] [heading Error Handling] Before we proceed, let me introduce a helper class, the @@ -129,8 +135,8 @@ Here's our `on_error` handler: , Exception const& x, Context const& context) { auto& error_handler = x3::get(context).get(); - std::string message = "Error! Expecting: " + x.which() + " here:"; - error_handler(x.where(), message); + std::string message = "Error! Expecting: " + x3::which(x) + " here:"; + error_handler(x3::where(x), message); return x3::error_handler_result::fail; } }; @@ -146,7 +152,7 @@ determining the line number and actual column position, and formatting the error message printed. All we have to do is provide the actual error string which we extract from the __x3_expectation_failure__ exception: - std::string message = "Error! Expecting: " + x.which() + " here:"; + std::string message = "Error! Expecting: " + x3::which(x) + " here:"; Then, we return `x3::error_handler_result::fail` to tell X3 that we want to fail the parse when such an event is caught. You can return one of: diff --git a/doc/x3/tutorial/non_throwing_expectations.qbk b/doc/x3/tutorial/non_throwing_expectations.qbk new file mode 100644 index 0000000000..253c7e0e94 --- /dev/null +++ b/doc/x3/tutorial/non_throwing_expectations.qbk @@ -0,0 +1,97 @@ +[/============================================================================== + 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) +===============================================================================/] + +[section:non_throwing_expectations Non-throwing Expectations] + +By default, X3 throws __x3_expectation_failure__ when an expectation failure occurs. +While C++ exceptions are straightforward, they come with significant overhead +that can drastically impact your application's processing speed, especially if +your parser is called within a performance-critical loop. + +In short, even the simplest grammar, like the one below, would throw exceptions +100,000 times if invoked 100,000 times with mismatched input. + + x3::lit('a') > 'b' + +You can change this behavior to store the error in a user-provided variable +instead of throwing an exception. + +Non-throwing mode can be up to *1-90 times faster* than the traditional mode, +depending on the complexity of your grammar. + +[tip The performance improvement is capped by the overhead of C++ exceptions, + meaning the reduction in parse time is limited by the overhead that + exceptions would have introduced. +] + +[heading Migration Guide] + +To switch to non-throwing mode, define the macro `BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE` +as `0` before including X3 headers, and then make a few modifications to your +parser's entry point. + +Here's an example of a parser in its default (throwing) mode: + + #include + + void do_parse() + { + // ... setup your variables here... + + try + { + bool const ok = x3::parse(first, last, parser); + if (!ok) + { + // error handling + } + } + catch (x3::expectation_failure const& failure) + { + // error handling + } + } + +Next, adjust your code as follows to switch to non-throwing mode: + + #define BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE 0 + #include + + void do_parse() + { + // ... setup your variables here... + + // convenient helper, defaults to `boost::optional>` + x3::expectation_failure_optional failure; + + bool const ok = x3::parse( + first, last, x3::with(failure)[parser]); + + if (!ok) + { + if (failure.has_value()) + { + // error handling + } + } + } + +[tip You can also inspect the variable within [link __tutorial_error_handling__ `on_error` handler]. +] + +That's it! All X3 parsers will behave semantically the same as before, +except that expectation failures will be stored in the variable instead of +being thrown as C++ exceptions. + +The following types are supported for the context value: + +* `bool` +* `std::optional>` +* `boost::optional>` +* `std::reference_wrapper` of optional types + +[endsect] From d9b53fedb0722e52be650576c20ddaf806273298 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Tue, 13 Aug 2024 20:02:38 +0900 Subject: [PATCH 3/3] Fix ODR violation, add tests --- .../spirit/home/x3/support/expectation.hpp | 597 +++++++++--------- test/x3/Jamfile | 6 + test/x3/expect.ipp | 15 +- test/x3/expect_odr.cpp | 45 ++ test/x3/expect_odr_nothrow.cpp | 9 + test/x3/expect_odr_throw.cpp | 9 + 6 files changed, 377 insertions(+), 304 deletions(-) create mode 100644 test/x3/expect_odr.cpp create mode 100644 test/x3/expect_odr_nothrow.cpp create mode 100644 test/x3/expect_odr_throw.cpp diff --git a/include/boost/spirit/home/x3/support/expectation.hpp b/include/boost/spirit/home/x3/support/expectation.hpp index fbcef06f0c..0c60b92c11 100644 --- a/include/boost/spirit/home/x3/support/expectation.hpp +++ b/include/boost/spirit/home/x3/support/expectation.hpp @@ -42,12 +42,14 @@ #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 +# define BOOST_SPIRIT_X3_EXPECTATION_FAILURE_NS throwing # include # include #else // non-throwing mode # define BOOST_SPIRIT_X3_EXPECTATION_FAILURE_API # define BOOST_SPIRIT_X3_EXPECTATION_FAILURE_BASE +# define BOOST_SPIRIT_X3_EXPECTATION_FAILURE_NS non_throwing #endif #include @@ -58,340 +60,333 @@ 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 + inline namespace BOOST_SPIRIT_X3_EXPECTATION_FAILURE_NS { - 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(); } + 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) + {} - template - BOOST_ATTRIBUTE_NODISCARD - constexpr decltype(auto) which(std::reference_wrapper>> const& failure) noexcept { return failure.get()->which(); } - } // expectation_failure_helpers + BOOST_ATTRIBUTE_NODISCARD + constexpr Iterator const& where() const noexcept { return where_; } - using expectation_failure_helpers::where; - using expectation_failure_helpers::which; + BOOST_ATTRIBUTE_NODISCARD + constexpr std::string const& which() const noexcept { return which_; } + private: + Iterator where_; + std::string 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 + using expectation_failure_t = std::remove_cv_t(std::declval()))>>; template - constexpr bool has_expectation_failure_impl(std::optional> const& failure) noexcept - { - return failure.has_value(); - } + using expectation_failure_optional = + typename traits::build_optional>::type; - 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()); - } + // x3::where(x), x3::which(x) + // Convenient accessors for absorbing the variation of + // optional/reference_wrapper interfaces. + // beware ADL - we should avoid overgeneralization here. - template - constexpr void set_expectation_failure_impl(bool& failure, T&& value) + namespace expectation_failure_helpers { - 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) + // 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; + + } // inline namespace BOOST_SPIRIT_X3_EXPECTATION_FAILURE_NS + + #if !BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE + namespace detail { + inline namespace BOOST_SPIRIT_X3_EXPECTATION_FAILURE_NS { - failure = std::forward(value); - } + 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()); + } + } // inline namespace BOOST_SPIRIT_X3_EXPECTATION_FAILURE_NS + } // detail + #endif - template - constexpr void set_expectation_failure_impl(std::reference_wrapper& failure, T&& value) - { - set_expectation_failure_impl(failure.get(), std::forward(value)); + inline namespace BOOST_SPIRIT_X3_EXPECTATION_FAILURE_NS + { + 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); - template - constexpr void clear_expectation_failure_impl(unused_type const&) noexcept = delete; - - template - constexpr void clear_expectation_failure_impl(bool& failure) noexcept - { - failure = 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." + ); + + 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 } - template - constexpr void clear_expectation_failure_impl(std::optional>& failure) noexcept - { - failure.reset(); - } + // + // 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); - template - constexpr void clear_expectation_failure_impl(boost::optional>& failure) noexcept - { - failure.reset(); - } + #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" + ); - template - constexpr void clear_expectation_failure_impl(std::reference_wrapper& ref) noexcept - { - return clear_expectation_failure_impl(ref.get()); + detail::set_expectation_failure_impl( + x3::get(context), existing_failure); + #endif } - } -#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); + template + constexpr decltype(auto) get_expectation_failure(Context const&) = delete; #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) + template + BOOST_ATTRIBUTE_NODISCARD + constexpr decltype(auto) get_expectation_failure(Context const& context) { - boost::ignore_unused(where, subject); - detail::set_expectation_failure_impl( - x3::get(context), - true); + 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); } - 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 + } - 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 - } + } // inline namespace BOOST_SPIRIT_X3_EXPECTATION_FAILURE_NS }}} #endif diff --git a/test/x3/Jamfile b/test/x3/Jamfile index e5434e7a8f..8d3fc3281f 100644 --- a/test/x3/Jamfile +++ b/test/x3/Jamfile @@ -78,8 +78,14 @@ run difference.cpp ; run eoi.cpp ; run eol.cpp ; run eps.cpp ; + run expect_throw.cpp ; run expect_nothrow.cpp ; + +obj expect_odr_throw : expect_odr_throw.cpp ; +obj expect_odr_nothrow : expect_odr_nothrow.cpp ; +run expect_odr.cpp expect_odr_throw expect_odr_nothrow ; + run extract_int.cpp ; run int1.cpp ; run kleene.cpp ; diff --git a/test/x3/expect.ipp b/test/x3/expect.ipp index 9398482161..72fc28b959 100644 --- a/test/x3/expect.ipp +++ b/test/x3/expect.ipp @@ -229,8 +229,14 @@ namespace detail } while (false) -int -main() +#if defined(TEST_MAIN_FUNC) +# define TEST_MAIN_FUNC_IS_MAIN 0 +#else +# define TEST_MAIN_FUNC_IS_MAIN 1 +# define TEST_MAIN_FUNC main +#endif + +int TEST_MAIN_FUNC() { using namespace std::string_view_literals; @@ -270,7 +276,6 @@ main() using spirit_test::test; using spirit_test::test_attr; - BOOST_SPIRIT_ASSERT_CONSTEXPR_CTORS(expect['x']); BOOST_SPIRIT_ASSERT_CONSTEXPR_CTORS(char_ > char_); @@ -794,5 +799,9 @@ main() }); } +#if TEST_MAIN_FUNC_IS_MAIN return boost::report_errors(); +#else + return 0; +#endif } diff --git a/test/x3/expect_odr.cpp b/test/x3/expect_odr.cpp new file mode 100644 index 0000000000..6f548bacdf --- /dev/null +++ b/test/x3/expect_odr.cpp @@ -0,0 +1,45 @@ +/*============================================================================= + 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 + +int main_expect_throw(); +int main_expect_nothrow(); + +// Test case for mixing throwing/non-throwing modes in a +// single executable. +int main() +{ + main_expect_throw(); + + // If you encounter a GS Security Check failure (on MSVC) or + // buffer-related assertion failures (on other compilers), + // that is not a bug in X3's buffer manipulation code. + // Instead, it's likely due to an ODR violation, which must + // be fixed in X3's implementation details. + // + // In common cases it should be detectable as a link error. + // + // The worst case is with Visual Studio 2022 (at least in version 17.10.5), + // which does not even detect the error at link time. It will + // compile and link successfully, but when you execute the + // resulting binary, the assembly mistakenly assumes that + // `x3::expectation_failure` is an instance of the *throwing* + // version, causing it to access a nonexistent base class. + // + // This situation leads to a genuine stack overrun, which + // will be caught by the GS Buffer Security Check: + // https://learn.microsoft.com/en-us/cpp/build/reference/gs-buffer-security-check + // + // Surprisingly, in a Release build, the assertion may not + // be triggered as the vulnerable parameters might be handled + // differently by the compiler. + main_expect_nothrow(); + + BOOST_TEST(true); + return boost::report_errors(); +} diff --git a/test/x3/expect_odr_nothrow.cpp b/test/x3/expect_odr_nothrow.cpp new file mode 100644 index 0000000000..f92b81363e --- /dev/null +++ b/test/x3/expect_odr_nothrow.cpp @@ -0,0 +1,9 @@ +/*============================================================================= + 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 +#define TEST_MAIN_FUNC main_expect_nothrow +#include "expect.ipp" diff --git a/test/x3/expect_odr_throw.cpp b/test/x3/expect_odr_throw.cpp new file mode 100644 index 0000000000..a5cfd7f7fc --- /dev/null +++ b/test/x3/expect_odr_throw.cpp @@ -0,0 +1,9 @@ +/*============================================================================= + 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 +#define TEST_MAIN_FUNC main_expect_throw +#include "expect.ipp"