Search code examples
c++boost-spiritboost-spirit-qiwavefront

auto concatenation of parse results into vectors


I've written some rules to parse floats into two std::vector's of floats, which in turn are stored in a struct:

Data input:

#
# object name01
#

v  -1.5701 33.8087 0.3592
v  -24.0119 0.0050 21.7439
# a comment

vn 0.0000 0.5346 0.8451
vn 0.8331 0.5531 -0.0000
# another comment

Struct:

struct ObjParseData
{
    ObjParseData() : verts(), norms() {}

    std::vector<float> verts;
    std::vector<float> norms;
};

And the relevant parsing code:

struct objGram : qi::grammar<std::string::const_iterator, ObjParseData(), iso8859::space_type>
    {
        objGram() : objGram::base_type(start)
        {
            vertex  = 'v' >> qi::double_ >> qi::double_ >> qi::double_;
            normal  = "vn" >> qi::double_ >> qi::double_ >> qi::double_;
            comment = '#' >> qi::skip(qi::blank)[ *(qi::print) ];
            vertexList = *(vertex | comment);
            normalList = *(normal | comment);
            start = vertexList >> normalList;
        }

        qi::rule<std::string::const_iterator, ObjParseData(), iso8859::space_type> start;
        qi::rule<std::string::const_iterator, std::vector<float>(), iso8859::space_type> vertexList;
        qi::rule<std::string::const_iterator, std::vector<float>(), iso8859::space_type> normalList;
        qi::rule<std::string::const_iterator, std::vector<float>(), iso8859::space_type> vertex;
        qi::rule<std::string::const_iterator, std::vector<float>(), iso8859::space_type> normal;
        qi::rule<std::string::const_iterator, iso8859::space_type> comment;
    } objGrammar;


    ObjParseData resultData;

    std::string::const_iterator f = data.cbegin();
    bool res = qi::phrase_parse( f, data.cend(), objGrammar, iso8859::space, resultData );

And this works. It parses all the floats preceded by 'v' into the verts vector of the struct and all floats preceded by "vn" into norms. Which is great, but I don't really know why this works.

Now if I understand this correctly, a rule defined like below puts all its results into a std::vector of floats.

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

So, looking at the parsing code shown above and knowing that a rule like vertex parses into a std::vector of floats, apparently a rule like vertexList (the one shown above) concatenates the results from vertex into one std::vector of floats? So seeing this behaviour, you would think that you could just write those two rules (vertex and vertexList) as one, but unfortunately this doesn't work:

vertex  = *('v' >> qi::double_ >> qi::double_ >> qi::double_) | comment;
normal  = *("vn" >> qi::double_ >> qi::double_ >> qi::double_) | comment;
comment = '#' >> qi::skip(qi::blank)[ *(qi::print) ];
start = vertex >> normal;

The code does compile and qi::phrase_parse does return a succesfull parse, but the std::vector's in the struct aren't filled anymore.. What am I missing here?


Solution

  • You missplaced the grouping parentheses: expanding

        vertexList = *(vertex | comment);
        normalList = *(normal | comment);
    

    by eliminating subrules leads to

        vertex     = *(('v'  >> qi::double_ >> qi::double_ >> qi::double_) | comment);
        normal     = *(("vn" >> qi::double_ >> qi::double_ >> qi::double_) | comment);
    

    or, as I'd prefer:

    Full working sample (please make your code samples SSCCE next time? https://meta.stackexchange.com/questions/22754/sscce-how-to-provide-examples-for-programming-questions):

    #include <iterator>
    #include <fstream>
    #include <boost/fusion/adapted.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/karma.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    
    namespace qi    = boost::spirit::qi;
    namespace karma = boost::spirit::karma;
    namespace phx   = boost::phoenix;
    
    struct ObjParseData
    {
        ObjParseData() : verts(), norms() {}
    
        std::vector<float> verts;
        std::vector<float> norms;
    };
    
    BOOST_FUSION_ADAPT_STRUCT(ObjParseData, (std::vector<float>, verts)(std::vector<float>, norms))
    
    
    
    template <typename It, typename Skipper = qi::space_type>
        struct parser : qi::grammar<It, ObjParseData(), Skipper>
    {
        parser() : parser::base_type(start)
        {
            using namespace qi;
    
    
            vertex     = 'v'  >> qi::double_ >> qi::double_ >> qi::double_;
            normal     = "vn" >> qi::double_ >> qi::double_ >> qi::double_;
            comment    = '#' >> qi::skip(qi::blank)[ *(qi::print) ];
    #if 0
            vertexList = *(vertex | comment);
            normalList = *(normal | comment);
            start      = vertexList >> normalList;
    #else
            vertex     = *(comment | ('v'  >> qi::double_ >> qi::double_ >> qi::double_));
            normal     = *(comment | ("vn" >> qi::double_ >> qi::double_ >> qi::double_));
            start      = vertex >> normal;                                              
    #endif
    
            BOOST_SPIRIT_DEBUG_NODE(start);
        }
    
      private:
        qi::rule<std::string::const_iterator, ObjParseData(), qi::space_type> start;
        qi::rule<std::string::const_iterator, std::vector<float>(), qi::space_type> vertexList;
        qi::rule<std::string::const_iterator, std::vector<float>(), qi::space_type> normalList;
        qi::rule<std::string::const_iterator, std::vector<float>(), qi::space_type> vertex;
        qi::rule<std::string::const_iterator, std::vector<float>(), qi::space_type> normal;
        qi::rule<std::string::const_iterator, qi::space_type> comment;
    };
    
    bool doParse(const std::string& input)
    {
        typedef std::string::const_iterator It;
        auto f(begin(input)), l(end(input));
    
        parser<It, qi::space_type> p;
        ObjParseData data;
    
        try
        {
            bool ok = qi::phrase_parse(f,l,p,qi::space,data);
            if (ok)   
            {
                std::cout << "parse success\n";
                std::cout << "data: " << karma::format_delimited(
                        "v: " << karma::auto_ << karma::eol <<
                        "n: " << karma::auto_ << karma::eol, ' ', data);
            }
            else      std::cerr << "parse failed: '" << std::string(f,l) << "'\n";
    
            if (f!=l) std::cerr << "trailing unparsed: '" << std::string(f,l) << "'\n";
            return ok;
        } catch(const qi::expectation_failure<It>& e)
        {
            std::string frag(e.first, e.last);
            std::cerr << e.what() << "'" << frag << "'\n";
        }
    
        return false;
    }
    
    int main()
    {
        std::ifstream ifs("input.txt", std::ios::binary);
        ifs.unsetf(std::ios::skipws);
        std::istreambuf_iterator<char> f(ifs), l;
    
        bool ok = doParse({ f, l });
    }
    

    Output:

    parse success
    data: v:  -1.57 33.809 0.359 -24.012 0.005 21.744 
     n:  0.0 0.535 0.845 0.833 0.553 0.0