Search code examples
c++c++11dslboost-spiritboost-spirit-qi

parse std::vector<int> from comma separated integers


I'm trying to implement a very specific grammar, which requires me at a certain point to parse a list of comma separated integers. The qi rule looks like the following:

qi::rule<Iterator, ascii::space_type> ident;
qi::rule<Iterator, ascii::space_type> nlist;

...

ident = char_ >> nlist;
nlist = ("(" >> int_ % "," >> ")");

...

I need to pass the values up to the ident rule (The expression ident has to create a syntax tree node, where the parsed values from nlist are required for the constructor). I thought about creating and filling a std::vector and use the semantic action like _val = vector<int>.... What is now unclear to me is how do I create a vector of arbitrary length from this rule, since I do not make any assumptions on how long the input will be or using a predefined vector like the examples.

Is this even possible or does is there a better way to do it?


Solution

  • This is the bread and butter of Spirit Qi.

    Just use any compatible attribute type and profit:

        using nlist_t = std::vector<int>;
        using ident_t = std::pair<char, nlist_t>;
    
        qi::rule<Iterator, ident_t(), qi::ascii::space_type> ident;
        qi::rule<Iterator, nlist_t(), qi::ascii::space_type> nlist;
    

    Note: For std::pair attribute compatibility, include the relevant fusion header:

    Live On Coliru

    #include <boost/fusion/adapted/std_pair.hpp>
    #include <boost/spirit/include/qi.hpp>
    
    namespace qi = boost::spirit::qi;
    
    int main()
    {
        using nlist_t = std::vector<int>;
        using ident_t = std::pair<char, nlist_t>;
    
        using Iterator = std::string::const_iterator;
        qi::rule<Iterator, ident_t(), qi::ascii::space_type> ident;
        qi::rule<Iterator, nlist_t(), qi::ascii::space_type> nlist;
    
        ident = qi::char_ >> nlist;
        nlist = '(' >> qi::int_ % ',' >> ')';
    
        for (std::string const input : { "a (1,2,3)", "+(881,-2,42)    \n", "?(0)" }) {
    
            ident_t data;
            if (qi::phrase_parse(input.begin(), input.end(), ident, qi::ascii::space, data)) {
                std::cout << "Parsed: " << data.first << "(";
                for (auto i : data.second) std::cout << i << ",";
                std::cout << ")\n";
            } else
                std::cout << "Parse failed: '" << input << "'\n";
        }
    }
    

    Prints

    Parsed: a(1,2,3,)
    Parsed: +(881,-2,42,)
    Parsed: ?(0,)
    

    BONUS

    Version with imagined Ast type using phoenix::construct:

    Also Live On Coliru

    #include <boost/fusion/adapted/std_pair.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/phoenix.hpp>
    
    namespace qi = boost::spirit::qi;
    namespace px = boost::phoenix;
    
    namespace OoShinyAst {
    
        using MyName = char;
        using MyArgument = int;
        using MyArgumentList = std::vector<MyArgument>;
    
        struct MyIdent {
            MyName         name;
            MyArgumentList args;
    
            MyIdent() = default;
            MyIdent(MyName name, MyArgumentList args)
                : name(std::move(name)), args(std::move(args)) { }
        };
    }
    
    int main()
    {
        using Iterator = std::string::const_iterator;
        qi::rule<Iterator, OoShinyAst::MyIdent(),        qi::ascii::space_type> ident;
        qi::rule<Iterator, OoShinyAst::MyArgumentList(), qi::ascii::space_type> nlist;
    
        nlist = '(' >> qi::int_ % ',' >> ')';
        ident = (qi::char_ >> nlist) [ qi::_val = px::construct<OoShinyAst::MyIdent>(qi::_1, qi::_2) ];
    
        for (std::string const input : { "a (1,2,3)", "+(881,-2,42)    \n", "?(0)" }) {
    
            OoShinyAst::MyIdent data;
            if (qi::phrase_parse(input.begin(), input.end(), ident, qi::ascii::space, data)) {
                std::cout << "Parsed: " << data.name << "(";
                for (auto i : data.args) std::cout << i << ",";
                std::cout << ")\n";
            } else
                std::cout << "Parse failed: '" << input << "'\n";
        }
    }