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

Grammar parsing with Spirit::Qi fails


I'm new to Spirit::Qi and I'm trying to write a simple Wavefront Obj parser. I've followed the tutorials from the Boost::Spirit documentation site (link) and I got most of the inline rules working. I've started experimenting with grammars, but I cannot seem to get them working. After a while I did get it to compile, but the parsing fails. I really don't know what I am doing wrong.

To start out simple, I've created a simple text file containing the following:

v  -1.5701 33.8087 0.3592
v  -24.0119 0.0050 21.7439
v  20.8717 0.0050 21.7439
v  20.8717 0.0050 -21.0255
v  -24.0119 0.0050 -21.0255
v  -1.5701 0.0050 0.3592

Just to be sure: Reading the input file works fine.

I've written a small function that should parse the input string, but for some reason it fails:

bool Model::parseObj( std::string &data, std::vector<float> &v )
{
    struct objGram : qi::grammar<std::string::const_iterator, float()>
    {
        objGram() : objGram::base_type(vertex)
        {
            vertex = 'v' >> qi::float_
                         >> qi::float_
                         >> qi::float_; 
        }

        qi::rule<std::string::const_iterator, float()> vertex;
    };

    objGram grammar;

    return qi::phrase_parse( data.cbegin(), data.cend(),
                                grammar, iso8859::space, v );
}

qi::phrase_parse keeps returning false and the std::vector v is still empty at the end...

Any suggestions?

EDIT:

After adding adding space skippers (is that the correct name?), only the first 'v' is added to the std::vector encoded as a float (118.0f), but the actual numbers aren't added. My guess is that my rule isn't correct. I want to only add the numbers and skip the v's.

Here is my modified function:

bool Model::parseObj( std::string &data, std::vector<float> &v )
{
    struct objGram : qi::grammar<std::string::const_iterator, float(), iso8859::space_type>
    {
        objGram() : objGram::base_type(vertex)
        {
            vertex = qi::char_('v') >> qi::float_
                         >> qi::float_
                         >> qi::float_; 
        }

        qi::rule<std::string::const_iterator, float(), iso8859::space_type> vertex;
    } objGrammar;

    return qi::phrase_parse( data.cbegin(), data.cend(),
                                objGrammar, iso8859::space, v );
}

Solution

  • Your rule declares the wrong exposed attribute. Change it:

    qi::rule<std::string::const_iterator, std::vector<float>(), iso8859::space_type> vertex;
    

    However, since you don't template your grammar struct on anything (like iterator/skipper type), it makes no sense to have a grammar struct. Instead, let phrase_parse simply deduce the iterator, skipper and rule types all at once and write:

    bool parseObj(std::string const& data, std::vector<float> &v )
    {
        return qi::phrase_parse( 
                data.cbegin(), data.cend(),
                'v' >> qi::float_ >> qi::float_ >> qi::float_, 
                qi::space, v);
    }
    

    I think you'll agree that's more to the point. And as a bonus, it "just works"(TM) because of the awesomeness that is automatic attribute propagation rules.

    However, seeing your grammar, you'll certainly want to see these:

    • How to parse space-separated floats in C++ quickly? showing how to parse into a vector of structs

      struct float3 {
          float x,y,z;
      };
      
      typedef std::vector<float3> data_t;
      

      with little or no extra work. Oh and it benchmarks the spirit approach reading a 500Mb file against the competing fscanf and atod calls. So, it parses multiple lines at once :)

    • Use the qi::double_ parser instead of qi::float_ even if you're ultimately assigning to single-precision float variables. See Boost spirit floating number parser precision