c++boostqi

boost phoenix not referencing previous match


i am trying to parse a character stream and do a lookup on the second character parsed to get the number of repeats required for the third character. Variable objSize however does not get referenced correctly within repeat and full parse fails. If i assign objSize = 3 i get Full Parse Pass. Here is the code i am using. Any help much appreciated.

#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <iostream>
#include <iomanip>
#include <boost/spirit/include/qi.hpp>
#include <boost/phoenix/phoenix.hpp>
#include <boost/spirit/include/qi_char.hpp>
#include <boost/spirit/include/qi_repeat.hpp>
#include <string>
#include <vector>

namespace qi = boost::spirit::qi;
namespace phx = boost::phoenix;

 void write_to_con (const std::vector<unsigned char> &v){
         for (int i = 0; i <v.size(); ++i)
            std::cout << std::setw(2) << std::setfill('0') << std::hex <<int(v[i]) <<" ";
            std::cout << std::endl;
    }

 int getObjSize(unsigned char t)
    {
        if(t == '\x02')
            return 3;
        else return 0;
    }

int main()
{
    std::vector<unsigned char> v = {'\x01','\x02','\x03','\x03','\x03'};
    int objSize = 0;
    auto it = v.begin();
    bool match = boost::spirit::qi::parse(it, v.end(),
            //Begin Grammar
            (
             (qi::char_('\x01'))>>(qi::char_[([&](unsigned char c){phx::ref(objSize) = getObjSize(c);})])>>(qi::repeat(phx::ref(objSize))[qi::char_])
            )
            //End Grammar
              );
    if(match){
        std::cout << "Parse Success\n";
    }
    else
        std::cout << "Parse Fail\n";

    if (it != v.end()){
        std::vector<unsigned char> ret(it, v.end());
        std::cout << "Full Parse Failed at:";
        write_to_con(ret);
    }
    else
        std::cout << "Full Parse Pass\t Size:" <<v.size() << "\n";
    return 0;
}

Solution

  • You action is a lambda:

    [&](unsigned char c) { phx::ref(objSize) = getObjSize(c); };
    

    That's not a Phoenix Actor. Yet you are using phx::ref inside it (that's not gonna do what you want).

    One valid way to specify the action would seem:

    qi::char_[phx::ref(objSize) = phx::bind(getObjSize, qi::_1)]
    

    Here's a cleanified way to write that:

    qi::rule<decltype(it)> p;
    {
        using namespace qi;
        p = (
            (char_('\x01'))
         >> (char_[phx::ref(objSize) = phx::bind(getObjSize, _1)])
         >> (repeat(phx::ref(objSize))[char_])
        );
    }
    
    bool match = boost::spirit::qi::parse(it, v.end(), p);
    

    Prints Live On Coliru

    Parse Success
    Full Parse Pass  Size:5
    

    Drawbacks, improving it

    This grammar has drawbacks: it refers to a local (objectSize) and as such has lifetime issues. Ideally the local should be part of the rule. This would, at once, make the rule reentrant.

    Enter qi::locals<>:

    qi::rule<decltype(it), qi::locals<int> > p;
    {
        using namespace qi;
        _a_type objSize; // the first local placeholder
    
        p = (
            (char_('\x01'))
         >> (char_[objSize = phx::bind(getObjSize, _1)])
         >> (repeat(objSize)[char_])
        );
    }
    

    Bonus:

    Because getObjSize is so simple, the phx::bind seems too ugly. Why not integrate it:

    qi::rule<decltype(it), qi::locals<int> > p;
    {
        using namespace qi;
        _a_type objSize; // the first local placeholder
    
        struct size_f { int operator()(unsigned char t) const { return t=='\x02'?3:0; } };
        phx::function<size_f> get_size;
    
        p = (
            char_('\x01')
         >> char_[objSize = get_size(_1)]
         >> repeat(objSize)[char_]
        );
    }
    

    See it Live On Coliru