2
0
mirror of https://github.com/boostorg/spirit.git synced 2026-01-19 04:42:11 +00:00

Sync from upstream.

This commit is contained in:
Rene Rivera
2024-08-20 09:56:54 -05:00
30 changed files with 1705 additions and 235 deletions

View File

@@ -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]

View File

@@ -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<x3::error_handler_tag>(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:

View File

@@ -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 <boost/spirit/home/x3.hpp>
void do_parse()
{
// ... setup your variables here...
try
{
bool const ok = x3::parse(first, last, parser);
if (!ok)
{
// error handling
}
}
catch (x3::expectation_failure<Iterator> 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 <boost/spirit/home/x3.hpp>
void do_parse()
{
// ... setup your variables here...
// convenient helper, defaults to `boost::optional<x3::expectation_failure<Iterator>>`
x3::expectation_failure_optional<Iterator> failure;
bool const ok = x3::parse(
first, last, x3::with<x3::expectation_failure_tag>(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<x3::expectation_failure<Iterator>>`
* `boost::optional<x3::expectation_failure<Iterator>>`
* `std::reference_wrapper` of optional types
[endsect]

View File

@@ -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 <boost/spirit/home/x3/support/context.hpp>
#include <boost/spirit/home/x3/directive/expect.hpp>
#include <boost/spirit/home/x3/support/expectation.hpp>
#include <boost/spirit/home/x3/core/parser.hpp>
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 <typename Subject, typename Handler>
@@ -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<Iterator> const& x)
{
#if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE
catch (expectation_failure<Iterator> 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;

View File

@@ -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 <boost/spirit/home/x3/support/expectation.hpp>
#include <boost/spirit/home/x3/core/parser.hpp>
#include <boost/spirit/home/x3/core/detail/parse_into_container.hpp>
#include <boost/spirit/home/x3/support/traits/attribute_category.hpp>
@@ -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.

View File

@@ -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 <boost/spirit/home/x3/support/expectation.hpp>
#include <boost/spirit/home/x3/support/unused.hpp>
#include <boost/spirit/home/x3/support/context.hpp>
#include <boost/spirit/home/x3/support/traits/attribute_category.hpp>
@@ -15,6 +18,7 @@
#include <boost/type_traits/remove_cv.hpp>
#include <boost/type_traits/remove_reference.hpp>
#include <boost/utility/declval.hpp>
#include <boost/core/ignore_unused.hpp>
namespace boost { namespace spirit { namespace x3
{
@@ -41,7 +45,7 @@ namespace boost { namespace spirit { namespace x3
struct is_unused_skipper<unused_skipper<Skipper>>
: mpl::true_ {};
template <>
template <>
struct is_unused_skipper<unused_type>
: mpl::true_ {};
@@ -58,29 +62,85 @@ namespace boost { namespace spirit { namespace x3
return unused_skipper.skipper;
}
template <typename Iterator, typename Skipper>
template <typename Iterator, typename Context, typename Skipper>
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<expectation_failure_t<Context>, 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<x3::expectation_failure_tag>(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<expectation_failure_tag>(
x3::get<expectation_failure_tag>(context));
while (skipper.parse(first, last, local_ctx, unused, unused))
/* loop */;
}
#endif
}
template <typename Iterator>
inline void skip_over(Iterator&, Iterator const&, unused_type)
template <typename Iterator, typename Context>
inline void skip_over(Iterator&, Iterator const&, Context&, unused_type)
{
}
template <typename Iterator, typename Skipper>
template <typename Iterator, typename Context, typename Skipper>
inline void skip_over(
Iterator&, Iterator const&, unused_skipper<Skipper> const&)
Iterator&, Iterator const&, Context&, unused_skipper<Skipper> const&)
{
}
}
// this tag is used to find the skipper from the context
struct skipper_tag;
template <typename Context>
struct has_skipper
: mpl::not_<detail::is_unused_skipper<
@@ -91,9 +151,9 @@ namespace boost { namespace spirit { namespace x3
template <typename Iterator, typename Context>
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<skipper_tag>(context));
detail::skip_over(first, last, context, x3::get<skipper_tag>(context));
}
}}}

View File

@@ -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 <boost/spirit/home/x3/core/parser.hpp>
#include <boost/spirit/home/x3/support/expectation.hpp>
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;
}

View File

@@ -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 <boost/spirit/home/x3/support/context.hpp>
#include <boost/spirit/home/x3/support/expectation.hpp>
#include <boost/spirit/home/x3/core/parser.hpp>
#include <boost/spirit/home/x3/core/detail/parse_into_container.hpp>
#include <boost/config.hpp> // for BOOST_SYMBOL_VISIBLE
#include <boost/throw_exception.hpp>
#include <stdexcept>
namespace boost { namespace spirit { namespace x3
{
template <typename Iterator>
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 <typename Subject>
struct expect_directive : unary_parser<Subject, expect_directive<Subject>>
{
@@ -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<Iterator>(
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<Iterator>(
first, what(parser.subject)));
#else
if (!has_expectation_failure(context))
{
set_expectation_failure(first, parser.subject, context);
}
#endif
}
return r;
}

View File

@@ -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 <boost/spirit/home/x3/core/parser.hpp>
#include <boost/spirit/home/x3/support/traits/move_to.hpp>
#include <boost/spirit/home/x3/support/expectation.hpp>
#include <boost/spirit/home/x3/support/unused.hpp>
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;
}

View File

@@ -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 <boost/spirit/home/x3/core/parser.hpp>
#include <boost/spirit/home/x3/operator/kleene.hpp>
#include <boost/spirit/home/x3/support/expectation.hpp>
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;

View File

@@ -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 <boost/spirit/home/x3/core/parser.hpp>
#include <boost/spirit/home/x3/support/expectation.hpp>
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;

View File

@@ -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 <boost/spirit/home/x3/support/context.hpp>
#include <boost/spirit/home/x3/support/unused.hpp>
#include <boost/spirit/home/x3/support/expectation.hpp>
#include <boost/spirit/home/x3/core/skip_over.hpp>
#include <boost/spirit/home/x3/core/parser.hpp>
#include <boost/utility/enable_if.hpp>
@@ -30,16 +32,22 @@ namespace boost { namespace spirit { namespace x3
, typename RContext, typename Attribute>
typename disable_if<has_skipper<Context>, 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<skipper_tag>(context));
return this->subject.parse(
first, last
, make_context<skipper_tag>(skipper, context)
, rcontext
, attr);
auto const local_ctx = make_context<skipper_tag>(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 <typename Iterator, typename Context
, typename RContext, typename Attribute>
@@ -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 <typename Iterator, typename Context
, typename RContext, typename Attribute>
template <typename Iterator, typename RContext, typename Attribute>
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_tag>(skipper), rcontext, attr);
}
template <typename Iterator, typename Context, typename RContext, typename Attribute>
bool parse(Iterator& first, Iterator const& last
, Context const& context, RContext& rcontext, Attribute& attr) const
{
return this->subject.parse(
first, last
, make_context<skipper_tag>(skipper, context)
, rcontext
, attr);
#if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE
return this->subject.parse(first, last, make_context<skipper_tag>(skipper, context), rcontext, attr);
#else
static_assert(
!std::is_same_v<expectation_failure_t<Context>, unused_type>,
"Context type was not specified for x3::expectation_failure_tag. "
"You probably forgot: `x3::with<x3::expectation_failure_tag>(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_tag>(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;

View File

@@ -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 <boost/spirit/home/x3/auxiliary/guard.hpp>
#include <boost/spirit/home/x3/core/parser.hpp>
#include <boost/spirit/home/x3/core/skip_over.hpp>
#include <boost/spirit/home/x3/support/expectation.hpp>
#include <boost/spirit/home/x3/directive/expect.hpp>
#include <boost/spirit/home/x3/nonterminal/detail/transform_attribute.hpp>
#include <boost/utility/addressof.hpp>
@@ -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<Iterator> const& x)
{
#if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE
catch (expectation_failure<Iterator> 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;
}
}

View File

@@ -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 <boost/spirit/home/x3/support/traits/attribute_of_binary.hpp>
#include <boost/spirit/home/x3/support/expectation.hpp>
#include <boost/spirit/home/x3/core/parser.hpp>
#include <boost/spirit/home/x3/operator/detail/alternative.hpp>
@@ -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 <typename Iterator, typename Context
@@ -39,7 +43,8 @@ namespace boost { namespace spirit { namespace x3
, Context const& context, RContext& rcontext, Attribute& attr) const
{
return detail::parse_alternative(this->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));
}
};

View File

@@ -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 <boost/spirit/home/x3/support/traits/attribute_of.hpp>
#include <boost/spirit/home/x3/support/traits/has_attribute.hpp>
#include <boost/spirit/home/x3/support/expectation.hpp>
#include <boost/spirit/home/x3/core/parser.hpp>
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);
}

View File

@@ -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 <boost/spirit/home/x3/core/parser.hpp>
#include <boost/spirit/home/x3/support/traits/container_traits.hpp>
#include <boost/spirit/home/x3/support/traits/attribute_of.hpp>
#include <boost/spirit/home/x3/support/expectation.hpp>
#include <boost/spirit/home/x3/core/detail/parse_into_container.hpp>
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);
}
};

View File

@@ -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 <boost/spirit/home/x3/core/parser.hpp>
#include <boost/spirit/home/x3/support/traits/container_traits.hpp>
#include <boost/spirit/home/x3/support/traits/attribute_of.hpp>
#include <boost/spirit/home/x3/support/expectation.hpp>
#include <boost/spirit/home/x3/core/detail/parse_into_container.hpp>
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);
}
};

