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.

1

There are 1 answers

0
Sheph On BEST ANSWER

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.