Skip to content

Commit

Permalink
Implement non-throwing version of x3::expect
Browse files Browse the repository at this point in the history
  • Loading branch information
saki7 committed Aug 12, 2024
1 parent 43112c3 commit 0286301
Show file tree
Hide file tree
Showing 24 changed files with 1,514 additions and 232 deletions.
39 changes: 30 additions & 9 deletions include/boost/spirit/home/x3/auxiliary/guard.hpp
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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
{
Expand All @@ -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>
Expand All @@ -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;
Expand Down
5 changes: 4 additions & 1 deletion include/boost/spirit/home/x3/core/proxy.hpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
/*=============================================================================
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)
==============================================================================*/
#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>
Expand All @@ -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.
Expand Down
82 changes: 71 additions & 11 deletions include/boost/spirit/home/x3/core/skip_over.hpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
/*=============================================================================
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)
==============================================================================*/
#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>
Expand All @@ -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
{
Expand All @@ -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_ {};

Expand All @@ -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<
Expand All @@ -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));
}
}}}

Expand Down
11 changes: 11 additions & 0 deletions include/boost/spirit/home/x3/directive/confix.hpp
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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
{
Expand Down Expand Up @@ -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;
}
Expand Down
45 changes: 19 additions & 26 deletions include/boost/spirit/home/x3/directive/expect.hpp
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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>>
{
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
8 changes: 8 additions & 0 deletions include/boost/spirit/home/x3/directive/matches.hpp
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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
Expand All @@ -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;
}
Expand Down
Loading

0 comments on commit 0286301

Please sign in to comment.