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

Boost Spirit Qi: Omit element in Kleene Star parser


I want to parse special constructs and throw the rest away. But I don't want to use a skipper.

I want to get a vector of these constructs, so I use a Kleene Star parser as main rule. But, everytime something gets thrown away, a default constructed element is inserted into the vector.

Here is a made up example. It just looks for the string Test and throws the rest away, at least this is the plan. But every time the rule garbage succeeds it adds a default constructed item to the vector in the rule all, giving an output of 7 insteat of 1. How can I tell Spirit to just add to the vector if the rule item succeeds?

#define BOOST_SPIRIT_USE_PHOENIX_V3

#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>

#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/phoenix.hpp>

#include <iostream>
#include <string>
#include <vector>

namespace qi = boost::spirit::qi;

struct container {
  std::string name;
  bool        dummy;
};
BOOST_FUSION_ADAPT_STRUCT(::container,
                          (std::string, name)
                          (bool, dummy))

int main() {
  typedef std::string::const_iterator iterator;

  qi::rule<iterator, std::vector<container>()> all;
  qi::rule<iterator, container()> item;
  qi::rule<iterator, std::string()> string_rule;
  qi::rule<iterator> garbage;

  all = *(garbage | item);
  garbage = qi::char_ - qi::lit("Test");
  string_rule = qi::string("Test");
  item = string_rule >> qi::attr(true);

  std::vector<container> ast;

  std::string input = "blaTestbla";

  iterator first = input.begin();
  iterator last = input.end();

  bool result = qi::parse(first, last, all, ast);
  if (result) {
    result = first == last;
  }

  if (result) {
    std::cout << "Parsed " << ast.size() << " element(s)" << std::endl;
  } else {
    std::cout << "failure" << std::endl;
  }

}

Solution

  • Since sehe's answer was more or less for educational purposes, we have now several solutions:

    *garbage >> -(item % *garbage) >> *garbage
    
    *garbage >> *(item >> *garbage)
    
    all = *(garbage | item[phx::push_back(qi::_val,qi::_1)]);
    

    And the solution from cv_and_he:

    #define BOOST_SPIRIT_USE_PHOENIX_V3
    
    #include <boost/config/warning_disable.hpp>
    #include <boost/spirit/include/qi.hpp>
    
    #include <boost/fusion/adapted/struct.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    
    #include <iostream>
    #include <string>
    #include <vector>
    
    namespace qi = boost::spirit::qi;
    
    struct container {
      std::string name;
      bool        dummy;
    };
    BOOST_FUSION_ADAPT_STRUCT(::container,
                              (std::string, name)
                              (bool, dummy))
    
    struct container_vector {   //ADDED
        std::vector<container> data;
    };
    
    namespace boost{ namespace spirit{ namespace traits //ADDED
    {
        template <>
        struct is_container<container_vector> : boost::mpl::true_ {};
    
        template <>
        struct container_value<container_vector> {
            typedef optional<container> type;
        };
    
        template <>
        struct push_back_container<container_vector,optional<container> > {
            static bool call(container_vector& cont, const optional<container>& val) {
                if(val)
                    cont.data.push_back(*val);
                return true;
            }
        };
    }}}
    
    int main() {
      typedef std::string::const_iterator iterator;
    
      qi::rule<iterator, container_vector()> all; //CHANGED
      qi::rule<iterator, container()> item;
      qi::rule<iterator, std::string()> string_rule;
      qi::rule<iterator> garbage;
    
      all = *(garbage | item);
      garbage = qi::char_ - qi::lit("Test");
      string_rule = qi::string("Test");
      item = string_rule >> qi::attr(true);
    
      container_vector ast;     //CHANGED
    
      std::string input = "blaTestbla";
    
      iterator first = input.begin();
      iterator last = input.end();
    
      bool result = qi::parse(first, last, all, ast);
      if (result) {
        result = first == last;
      }
    
      if (result) {
        std::cout << "Parsed " << ast.data.size() << " element(s)" << std::endl;   //CHANGED 
      } else {
        std::cout << "failure" << std::endl;
      }
    
    }
    

    Although I didn't want to use a skipper I ended up with:

    start = qi::skip(garbage.alias())[*item];
    

    This last solution was the fastest (by 1-2%) in my unscientific tests using the c-files of the Linux kernel with my production rules.