2

So I'm trying hard to get boost::spirit::qi under my skin. My toy example so far is a parser that parses Wavefront OBJ material libraries that have the following format:

newmtl ShortBox
Ka  0.6 0.6 0.6
Kd  0.5 0.5 0.5
Ks  0 0 0
d  1
Ns  0
illum 2

However the ordering of the arguments to the material ShortBox can vary. I've created the following grammar that successfully parses it:

template <typename Iterator>
struct mtllib_grammar : qi::grammar<Iterator, qi::blank_type> {

    mtllib_grammar() : mtllib_grammar::base_type(mtl)
    {
        using qi::char_;
        using qi::double_;
        using qi::int_;


        mtl =
            (
            ("newmtl" >> +(char_ - qi::eol)  >> qi::eol >> *(mtl_details) >> *(mtl))
            | ("#" >> *(qi::char_ - qi::eol) >> qi::eol >> *(mtl))
            );

        mtl_details =
            (
            ("Ka" >> double_ >> double_ >> double_ >> qi::eol >> *(mtl_details))
            | ("Kd" >> double_ >> double_ >> double_ >> qi::eol >> *(mtl_details))
            | ("Ks" >> double_ >> double_ >> double_ >> qi::eol >> *(mtl_details))
            | ("d" >> int_ >> qi::eol >> *(mtl_details))
            | ("Ns" >> int_ >> qi::eol >> *(mtl_details))
            | ("illum" >> int_ >> qi::eol >> *(mtl_details))
            );
    }
    qi::rule<Iterator, qi::blank_type> mtl;
    qi::rule<Iterator, qi::blank_type> mtl_details;
};

Now I would like to build a std::map<std::string,Material> where Material is defined as:

struct Material {
    Material()
    {
        Ns = 0.0f;
        Ke = glm::vec3(0.0f);
        Kd = glm::vec3(0.0f);
        Ks = glm::vec3(0.0f);
        Ka = glm::vec3(0.0f);
    }
    ~Material() {}
    glm::vec3 Ka;
    glm::vec3 Kd;
    glm::vec3 Ks;
    glm::vec3 Ke;
    float Ns;
};

with the following fusion adaptations:

BOOST_FUSION_ADAPT_STRUCT(
    glm::vec3,
    (float, x)
    (float, y)
    (float, z)
    )

BOOST_FUSION_ADAPT_STRUCT(
    Material,
    (glm::vec3, Ka)
    (glm::vec3, Kd)
    (glm::vec3, Ks)
    (glm::vec3, Ke)
    (float, Ns)
    )

So my current idea is to change rule mtl_detailssuch that it returns a complete Material and rule mtl into returning a map of said Material with key being the string after newmtl. However, I'm lost at how to use attributes to build the Material object from the parse tree, mapping all hits of Ka, Kd, Ks ect. onto the same struct. Reading examples they all seem to either implicitly get mapped onto whatever variable is associated to it, or they map only to simple values, not objects.

Sheph
  • 625
  • 1
  • 6
  • 19
  • Should `d` and `illum` also be in `Material`? You probably want the [permutation parser](http://www.boost.org/libs/spirit/doc/html/spirit/qi/reference/operator/permutation.html). – llonesmiz Jan 27 '16 at 18:09
  • I could add them but not necessarily needed. Permutation parser sounds interesting, but how do I associate the values after eg `"Ks"` with the Ks field in `Material`? – Sheph Jan 27 '16 at 18:20
  • You need to put them in the same order as the ones in your adaptation macro (`Ka^Kd^Ks^Ke^Ns` ignoring the floats). I don't have time ATM but I'm sure an answer is coming. – llonesmiz Jan 27 '16 at 18:24
  • Thank you! Your tips led me to the solution. I changed my grammar to be less recursive per rule and some google-fu led me to omit[] to ignore my comment rule, which otherwise couldn't map onto `std::map`. Also, fiddling around I found that omit on unused fields combined with only mapping parsable parameters in `BOOST_FUSION_ADAPT_STRUCT` (Ke on my material cannot exist in the mtl file) makes it map perfectly. If you want the answer, type up a reply. Otherwise I'm adding my result below. – Sheph Jan 27 '16 at 19:27
  • Feel free to put your own answer, I'll upvote it. – llonesmiz Jan 27 '16 at 19:29

1 Answers1

3

Thanks to cv_and_he I found the solution. First ONLY the parameters that can occur in the file should be mapped using BOOST_FUSION_ADAPT_STRUCT, so don't map Material::Ke since it cannot occur naturally in the file format.

BOOST_FUSION_ADAPT_STRUCT(
    Material,
    (glm::vec3, Ka)
    (glm::vec3, Kd)
    (glm::vec3, Ks)
    (float, Ns)
    )

Next, my grammer ended up like the following:

template <typename Iterator>
struct mtllib_grammar : qi::grammar<Iterator, std::map<std::string, Material>(), qi::blank_type> {

    mtllib_grammar() : mtllib_grammar::base_type(mtl)
    {
        using qi::char_;
        using qi::float_;
        using qi::int_;
        using qi::omit;


        mtl =
            (
            *(("newmtl" >> string_rule >> qi::eol >> mtl_details) |
            ("#" >> omit[*(qi::char_ - qi::eol)] >> qi::eol ))
            );

        mtl_details =
            (
            ("Ka" >> glm_rule >> qi::eol) ^
            ("Kd" >> glm_rule >> qi::eol) ^
            ("Ks" >> glm_rule >> qi::eol) ^
            ("d" >> omit[int_] >> qi::eol) ^
            ("Ns" >> int_ >> qi::eol) ^
            ("illum" >> omit[int_] >> qi::eol)
            );

        string_rule = +(char_ - qi::eol);
        glm_rule = float_ >> float_ >> float_;
    }
    qi::rule<Iterator, std::map<std::string, Material>(), qi::blank_type> mtl;

    qi::rule<Iterator, Material(), qi::blank_type> mtl_details;

    qi::rule<Iterator, std::string(), qi::blank_type> string_rule;
    qi::rule<Iterator, glm::vec3(), qi::blank_type> glm_rule;
};

With omit around unused parameters PLUS comments. Otherwise the compiler borks about being unable to map onto the types given in the rules.

Sheph
  • 625
  • 1
  • 6
  • 19