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

Boost.Spirit.Qi crashes when assigning rule to a sequence including itself


I have the following MWE:

#include <string>

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/support_istream_iterator.hpp>

namespace spirit = boost::spirit;
namespace qi = boost::spirit::qi;
namespace phoenix = boost::phoenix;


int main() {

    std::string input("1 2");

    qi::rule<std::string::iterator, void(), qi::space_type> parser;
    qi::rule<std::string::iterator, void(), qi::space_type> parser2;
    qi::rule<std::string::iterator, void(), qi::space_type> parser3;

    parser = qi::int_[
        std::cerr << phoenix::val("First int: ") << qi::_1 << std::endl
    ];

    parser2 = qi::int_[
        std::cerr << phoenix::val("Second int: ") << qi::_1 << std::endl
    ];

    try {
        // Comment out these two lines, (finished below ...)
        parser3 = parser >> parser2;
        phrase_parse(input.begin(), input.end(), parser3, qi::space);

        // ... then un-comment these lines, and the program will crash (and no
        // exception is caught below).
//        parser = parser >> parser2;
//        phrase_parse(input.begin(), input.end(), parser, qi::space);
    }
    catch (...) {
        std::cerr << "Exception caught." << std::endl;
    }

}

As noted in the commented lines, if I assign a third qi::rule to a sequence of another two rules, and parse using that third rule, my program works as expected. However, if I assign the same sequence to the first rule in the sequence, then parse using that first rule, the program will crash when I run it, apparently without even throwing an exception since the catch (...) { . . . } block does not execute.

So my question is: is there some rule about 'qi::rule's I should know that forbids assigning a sequence that contains a rule to that very same rule, or is this crash due to a bug in Boost.Spirit.Qi?

Intent

To clarify, in light of cv_and_he's comment, my goal behind this little toy example is to figure out how to do some dynamic parser generation at runtime; specifically how to generate a rule from a sequence of rules whose count is only know at runtime, such as parser = A1 >> A2 >> ... >> AN;, where N is not known at compile-time, so I can't just hard-code one rule with a fixed number of '>>' that way. This would be something akin to building a list at run time by appending elements to the end, one at a time.


Solution

  • I'm not sure what you were trying to achieve, but copy() would seem to be what you're after

        parser = parser.copy() >> parser2;
    

    See it Live on Coliru


    Background

    The problem is Qi takes non-terminals by reference, so you get the parser semantics a PEG grammar would suggest.

    Besides that, Proto expression trees (expression templates) do take some of their arguments by reference.

    These two combined have a potential to really mess up your life, especially when construction parsers dynamically. In short, I'd argue that, outside

    • using inherited attributes
    • and qi::symbols (including the Nabialek trick)

    constructing rules on the fly is not well supported in Spirit V2. Proto x11 / Spirit X3 may change this for the better.

    See more background here:


    Sample code

    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <boost/spirit/include/support_istream_iterator.hpp>
    
    namespace spirit = boost::spirit;
    namespace qi = boost::spirit::qi;
    namespace phoenix = boost::phoenix;
    
    
    int main() {
    
        std::string input("1 2");
    
        qi::rule<std::string::iterator, void(), qi::space_type> parser;
        qi::rule<std::string::iterator, void(), qi::space_type> parser2;
        qi::rule<std::string::iterator, void(), qi::space_type> parser3;
    
        parser = qi::int_[
            std::cerr << phoenix::val("First int: ") << qi::_1 << std::endl
        ];
    
        parser2 = qi::int_[
            std::cerr << phoenix::val("Second int: ") << qi::_1 << std::endl
        ];
    
        try {
            // Comment out these two lines, (finished below ...)
            parser3 = parser >> parser2;
            phrase_parse(input.begin(), input.end(), parser3, qi::space);
    
            parser = parser.copy() >> parser2;
            phrase_parse(input.begin(), input.end(), parser, qi::space);
        }
        catch (...) {
            std::cerr << "Exception caught." << std::endl;
        }
    
    }