Search code examples
c++boost-spirit

Changing attribute value while parsing with boost spirit


Given a string "1, 2, 3, 10, 15", the expected output should be 01, 02, 03, 10, 15 - that is, appending "0" to parsed attribute if it's size is not 2. I would easily do that with lambdas as semantic actions, but how to complete the task without using them? I suspect some tricky qi::_val and qi::_1 games should be played here. What I really wanted would be

s = qi::repeat(1,2)[qi::digit] 
[(
        [](auto& parsed_number)
        { 
            return parsed_number.size()==2 ? 
            parsed_number : std::string("0") + parsed_number;
        } 
 ]); 

but it doesn't work that way

#include <boost/spirit/include/qi.hpp>
#include <vector>
#include <string>
#include <iostream>

namespace qi = boost::spirit::qi;

using V = std::vector<std::string>;

template<typename It, typename Skipper>
struct G: qi::grammar<It, V(), Skipper>
{
    G(): G::base_type(v)
    {
        v = s % ',';
        s = qi::repeat(1,2)[qi::digit];
    }
private:
    qi::rule<It, V(), Skipper> v;
    qi::rule<It, std::string(), Skipper> s;
};


int main()
{
    const std::string s = "1, 2, 3, 10, 15";
    std::string::const_iterator beg(s.begin()), e;
    G<decltype(beg), qi::space_type> g;
    V R;
    bool b = qi::phrase_parse(beg, e, g, qi::space, R);
    if(!b){
        std::cerr << "parsing failed\n";
        return -1;
    }

    for(const auto& r: R) std::cout << r << '\n';
}

Solution

  • #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <boost/fusion/include/at.hpp>
    #include <vector>
    #include <string>
    #include <iostream>
    
    namespace qi = boost::spirit::qi;
    namespace phx = boost::phoenix;
    namespace fusion= boost::fusion;
    
    using V = std::vector<std::string>;
    
    template<typename It, typename Skipper>
    struct G: qi::grammar<It, V(), Skipper>
    {
        G(): G::base_type(v)
        {
            v = s % ',';
            //s = qi::hold[qi::repeat(2)[qi::digit]]|(qi::attr('0') >> qi::digit);
            //s = (&qi::repeat(2)[qi::digit] | qi::attr('0')) >> qi::repeat(1,2)[qi::digit];
            //s = qi::as_string[qi::repeat(1,2)[qi::digit]] [qi::_val = phx::if_else(phx::size(qi::_1)==2,qi::_1,phx::val('0')+qi::_1)]; 
            s = qi::as_string[qi::repeat(1,2)[qi::digit]]
    [(
            [](auto& parsed_number, auto& ctx)
            { 
                fusion::at_c<0>(ctx.attributes) = parsed_number.size()==2 ? 
                parsed_number : '0' + parsed_number;
            } 
     )]; 
        }
    private:
        qi::rule<It, V(), Skipper> v;
        qi::rule<It, std::string(), Skipper> s;
    };
    
    
    int main()
    {
        const std::string s = "1, 2, 3, 10, 15";
        std::string::const_iterator beg(s.begin()), e;
        G<decltype(beg), qi::space_type> g;
        V R;
        bool b = qi::phrase_parse(beg, e, g, qi::space, R);
        if(!b){
            std::cerr << "parsing failed\n";
            return -1;
        }
    
        for(const auto& r: R) std::cout << r << '\n';
    }