Search code examples
c++boostboost-spirit

boost::spirit::qi and boost::phoenix::push_back


Based on a QA on Using boost::spirit::qi and boost::phoenix::push_back, the following code works fine - compiled with C++14.

#include <string>
#include <vector>
#include <iostream>

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
namespace qi = boost::spirit::qi;

typedef std::vector<unsigned int> uint_vector_t;

std::ostream& operator<<(std::ostream& out, const uint_vector_t &data)
{
    for (unsigned int i(0); i < data.size(); i++)
    {
        out << data[i] << '\n';
    }
    return out;
}

struct MyStruct
{
    uint_vector_t m_aList;
    uint_vector_t m_bList;
};


template<typename Iterator, typename Skipper>
struct MyParser : public boost::spirit::qi::grammar<Iterator,
        MyStruct(),Skipper>
{
    MyParser() :
        MyParser::base_type(Parser, "Parser")
    {
        using boost::spirit::qi::uint_;
        using boost::spirit::qi::_val;
        using boost::spirit::qi::_1;

        using boost::phoenix::at_c;
        using boost::phoenix::push_back;
        using boost::phoenix::bind;

        aParser = "a=" >> uint_;
        bParser = "b=" >> uint_;

        Parser =
           *(  aParser [push_back(bind(&MyStruct::m_aList, _val), _1)]
            |  bParser [push_back(bind(&MyStruct::m_bList, _val), _1)]
            );
    }
    boost::spirit::qi::rule<Iterator, MyStruct(), Skipper> Parser;
    boost::spirit::qi::rule<Iterator, unsigned int(), Skipper> aParser, bParser;
};

int main()
{
    using boost::spirit::qi::phrase_parse;

    std::string input("a=0\nb=7531\na=2\na=3\nb=246\n");
    std::string::const_iterator begin = input.begin();
    std::string::const_iterator end = input.end();
    MyParser<std::string::const_iterator, qi::space_type> parser;

    MyStruct result;
    bool succes = qi::phrase_parse(begin, end, parser,qi::space,result);
    assert(succes);

    std::cout << "===A===\n" <<result.m_aList << "===B===\n" << result.m_bList << std::endl;
    return 0;
}

The result is:

===A===
0
2
3
===B===
7531
246

Adding another qi::symbols element into the structure, the newly added element is expected to be parsed as FRUIT::APPLE (0), but it is actually indeterminate (appearing random).

#include <string>
#include <vector>
#include <iostream>

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
namespace qi = boost::spirit::qi;

typedef std::vector<unsigned int> uint_vector_t;

std::ostream& operator<<(std::ostream& out, const uint_vector_t &data)
{
    for (unsigned int i(0); i < data.size(); i++)
    {
        out << data[i] << '\n';
    }
    return out;
}

struct MyStruct
{
    enum class  FRUIT
    {
        APPLE,
        BANANA,
        PEAR,
    } fruit;
    uint_vector_t m_aList;
    uint_vector_t m_bList;
};

BOOST_FUSION_ADAPT_STRUCT
(
    MyStruct,
    (MyStruct::FRUIT, fruit)
    (uint_vector_t, m_aList)
    (uint_vector_t, m_bList)
)


template<typename Iterator, typename Skipper>
struct MyParser : public boost::spirit::qi::grammar<Iterator,
        MyStruct(),Skipper>
{
    MyParser() :
        MyParser::base_type(Parser, "Parser")
    {
        using boost::spirit::qi::uint_;
        using boost::spirit::qi::_val;
            using boost::spirit::qi::_1;

        using boost::phoenix::at_c;
        using boost::phoenix::push_back;
        using boost::phoenix::bind;

        fruiter.add
                ("apple",   MyStruct::FRUIT::APPLE)
                ("banana",  MyStruct::FRUIT::BANANA)
                ("pear",    MyStruct::FRUIT::PEAR)
                ;

        aParser = "a=" >> uint_;
        bParser = "b=" >> uint_;

        Parser = fruiter >>
           *(  aParser [push_back(bind(&MyStruct::m_aList, _val), _1)]
            |  bParser [push_back(bind(&MyStruct::m_bList, _val), _1)]
            );
    }
    boost::spirit::qi::rule<Iterator, MyStruct(), Skipper> Parser;
    boost::spirit::qi::rule<Iterator, unsigned int(), Skipper> aParser, bParser;
    boost::spirit::qi::symbols<char, MyStruct::FRUIT>                   fruiter;
};

int main()
{
    using boost::spirit::qi::phrase_parse;

    std::string input("apple\na=0\nb=7531\na=2\na=3\nb=246\n");
    std::string::const_iterator begin = input.begin();
    std::string::const_iterator end = input.end();
    MyParser<std::string::const_iterator, qi::space_type> parser;

    MyStruct result;
    bool succes = qi::phrase_parse(begin, end, parser,qi::space,result);
    assert(succes);

    std::cout << "Fruit: " << int(result.fruit) << "\n===A===\n" <<result.m_aList << "===B===\n" << result.m_bList << std::endl;
    return 0;
}

The resulting qi::symbols element is random instead of 0. An example output looks like

Fruit: 29899839
===A===
0
2
3
===B===
7531
246

However the qi::symbols element itself only works fine, too.

#include <string>
#include <vector>
#include <iostream>

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
namespace qi = boost::spirit::qi;


struct MyStruct
{
    enum class  FRUIT
    {
        APPLE,
        BANANA,
        PEAR,
    } fruit;
};

BOOST_FUSION_ADAPT_STRUCT
(
    MyStruct,
    (MyStruct::FRUIT, fruit)
)


