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

boost spirit selects unmatched result


I have a file with the following format

metal 1 1.2 2.2
wire 1.1 2.3
metal 2 3.2 12.2
...

This is a very simple format. "metal" and "wire" are keywords. And "metal" is followed by 1 uint and 2 double, while "wire" is followed by 2 double. I try using Boost::Qi to parse it, but the result is very strange and I cannot figure out why.

#include <iostream>
#include <string>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/phoenix_bind.hpp>
#include <boost/bind.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/spirit/include/lex_lexertl.hpp>
using std::cout;
using std::endl;
using std::string;

using namespace boost::spirit;

namespace client
{
    namespace qi = boost::spirit::qi;
    namespace ascii = boost::spirit::ascii;
    namespace spirit = boost::spirit;
    namespace phoenix =  boost::phoenix;

    // grammar 
    template <typename Iterator>
    struct TimingLibGrammar : 
        qi::grammar<Iterator, ascii::space_type>
    {
        qi::rule<Iterator, ascii::space_type> expression;

        TimingLibGrammar() : TimingLibGrammar::base_type(expression)
        {
            using qi::uint_;
            using qi::int_;
            using qi::double_;
            using qi::char_;
            using qi::_1;
            using qi::_2;
            using qi::_3;
            using qi::_val;
            using qi::lexeme;
            using qi::lit;

            expression = 
                +(
                    ((
                      "metal" 
                    >> uint_
                    >> double_
                    >> double_)[cout << "metal" << " "<< _1 << " " << _2 << " " << _3 << endl])
                        | 
                    ((
                      "wire"
                    >> double_
                    >> double_)[cout << "wire" << " "<< _1 << " " << _2 << endl])
                );

        }
    };
}


int main()
{
    using boost::spirit::ascii::space;
    using namespace client;
    string str = "metal 3 1.0 2.0";
    TimingLibGrammar<string::const_iterator> tlg;
    string::const_iterator iter = str.begin();
    string::const_iterator end = str.end();

    client::qi::phrase_parse(iter, end, tlg, space);

            return 0;
}

The main part of the code is actually very short. Please ignore those useless includes.

When I try parsing a line

metal 3 1.0 2.0,

the parser gave me the result as follows:

wire metal 3 1 2

This result is incorrect. It is supposed to output "metal 3 1 2", but I've no idea where this "wire" comes from. I also tried to follow the several sample code in the boost libs. But it still failed to get it right. The code is compiled with g++ 4.7.2 with -std=c++11 flag.

Any suggestion will be helpful. I'm new to boost spirit, so I hope to learn something. Thanks in advance.


Solution

  • The culprit is lines like these:

    cout << "metal" << " "<< _1 << " " << _2 << " " << _3 << endl
    
    cout << "wire" << " "<< _1 << " " << _2 << endl
    

    Remember that lambdas in Boost (both the Boost.Phoenix and Boost.Lambda varieties) are the result of operator overloading. cout << _1 (equivalent of operator<<(cout, _1)) creates a lambda, because _1 is defined by Phoenix (and imported into Spirit.Qi's namespace). cout << "wire" << _1, however, is operator<<(operator<<(cout, "wire"), _1). It will print out "wire" immediately, and use the return value of operator<<(cout, "wire") - cout - to construct the lambda. The inner operator<< is the standard library function.

    To solve this problem, wrap them in phoenix::val:

    cout << phoenix::val("metal") << " "<< _1 << " " << _2 << " " << _3 << endl
    
    cout << phoenix::val("wire") << " "<< _1 << " " << _2 << endl