3

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;
  }

}
Mike M
  • 2,263
  • 3
  • 17
  • 31

2 Answers2

3

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.

Mike M
  • 2,263
  • 3
  • 17
  • 31
2

A quick fix (not necessarily most performant) would be

all         = -(item - garbage) % +garbage;

It prints:

Parsed 3 element(s)

See it Live on Coliru

sehe
  • 374,641
  • 47
  • 450
  • 633
  • 1
    Thank you, but it should find just one element. But your answer got me thinking; my problem was the different propagation type of `|` and `>>`. This leads to the rule `*garbage >> -(item % *garbage) >> *garbage` or shorter `*garbage >> *(item >> *garbage)` which give the desired result. – Mike M Nov 18 '13 at 10:50
  • @Mike Cheers, that was what I intended to get you started with. – sehe Nov 18 '13 at 12:37
  • :-D (well some more text, since a smiley alone is not allowed) – Mike M Nov 18 '13 at 12:47
  • @MikeM You should probably put that as an answer (and accept it) unless sehe edits his answer. As it stands the accepted answer does not work. [This](http://coliru.stacked-crooked.com/a/3ee50447fc105525) is a, probably overcomplicated, alternative based on [this mailing list thread](http://boost.2283326.n4.nabble.com/Omitting-unused-type-inside-Kleene-Star-td4643647.html). The easiest way would be to use semantic actions: `all = *(garbage | item[phx::push_back(qi::_val,qi::_1)]);`. – llonesmiz Nov 22 '13 at 08:18
  • @cv_and_he Thank you for your completely different approach. I also added the answer. – Mike M Nov 22 '13 at 11:41