Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Non-throwing x3::expect #788

Merged
merged 3 commits into from
Aug 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading