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

boost spirit rule - rule concatenation


I have a map of string-rule pairs and I would like to create a "joint rule"(rule_t joint_rule;) of them somehow. If I do this this way:

joint_rule = convert_logformat["%h"] >> convert_logformat["%t"];

than the joint rule with the parse_phrase matches the string

std::string entry = "127.0.0.1 [16/Aug/2012:01:50:02 +0000]";

But if I create the joint rule this way:

for (it = convert_logformat.begin(); it != convert_logformat.end(); it++)
{
   joint_rule = joint_rule.copy() >> (*it).second.copy();
}

It does not match the same string. Why? How could I achieve something similar to the latter?


Relevant code:

    template <typename Iterator>
bool parse_logentry(Iterator first, Iterator last, std::vector<char>& ip, std::vector<char>& timestamp, std::vector<char>& req, unsigned int& status, unsigned int& transferred_bytes, std::vector<char>& referer, std::vector<char>& ua)
{
    using boost::spirit::qi::char_;
    using boost::spirit::qi::int_;
    using boost::spirit::qi::uint_;
    using boost::spirit::qi::phrase_parse;
    using boost::spirit::ascii::space;
    using boost::spirit::ascii::space_type;
    using boost::phoenix::ref;
    using boost::phoenix::push_back;
    using boost::spirit::qi::_1;
    using boost::spirit::qi::lexeme;
    using boost::spirit::qi::rule;

    typedef boost::spirit::qi::rule<Iterator, std::string(), space_type> rule_t;
    rule_t ip_rule, timestamp_rule, user_rule, req_rule, ref_rule, ua_rule, bytes_rule, status_rule;
    ip_rule %= lexeme[(+char_("0-9."))[ref(ip) = _1]];
    timestamp_rule %= lexeme[('[' >> +(~char_(']')) >> ']')[ref(timestamp) = _1]];
    user_rule %= lexeme[(+~char_(" "))];
    req_rule %= lexeme[('"' >> +(~char_('"')) >> '"')[ref(req) = _1]];
    ref_rule %= lexeme[('"' >> +(~char_('"')) >> '"')[ref(referer) = _1]];
    ua_rule %= lexeme[('"' >> +(~char_('"')) >> '"')[ref(ua) = _1]];
    bytes_rule %= uint_[ref(transferred_bytes) = _1];
    status_rule %= uint_[ref(status) = _1];
    std::map<std::string, rule_t> convert_logformat;
    typename std::map<std::string, rule_t>::iterator it;

    convert_logformat.insert(std::pair<std::string, rule_t>("%h", ip_rule));
    convert_logformat.insert(std::pair<std::string, rule_t>("%t", timestamp_rule));
    //convert_logformat.insert(std::pair<std::string, rule_t>("%r", req_rule));
    //convert_logformat.insert(std::pair<std::string, rule_t>("%>s", status_rule));
    //convert_logformat.insert(std::pair<std::string, rule_t>("%b", bytes_rule));
    //convert_logformat.insert(std::pair<std::string, rule_t>("%u", user_rule));
    //convert_logformat.insert(std::pair<std::string, rule_t>("%{User-agent}i", ua_rule));
    //convert_logformat.insert(std::pair<std::string, rule_t>("%{Referer}i", ref_rule));

    rule_t joint_rule;

    //joint_rule = convert_logformat["%h"] >> convert_logformat["%t"];

    for (it = convert_logformat.begin(); it != convert_logformat.end(); it++)
    {
        joint_rule = joint_rule.copy() >> (*it).second.copy();
        std::cout << (*it).first << ": " << typeid((*it).second).name() << "\n";
    }

    std::cout << "convert_logformath: " << typeid(convert_logformat["%h"]).name() << "\n";

    bool r = phrase_parse(first, last, joint_rule, space);
    if (first != last)
        return false;
    return r;
}

Solution

  • Ahem. It is really quite simple. You should initialize your variables :)

    rule_t joint_rule; // what is it initialized to?
    
    for (auto it = convert_logformat.begin(); it != convert_logformat.end(); it++)
    {
        joint_rule = joint_rule.copy() >> (*it).second.copy();
    }
    

    Change the first line to

    rule_t joint_rule = qi::eps;
    

    And it works:

    sehe@mint12:/tmp$ ./test 
    127.0.0.1
    16/Aug/2012:01:50:02 +0000
    

    Your parser lacks some (good) common practice. See below for tidied up source (C++11).

    Note that using a map to store the rules looks odd, because maps iteration will order by the key, not insertion order.

    See the code live at http://liveworkspace.org/code/a7f2f94840d63fce43d8c3f56236330e

    // #define BOOST_SPIRIT_DEBUG
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <typeinfo>
    
    namespace qi = boost::spirit::qi;
    namespace phx = boost::phoenix;
    
    template <typename Iterator>
    struct Grammar : qi::grammar<Iterator, std::string(), qi::space_type>
    {
        Grammar() : Grammar::base_type(joint_rule)
        {
            using namespace qi;
            ip_rule        %= lexeme[ (+char_("0-9."))[phx::ref(ip)                      =  _1] ]; 
            timestamp_rule %= lexeme[ ('[' >> +(~char_(']')) >> ']')[phx::ref(timestamp) =  _1] ]; 
            user_rule      %= lexeme[ (+~char_(" "))                                ]; 
            req_rule       %= lexeme[ ('"' >> +(~char_('"')) >> '"')[phx::ref(req)       =  _1] ]; 
            ref_rule       %= lexeme[ ('"' >> +(~char_('"')) >> '"')[phx::ref(referer)   =  _1] ]; 
            ua_rule        %= lexeme[ ('"' >> +(~char_('"')) >> '"')[phx::ref(ua)        =  _1] ]; 
            bytes_rule     %= uint_[phx::ref(transferred_bytes)                          =  _1  ]; 
            status_rule    %= uint_[phx::ref(status)                                     =  _1  ]; 
    
            auto convert_logformat = std::map<std::string, rule_t> {
                { "%h"            , ip_rule }       ,
                { "%t"            , timestamp_rule },
            //  { "%r"            , req_rule }      ,
            //  { "%>s"           , status_rule }   ,
            //  { "%b"            , bytes_rule }    ,
            //  { "%u"            , user_rule }     ,
            //  { "%{User-agent}i", ua_rule }       ,
            //  { "%{Referer}i"   , ref_rule }
            };
    
            joint_rule = eps;
    
            for (auto const& p: convert_logformat)
            {
                joint_rule = joint_rule.copy() >> p.second.copy();
            }
    
            BOOST_SPIRIT_DEBUG_NODE(ip_rule);
            BOOST_SPIRIT_DEBUG_NODE(timestamp_rule);
            BOOST_SPIRIT_DEBUG_NODE(user_rule);
            BOOST_SPIRIT_DEBUG_NODE(req_rule);
            BOOST_SPIRIT_DEBUG_NODE(ref_rule);
            BOOST_SPIRIT_DEBUG_NODE(ua_rule);
            BOOST_SPIRIT_DEBUG_NODE(bytes_rule);
            BOOST_SPIRIT_DEBUG_NODE(status_rule);
        }
    
        typedef qi::rule<Iterator, std::string(), qi::space_type> rule_t;
        rule_t ip_rule, timestamp_rule, user_rule, req_rule, ref_rule, ua_rule, bytes_rule, status_rule;
        rule_t joint_rule;
    
        std::vector<char> ip;
        std::vector<char> timestamp;
        std::vector<char> req;
        unsigned int status;
        unsigned int transferred_bytes;
        std::vector<char> referer;
        std::vector<char> ua;
    };
    
    template <typename Iterator>
    bool parse_logentry(Iterator first, Iterator last, 
            Grammar<Iterator>& parser)
    {
        bool r = phrase_parse(first, last, parser, qi::space);
    
        return (r && (first == last));
    }
    
    int main(void)
    {
        std::string entry = "127.0.0.1 [16/Aug/2012:01:50:02 +0000]";
        //std::string entry = "127.0.0.1 [16/Aug/2012:01:50:02 +0000] \"GET /check.htm HTTP/1.1\" 200 17 \"-\" \"AgentName/0.1 libwww-perl/5.833\"";
    
        Grammar<std::string::iterator> parser;
    
        if (parse_logentry(entry.begin(), entry.end(), parser))
        {
            for (auto i : parser.ip)
                std::cout << i;
            std::cout << "\n";
    
            for (auto ts: parser.timestamp)
                std::cout << ts;
            std::cout << "\n";
        }
        else
        {
            std::cout << "not ok\n";
        }
    
        return 0;
    }
    

    Note that, among other things, this setup allows you to enable debugging of your grammar by simply defining BOOST_SPIRIT_DEBUG at the start.