1

In boost::spirit, how can I read in a string, immediately look up its int value in a database and have boost::spirit assign the value to an int? So the attribute of the rule would be an int, even though it is parsed as a string.

For example, this input

myCoolKey 3.4

could be parsed as a (int,double) pair: (87, 3.4), where the string "myCoolKey" is mapped to 87 through a (Berkeley) DB lookup.

I would want code like this:

typedef std::pair<int, double> Entry;
qi::rule<Iterator, Entry(), Skipper> entry;
entry %= +qi::char_[qi::_val = mylookup(qi::_1)]
         >> qi::double_;

Here is a full code example. How would I call the function that looks up the parsed string, and make boost::spirit assign the looked up value to an int?

#include <iostream>
#include <boost/foreach.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/support_istream_iterator.hpp>
#include <boost/fusion/include/std_pair.hpp>
namespace qi = boost::spirit::qi;

typedef std::pair<int, double> Entry;

struct Lookup {
  MyDB db;
  int lookup(std::string const& str) {
    return db.lookup(str);
  }
};

template <typename Iterator, typename Skipper>
struct MyGrammar : qi::grammar<Iterator, std::vector<Entry>(), Skipper> {
  MyGrammar() : MyGrammar::base_type(entries) {
    entry %= +qi::char_[qi::_val = myLookup.lookup(qi::_1)]
        >> qi::double_;
    entries = +entry;
  }
  Lookup myLookup;
  qi::rule<Iterator, Entry(), Skipper> entry;
  qi::rule<Iterator, std::vector<Entry>(), Skipper> entries;
};

int main() {
  typedef boost::spirit::istream_iterator It;
  std::cin.unsetf(std::ios::skipws);
  It it(std::cin), end;

  MyGrammar<It, qi::space_type> entry_grammar;
  std::vector<Entry> entries;
  if (qi::phrase_parse(it, end, entry_grammar, qi::space, entries)
      && it == end) {
    BOOST_FOREACH(Entry const& entry, entries) {
      std::cout << entry.first << " and " << entry.second << std::endl;
    }
  }
  else {
    std::cerr << "FAIL" << std::endl;
    exit(1);
  }
  return 0;
}
Frank
  • 64,140
  • 93
  • 237
  • 324

1 Answers1

1

The quickest way would be using phoenix bind

qi::_val = phx::bind(&Lookup::lookup, myLookup, qi::_1)

There are more elegant solutions using fused functions or 'Polymorphic Calleable Objects' but I refer to the docs for now:

Edit I found out the rest of your code wouldn't compile (because of other reasons). Here is a compiling sample (with a stub lookup function):

#include <boost/spirit/include/phoenix.hpp>
namespace phx = boost::phoenix;
//  ...

template <typename Iterator, typename Skipper>
struct MyGrammar : qi::grammar<Iterator, std::vector<Entry>(), Skipper>
{
    MyGrammar() : MyGrammar::base_type(entries)
    {
        using namespace qi;
        intpart = as_string [+char_] 
                 [ _val = phx::bind(&Lookup::lookup, myLookup, _1) ];
        entry = intpart >> double_;
        entries = +entry;
    }
  private:
    Lookup myLookup;
    qi::rule<Iterator, int(), Skipper> intpart;
    qi::rule<Iterator, Entry(), Skipper> entry;
    qi::rule<Iterator, std::vector<Entry>(), Skipper> entries;
};

You can get around having the extra rule for intpart by doing qi::as<> or qi::attr_cast<> wizardry, but frankly it is a lot more readable this way.

sehe
  • 374,641
  • 47
  • 450
  • 633
  • updated my answer with fixes for the 'other' issue - which crops up after fixing the SA – sehe May 21 '12 at 10:10
  • Thanks, that works. But I had to replace the `as_string[+char_]` by `as_string[ lexeme[+~space] ]` because the `+char_` would eat up the space otherwise (see also here http://boost.2283326.n4.nabble.com/How-does-space-parser-work-td3381807.html). – Frank May 21 '12 at 18:35
  • ... And, if I leave out the Skipper for the rule `intpart` then I don't need `lexeme` (http://stackoverflow.com/a/4029386/60628). – Frank May 21 '12 at 19:11
  • Actually it turns out `as_string` is not needed if your rule is a lexeme (or, equivalently, doesn't have a skipper). – Frank May 21 '12 at 20:05
  • @Frank, well your code didn't mention lexeme, so all of that is true, but it wasn't (in) the question :) Of course you can skip as_string if you already use a lexeme. It seems to me you are getting your head around skippers; I'll see you when you have more questions :) – sehe May 21 '12 at 21:34