Krienie
Krienie

Reputation: 631

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 );
}

Upvotes: 2

Views: 573

Answers (1)

sehe
sehe

Reputation: 393709

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

Upvotes: 2

Related Questions