Search code examples
c++boost-spirit-qi

boost::spirit stops at first match


I'm trying to parse the following:

  [SOFT] AQUA+
  [FORWARD_SPEED]   0.00
  [some other key] string value
  [PERIODS_NUMBER]  6
  [HEADINGS_NUMBER] 13
  [LOWEST_HEADINGS]   0.00
  [HIGHEST_HEADINGS] 180.00

I don't know in advance what the keys are.

I want to parse them in a structure of vectors.

namespace hdb
{
    typedef std::string Header;
    template <typename T> struct Key
    {
        Header header;
        T      value;
    };

    struct AST
    {
        std::vector<Key<std::string> > string_keys;
        std::vector<Key<double> >      value_keys;
    }
}

I wrote the adaptors:

BOOST_FUSION_ADAPT_STRUCT(
    hdb::Key<std::string>,
    (hdb::Header, header)
    (std::string, value)
)

BOOST_FUSION_ADAPT_STRUCT(
    hdb::Key<double>,
    (hdb::Header, header)
    (double, value)
)

namespace boost { namespace spirit { namespace traits {

template<>
    struct is_container<hdb::AST, void> : mpl::true_ { };
template<>
    struct container_value<hdb::AST, void> {
         typedef boost::variant<
                 hdb::Key<std::string>
                ,hdb::Key<double>
         > type;
    };

template <>
    struct push_back_container<hdb::AST, hdb::Key<std::string>, void> {
        static bool call(hdb::AST& f, const hdb::Key<std::string>& val) {
            f.string_keys.push_back(val);
            return true;
        }
    };
template <>
        struct push_back_container<hdb::AST, hdb::Key<double>, void> {
            static bool call(hdb::AST& f, const hdb::Key<double>& val) {
                f.value_keys.push_back(val);
                return true;
            }
        };

The grammar is the following:

template <typename Iterator>
struct hdb_grammar : qi::grammar<Iterator, hdb::AST(), ascii::space_type>
{

    hdb_grammar() : hdb_grammar::base_type(ast)
    {

        ast        %= string_key | value_key;
        header     %= lexeme['[' >> +(char_ - '[' - ']') >> ']'];
        string_key %= header >> +(char_ - '[' - ']');
        value_key  %= header >> double_;
    }

    qi::rule<Iterator, hdb::AST(), ascii::space_type>                  ast;
    qi::rule<Iterator, hdb::Header(), ascii::space_type>               header;
    qi::rule<Iterator, hdb::Key<double>(), ascii::space_type>          value_key;
    qi::rule<Iterator, hdb::Key<std::string>(), ascii::space_type>     string_key;
};

I call the parser like this:

const std::string contents = "[key 1] value 1\n"
                             "[key 2] value 2\n";

typedef hdb_grammar<std::string::const_iterator> grammar;
grammar g; // Our grammar
hdb::AST ast; // Our tree

using boost::spirit::ascii::space;
std::string::const_iterator begin = contents.begin(), end = contents.end();
bool success = qi::phrase_parse(begin, end, g.ast, space, ast); // Only fills "key 1" & not "key 2"

I think the problem lies with my grammar, but I can't for the life of me see what's wrong with it. Any help would be greatly appreciated.


Solution

  • You just parse (string_key | value_key) effectively. If you wanted it repeated, use Kleene star or plus:

    bool success = qi::phrase_parse(begin, end, *g, space, ast); 
    

    This allows 0 or more AST matches, and the same attribute will be passed each time.

    See Live On Coliru with debug information.


    Simplifications

    That said, I'd prefer to write the same using natural Spirit patterns. Here's the same functionality wrapped in 22 lines (from ~100):

    Live On Coliru

    #include <boost/fusion/adapted/std_pair.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <map>
    
    typedef boost::variant<double, std::string> Value;
    typedef std::map<std::string, Value>        AST;
    
    int main() {
        namespace qi = boost::spirit::qi;
        typedef boost::spirit::istream_iterator It;
        static qi::rule<It, std::string()> const string_ = +~qi::char_("[]\r\n");
    
        It begin(std::cin >> std::noskipws), end;
    
        AST ast;
        bool success = qi::phrase_parse(begin, end, 
                ('[' >> string_ >> ']' >> (qi::double_|string_)) % qi::eol,
                qi::blank, ast);
    
        if (success) for (auto& e : ast) 
            std::cout << '[' << e.first << "]\t" << e.second << "\n";
    }
    

    Prints

    [FORWARD_SPEED] 0
    [HEADINGS_NUMBER]   13
    [HIGHEST_HEADINGS]  180
    [LOWEST_HEADINGS]   0
    [PERIODS_NUMBER]    6
    [SOFT]  AQUA+
    [some other key]    string value