View File

@@ -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 <boost/spirit/home/x3/core/parser.hpp>
#include <boost/spirit/home/x3/support/expectation.hpp>
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);
}
};

View File

@@ -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 <boost/spirit/home/x3/core/proxy.hpp>
#include <boost/spirit/home/x3/core/detail/parse_into_container.hpp>
#include <boost/spirit/home/x3/support/expectation.hpp>
#include <boost/spirit/home/x3/support/traits/attribute_of.hpp>
#include <boost/spirit/home/x3/support/traits/move_to.hpp>
#include <boost/spirit/home/x3/support/traits/optional_traits.hpp>
@@ -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;
}

View File

@@ -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 <boost/spirit/home/x3/core/parser.hpp>
#include <boost/spirit/home/x3/support/expectation.hpp>
#include <boost/spirit/home/x3/support/traits/container_traits.hpp>
#include <boost/spirit/home/x3/support/traits/attribute_of.hpp>
#include <boost/spirit/home/x3/core/detail/parse_into_container.hpp>
@@ -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);
}
};

View File

@@ -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 <boost/spirit/home/x3/support/traits/attribute_of_binary.hpp>
#include <boost/spirit/home/x3/support/expectation.hpp>
#include <boost/spirit/home/x3/core/parser.hpp>
#include <boost/spirit/home/x3/operator/detail/sequence.hpp>
#include <boost/spirit/home/x3/directive/expect.hpp>
@@ -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;
}