template<typename Iterator, typename Skipper>
struct MyParser : public boost::spirit::qi::grammar<Iterator,
        MyStruct(),Skipper>
{
    MyParser() :
        MyParser::base_type(Parser, "Parser")
    {
        using boost::spirit::qi::uint_;
        using boost::spirit::qi::_val;
            using boost::spirit::qi::_1;

        using boost::phoenix::at_c;
        using boost::phoenix::push_back;
        using boost::phoenix::bind;

        fruiter.add
                ("apple",   MyStruct::FRUIT::APPLE)
                ("banana",  MyStruct::FRUIT::BANANA)
                ("pear",    MyStruct::FRUIT::PEAR)
                ;

        Parser = fruiter;
    }
    boost::spirit::qi::rule<Iterator, MyStruct(), Skipper> Parser;
    boost::spirit::qi::symbols<char, MyStruct::FRUIT>                   fruiter;
};

int main()
{
    using boost::spirit::qi::phrase_parse;

    std::string input("apple");
    std::string::const_iterator begin = input.begin();
    std::string::const_iterator end = input.end();
    MyParser<std::string::const_iterator, qi::space_type> parser;

    MyStruct result;
    bool succes = qi::phrase_parse(begin, end, parser,qi::space,result);
    assert(succes);

    std::cout << "Fruit: " << int(result.fruit) << "\n";
    return 0;
}

The result looks like:

Fruit: 0

What did I do wrong? Thanks in advance.


Solution

  • Semantic actions inhibit automatic propagation of attributes. This is obviously also the reason why the first version of the program didn't nead any for of adaptation for the MyResult struct.

    So either

    1. Stick to semantic actions¹ (dropping adapt-struct)

      Live On Coliru

          Parser = fruiter[bind(&MyStruct::fruit, _val) = _1] >> 
              *( aParser [push_back(bind(&MyStruct::m_aList, _val), _1)] 
              |  bParser [push_back(bind(&MyStruct::m_bList, _val), _1)]
              );
      
    2. Or use operator%= to re-enable automatic attribute propagation semantics.

      // NOTE c++11+ syntax:
      BOOST_FUSION_ADAPT_STRUCT(MyStruct, fruit, m_aList, m_bList)
      
      Parser %= fruiter >> 
          *( aParser [push_back(bind(&MyStruct::m_aList, _val), _1)] 
          |  bParser [push_back(bind(&MyStruct::m_bList, _val), _1)]
          );
      

      Note that this could easily break down had fruit not been the first adapted sequence element. In fact, it's much cleaner to only adapt the expected element:

      BOOST_FUSION_ADAPT_STRUCT(MyStruct, fruit)
      

      Even cleaner to make explicit which attributes are expected to propagate:

      Parser %= fruiter >> 
          omit [
             *( aParser [push_back(bind(&MyStruct::m_aList, _val), _1)] 
              | bParser [push_back(bind(&MyStruct::m_bList, _val), _1)]
              )
          ];
      

      Live On Coliru

    Full Listing

    Live On Coliru

    #include <iostream>
    #include <string>
    #include <vector>
    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <boost/spirit/include/qi.hpp>
    namespace qi = boost::spirit::qi;
    
    typedef std::vector<unsigned int> Uints;
    
    namespace std {
        std::ostream& operator<<(std::ostream& out, const Uints &data) {
            for (auto i : data) out << i << " ";
            return out << "\n";
        }
    }
    
    struct MyStruct {
        enum class FRUIT {
            APPLE,
            BANANA,
            PEAR,
        } fruit;
    
        friend std::ostream& operator<<(std::ostream& out, FRUIT f) {
            switch(f) {
                case FRUIT::APPLE:  return out << "APPLE";
                case FRUIT::BANANA: return out << "BANANA";
                case FRUIT::PEAR:   return out << "PEAR";
            }
            return out << "FRUIT[?" << static_cast<int>(f) << "]";
        }
        Uints m_aList;
        Uints m_bList;
    };
    
    BOOST_FUSION_ADAPT_STRUCT(MyStruct, fruit)
    
    template <typename Iterator, typename Skipper>
    struct MyParser : public qi::grammar<Iterator, MyStruct(), Skipper> {
        MyParser() : MyParser::base_type(Parser, "Parser") {
            using namespace qi;
            using boost::phoenix::push_back;
            using boost::phoenix::bind;
    
            fruiter.add("apple", MyStruct::FRUIT::APPLE)("banana", MyStruct::FRUIT::BANANA)("pear", MyStruct::FRUIT::PEAR);
    
            aParser = "a=" >> uint_;
            bParser = "b=" >> uint_;
    
            Parser %= fruiter >> 
                omit [
                   *( aParser [push_back(bind(&MyStruct::m_aList, _val), _1)] 
                    | bParser [push_back(bind(&MyStruct::m_bList, _val), _1)]
                    )
                ];
        }
      private:
        qi::rule<Iterator, MyStruct(), Skipper> Parser;
        qi::rule<Iterator, unsigned int(), Skipper> aParser, bParser;
        qi::symbols<char, MyStruct::FRUIT> fruiter;
    };
    
    int main() {
        std::string input("banana\na=0\nb=7531\na=2\na=3\nb=246\n");
        using It = std::string::const_iterator;
        It begin = input.begin(), end = input.end();
        MyParser<It, qi::space_type> parser;
    
        MyStruct result;
        bool succes = qi::phrase_parse(begin, end, parser, qi::space, result);
    
        if (succes) {
            std::cout 
                << "Fruit: " << result.fruit 
                << "\n===A===\n" <<result.m_aList << "===B===\n" << result.m_bList << std::endl;
        } else {
            std::cout << "Parse failed\n";
        }
    }
    

    Prints

    Fruit: BANANA
    ===A===
    0 2 3 
    ===B===
    7531 246 
    

    ¹ repeating my mantra: Boost Spirit: "Semantic actions are evil"?