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

boost spirit rule with phoenix bind to structure - compile failure


I want to parse the following string: id=1;entry1=[A,B,D];entry2=[bla,blubb];factor=[1,5]!

My Parser:

struct Entry
{
  uint32_t id;
  std::vector< std::string > entry1;
  std::vector< std::string > entry2;

  bool useFactor;
  std::pair<int, int> factor;
};

BOOST_FUSION_ADAPT_STRUCT( Entry, id, entry1, entry2, useFactor, factor)

template<typename It>
struct testParser : qi::grammar<It, Entry()>
{
   testParser : testParser::base_type(start)
   {
     using namespace qi;
     id_ %= lit("id=") >> int_ ;
     entry1_ %= lit("entry1=") >> char_('[') >> +(char_ -char_(']') % ',') >> char_(']');
     entry2_ %= lit("entry1=") >> char_('[') >> +(char_ -char_(']') % ',') >> char_(']');
    factor %= lit("factor=") >> char_('[') >> int_ >> char_(',') >> int_ >> char_(']');

  start = id >> ';' >> entry1 >> ';' >> entry2 >> (( ';' >> factor[ phx::bind(&Entry::useFactor,_1) = true;] ) >> '!') | '!';

   qi::rule<It, Entry()> start;
   qi::rule<It, int()> id;
   qi::rule<It, std::vector<std::string>()> entry1_, entry2_;
   qi::rule<It, std::pair<int,int>()> factor;
}
};

Im getting extreme large compile error messages. I think its because of the entry1 and entry2 rule ( vector of string insertion )


Solution

  • As you might have witnessed in the livestream, I've incrementally fixed up the grammar.

    There were quite a number of "are-you-drunk" moments in there, so I'm not sure I'll be able to capture the essential steps in prose here. By all means, have a look at the recorded stream¹.

    Some substantial notes (ignoring random typos and clearly confused code):

    • use attr(v) to simply expose an attribute (as, in this case attr(true))
    • use lit('c'), lit("abc") or just 'c' and "abc" to match literal character (strings) without capturing their content
    • use %= only in the presence of a semantic action
    • the semantic runs after it's subject parser succeeded (and only iff it succeeded).

      Therefore even if you should/should have set the useFactor member from within a semantic action, it would only have run iff the factor part was present (and indeterminate values would be left otherwise).

    • formatting is important.

    • debugging is important.

    Optionally:

    • using BOOST_SPIRIT_DEBUG* to debug rules
    • using as_vector to hack a quick way to print the results
    • I've shown the technique to use inherited arguments so you can reuse the entry rule for entry1 and entry2

    Not shown:

    • use a skipper for whitespace insensitivity
    • use no_case for ... case insensitivity
    • with a skipper: beware of lexeme to avoid matching the keywords across skipped input characters

    The working code, as a SSCCE:

    Live On Coliru

    #define BOOST_SPIRIT_DEBUG
    #include <iostream>
    #include <vector>
    
    namespace std {
        template <typename T>
        static ostream& operator<<(ostream& os, vector<T> const& v) {
            os << "vector{ ";
            for(auto& e : v)
                os << "'" << e << "', ";
            return os << "}";
        }
        template <typename T, typename U>
        static ostream& operator<<(ostream& os, pair<T,U> const& p) {
            return os << "pair{ '" << p.first << "', '" << p.second << "' }";
        }
    }
    
    #include <boost/fusion/adapted/struct.hpp>
    #include <boost/fusion/adapted/std_pair.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    
    namespace qi = boost::spirit::qi;
    namespace phx = boost::phoenix;
    
    struct Entry
    {
        uint32_t id;
        std::vector<std::string> entry1;
        std::vector<std::string> entry2;
    
        bool useFactor;
        std::pair<int, int> factor;
    };
    
    BOOST_FUSION_ADAPT_STRUCT(Entry, id, entry1, entry2, useFactor, factor)
    
    template<typename It>
    struct testParser : qi::grammar<It, Entry()>
    {
        testParser() : testParser::base_type(start)
        {
            using namespace qi;
    
            id      = "id=" >> int_;
            entry   = lit(_r1) >> ('[' >> +~char_("],") % ',' >> ']');
            factor  = "factor=" >> ('[' >> int_ >> ',' >> int_ >> ']');
    
            start = 
                    id                >> ';' 
                 >> entry(+"entry1=") >> ';' 
                 >> entry(+"entry2=") >> ';' 
                 >> attr(true)  
                 >> (factor | attr(std::pair<int,int>{1,1}))
                 >> '!';
    
            BOOST_SPIRIT_DEBUG_NODES((start)(entry)(id)(factor))
    #if 0
    #endif
        }
      private:
        qi::rule<It, Entry()>                               start;
        qi::rule<It, int()>                                 id;
        qi::rule<It, std::vector<std::string>(std::string)> entry;
        qi::rule<It, std::pair<int,int>()>                  factor;
    };
    
    int main() {
        std::string const input = "id=1;entry1=[A,B,D];entry2=[bla,blubb];factor=[1,5]!";
        using It = std::string::const_iterator;
    
        testParser<It> g;
        It f = input.begin(), l = input.end();
    
        Entry entry;
        bool ok = qi::parse(f, l, g, entry);
    
        std::cout << std::boolalpha;
        if (ok) {
            std::cout << "Parsed: " << boost::fusion::as_vector(entry) << "\n";
        } else {
            std::cout << "Parse failed\n";
        }
    
        if (f!=l)
            std::cout << "Remaining unparsed: '" << std::string(f,l) << "'\n";
    }
    

    Prints:

    <start>
    <try>id=1;entry1=[A,B,D];</try>
    <id>
        <try>id=1;entry1=[A,B,D];</try>
        <success>;entry1=[A,B,D];entr</success>
        <attributes>[1]</attributes>
    </id>
    <entry>
        <try>entry1=[A,B,D];entry</try>
        <success>;entry2=[bla,blubb];</success>
        <attributes>[[[A], [B], [D]], [e, n, t, r, y, 1, =]]</attributes>
    </entry>
    <entry>
        <try>entry2=[bla,blubb];f</try>
        <success>;factor=[1,5]!</success>
        <attributes>[[[b, l, a], [b, l, u, b, b]], [e, n, t, r, y, 2, =]]</attributes>
    </entry>
    <factor>
        <try>factor=[1,5]!</try>
        <success>!</success>
        <attributes>[[1, 5]]</attributes>
    </factor>
    <success></success>
    <attributes>[[1, [[A], [B], [D]], [[b, l, a], [b, l, u, b, b]], 1, [1, 5]]]</attributes>
    </start>
    Parsed: (1 vector{ 'A', 'B', 'D', } vector{ 'bla', 'blubb', } true pair{ '1', '5' })
    

    ¹ (I missed the first part where I was fixing headers, balancing brackets and adding parens to constructors etc... o.O).