As the other answer almost already made clear, Qi already has a mechanism to generate parsers on the fly, given an attribute type.
The end-user facing bit here is qi::auto_
. qi::auto_
is a parser, instead of a grammar.
This has distinct advantages[1].
- Most of all, it allows users to use the parser inside a grammar with a skipper of their own choice as well as perhaps using
qi::locals<>
.
- Also, the
auto_
Qi expression terminal is already defined, so there is no need at all to instantiate a grammar using a verbose template argument list:
- Finally, the parser returns an expression template, so there is no type-erasure going on, and combining several auto_ parsers in this way is therefore not less efficient than manually composing the grammar (whereas both wrapping in a
qi::rule<>
and qi::grammar<>
incur performance overhead)
Let's see how it's used:
std::vector<std::pair<double, int> > parsed;
bool result_ = qi::phrase_parse(first, last, qi::auto_, qi::space, parsed);
As you can see, this accomodates a skipper, as well as 'magically' selects the parser that matches parsed
. Now, to get your sample format from the OP, you'd need to hook into the customization point for the auto_
parser:
namespace boost { namespace spirit { namespace traits {
// be careful copying expression templates. Boost trunk has `qi::copy` for this too, now
#define PARSER_DEF(a) using type = decltype(boost::proto::deep_copy(a)); static type call() { return boost::proto::deep_copy(a); }
template<typename T1, typename T2>
struct create_parser<std::pair<T1, T2> >
{
PARSER_DEF('(' >> create_parser<T1>::call() >> ',' >> create_parser<T2>::call() >> ')');
};
template<typename TV, typename... TArgs>
struct create_parser<std::vector<TV, TArgs...> >
{
PARSER_DEF('[' >> qi::omit[qi::uint_] >> ',' >> '(' >> create_parser<TV>::call() % ',' >> ')' >> ']' );
};
#undef PARSER_DEF
} } }
That's literally all that's needed. Here's a demo that parses:
VECTOR[ 1 ,
(
PAIR (0.97,
5),
PAIR (1.75,10)
)
]
And prints the parsed data as:
Parsed:
0.97 5
1.75 10
See it Live On Coliru
Full Code Listing
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/home/qi.hpp>
namespace qi = boost::spirit::qi;
namespace boost { namespace spirit { namespace traits {
// be careful copying expression templates. Boost trunk has `qi::copy` for this too, now
#define PARSER_DEF(a) using type = decltype(boost::proto::deep_copy(a)); static type call() { return boost::proto::deep_copy(a); }
template<typename T1, typename T2>
struct create_parser<std::pair<T1, T2> >
{
PARSER_DEF(lexeme [ lit("PAIR") ] >> '(' >> create_parser<T1>::call() >> ',' >> create_parser<T2>::call() >> ')');
};
template<typename TV, typename... TArgs>
struct create_parser<std::vector<TV, TArgs...> >
{
PARSER_DEF(lexeme [ lit("VECTOR") ] >> '[' >> qi::omit[qi::uint_] >> ',' >> '(' >> create_parser<TV>::call() % ',' >> ')' >> ']' );
};
#undef PARSER_DEF
} } }
#include <boost/spirit/home/karma.hpp>
namespace karma = boost::spirit::karma;
int main()
{
std::string const input("VECTOR[ 1 ,\n"
" ( \n"
" PAIR (0.97, \n"
" 5), \n"
" PAIR (1.75,10) \n"
" ) \n"
"]");
std::cout << input << "\n\n";
auto first = input.begin();
auto last = input.end();
std::vector<std::pair<double, int> > parsed;
bool result_ = qi::phrase_parse(first, last, qi::auto_, qi::space, parsed);
if (first!=last)
std::cout << "Remaining unparsed input: '" << std::string(first, last) << "'\n";
if (result_)
std::cout << "Parsed:\n " << karma::format_delimited(karma::auto_ % karma::eol, " ", parsed) << "\n";
else
std::cout << "Parsing did not succeed\n";
}
[1] A potential downside would be that the customization point is fixed, and hence you would only be able to associate 1 auto_
parser with any type. Rolling your own base template gives you more control and enables you to (more) easily have different 'parser flavours'. However, in the end it's possible to have the best of both worlds, so I'd go for convenience first.