Search code examples
c++structboost-spirit

How to parse data into C++ Struct with boost spirit if I have an "or" ('|') in my grammar


I have the following C++ Struct:

struct Dimension {
    enum Type { PARENT, CHILD, PIXEL };

    Type mWidth_type = Type::PIXEL;
    int mWidth = 0;

    Type mHeight_type = Type::PIXEL;
    int mHeight = 0;
};

My grammar looks like this:

+(char_ - "{") >> "{" >>
-(lit("width") >> ":" >> (int_ | lit("_parent") | lit("_child")) >> ";") >>
-(lit("height") >> ":" >> (int_ | lit("_parent") | lit("_child")) >> ";") >>
"}"

I have a hierarchical structure, where some nodes might take the width or/and height of the parent or child node. So in my logic I check each node's Dimension type first. If it is PIXEL I get the value, otherwise I ask the value form the parent or child node. Because of this in my file I can have the following possibilities (same for height):

width: 10;

In this case, I want to leave Type with the default enum PIXEL and set the value for mWidth.

or

widht: _parent;

In this case, I want to set Type to PARENT and leave mWidth on default 0.

or

width: _child;

In this case, I want to set Type to CHILD and leave mWidth on default 0.

How can I parse this into a Struct? If my dimensions could take only numbers, then I would be able to proceed, but I am stuck because this is a different case. Any hints, ideas, help is much appreciated!

EDIT1:

Here is an example of the text file which needs to be parsed into the above Struct:

.struct1 {
    width: 12;
    height: 50;
}

.struct2 {
    width: _parent;
    height: 50;
}

.struct3 {
    width: 40;
    height: _child;
}

.struct4 {
    width: _parent;
    height: _child;
}

Solution

  • I'd suggest to factor the AST type so as to not repeat yourself:

    struct Dimension {
        struct Value {
            enum Type { PARENT, CHILD, PIXEL } type;
            int value;
    
            friend std::ostream& operator<<(std::ostream& os, Value const& v) {
                switch(v.type) {
                    case PARENT: return os << "[PARENT:" << v.value << "]";
                    case CHILD:  return os << "[CHILD:"  << v.value << "]";
                    case PIXEL:  return os << "[PIXEL:"  << v.value << "]";
                }
                return os << "?";
            }
        };
    
        Value mWidth, mHeight;
    };
    

    Adapt it for fusion:

    BOOST_FUSION_ADAPT_STRUCT(Dimension::Value, (Dimension::Value::Type, type)(int, value))
    BOOST_FUSION_ADAPT_STRUCT(Dimension, (Dimension::Value, mWidth)(Dimension::Value, mHeight))
    

    Now, I'd write the grammar to match:

        start   = width_ ^ height_;
        width_  = lit("width")  >> ':' >> value_ >> ';';
        height_ = lit("height") >> ':' >> value_ >> ';';
    
        value_  =
            ( "_child"  >> attr(Dimension::Value::CHILD)  >> attr(0)
            | "_parent" >> attr(Dimension::Value::PARENT) >> attr(0)
            | eps       >> attr(Dimension::Value::PIXEL)  >> int_
            );
    

    Note:

    • you could use the permutation parser to be more versatile
    • you can see the use of attr to inject attributes so the branches all synthesize a vector2<Type, int>

    Adding debugging and a few test cases:

    Live On Coliru

    #define BOOST_SPIRIT_DEBUG
    #include <boost/fusion/adapted/struct.hpp>
    #include <boost/spirit/include/qi.hpp>
    
    namespace qi = boost::spirit::qi;
    
    struct Dimension {
        struct Value {
            enum Type { PARENT, CHILD, PIXEL } type;
            int value;
    
            friend std::ostream& operator<<(std::ostream& os, Value const& v) {
                switch(v.type) {
                    case PARENT: return os << "[PARENT:" << v.value << "]";
                    case CHILD:  return os << "[CHILD:"  << v.value << "]";
                    case PIXEL:  return os << "[PIXEL:"  << v.value << "]";
                }
                return os << "?";
            }
        };
    
        Value mWidth, mHeight;
    };
    
    BOOST_FUSION_ADAPT_STRUCT(Dimension::Value, (Dimension::Value::Type, type)(int, value))
    BOOST_FUSION_ADAPT_STRUCT(Dimension, (Dimension::Value, mWidth)(Dimension::Value, mHeight))
    
    template <typename It, typename Skipper>
    struct grammar : qi::grammar<It, Dimension(), Skipper>
    {
        grammar() : grammar::base_type(start) {
            using namespace qi;
    
            start   = width_ ^ height_;
            width_  = lit("width")  >> ':' >> value_ >> ';';
            height_ = lit("height") >> ':' >> value_ >> ';';
            value_  =
                ( "_child"  >> attr(Dimension::Value::CHILD)  >> attr(0)
                | "_parent" >> attr(Dimension::Value::PARENT) >> attr(0)
                | eps       >> attr(Dimension::Value::PIXEL)  >> int_
                );
    
            BOOST_SPIRIT_DEBUG_NODES((start)(value_)(width_)(height_))
        }
      private:
        qi::rule<It, Dimension(), Skipper> start;
        qi::rule<It, Dimension::Value(), Skipper> value_, width_, height_;
    };
    
    int main() {
        using It = std::string::const_iterator;
        grammar<It, qi::space_type> p;
    
        for (std::string const input : {
                "width: 10;      height: _child;",
                "width: _parent; height: 10;",
                "width: _child;  height: 10;"
                })
        {
            It f = input.begin(), l = input.end();
            std::cout << "\n-----------------------------------\n"
                      << "Parsing '" << input << "'\n";
    
            Dimension parsed;
            bool ok = qi::phrase_parse(f, l, p, qi::space, parsed);
    
            if (ok)
                std::cout << "Parsed: (" << parsed.mWidth << "x" << parsed.mHeight << ")\n";
            else
                std::cout << "Parse failed\n";
    
            if (f!=l)
                std::cout << "Remaining input: '" << std::string(f,l) << "'\n";
        }
    }
    

    Output (without debug information):

    -----------------------------------
    Parsing 'width: 10;      height: _child;'
    Parsed: ([PIXEL:10]x[CHILD:0])
    
    -----------------------------------
    Parsing 'width: _parent; height: 10;'
    Parsed: ([PARENT:0]x[PIXEL:10])
    
    -----------------------------------
    Parsing 'width: _child;  height: 10;'
    Parsed: ([CHILD:0]x[PIXEL:10])