As I try to rewrite my Spirit X3 grammar, I put some rules (e.g. char_ > ':' > int_ > '#'
) into a parser class like this one shown here:
struct item_parser : x3::parser<item_parser> {
using attribute_type = ast::item_type;
template <typename IteratorT, typename ContextT>
bool parse(IteratorT& first, IteratorT const& last, ContextT const& ctx, x3::unused_type,
attribute_type& attribute) const
{
skip_over(first, last, ctx);
auto const grammar_def = x3::lexeme[ x3::char_ > ':' > x3::int_ > '#' ];
auto const grammar = grammar_def.on_error(
[this](auto&f, auto l, auto const& e, auto const& ctx){
return recover_error(f, l, e, ctx); }
);
auto const parse_ok = x3::parse(first, last, grammar, attribute);
return parse_ok;
}
auto recover_error(auto&f, auto l, auto const& e, auto const&) const {
std::cout << "item_parser::recover_error: " << e.which() << '\n';
std::cout << "+++ error_handler in: " << excerpt(f, l) << "\n";
if (auto semicolon = find(f, l, ';'); semicolon == l) {
return x3::error_handler_result::fail;
} else { // move iter behind ';'
f = semicolon + 1;
std::cout << "+++ error_handler out: " << excerpt(f, l) << "\n";
return x3::error_handler_result::accept;
}
}
};
(https://godbolt.org/z/MhjYWbE6x) - which works with good input:
std::string const input = R"(
X := a:42#;
X := b:66#;
X := c:4711#;
)";
as expected. If I change one line in the input to e.g. X := b66#;
(note the missing colon :
inside the literal) it recovers from parser error as expected, but "failed finally" at the end (https://godbolt.org/z/3zabbsWsP).
Input
std::string const input = R"(
X := a:42#;
X := b66#;
X := c:4711#;
)";
with output:
item_parser::recover_error: ':'
+++ error_handler in: 'b66#;
X := c:4711#;
'
+++ error_handler out: '
X := c:4711#;
'
failure
For the return of parse()
(aka parse_ok
) I expected true since it's never returned x3::error_handler_result::fail
. Nevertheless, I use an error counter in real.
If I further change the outer rule item_assign
to x3::eps > "X" > ":=" > item > ';'
the program is terminated (uncaught expectation_failure) which I haven't expected since it's guarded with an error_handler (tag), see https://godbolt.org/z/dGcYfbG3r
terminate called after throwing an instance of 'boost::wrapexcept<boost::spirit::x3::expectation_failure<__gnu_cxx::__normal_iterator<char const*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >'
what(): boost::spirit::x3::expectation_failure
Maybe I don't see my fault ... or I trigger a bug? Note, that I'm using rule's on_error()
which mentioned at https://github.com/boostorg/spirit/issues/657 . Thanks in advance for help.
First off, you need to make up your mind. You're duplicating the error handling code (inconsistently), using a function object as the rule tag (error_handler
), (but operator()
is completely ignored there), complicating things because you are using the same tag on different rules.
Then you're also using the undocumented interface parser.on_error(f)
- but it's probably undocumented for a reason. Finally, you're mixing that - for no apparent reason - with a custom parser, which you both want to pre-skip and behave as a lexeme.
Oh, and you're dragging in position_tagged
, annotate_on_success
and error_reporting
, none of which you are actually using.
I'd simplify all the way. When you do, you may find that the real reason your stuff doesn't work as expected, is that you're telling the rule to accept result as-if success (error_handler_result::accept
) but after a successful result ';'
is required. Since you position the cursor after the semicolon, that's going to fail:
========= "
X := a:42#;
X := b66#;
X := c:4711#;
" =====
<grammar>
<try>\n X := a:42#;\n </try>
<item_assign>
<try>\n X := a:42#;\n </try>
<item>
<try> a:42#;\n X := b66</try>
<success>;\n X := b66#;\n </success>
<attributes>[a, 42]</attributes>
</item>
<success>\n X := b66#;\n </success>
<attributes>[a, 42]</attributes>
</item_assign>
<item_assign>
<try>\n X := b66#;\n </try>
<item>
<try> b66#;\n X := c:47</try>
+++ error_handler expected: ':'
+++ error_handler in: '66#;
X := c'
+++ error_handler out: '
X := c:471'
<success>\n X := c:4711#;\n</success>
<attributes>[b, 0]</attributes>
</item>
<fail/>
</item_assign>
<fail/>
</grammar>
failure
Simply put the error-handler on the item_assign
rule - the one that you know how to recover for:
struct item_assign_tag : error_handler {};
auto const item
= x3::lexeme[x3::char_ > ':' > x3::int_ > '#'];
auto const item_assign //
= x3::rule<item_assign_tag, ast::item_type>{"item_assign"} //
= x3::eps >> "X" >> ":=" >> item >> ';';
auto const grammar //
= x3::skip(x3::space)[*item_assign >> x3::eoi];
Now the output becomes: Live On Compiler Explorer
<item_assign>
<try>X := a:42#;\n </try>
<success>\n X :</success>
<attributes>[a, 42]</attributes>
</item_assign>
<item_assign>
<try>\n X :</try>
+++ error_handler expected: ':'
+++ error_handler in: '66#;
'
+++ error_handler out: '
X :'
<success>\n X :</success>
<attributes>[b, 0]</attributes>
</item_assign>
<item_assign>
<try>\n X :</try>
<success>\n </success>
<attributes>[c, 4711]</attributes>
</item_assign>
<item_assign>
<try>\n </try>
<fail/>
</item_assign>
- a:42
- b:0
- c:4711
Now, in my opinion it would be better if b:0
wasn't in the output, so perhaps use retry
instead of accept
: Live On CE:
+++ error_handler expected: ':'
+++ error_handler in: '66#;
X := c:'
+++ error_handler out: '
X := c:4711'
- a:42
- c:4711