Search code examples
boostboost-spirit

Parsing a Character followed by a variable number of Integers in Boost Spirit


I am trying to parse a string of the form:

f 1 2 3 4 f 1 2 3 f 1 2 3 4 5

Using boost spirit, I have:

using boost::spirit::qi::uint_;
using boost::spirit::qi::double_;
using boost::spirit::qi::_1;
using boost::spirit::qi::phrase_parse;
using boost::spirit::ascii::space;
using boost::phoenix::ref;
using boost::phoenix::push_back;

std::vector<unsigned int> v;

r = phrase_parse(first_, last_,

    //  Begin grammar
    (
        'f' >> uint_[push_back(ref(v), _1)] >>*(uint_[push_back(ref(v), _1)])
    )
    ,
    //  End grammar

    space);//  Begin grammar
      

                

However, I am getting a bunch of compile errors:

Error C2064 term does not evaluate to a function taking 2 arguments Error C2668 'boost::phoenix::ref': ambiguous call to overloaded function
Error C2780 'bool boost::spirit::qi::phrase_parse(Iterator &,Iterator,Expr &,const Skipper &,boost::spirit::qi::skip_flag)': expects 5 arguments - 3 provided

Not really sure whats wrong because the form just follows the example program given in the boost spirit documentation.

Also, note that I am using boost 1_55 with MSVC 2015 ( can't really change these)

For a fixed number of integers after the 'f' the following compiles and works:

unsigned int v1 = 0, v2 = 0, v3 = 0;

r = phrase_parse(first_, last_,

     //  Begin grammar
     (
          'f' >> uint_[ref(v1) = _1]
      >> uint_[ref(v2) = _1] >> uint_[ref(v3) = _1]
     ),
    //  End grammar

   space);

Solution

  • Your code isn't self contained, but we can guess that ref is ambgious, since std::ref will be picked due to std::vector being declared in namespace std: ADL.

    (That problem doesn't occur with the separate v1, v2, v3 since primitive types do not have an associated namespace).

    Therefore being less liberal (more hygienic) with the using declarations this compiles fine for me on Boost 1.55.0:

    Live On Coliru(c++11)

    #include <boost/spirit/include/phoenix.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/version.hpp>
    namespace qi = boost::spirit::qi;
    namespace px = boost::phoenix;
    
    std::string values(std::vector<unsigned> const& v) {
        std::string r = "[";
        for (auto& el : v)
            r += (r.length() > 1 ? "," : "") + std::to_string(el);
        return r + "]";
    }
    
    int main() {
        std::cout << "Boost: " << BOOST_VERSION << " Spirit: " << std::hex << SPIRIT_VERSION << std::dec
                  << std::endl;
        for (std::string const input :
             {"f ", "f1", "f", "f-3", "f1f2", "f 1 2 3 4 1 2 3 1 2 3 4 5", "f 1 2 3 4 f 1 2 3 f 1 2 3 4 5"}) {
            std::vector<unsigned int>   v;
            std::string::const_iterator first_ = input.begin(), last_ = input.end();
    
            bool r = qi::phrase_parse( //
                first_, last_,
                ('f' >> qi::uint_[px::push_back(px::ref(v), qi::_1)] >>
                 *(qi::uint_[px::push_back(px::ref(v), qi::_1)])),
                qi::space);
    
            std::cout << (r ? "OK" : "FAIL") << "\t"                      //
                      << input << " -> " << values(v)                     //
                      << " remain '" << std::string(first_, last_) << "'" //
                      << std::endl;
        }
    }
    

    Tested locally with Boost 1.55/Spirit v2.53:

    Side Notes: UNCOMPLICATE

    However, there's no need for any of the complexity.

    1. any sequence p >> *p is by definition equivalent to +p

      bool r = qi::phrase_parse(                                //
          first_, last_,                                        //
          'f' >> +qi::uint_[px::push_back(px::ref(v), qi::_1)], //
          qi::space);
      
    2. push-back (back-insertion) is already the default action for container attributes, so why have semantic actions?

      bool r = qi::phrase_parse(first_, last_, 'f' >> +qi::uint_, qi::space, v);
      

      This has the exact same effect without the compilation overhead and code complexity. You'd never run into the ADL snag in the first place.

    **Live On Coliru**¹

    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/version.hpp>
    namespace qi = boost::spirit::qi;
    
    std::string values(std::vector<unsigned> const& v) {
        std::string r = "[";
        for (auto& el : v)
            r += (r.length() > 1 ? "," : "") + std::to_string(el);
        return r + "]";
    }
    
    int main() {
        std::cout << "Boost: " << BOOST_VERSION << " Spirit: " << std::hex << SPIRIT_VERSION << std::dec
                  << std::endl;
        for (std::string const input :
             {"f ", "f1", "f", "f-3", "f1f2", "f 1 2 3 4 1 2 3 1 2 3 4 5", "f 1 2 3 4 f 1 2 3 f 1 2 3 4 5"}) {
            std::vector<unsigned int> v;
            auto f = input.begin(), l = input.end();
            bool r = qi::phrase_parse(f, l, 'f' >> +qi::uint_, qi::space, v);
    
            std::cout << (r ? "OK" : "FAIL") << "\t"             //
                      << input << " -> " << values(v)            //
                      << " remain '" << std::string(f, l) << "'" //
                      << std::endl;
        }
    }
    

    Printing the exact same output.


    ¹ note how the simplification also removed the dependance on now-deprecated Phoenix headers