View File

@@ -0,0 +1,392 @@
/*=============================================================================
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 <boost/config.hpp> // for BOOST_SYMBOL_VISIBLE, BOOST_ATTRIBUTE_NODISCARD
#include <boost/core/ignore_unused.hpp>
#include <boost/spirit/home/x3/support/unused.hpp>
#include <boost/spirit/home/x3/support/context.hpp>
// 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<T>`, and use `std::optional<T>` 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<Iterator>` into their parser,
// safely assuming that the value is no-op in throwing mode.
#include <boost/spirit/home/x3/support/traits/optional_traits.hpp>
// This is required for partial specialization of relevant helpers.
// TODO: Add a macro to discard all #includes of <boost/optional.hpp>.
// (this is TODO because it requires changes in `optional_traits.hpp`.)
#include <boost/optional.hpp>
#include <optional>
#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 <boost/throw_exception.hpp>
# include <stdexcept>
#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 <string>
#include <type_traits>
namespace boost { namespace spirit { namespace x3
{
struct expectation_failure_tag;
inline namespace BOOST_SPIRIT_X3_EXPECTATION_FAILURE_NS
{
template <typename Iterator>
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 <typename Context>
using expectation_failure_t = std::remove_cv_t<std::remove_reference_t<
decltype(x3::get<expectation_failure_tag>(std::declval<Context>()))>>;
template <typename Iterator>
using expectation_failure_optional =
typename traits::build_optional<expectation_failure<Iterator>>::type;
// x3::where(x), x3::which(x)
// Convenient accessors for absorbing the variation of
// optional/reference_wrapper interfaces.
// beware ADL - we should avoid overgeneralization here.
namespace expectation_failure_helpers
{
// bare type
template <typename Iterator>
BOOST_ATTRIBUTE_NODISCARD
constexpr decltype(auto) where(expectation_failure<Iterator> const& failure) noexcept { return failure.where(); }
template <typename Iterator>
BOOST_ATTRIBUTE_NODISCARD
constexpr decltype(auto) which(expectation_failure<Iterator> const& failure) noexcept { return failure.which(); }
// std::optional
template <typename Iterator>
BOOST_ATTRIBUTE_NODISCARD
constexpr decltype(auto) where(std::optional<expectation_failure<Iterator>> const& failure) noexcept { return failure->where(); }
template <typename Iterator>
BOOST_ATTRIBUTE_NODISCARD
constexpr decltype(auto) which(std::optional<expectation_failure<Iterator>> const& failure) noexcept { return failure->which(); }
// boost::optional
template <typename Iterator>
BOOST_ATTRIBUTE_NODISCARD
constexpr decltype(auto) where(boost::optional<expectation_failure<Iterator>> const& failure) noexcept { return failure->where(); }
template <typename Iterator>
BOOST_ATTRIBUTE_NODISCARD
constexpr decltype(auto) which(boost::optional<expectation_failure<Iterator>> const& failure) noexcept { return failure->which(); }
// std::optional + std::reference_wrapper
template <typename Iterator>
BOOST_ATTRIBUTE_NODISCARD
constexpr decltype(auto) where(std::reference_wrapper<std::optional<expectation_failure<Iterator>>> const& failure) noexcept { return failure.get()->where(); }
template <typename Iterator>
BOOST_ATTRIBUTE_NODISCARD
constexpr decltype(auto) which(std::reference_wrapper<std::optional<expectation_failure<Iterator>>> const& failure) noexcept { return failure.get()->which(); }
// boost::optional + std::reference_wrapper
template <typename Iterator>
BOOST_ATTRIBUTE_NODISCARD
constexpr decltype(auto) where(std::reference_wrapper<boost::optional<expectation_failure<Iterator>>> const& failure) noexcept { return failure.get()->where(); }
template <typename Iterator>
BOOST_ATTRIBUTE_NODISCARD
constexpr decltype(auto) which(std::reference_wrapper<boost::optional<expectation_failure<Iterator>>> 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
{
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 <typename Iterator>
constexpr bool has_expectation_failure_impl(std::optional<expectation_failure<Iterator>> const& failure) noexcept
{
return failure.has_value();
}
template <typename Iterator>
constexpr bool has_expectation_failure_impl(boost::optional<expectation_failure<Iterator>> const& failure) noexcept
{
return failure.has_value();
}
template <typename T>
constexpr bool has_expectation_failure_impl(std::reference_wrapper<T> const& ref) noexcept
{
return has_expectation_failure_impl(ref.get());
}
template <typename Iterator, typename T>
constexpr void set_expectation_failure_impl(bool& failure, T&& value)
{
failure = std::forward<T>(value);
}
template <typename Iterator, typename T>
constexpr void set_expectation_failure_impl(std::optional<expectation_failure<Iterator>>& failure, T&& value)
{
failure = std::forward<T>(value);
}
template <typename Iterator, typename T>
constexpr void set_expectation_failure_impl(boost::optional<expectation_failure<Iterator>>& failure, T&& value)
{
failure = std::forward<T>(value);
}
template <typename AnyExpectationFailure, typename T>
constexpr void set_expectation_failure_impl(std::reference_wrapper<AnyExpectationFailure>& failure, T&& value)
{
set_expectation_failure_impl(failure.get(), std::forward<T>(value));
}
template <typename Iterator>
constexpr void clear_expectation_failure_impl(unused_type const&) noexcept = delete;
template <typename Iterator>
constexpr void clear_expectation_failure_impl(bool& failure) noexcept
{
failure = false;
}
template <typename Iterator>
constexpr void clear_expectation_failure_impl(std::optional<expectation_failure<Iterator>>& failure) noexcept
{
failure.reset();
}
template <typename Iterator>
constexpr void clear_expectation_failure_impl(boost::optional<expectation_failure<Iterator>>& failure) noexcept
{
failure.reset();
}
template <typename T>
constexpr void clear_expectation_failure_impl(std::reference_wrapper<T>& ref) noexcept
{
return clear_expectation_failure_impl(ref.get());
}
} // inline namespace BOOST_SPIRIT_X3_EXPECTATION_FAILURE_NS
} // detail
#endif
inline namespace BOOST_SPIRIT_X3_EXPECTATION_FAILURE_NS
{
template <typename Context>
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<Context>;
static_assert(
!std::is_same_v<unused_type, T>,
"Context type was not specified for x3::expectation_failure_tag. "
"You probably forgot: `x3::with<x3::expectation_failure_tag>(failure)[p]`. "
"Note that you must also bind the context to your skipper."
);
return detail::has_expectation_failure_impl(
x3::get<expectation_failure_tag>(context));
#endif
}
//
// Creation of a brand new expectation_failure instance.
// This is the primary overload.
//
template <typename Iterator, typename Subject, typename Context>
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<Context>;
static_assert(
!std::is_same_v<unused_type, T>,
"Context type was not specified for x3::expectation_failure_tag. "
"You probably forgot: `x3::with<x3::expectation_failure_tag>(failure)[p]`. "
"Note that you must also bind the context to your skipper."
);
if constexpr (std::is_same_v<T, bool>)
{
boost::ignore_unused(where, subject);
detail::set_expectation_failure_impl(
x3::get<expectation_failure_tag>(context),
true);
}
else
{
detail::set_expectation_failure_impl(
x3::get<expectation_failure_tag>(context),
expectation_failure<Iterator>(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 <typename AnyExpectationFailure, typename Context>
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<Context>;
static_assert(
!std::is_same_v<T, unused_type>,
"Context type was not specified for x3::expectation_failure_tag. "
"You probably forgot: `x3::with<x3::expectation_failure_tag>(failure)[p]`. "
"Note that you must also bind the context to your skipper."
);
static_assert(
std::is_assignable_v<T, AnyExpectationFailure const&>,
"previous/current expectation failure types should be compatible"
);
detail::set_expectation_failure_impl(
x3::get<expectation_failure_tag>(context), existing_failure);
#endif
}
#if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE
template <typename Context>
constexpr decltype(auto) get_expectation_failure(Context const&) = delete;
#else
template <typename Context>
BOOST_ATTRIBUTE_NODISCARD
constexpr decltype(auto) get_expectation_failure(Context const& context)
{
using T = expectation_failure_t<Context>;
static_assert(
!std::is_same_v<T, unused_type>,
"Context type was not specified for x3::expectation_failure_tag. "
"You probably forgot: `x3::with<x3::expectation_failure_tag>(failure)[p]`. "
"Note that you must also bind the context to your skipper."
);
return x3::get<expectation_failure_tag>(context);
}
#endif
template <typename Context>
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<Context>;
static_assert(
!std::is_same_v<T, unused_type>,
"Context type was not specified for x3::expectation_failure_tag. "
"You probably forgot: `x3::with<x3::expectation_failure_tag>(failure)[p]`. "
"Note that you must also bind the context to your skipper."
);
detail::clear_expectation_failure_impl(
x3::get<expectation_failure_tag>(context));
#endif
}
} // inline namespace BOOST_SPIRIT_X3_EXPECTATION_FAILURE_NS
}}}
#endif

View File

@@ -79,7 +79,14 @@ run difference.cpp ;
run eoi.cpp ;
run eol.cpp ;
run eps.cpp ;
run expect.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 ;

View File

@@ -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 <boost/spirit/home/x3.hpp>
#include <boost/fusion/include/vector.hpp>
#include <boost/fusion/include/at.hpp>
#include <string>
#include <iostream>
#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<char const*> 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<char const*> 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<char const*> 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<char, char, char> 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<char, char, char> 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<char, char, char> 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<char const*> 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<char const*> const& x)
{
BOOST_TEST_CSTR_EQ(x.which().c_str(), "\"foo\"");
BOOST_TEST_CSTR_EQ(x.where(), "bar");
}
}
return boost::report_errors();
}

807
test/x3/expect.ipp Normal file
View File

@@ -0,0 +1,807 @@
/*=============================================================================
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 <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/binary.hpp>
#include <boost/spirit/home/x3/directive/with.hpp>
#include <boost/spirit/home/x3/support/expectation.hpp>
#include <boost/fusion/include/vector.hpp>
#include <boost/fusion/include/at.hpp>
#include <boost/preprocessor/facilities/overload.hpp>
#include <boost/preprocessor/facilities/expand.hpp>
#include <optional>
#include <string>
#include <string_view>
#include <iostream>
#include <utility>
#include <type_traits>
#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<char const*> const& x) \
{ \
catch_stmt \
throw; \
} \
}, \
expectation_failure<char const*> \
)
# define TEST_FAILURE_IMPL_5(tester, input, parser, arg0, catch_stmt) \
BOOST_TEST_THROWS( \
{ \
try \
{ \
BOOST_TEST(tester(input, parser, arg0)); \
} \
catch (expectation_failure<char const*> const& x) \
{ \
catch_stmt \
throw; \
} \
}, \
expectation_failure<char const*> \
)
# 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<char const*> const& x) \
{ \
catch_stmt \
throw; \
} \
}, \
expectation_failure<char const*> \
)
#else
using expectation_failure_optional_t = x3::expectation_failure_optional<char const*>;
namespace detail
{
template <typename T, typename Enabled = std::enable_if_t<!std::is_const_v<T>>>
decltype(auto) wrap_with_expectation_failure(expectation_failure_optional_t&, T& attr)
{
return attr;
}
template <typename T>
auto wrap_with_expectation_failure(expectation_failure_optional_t& xopt, T const& skipper)
{
return x3::with<x3::expectation_failure_tag>(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<expectation_failure_tag>(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<expectation_failure_tag>(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<expectation_failure_tag>(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<expectation_failure_tag>(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<expectation_failure_tag>(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<expectation_failure_tag>(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)
#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;
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<char, char, char> 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<char, char, char> 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<char, char, char> 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<T> 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);
});
}
#if TEST_MAIN_FUNC_IS_MAIN
return boost::report_errors();
#else
return 0;
#endif
}

View File

@@ -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"

45
test/x3/expect_odr.cpp Normal file
View File

@@ -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 <boost/core/lightweight_test.hpp>
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();
}

View File

@@ -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"

View File

@@ -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"

8
test/x3/expect_throw.cpp Normal file
View File

@@ -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"