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

Saving Boost Spirit recursive match results to a C++ struct


I succeeded to parse strings like "A, (B, C), (D, E, (F, G)), H".

But, I failed to save the matched results to a C++ struct.

I couldn't figure out the rule's right attribute types.

Following is a minimum test case for the parsing.

TEST_CASE("recursive match", "[qi]")
{
    namespace qi = boost::spirit::qi;
    using qi::char_;

    struct recursive_match
        : qi::grammar<std::string::iterator, qi::ascii::space_type>
    {
        recursive_match() : recursive_match::base_type(div_)
        {
            subdiv_ = '(' >> div_ >> ')';
            div_ = (char_("A-Z") | subdiv_) % ',';
        }

        qi::rule<std::string::iterator, qi::ascii::space_type> subdiv_;
        qi::rule<std::string::iterator, qi::ascii::space_type> div_;
    };

    std::string s = "A, (B, C), (D, E, (F, G)), H";
    auto begin = s.begin();
    auto end = s.end();
    recursive_match rule_;
    bool r = qi::phrase_parse(begin, end, rule_, qi::ascii::space);
    REQUIRE(r);
    REQUIRE(begin == end);
}

Anything will be helpful for me. Thanks.


Solution

  • I would suggest a recursive variant:

    using node = boost::make_recursive_variant<
            char,
            std::vector<boost::recursive_variant_>
        >::type;
    
    using nodes = std::vector<node>;
    

    Now, you'd declare the rules slightly more simply:

    qi::rule<It, Ast::node(),  qi::ascii::space_type> node_;
    qi::rule<It, Ast::nodes(), qi::ascii::space_type> list_;
    

    Where the definition is something like

    node_ = char_("A-Z") | '(' >> list_ >> ')';
    list_ = node_ % ',';
    

    Full Demo

    Adding some code for debug output:

    Live On Coliru

    //#define BOOST_SPIRIT_DEBUG
    #include <boost/spirit/include/qi.hpp>
    #include <boost/fusion/include/io.hpp>
    namespace qi = boost::spirit::qi;
    
    namespace std {
        // for debug output
        template <typename T>
        inline static std::ostream& operator<<(std::ostream& os, std::vector<T> const& v) {
            os << "(";
            bool first = true;
            for (auto& el : v) {
                (first?os:os << ", ") << el;
                first = false;
            }
            return os << ")";
        }
    }
    
    namespace Ast {
        using node = boost::make_recursive_variant<
                char,
                std::vector<boost::recursive_variant_>
            >::type;
    
        using nodes = std::vector<node>;
    }
    
    template <typename It = std::string::const_iterator>
    struct recursive_match : qi::grammar<It, Ast::nodes(), qi::ascii::space_type> {
        recursive_match() : recursive_match::base_type(list_) {
            using namespace qi;
    
            node_ = char_("A-Z") | '(' >> list_ >> ')';
            list_ = node_ % ',';
    
            BOOST_SPIRIT_DEBUG_NODES((node_)(list_))
        }
    
      private:
        qi::rule<It, Ast::node(),  qi::ascii::space_type> node_;
        qi::rule<It, Ast::nodes(), qi::ascii::space_type> list_;
    };
    
    int main() {
        using qi::char_;
    
        std::string const s = "A, (B, C), (D, E, (F, G)), H";
        auto begin = s.begin();
        auto end = s.end();
        recursive_match<> rule_;
        Ast::nodes parsed;
        bool ok = qi::phrase_parse(begin, end, rule_, qi::ascii::space, parsed);
    
        if (ok)
            std::cout << "Parsed: " << parsed << "\n";
        else
            std::cout << "Parse failed\n";
    
        if (begin != end)
            std::cout << "Remaining unparsed input: '" << std::string(begin, end) << "'\n";
    }
    

    Prints:

    Parsed: (A, (B, C), (D, E, (F, G)), H)