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

Boost spirit x3: Parse vector, but only conditionally take items into result


Consider strings of the form (int ":" int )*. I want to parse such a string into a vector of integers, in the following way: if the second value is odd, add the first to the result. Otherwise, do nothing for this pair.

I am trying around with

  auto pair_ = x3::rule<class pair_, int>() 
             = x3::int_ >> ":" >> x3::int_;
  auto vec   = x3::rule<class vec, std::vector<int>>() 
             = (*pair_[
                 ([](auto &c){
                     if(x3::_attr(c).second % 2) 
                         x3::_val(c).push_back(x3::_attr(c).first);
                 })
               ]);

which seems to be wrong.

Additional task: make : int optional, and default to 1. I tried

  auto pair_ = x3::rule<class pair_, int>() 
             = x3::int_ >> ((":" >> x3::int_) | x3::attr(1));

Example: 10 11:0 12:3 should become the vector [10, 12].

which also seems to be wrong.

How do I do it correctly?


Solution

  • Your pair_ rule exposes only an int. Changing

    auto pair_ 
        = x3::rule<class pair_, std::pair<int, int> >() 
        = x3::int_ >> ":" >> x3::int_;
    

    Will work, although you have to include

    #include <boost/fusion/adapted/std_pair.hpp>
    

    directly or indirectly. The optional version also just works:

    auto pair_ 
        = x3::rule<class pair_, std::pair<int, int> >() 
        = x3::int_ >> (":" >> x3::int_ | x3::attr(1));
    

    Live On Coliru

    #include <boost/fusion/adapted/std_pair.hpp>
    #include <boost/spirit/home/x3.hpp>
    #include <fmt/ranges.h>
    namespace x3 = boost::spirit::x3;
    
    namespace Parsing {
        auto pair_ 
            = x3::rule<class pair_, std::pair<int, int> >() 
            = x3::int_ >> (":" >> x3::int_ | x3::attr(1));
    
        auto action = [](auto& ctx) {
            if (_attr(ctx).second % 2)
                _val(ctx).push_back(_attr(ctx).first);
        };
        auto vec 
            = x3::rule<class vec, std::vector<int>>() 
            = *pair_[action];
    }
    
    int main() {
        for (std::string_view input : {
                "",
                "12:4 1:1",
                "12:4 1:1 2:2 3:3 4:4 5:5",
                "12:4 1 2 3 4 5:5",
                }) {
            std::vector<int> v;
            if (bool ok = phrase_parse(begin(input), end(input), Parsing::vec, x3::space, v))
                fmt::print("{}\t'{}' -> {}\n", ok, input, v);
        }
    }
    

    Printing

    true    '' -> []
    true    '12:4 1:1' -> [1]
    true    '12:4 1:1 2:2 3:3 4:4 5:5' -> [1, 3, 5]
    true    '12:4 1 2 3 4 5:5' -> [1, 2, 3, 4, 5]