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

Add documentation for non-throwing expectations

This commit is contained in:
Nana Sakisaka
2024-08-12 23:14:19 +09:00
parent 88d9987167
commit cf643c13c0
3 changed files with 108 additions and 3 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]