Search code examples
c++boost-spiritboost-spirit-x3

Avoid throwing expectation_failure when expectation parser fails


How to avoid throwing an exception, when expectation parser fails?

I have a rule "function" > (!x3::lexeme[keyword >> !(x3::alnum | '_')] >> symbol) > ('(' > -lvalue_list > ')') > statements > "end" to parse code like:

function a() return one end

keywords is (zero, one, function, return, end etc).

If I feed the parser with function one() return zero end code, then in function expect_directive::parse exception thrown from here:

if (!r)
{
    boost::throw_exception(
        expectation_failure<Iterator>(
        first, what(this->subject)));
}

When it happens, I got The program has unexpectedly finished. or Aborted (core dumped) (depending on terminal used).

When debugging the code gdb automatically breaks on closing brace '}' in boost::throw_exception function with message:

The inferior stopped because it received a signal from the Operating System.

Signal name : 
SIGABRT
Signal meaning : 
Aborted

When stepping through mentioned function step by step it is seen, that throw enable_current_exception(enable_error_info(e)); line is the last line executed before signal emitting. Why there is no stack unwinding for exception handler searching? Why abort instantly raised (looks like boost::throw_exception have noexcept specifier)?

I have embraced into try { ... } catch (x3::expectation_failure< input_iterator_type > const & ef) { ... } x3::phrase_parse function call. x3::expectation_failure< input_iterator_type > is exactly the expection thrown from boost::throw_exception. All it does not matter.

Is there a way to completely avoid x3::expectation_failure exception in Boost.Spirit X3, but still interrupt parsing of code overall and make x3::phrase_parse to return false on expectation failure?

My suspicions are next:

Due to conventional return value of parse() member function of all parsers (as concept in X3) is bool, I suspect there are only two ways to report about failure: exception xor return code (which can be only true or false, and true already occupied for Parse successful result reporting). It is inherent for recursive descending parsers implementation in C++. But if we change result type of parse from bool to something more wide, we can distinct reporting hard or soft errors (or something else) during parse in more flexible way — by means of different values of return code.


Solution

  • You cannot avoid throwing the expectation failure when using the expectation parser. It's the purpose of this operator.

    Use operator>> for "back-trackable expectations" (i.e. alternatives).

    When you do use expectation points (operator>) just handle the exception too¹.

    Note This looks like a typo

    ('(' > -lvalue_list > '>')
    

    should probably be

    ('(' > -lvalue_list > ')')
    

    Also return one end doesn't match "begin" >> statements >> "end" regardless of what statements is defined as...

    Fixing things:

    Live With Rule Debugging (c++14 only)

    #define BOOST_SPIRIT_X3_DEBUG
    #include <iostream>
    #include <boost/spirit/home/x3.hpp>
    
    namespace SO {
        namespace x3 = boost::spirit::x3;
    
        x3::symbols<char> const keyword = []{
            x3::symbols<char> kw;
            kw += "for","begin","end","function","while","break","switch";
            return kw;
        }();
    
        x3::rule<struct symbol_tag>      const symbol     ("symbol");
        x3::rule<struct identifier_tag>  const identifier ("identifier");
        x3::rule<struct lvalue_list_tag> const lvalue_list("lvalue_list");
        x3::rule<struct statements_tag>  const statements ("statements");
        x3::rule<struct rule_tag>        const rule       ("rule");
    
        auto symbol_def      = x3::lexeme[x3::alnum >> *(x3::alnum | '_')];
        auto identifier_def  = (!(x3::lexeme[keyword >> !(x3::alnum | '_')]) >> symbol);
        auto lvalue_list_def = identifier % ',';
        auto statements_def  = *identifier;
        auto rule_def        = "function"
                         >> identifier
                         >> ('(' > -lvalue_list > ')')
                         >> ("begin" > statements > "end")
                         ;
    
        BOOST_SPIRIT_DEFINE(symbol, identifier, lvalue_list, statements, rule)
    }
    
    int main() {
        std::string const sample = "function a() begin return one end";
        auto f = sample.begin(), l = sample.end();
    
        bool ok = phrase_parse(f, l, SO::rule, SO::x3::space);
        if (ok)
            std::cout << "Parse success\n";
        else
            std::cout << "Parse failed\n";
    
        if (f!=l)
            std::cout << "Remaining unparsed: '" << std::string(f,l) << "'\n";
    }
    

    Prints:

    <rule>
      <try>function a() begin r</try>
      <identifier>
        <try> a() begin return on</try>
        <symbol>
          <try> a() begin return on</try>
          <success>() begin return one </success>
        </symbol>
        <success>() begin return one </success>
      </identifier>
      <lvalue_list>
        <try>) begin return one e</try>
        <identifier>
          <try>) begin return one e</try>
          <symbol>
            <try>) begin return one e</try>
            <fail/>
          </symbol>
          <fail/>
        </identifier>
        <fail/>
      </lvalue_list>
      <statements>
        <try> return one end</try>
        <identifier>
          <try> return one end</try>
          <symbol>
            <try> return one end</try>
            <success> one end</success>
          </symbol>
          <success> one end</success>
        </identifier>
        <identifier>
          <try> one end</try>
          <symbol>
            <try> one end</try>
            <success> end</success>
          </symbol>
          <success> end</success>
        </identifier>
        <identifier>
          <try> end</try>
          <fail/>
        </identifier>
        <success> end</success>
      </statements>
      <success></success>
    </rule>
    Parse success
    

    Without Debug

    It gets a lot simpler:

    Live On Coliru (g++/clang++)

    #include <boost/spirit/home/x3.hpp>
    #include <iostream>
    
    int main() {
        namespace x3 = boost::spirit::x3;
    
        x3::symbols<char> keyword;
        keyword += "for","begin","end","function","while","break","switch";
    
        static auto symbol      = x3::lexeme[x3::alnum >> *(x3::alnum | '_')];
        static auto identifier  = (!(x3::lexeme[keyword >> !(x3::alnum | '_')]) >> symbol);
        static auto lvalue_list = identifier % ',';
        static auto statements  = *identifier;
        static auto rule        = "function"
                                >> identifier
                                >> ('(' > -lvalue_list > ')')
                                >> ("begin" > statements > "end")
                                ;
    
        std::string const sample = "function a() begin return one end";
        auto f = sample.begin(), l = sample.end();
    
        bool ok = phrase_parse(f, l, rule, x3::space);
        if (ok)
            std::cout << "Parse success\n";
        else
            std::cout << "Parse failed\n";
    
        if (f!=l)
            std::cout << "Remaining unparsed: '" << std::string(f,l) << "'\n";
    }
    

    Just prints

    Parse success
    

    ¹ And just to show you can handle the expectation failure just fine: Expectation Failure Handling