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

boost spirit qi assign value from subrule


I am trying to parse 2 different type of strings and assign values into structures. For performance I am trying to use boost spirit subrules.

strings can be of the following types

   Animal Type | Animal Attributes 

Ex
   DOG | Name=tim | Barks=Yes | Has a Tail=N | Address=3 infinite loop
   BIRD| Name=poc | Tweets=Yes| Address=10 stack overflow street

The values are stored in an array of Dog and Bird structures below

#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp> 
#include <boost/spirit/repository/include/qi_subrule.hpp>  
#include <boost/spirit/include/qi_symbols.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <string> 
#include <iostream> 

using std::cout; 
using std::endl; 
using std::cerr; 

struct Dog 
{
   std::string Name; 
   bool Barks;
   bool HasATail; 
   std::string Address; 
}; 

struct Bird
{
    std::string Name; 
    bool Tweets; 
    std::string Address; 
};

namespace qi = boost::spirit::qi;
namespace repo = boost::spirit::repository;
namespace ascii = boost::spirit::ascii;
namespace phx = boost::phoenix; 

template <typename Iterator>
struct ZooGrammar : public qi::grammar<Iterator, ascii::space_type>
{
     ZooGrammar() : ZooGrammar::base_type(start_)
     {

        using qi::char_; 
        using qi::lit_; 
        using qi::_1;
        using boost::phoenix::ref; 

        boost::spirit::qi::symbols<char, bool> yesno_; 
        yesno_.add("Y", true)("N", false); 

         start_ = (
             dog_ | bird_, 
             dog_ = "DOG" >> lit_[ref(d.Name) = _1]>> '|'
                     >>"Barks=">>yesno_[ref(d.Barks) = _1] >>'|'
                     >>"Has a Tail=">>yesno_[ref(d.HasATail) = _1] >> '|'
                     >>lit_[ref(d.Address) = _1]
             , 
             bird_ = "BIRD" >> lit_[ref(b.Name) = _1]>> '|'
                     >>"Tweets=">>yesno_[ref(b.Tweets) = _1] >>'|'
                     >>lit_[ref(b.Address) = _1]
         );
     } 

    qi::rule<Iterator, ascii::space_type> start_; 
    repo::qi::subrule<0> dog_;
    repo::qi::subrule<1> bird_; 
    Bird b;
    Dog d; 
}; 


int main()
{
    std::string test1="DOG | Name=tim | Barks=Yes | Has a Tail=N | Address=3 infinite loop"; 
    std::string test2="BIRD| Name=poc | Tweets=Yes| Address=10 stack overflow street"; 
    using boost::spirit::ascii::space;
    typedef std::string::const_iterator iterator_type;
    typedef ZooGrammar<iterator_type> grammar;
    iterator_type start = test1.begin(); 
    iterator_type end   = test1.end();
    ZooGrammar g; 
    if(boost::spirit::qi::phrase_parse(start, end, g, space))
    {
        cout<<"matched"<<endl; 
    }
}

The code above crashes the compiler GCC 4.8 and 4.9. I don't know where I am making the mistake.

Please test run the code above in Coliru link

Many thanks in advance !


