I'm facing with an issue in defining a grammar with variadic templates.
I started by defining some simple grammars contained into some struct (e.g. Latitude, Longitude) as follows:
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/variant.hpp>
#include <iostream>
#include <string>
using namespace boost::spirit;
template <class Attribute>
using command_rule =
qi::rule<std::string::iterator, Attribute, ascii::space_type>;
template <class Attribute>
using command_grammar =
qi::grammar<std::string::iterator, Attribute, ascii::space_type>;
struct Latitude {
struct return_type {
double lat_;
};
struct grammar : command_grammar<return_type()> {
grammar() : grammar::base_type{latitude_} {
latitude_ = "LAT=" >> qi::double_;
}
private:
command_rule<return_type()> latitude_;
};
};
BOOST_FUSION_ADAPT_STRUCT(Latitude::return_type, (double, lat_))
struct Longitude {
struct return_type {
double lon_;
};
struct grammar : command_grammar<return_type()> {
grammar() : grammar::base_type{longitude_} {
longitude_ = "LON=" >> qi::double_;
}
private:
command_rule<return_type()> longitude_;
};
};
BOOST_FUSION_ADAPT_STRUCT(Longitude::return_type, (double, lon_))
Then, I would like to combine them in a complete grammar that is able to parse strings belonging to any of these simple grammars. To to this, I have defined a variadic template struct that tries to expand a list of sub-grammars into an expression like "grammar1 | grammar2 | ..."
template <class... Commands>
struct device_grammar : boost::spirit::qi::grammar<
std::string::iterator,
boost::variant<typename Commands::return_type...>(),
boost::spirit::ascii::space_type> {
typedef boost::variant<typename Commands::return_type...> return_type;
device_grammar() : device_grammar::base_type{rule_}{
build_rule<typename Commands::grammar...>();
}
private:
template <class CommandGrammar> void build_rule() {
rule_ = CommandGrammar();
}
template <class FirstGrammar, class SecondGrammar, class... Others>
void build_rule() {
build_rule<SecondGrammar, Others...>();
rule_ = rule_ | FirstGrammar();
}
boost::spirit::qi::rule<std::string::iterator, return_type(),
boost::spirit::ascii::space_type>
rule_;
};
typedef device_grammar<Latitude, Longitude> CoordinatesGrammar;
The code compiles (see the complete example below); the problem is that when it try to parse the input string, a segmentation fault is generated. Can someone please help me to fix this issue?
Thanks a lot in advance.
EXAMPLE CODE (g++-4.9 or clang++-3.9):
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/variant.hpp>
#include <iostream>
#include <string>
template <class... Commands>
struct device_grammar : boost::spirit::qi::grammar<
std::string::iterator,
boost::variant<typename Commands::return_type...>(),
boost::spirit::ascii::space_type> {
typedef boost::variant<typename Commands::return_type...> return_type;
device_grammar() : device_grammar::base_type{rule_}{
build_rule<typename Commands::grammar...>();
}
private:
template <class CommandGrammar> void build_rule() {
rule_ = CommandGrammar();
}
template <class FirstGrammar, class SecondGrammar, class... Others>
void build_rule() {
build_rule<SecondGrammar, Others...>();
rule_ = rule_ | FirstGrammar();
}
boost::spirit::qi::rule<std::string::iterator, return_type(),
boost::spirit::ascii::space_type>
rule_;
};
using namespace boost::spirit;
template <class Attribute>
using command_rule =
qi::rule<std::string::iterator, Attribute, ascii::space_type>;
template <class Attribute>
using command_grammar =
qi::grammar<std::string::iterator, Attribute, ascii::space_type>;
struct Latitude {
struct return_type {
double lat_;
};
struct grammar : command_grammar<return_type()> {
grammar() : grammar::base_type{latitude_} {
latitude_ = "LAT=" >> qi::double_;
}
private:
command_rule<return_type()> latitude_;
};
};
BOOST_FUSION_ADAPT_STRUCT(Latitude::return_type, (double, lat_))
struct Longitude {
struct return_type {
double lon_;
};
struct grammar : command_grammar<return_type()> {
grammar() : grammar::base_type{longitude_} {
longitude_ = "LON=" >> qi::double_;
}
private:
command_rule<return_type()> longitude_;
};
};
BOOST_FUSION_ADAPT_STRUCT(Longitude::return_type, (double, lon_))
typedef device_grammar<Latitude, Longitude> CoordinatesGrammar;
struct print : public boost::static_visitor<> {
void operator()(Latitude::return_type &t) const {
std::cout << "Latitude = " << t.lat_ << " deg" << std::endl;
;
}
void operator()(Longitude::return_type &t) const {
std::cout << "Longitude = " << t.lon_ << " deg" << std::endl;
;
}
};
int main() {
std::string s;
CoordinatesGrammar g;
CoordinatesGrammar::return_type v;
while (1) {
std::getline(std::cin, s);
auto it = s.begin();
if (qi::phrase_parse(it, s.end(), g, ascii::space, v)) {
print p;
boost::apply_visitor(p, v);
}
}
return 0;
}
EDIT: As far as I understand, the problem is in the lines
rule_ = CommandGrammar();
...
rule_ = rule_ | FirstGrammar();
It seems that the grammar objects can not be temporary and have to be stored as members of the class. How can I do that?
EDIT:
I have also tried to store such objects in a std::tuple
, but it seems still not working.
What you are creating closely resembles what qi's auto parser already does: http://www.boost.org/doc/libs/1_64_0/libs/spirit/doc/html/spirit/qi/reference/auto.html
If you specialize
create_parser<>
for your datatypes you can simply useqi::auto_
straight-away:Live On Coliru
Prints
Nicer things
If you don't use
qi::rule<>
you don't need to hard-code the iterator either. Let's go full fun mode and get rid of the visitor too:[Live On Coliru](http://coliru.stacked-crooked.com/a/84f7a8c9a453fc1b
Prints
If you can afford to enable c++1z, you can use Spirit X3 with fold-expressions:
Live On Coliru
Prints