Skip to content

Commit

Permalink
Merge pull request #788 from saki7/nothrow-expect
Browse files Browse the repository at this point in the history
Non-throwing x3::expect
  • Loading branch information
djowel authored Aug 17, 2024
2 parents 5d7aa47 + d9b53fe commit 52c8d66
Show file tree
Hide file tree
Showing 30 changed files with 1,705 additions and 235 deletions.
2 changes: 2 additions & 0 deletions doc/x3/spirit_x3.qbk
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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]
Expand Down
12 changes: 9 additions & 3 deletions doc/x3/tutorial/error_handling.qbk
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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;
}
};
Expand All @@ -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:
Expand Down
97 changes: 97 additions & 0 deletions doc/x3/tutorial/non_throwing_expectations.qbk
Original file line number Diff line number Diff line change
@@ -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]
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
Loading

0 comments on commit 52c8d66

Please sign in to comment.