Search code examples
c++parsingboostboost-spiritboost-spirit-qi

Subparser attribute


I am having trouble composing grammars. Suppose I have a class Derived which inherits from Base. GrammarDerived has a Derived synthesized attribute while GrammarBase has a Base synthesized attribute. How can I use GrammarBase in a GrammarDerived parsing rule? I feel this should be possible because I can bind a Base & to a Derived & but nothing seems to work.

In other words, how do I get grammarBase to interact with _val by reference below?

template<typename Iterator>
struct GrammarDerived : public grammar <Iterator, Derived()> {
    GrammarDerived() : GrammarDerived::base_type(start) {
        start = rule1[bind(someFunc, _val)] >> grammarBase;
        rule1 = /* ... */;
    }
    rule<Iterator, Derived()> start;
    rule<Iterator, Derived()> rule1;
    GrammarBase grammarBase;
};

Solution

  • In a simpler setting, this shows how it's mostly a limitation of type deduction here:

    Derived parse_result;
    bool ok = qi::phrase_parse(f, l, base_, qi::space, data);
    

    will not work when the parser exposes a Base, however you can fix it with a "type hint" for the template instantion[1]:

    bool ok = qi::phrase_parse(f, l, base_, qi::space, static_cast<Base&>(data));
    

    Full demo Live On Coliru

    #include <algorithm>
    #include <boost/fusion/adapted/struct.hpp>
    #include <boost/spirit/include/qi.hpp>
    
    namespace qi      = boost::spirit::qi;
    namespace phoenix = boost::phoenix;
    
    struct Base {
        int x;
        double y;
    };
    
    BOOST_FUSION_ADAPT_STRUCT(Base, (int,x)(double,y))
    
    struct Derived : Base { };
    
    int main()
    {
        typedef std::string::const_iterator It;
        qi::rule<It, Base(), qi::space_type> base_ = qi::int_ >> qi::double_;
    
        std::string const input = "1 3.14";
        auto f(input.begin()), l(input.end());
    
        Derived parse_result;
        bool ok = qi::phrase_parse(f, l, base_, qi::space, static_cast<Base&>(parse_result));
        if (ok)
        {
            std::cout << "Parsed: " << parse_result.x << " " << parse_result.y << "\n";
        } else
        {
            std::cout << "Parse failed\n";
        }
    
        if (f != l)
        {
            std::cout << "Input remaining: '" << std::string(f,l) << "'\n";
        }
    }
    

    Alternatively

    You can avoid the confusion by explicitly passing a reference to the exposable attribute to the base parser/rule:

    template <typename It, typename Skipper = qi::space_type>
    struct derived_grammar : qi::grammar<It, Derived(), Skipper>
    {
        derived_grammar() : derived_grammar::base_type(start) {
            base_ = qi::int_ >> qi::double_;
            glue_ = base_ [ qi::_r1 = qi::_1 ];
            start = "derived:" >> glue_(qi::_val); // passing the exposed attribute for the `Base&` reference
        }
      private:
        qi::rule<It, Derived(),   Skipper> start;
        qi::rule<It, void(Base&), Skipper> glue_;
        qi::rule<It, Base(),      Skipper> base_; // could be a grammar instead of a rule
    };
    

    If you really insist, you can do without the glue_/base_ separationby using qi::attr_cast<Base, Base> (but I wouldn't do this for legibility).

    Full code again for reference Live On Coliru

    #define BOOST_SPIRIT_USE_PHOENIX_V3
    #include <algorithm>
    #include <boost/fusion/adapted/struct.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    
    namespace qi  = boost::spirit::qi;
    namespace phx = boost::phoenix;
    
    struct Base {
        int x;
        double y;
    };
    
    BOOST_FUSION_ADAPT_STRUCT(Base, (int,x)(double,y))
    
    struct Derived : Base { };
    
    template <typename It, typename Skipper = qi::space_type>
    struct derived_grammar : qi::grammar<It, Derived(), Skipper>
    {
        derived_grammar() : derived_grammar::base_type(start) {
            base_ = qi::int_ >> qi::double_;
            glue_ = base_ [ qi::_r1 = qi::_1 ];
            start = "derived:" >> glue_(qi::_val); // passing the exposed attribute for the `Base&` reference
        }
      private:
        qi::rule<It, Derived(),   Skipper> start;
        qi::rule<It, void(Base&), Skipper> glue_;
        qi::rule<It, Base(),      Skipper> base_; // could be a grammar instead of a rule
    };
    
    int main()
    {
        typedef std::string::const_iterator It;
        derived_grammar<It> g;
    
        std::string const input = "derived:1 3.14";
        auto f(input.begin()), l(input.end());
    
        Derived parse_result;
        bool ok = qi::phrase_parse(f, l, g, qi::space, parse_result);
        if (ok)
        {
            std::cout << "Parsed: " << parse_result.x << " " << parse_result.y << "\n";
        } else
        {
            std::cout << "Parse failed\n";
        }
    
        if (f != l)
        {
            std::cout << "Input remaining: '" << std::string(f,l) << "'\n";
        }
    }
    

    [1] referring to the instantiation of function template qi::phrase_parse here