2
votes

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 );
}
1
1) If you provide complete code you have a chance of more attention 2) Making a quick glance ... 118 is ascii code for your 'v' so it propagates to the output by mistake. If you want to avoid it you have two choices ( maybe more ): a) qi::lit('v') >> qi::float_ >> qi::float_ >> qi::float_ b) qi::omit[ qi::char_('v') ] >> qi::float_ >> qi::float_ >> qi::float_G. Civardi
@G.Civardi Ah. You found the question around the same time I did. You missed the culprit, but I linked to your answer on the float precision question :) Cheerssehe
@sehe no problem and many thanks :-) CheersG. Civardi

1 Answers

2
votes

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