c++boost-spiritboost-spirit-x3

even using error_handler got uncaught x3::expectation_failure


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.


Solution

  • 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