2

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 );
}
Krienie
  • 611
  • 1
  • 6
  • 14
  • 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 Jul 08 '13 at 21:38
  • @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 :) Cheers – sehe Jul 08 '13 at 21:48

1 Answers1

2

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

Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Great answer! Thank you very much. So what you're saying in the beginning is that I should only use grammar structs if I use templates? Another question: how can I implement multiple rules when using phrase_parse the way you describe? Because eventually I also want to parse floats preceded by "vn" and "vt", which are all in the same *.obj file. Do I just run the phrase_parse function for each separate rule? Thx :) – Krienie Jul 08 '13 at 22:05
  • @KrienLinnenbank I was saying the grammar struct wasn't pulling it's weight. There _are_ more reasons to have grammar structs, but you wouldn't have it locally defined then, anyways. – sehe Jul 08 '13 at 22:12
  • 1
    Regarding the second question: I'd split out a grammar struct anyways, and parse your grammar _as it is_ (that's what you use a parser generator for, after all!). I.e. I'd **not** run separate lines through separate rules. Perhaps you should make it a question if you get stuck. (For now, be sure to visit the first linked other answer for ideas) – sehe Jul 08 '13 at 22:13