Solution

  • Subrules are a bit antiquated. To be honest, I didn't even know there was still such a thing in Spirit V2.

    I suggest using regular Spirit V2 attribute propagation, which makes things a bit more readable at once:

    dog_ = qi::lit("DOG") >> '|' >> "Name=" >> lit_   >> '|'
                          >> "Barks="       >> yesno_ >> '|'
                          >> "Has a Tail="  >> yesno_ >> '|'
                          >> "Address="     >> lit_
         ;
    
    bird_ = qi::lit("BIRD") >> '|' >> "Name=" >> lit_   >> '|'
                            >> "Tweets="      >> yesno_ >> '|'
                            >> "Address="     >> lit_
          ;
    
    start_ = dog_ | bird_;
    

    I've imagined a lit_ rule (as qi::lit_ doesn't ring any bells):

    lit_   = qi::lexeme [ *~qi::char_('|') ];
    

    Of course, you need to adapt the attribute types as far as they don't have builtin support (as with boost::variant<Dog, Bird>, std::string and bool which are all handled without any additional code):

    BOOST_FUSION_ADAPT_STRUCT(Dog, 
        (std::string, Name)(bool, Barks)(bool, HasATail)(std::string, Address))
    BOOST_FUSION_ADAPT_STRUCT(Bird, 
        (std::string, Name)(bool, Tweets)(std::string, Address))
    

    Now with the program extended to print some debug information, output is: Live On Coliru

    Matched: [DOG|Name=tim |Barks=Yes|Has a Tail=No|Address=3 infinite loop]
    Matched: [BIRD|Name=poc |Tweets=Yes|Address=10 stack overflow street]
    

    Full Sample Code

    //#define BOOST_SPIRIT_DEBUG
    #define BOOST_SPIRIT_USE_PHOENIX_V3
    #include <boost/fusion/adapted/struct.hpp>
    #include <boost/spirit/include/qi.hpp> 
    #include <boost/spirit/include/qi_symbols.hpp>
    
    static const char* YesNo(bool b) { return b?"Yes":"No"; }
    
    struct Dog {
        std::string Name;
        bool        Barks;
        bool        HasATail;
        std::string Address;
    
        friend std::ostream& operator <<(std::ostream& os, Dog const& o) {
            return os << "[DOG|Name=" << o.Name << "|Barks=" << YesNo(o.Barks) << "|Has a Tail=" << YesNo(o.HasATail) << "|Address=" << o.Address << "]";
        }
    }; 
    
    struct Bird {
        std::string Name;
        bool        Tweets;
        std::string Address;
    
        friend std::ostream& operator <<(std::ostream& os, Bird const& o) {
            return os << "[BIRD|Name=" << o.Name << "|Tweets=" << YesNo(o.Tweets) << "|Address=" << o.Address << "]";
        }
    };
    
    typedef boost::variant<Dog, Bird> ZooAnimal;
    
    BOOST_FUSION_ADAPT_STRUCT(Dog, (std::string, Name)(bool, Barks)(bool, HasATail)(std::string, Address))
    BOOST_FUSION_ADAPT_STRUCT(Bird, (std::string, Name)(bool, Tweets)(std::string, Address))
    
    namespace qi    = boost::spirit::qi;
    namespace ascii = boost::spirit::ascii;
    
    template <typename Iterator>
    struct ZooGrammar : public qi::grammar<Iterator, ZooAnimal(), ascii::space_type>
    {
        ZooGrammar() : ZooGrammar::base_type(start_)
        {
            using qi::_1;
    
            yesno_.add("Yes", true)("Y", true)("No", false)("N", false); 
    
            dog_ = qi::lit("DOG") >> '|' >> "Name=" >> lit_   >> '|'
                                  >> "Barks="       >> yesno_ >> '|'
                                  >> "Has a Tail="  >> yesno_ >> '|'
                                  >> "Address="     >> lit_
                 ;
    
            bird_ = qi::lit("BIRD") >> '|' >> "Name=" >> lit_   >> '|'
                                    >> "Tweets="      >> yesno_ >> '|'
                                    >> "Address="     >> lit_
                  ;
    
            start_ = dog_ | bird_;
    
            lit_   = qi::lexeme [ *~qi::char_('|') ];
    
            BOOST_SPIRIT_DEBUG_NODES((dog_)(bird_)(start_)(lit_))
        } 
    
      private:
        qi::rule<Iterator, ZooAnimal(),   ascii::space_type> start_;
        qi::rule<Iterator, std::string(), ascii::space_type> lit_;
        qi::rule<Iterator, Dog(),         ascii::space_type> dog_;
        qi::rule<Iterator, Bird(),        ascii::space_type> bird_;
        qi::symbols<char, bool> yesno_; 
    }; 
    
    int main()
    {
        typedef std::string::const_iterator iterator_type;
        typedef ZooGrammar<iterator_type> grammar;
    
        for (std::string const input : { 
                "DOG | Name=tim | Barks=Yes | Has a Tail=N | Address=3 infinite loop",
                "BIRD| Name=poc | Tweets=Yes| Address=10 stack overflow street"
                })
        {
            iterator_type start = input.begin(); 
            iterator_type end   = input.end();
            grammar g; 
    
            ZooAnimal animal;
            if(qi::phrase_parse(start, end, g, ascii::space, animal))
                std::cout << "Matched: " << animal << "\n";
            else
                std::cout << "Parse failed\n";
    
            if (start != end)
                std::cout << "Remaining input: '" << std::string(start, end) << "'\n";
        }
    }