Reputation: 393114
(question lifted from Spirit-general mailing list)
Hello,
I'm working in a parser with spirit qi. The grammar is working good, but I have some problems to populate my struct instance with Semantic Actions.
With the direct struct attributes, like "Request.id" and "Request.url", the code is working. But I don't know how to populate the attributes inside the nested struct "Info", neither how to push values in "Request.list".
Here is my code (The string to parse could have the values in any order):
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <iostream>
#include <vector>
#include <string>
struct Request
{
std::string id;
std::string url;
std::vector<std::string> list;
struct Info
{
std::string id;
std::string desc;
};
Info info;
};
BOOST_FUSION_ADAPT_STRUCT(
Request,
(std::string, id)
)
template <typename Iterator>
struct request_parser : boost::spirit::qi::grammar<Iterator, Request(), boost::spirit::ascii::space_type>
{
request_parser() : request_parser::base_type(start)
{
using namespace boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
namespace phx = boost::phoenix;
quoted_string %= lexeme['"' >> +(char_ - '"') >> '"'];
start %=
BR_OP
>>((DQUOTE >> lit(INFO) >> DQUOTE >> COLON
>> BR_OP
>> ((DQUOTE >> lit(DESC) >> DQUOTE >> COLON >> quoted_string)
^ (DQUOTE >> lit(ID) >> DQUOTE >> COLON >> quoted_string)) % COMMA
>> BR_CL)
^(DQUOTE >> lit(ID) >> DQUOTE >> COLON >> quoted_string[phx::bind(&Request::id, _val) = _1])
^(DQUOTE >> lit(URL) >> DQUOTE >> COLON >> quoted_string[phx::bind(&Request::url, _val) = _1])
^(DQUOTE >> lit(LIST) >> DQUOTE >> COLON
>> SQ_OP
>> quoted_string % COMMA
>> SQ_CL)) % COMMA
>>
BR_CL
;
}
boost::spirit::qi::rule<Iterator, std::string(), boost::spirit::ascii::space_type> quoted_string;
boost::spirit::qi::rule<Iterator, Request(), boost::spirit::ascii::space_type> start;
char BR_OP = '{';
char BR_CL = '}';
char DQUOTE = '"';
char COLON = ':';
char SQ_OP = '[';
char SQ_CL = ']';
char COMMA = ',';
const char* LIST = "list";
const char* ID = "id";
const char* URL = "url";
const char* INFO = "info";
const char* DESC = "desc";
};
int main()
{
typedef std::string::iterator iterator_type;
typedef request_parser<iterator_type> requester_parser;
std::string str = "{\"list\":[\"data1\",\"data2\"],\"info\":{\"desc\":\"description\",\"id\":\"23\"},\"id\":\"1234\",\"url\":\"ok.com\"}";
Request rqst;
requester_parser parser;
using boost::spirit::ascii::space;
boost::spirit::qi::phrase_parse(str.begin(), str.end(), parser, space, rqst);
using std::cout;
using std::endl;
cout << rqst.id << endl;
cout << rqst.url << endl;
cout << rqst.list.size() << endl;
cout << rqst.info.id << endl;
cout << rqst.info.desc << endl;
}
Thanks! Emiliano
Upvotes: 2
Views: 187
Reputation: 393114
As always, I'm going to suggest the solution not using semantic actions (Boost Spirit: "Semantic actions are evil"?). I truly believe semantic-actions should be used sparingly as they mostly have potential to complicate things. (Like new
and delete
, there are very limited occasions where you should be using them).
In this case, you're nearly there: automatic attribute propagation synthesizes a tuple of permutation members in sequence.
So, if you just adapt your struct in the order of your grammar, you'll be fine:
BOOST_FUSION_ADAPT_STRUCT(Request::Info, id, desc)
BOOST_FUSION_ADAPT_STRUCT(Request, info, id, url, list)
The grammar itself had some issues ((a ^ b) % ','
does not do something similar to (a | b) % ','
: it would parse a
, b
, ab
, ba
, a,ba,b
etc.).
I split the rules up a bit to remove redundancy and used a "smart" delimiter to expect commas in the right places:
delim_ = &lit('}') | ','; // unless at end of block, expect a comma
The full grammar became:
string_ = '"' >> *~char_('"') >> '"';
key_ = '"' >> string(_r1) >> '"';
prop_ = key_(_r1) >> ':' >> string_ >> delim_;
info_ = key_("info"s) >> ':'
>> '{'
>> (prop_("desc"s) ^ prop_("id"s))
>> '}'
>> delim_
;
list_ = key_("list"s) >> ':'
>> '[' >> string_ % ',' >> ']'
>> delim_
;
request_ = '{' >> (info_ ^ prop_("id"s) ^ prop_("url"s) ^ list_) >> '}';
Note
key_
and prop_
. You could use the Nabialek trick to further generalize for non-string properties start = skip(space) [ request_ ];
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <iostream>
#include <vector>
#include <string>
using namespace std::literals::string_literals;
struct Request
{
std::string id;
std::string url;
std::vector<std::string> list;
struct Info {
std::string id;
std::string desc;
};
Info info;
};
BOOST_FUSION_ADAPT_STRUCT(Request::Info, id, desc)
BOOST_FUSION_ADAPT_STRUCT(Request, info, id, url, list)
template <typename Iterator>
struct request_parser : boost::spirit::qi::grammar<Iterator, Request()>
{
request_parser() : request_parser::base_type(start)
{
using namespace boost::spirit::qi;
delim_ = &lit('}') | ',';
string_ = '"' >> *~char_('"') >> '"';
key_ = '"' >> string(_r1) >> '"';
prop_ = key_(_r1) >> ':' >> string_ >> delim_;
info_ = key_("info"s) >> ':'
>> '{'
>> (prop_("desc"s) ^ prop_("id"s))
>> '}'
>> delim_
;
list_ = key_("list"s) >> ':'
>> '[' >> string_ % ',' >> ']'
>> delim_
;
request_ = '{' >> (info_ ^ prop_("id"s) ^ prop_("url"s) ^ list_) >> '}';
start = skip(space) [ request_ ];
}
private:
using Skipper = boost::spirit::qi::space_type;
boost::spirit::qi::rule<Iterator, Request()> start;
boost::spirit::qi::rule<Iterator, Request(), Skipper> request_;
boost::spirit::qi::rule<Iterator, Request::Info(), Skipper> info_;
boost::spirit::qi::rule<Iterator, std::vector<std::string>(), Skipper> list_;
boost::spirit::qi::rule<Iterator, std::string(std::string), Skipper> prop_;
// lexemes
boost::spirit::qi::rule<Iterator, std::string()> string_;
// literals
boost::spirit::qi::rule<Iterator, void(std::string)> key_;
boost::spirit::qi::rule<Iterator> delim_;
};
int main()
{
typedef std::string::iterator iterator_type;
typedef request_parser<iterator_type> requester_parser;
std::string str = R"({
"list": ["data1", "data2"],
"info": {
"desc": "description",
"id": "23"
},
"id": "1234",
"url": "ok.com"
})";
Request parsed;
requester_parser parser;
parse(str.begin(), str.end(), parser, parsed);
std::cout << "parsed.id: " << parsed.id << "\n";
std::cout << "parsed.url: " << parsed.url << "\n";
std::cout << "parsed.list.size(): " << parsed.list.size() << "\n";
std::cout << "parsed.info.id: " << parsed.info.id << "\n";
std::cout << "parsed.info.desc: " << parsed.info.desc << "\n";
}
Prints:
parsed.id: 1234
parsed.url: ok.com
parsed.list.size(): 2
parsed.info.id: description
parsed.info.desc: 23
Upvotes: 6