Search code examples
c++boostboost-spiritboost-fusion

How to convert a std::vector<std::string> to be a member of a struct in boost::spirit?


I have a chunk of Spirit code which correctly parses std::string input = "RED.MAGIC( 1, 2, 3 )[9].GREEN" into a simple std::vector<std::string>, by using std::vector<std::string> as the primary attribute.

I would like to replace the std::vector<std::string> into a struct my_rec which contains a std::vector<std::string>, but continue using the auto generator, if possible.

When I compile with -DUSE_MY_REC, I get a wall of impenetrable compile errors.

sample compile and run

/tmp$ g++ -g -std=c++11 sandbox.cpp -o sandbox && ./sandbox 
Finished.
MATCHED
/tmp$ g++ -DUSE_MY_REC -g -std=c++11 sandbox.cpp -o sandbox && ./sandbox
WALL OF COMPILE ERRORS --------------------------------------------

sandbox.cpp

// #define BOOST_SPIRIT_DEBUG
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_fusion.hpp>
#include <boost/fusion/include/adapt_struct.hpp>

#include <string>
#include <iostream>

namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;

#ifdef USE_MY_REC
struct my_rec
{
    std::vector<std::string>    m_pieces;
};

BOOST_FUSION_ADAPT_STRUCT(
  my_rec,
  (std::vector<std::string>,  m_pieces)
)

typedef struct my_rec            MY_TYPE;
#else
typedef std::vector<std::string> MY_TYPE;
#endif

template <typename ITERATOR>
struct my_parser : 
    qi::grammar<ITERATOR, MY_TYPE(), ascii::space_type>
{
    my_parser() : 
        my_parser::base_type( start )
    {
        start %= ( color | fun_call ) % '.'
                ;

        color %=
                qi::string( "RED" )
                | qi::string( "GREEN" )
                | qi::string( "BLUE" )
                ;

        fun_call %= 
                qi::string( "MAGIC" )
                >> '('
                >> +qi::char_("0-9") % ','
                >> ')'
                >> '['
                >> +qi::char_("0-9")
                >> ']'
                ;
    }
    qi::rule<ITERATOR, MY_TYPE(),     ascii::space_type> start, fun_call;
    qi::rule<ITERATOR, std::string(), ascii::space_type> color;
};

int
main( int argc, char* argv[] )
{
    namespace qi    = boost::spirit::qi;
    namespace ascii = boost::spirit::ascii;

    MY_TYPE                  v;
    std::string              str = "RED.MAGIC( 1, 2, 3 )[9].GREEN";
    std::vector<std::string> exp = {{ "RED", "MAGIC", "1", "2", "3", "9", "GREEN" }};
    auto                     it  = str.begin(), end = str.end();
    my_parser<decltype(it)>  g;

    if( qi::phrase_parse( it, end, g, ascii::space, v ) && it==end ) 
    {
        std::cout << "Finished." << std::endl;
#ifndef USE_MY_REC
        if ( !std::equal( v.begin(), v.end(), exp.begin() ))
        {
            std::cout << "MISMATCH" << std::endl;
            for( const auto& x : v )
                std::cout << x << std::endl;
        } else {
            std::cout << "MATCHED" << std::endl;
        }
#endif
    } else
        std::cout << "Error." << std::endl;

    return 0;
}

Solution

  • As shown in the question linked in the comments in order to assign to an adapted struct with a single member you need to use qi::eps in a sequence. Also you need to change the attribute of your intermediate fun_call rule to std::vector<std::string>().

    // #define BOOST_SPIRIT_DEBUG
    #include <boost/config/warning_disable.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix_fusion.hpp>
    #include <boost/fusion/include/adapt_struct.hpp>
    
    #include <string>
    #include <iostream>
    
    namespace qi = boost::spirit::qi;
    namespace ascii = boost::spirit::ascii;
    
    
    struct my_rec
    {
        std::vector<std::string>    m_pieces;
    };
    
    BOOST_FUSION_ADAPT_STRUCT(
      my_rec,
      (std::vector<std::string>,  m_pieces)
    )
    
    typedef struct my_rec            MY_TYPE;
    
    
    template <typename ITERATOR>
    struct my_parser : 
        qi::grammar<ITERATOR, MY_TYPE(), ascii::space_type>
    {
        my_parser() : 
            my_parser::base_type( start )
        {
            start %= qi::eps >>( color | fun_call ) % '.' //add qi::eps to make the adaptation of the single member struct work
                    ;
    
            color %=
                    qi::string( "RED" )
                    | qi::string( "GREEN" )
                    | qi::string( "BLUE" )
                    ;
    
            fun_call %= 
                    qi::string( "MAGIC" )
                    >> '('
                    >> +qi::char_("0-9") % ','
                    >> ')'
                    >> '['
                    >> +qi::char_("0-9")
                    >> ']'
                    ;
        }
        qi::rule<ITERATOR, MY_TYPE(),     ascii::space_type> start;
        qi::rule<ITERATOR, std::vector<std::string>(), ascii::space_type> fun_call; //changed this rule's attribute to vector<string>
        qi::rule<ITERATOR, std::string(), ascii::space_type> color;
    };
    
    int
    main( int argc, char* argv[] )
    {
        namespace qi    = boost::spirit::qi;
        namespace ascii = boost::spirit::ascii;
    
        MY_TYPE                  v;
        std::string              str = "RED.MAGIC( 1, 2, 3 )[9].GREEN";
        std::vector<std::string> exp;
        exp.push_back("RED");
        exp.push_back("MAGIC");
        exp.push_back("1");
        exp.push_back("2");
        exp.push_back("3");
        exp.push_back("9");
        exp.push_back("GREEN");
        auto                     it  = str.begin(), end = str.end();
        my_parser<decltype(it)>  g;
    
        if( qi::phrase_parse( it, end, g, ascii::space, v ) && it==end ) 
        {
            std::cout << "Finished." << std::endl;
    
            if ( !std::equal( v.m_pieces.begin(), v.m_pieces.end(), exp.begin() ))
            {
                std::cout << "MISMATCH" << std::endl;
                for( const auto& x : v.m_pieces )
                    std::cout << x << std::endl;
            } else {
                std::cout << "MATCHED" << std::endl;
            }
    
        } else
            std::cout << "Error." << std::endl;
    
        return 0;
    }