Search code examples
c++parsingboostgrammarboost-spirit

Pass a boost::spirit symbol table into a grammar as inherited attribute


I want to create the grammar that parses a list of key-values pairs, but accepts only a given keys. If the input list contains unknown keys the grammar should fail. The keys of "good" keys can be passed to grammar as qi::symbol table.

The question: is it possible to pass the keys as inherited attribute of the grammar?

I create the code prototype, but do not know how to convert the attribute info the parser or rule or subgrammar inside the grammar.

#include <iostream>
#include <vector>

#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/std_pair.hpp>

using namespace std;
using namespace boost;

namespace qi = boost::spirit::qi;

template <typename I> using string_range = iterator_range<I>;

template <typename I> using pair_type = pair<
  boost::optional<int>,
  boost::optional<string_range<I>>
>;

template <typename I> using pairs_type = vector<pair_type<I>>;

using symbol_table = qi::symbols<char, int>;

template <typename Iterator>
struct keys_and_values
  : qi::grammar<Iterator, pairs_type<Iterator>(symbol_table const&)>
{
  keys_and_values()
    : keys_and_values::base_type(query)
  {
    using namespace qi;
    query =  pair (_r1) >> *((qi::lit(';') | '&') >> pair (_r1));
// How to convert the attribute into the parsing object???
    pair  =  -matches[_r1] >> -('=' >> -value);
    value =  raw[+qi::char_("a-zA-Z_0-9")];
  }
  qi::rule<Iterator, pairs_type<Iterator>(symbol_table const&)> query;
  qi::rule<Iterator, pair_type<Iterator>(symbol_table const&)> pair;
  qi::rule<Iterator, string_range<Iterator>()> value;
};

int main ()
{
  namespace qi = boost::spirit::qi;
  string input { "key1=v1;key2=v2;key3=v3" };

  using string_iterator = string::const_iterator;

  static const keys_and_values <string_iterator> p;
  pairs_type <string_iterator> m;

  symbol_table keys;
  keys.add ("key1", 1) ("key2", 2) ("key3", 3) ;

  string_iterator begin{boost::begin (input)}, end{boost::end(input)};

  if (qi::parse (begin, end, p(boost::phoenix::ref(keys)), m))
    cout << "parse ok\n";
}

A link to the same code on coilru.


Solution

  • Sure. This is closely related to the famed Nabialek Trick.

    And the enabling mechanism is qi::lazy:

        pair  =  -matches[lazy(_r1)] >> -('=' >> -value);
    

    I also add #define BOOST_SPIRIT_USE_PHOENIX_V3 (which you may not explicitly have to set, depending on compiler/boost version).

    Live On Coliru

    #define BOOST_SPIRIT_USE_PHOENIX_V3
    #include <iostream>
    #include <vector>
    
    #include <boost/spirit/include/qi.hpp>
    #include <boost/fusion/include/std_pair.hpp>
    
    using namespace std;
    using namespace boost;
    
    namespace qi = boost::spirit::qi;
    
    template <typename I> using string_range = iterator_range<I>;
    
    template <typename I> using pair_type = pair<
        boost::optional<int>,
        boost::optional<string_range<I>>
    >;
    
    template <typename I> using pairs_type = vector<pair_type<I>>;
    
    using symbol_table = qi::symbols<char, int>;
    
    template <typename Iterator>
    struct keys_and_values
        : qi::grammar<Iterator, pairs_type<Iterator>(symbol_table const&)>
    {
        keys_and_values()
        : keys_and_values::base_type(query)
        {
            using namespace qi;
            query =  pair (_r1) >> *((qi::lit(';') | '&') >> pair (_r1));
            // How to convert the attribute into the parsing object???
            pair  =  -matches[lazy(_r1)] >> -('=' >> -value);
            value =  raw[+qi::char_("a-zA-Z_0-9")];
        }
        qi::rule<Iterator, pairs_type<Iterator>(symbol_table const&)> query;
        qi::rule<Iterator, pair_type<Iterator>(symbol_table const&)> pair;
        qi::rule<Iterator, string_range<Iterator>()> value;
    };
    
    int main ()
    {
        namespace qi = boost::spirit::qi;
        string input { "key1=v1;key2=v2;key3=v3" };
    
        using string_iterator = string::const_iterator;
    
        static const keys_and_values <string_iterator> p;
        pairs_type <string_iterator> m;
    
        symbol_table keys;
        keys.add ("key1", 1) ("key2", 2) ("key3", 3) ;
    
        string_iterator begin{boost::begin (input)}, end{boost::end(input)};
    
        if (qi::parse (begin, end, p(boost::phoenix::ref(keys)), m))
            cout << "parse ok\n";
    }
    

    Output:

    parse ok