How do I write a semantic action to populate a map using a known key?
I am trying to dynamically build a parser from other parameters using boost::spirit:qi
. The goal is to parse the string and populate it with std::map<std::string, std::string>
keys and values. However, the key field of the map is not parsed (that is, it is known before the parser is generated).
I guess I need to write a semantic action that sets the map key to the appropriate parse value. I see what qi::_1
the parser content is providing, but how can I refer to the return structure (in this case a std::map
)?
If std::map
was in scope, I could directly assign it like this:
parser = lit(prefix) >> value_parser[map_[key] = _1];
But in my case, I want to create a parser, not a parser. I'm guessing I need to replace something map_[key]
.
To provide a little more context (as requested):
First, I parse the "pattern" line, which looks something like this:
/path/to/:somewhere:/nifty.json
:somewhere:
is intended to represent any string that can later be referenced by a name somewhere
. This parser works well for me.
Next, I want to generate another parser from this template that parses strings like this:
/path/to/anywhere/nifty.json
And show me std::map<std::string, std::string> m
where m["somewhere"] == "anywhere"
.
source to share
I'm not sure if this is what you meant, but inherited attributes might be the answer for you. Instead of dynamically creating a parser, you create a single parser that takes the key and reference to your map as inherited attributes that you specify with each call:
// an attempt to demonstrate a parser that takes a std::map by reference and a key by value,
// then stores a parsed value into the map as the value associated with the given key
#include <string>
#include <map>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
typedef std::string::const_iterator fwd_iter_t;
namespace qi = boost::spirit::qi;
namespace phoenix = boost::phoenix;
typedef int value_t; // or whatever
typedef std::map<std::string, value_t> result_map_t;
// key insight - rules can take "inherited" attributes (parameters in 2nd argument):
typedef qi::rule<fwd_iter_t,
void(result_map_t&, std::string), // inherit map ref and key to use
boost::spirit::ascii::space_type> map_insert_rule_t;
int main() {
result_map_t result_map;
std::vector<std::string> keys = { "A", "B", "C" };
std::string test_data = "PREFIX 1\nPREFIX 2\nPREFIX 3";
using boost::phoenix::construct; // to create pairs
using boost::phoenix::insert; // to add pairs to the map
typedef result_map_t::value_type result_map_pair_t;
// use Phoenix actions to construct the key/value pair and insert it
map_insert_rule_t maprule = qi::lit("PREFIX")
>> qi::int_[insert(qi::_r1, // inherited map ref
construct<result_map_pair_t>(qi::_r2, qi::_1))];
fwd_iter_t beg = test_data.begin();
fwd_iter_t end = test_data.end();
for (auto k_it = keys.begin(); k_it != keys.end(); ++k_it) {
using boost::spirit::ascii::space;
if (!qi::phrase_parse(beg, end,
maprule(phoenix::ref(result_map), *k_it),
space)) {
std::cerr << "parse failed!" << std::endl;
return 1;
}
}
std::cout << "parse results:" << std::endl;
for (auto r_it = result_map.begin(); r_it != result_map.end(); ++r_it) {
std::cout << r_it->first << " " << r_it->second << std::endl;
}
return 0;
}
You can probably exclude the std :: map reference from the call by inheriting from qi :: rule and making it a private data member.
source to share
You should be able to use the phoenix binding to do what you are asking for, but it looks like if we had a little more context, a cleaner solution would be available.
parser = lit(prefix) >> value_parser[phx::ref(map)[key] = qi::_1]
Depending on where the key comes from, you may need phx :: ref as well.
source to share