The grammar you show is more of a list of token definitions. The process you're implementing, then, becomes lexing (or token scanning), not parsing.
In your grammar, all your rules (except start
) are defined as
qi::rule<It, char const*>
Which evaluates to
qi::rule<std::string::const_iterator, char const*>
This is an allowed shorthand for
qi::rule<std::string::const_iterator, char const*()>
Meaning that the synthesized attribute is also char const*
. That seems not what you meant, but we have to guess what you thought it would do.
Also note that none of these rules declare a skipper, so the toplevel skip(space)
in start
has very little effect (except pre-skipping before the first query
element). See Boost spirit skipper issues
First Step
As a first step I removed some of the previously mentioned problems, introduced an AST namespace (which is the more general name for your "result variant"), added rule debugging and made some consistency fixes.
At least it now compiles and runs: Live On Coliru
// #define BOOST_SPIRIT_DEBUG
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/std_pair.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iomanip>
#include <map>
#include <optional>
#include <variant>
namespace qi = boost::spirit::qi;
// my AST types
namespace AST {
struct Member { std::string name; };
struct Subscript { std::size_t indice; };
using Class_list = std::vector<std::string>;
struct Instance_id {
std::string class_name;
std::string instance_name;
};
using Instance_list = std::vector<Instance_id>;
struct Deep_search{ std::optional<int> max_depth; };
struct Property_filter {};
struct Parent {};
struct Self {};
struct Child {};
using Value = std::variant< //
Member, //
Class_list, //
Instance_list, //
Deep_search, //
Subscript, //
Property_filter, //
Parent, //
Self, //
Child>;
using Result = std::vector<Value>;
} // namespace AST
template <typename It> struct Query_parser : qi::grammar<It, AST::Result()> {
Query_parser() : Query_parser::base_type(start) {
start = qi::skip(qi::space)[*query];
// copy of the test rules from bottom
identifier =
qi::lexeme[qi::char_("a-zA-Z_") >> *qi::char_("a-zA-Z0-9_")];
subscript = '[' >> qi::uint_ >> ']';
member = identifier >> -subscript;
class_list = '%' >> (identifier | '(' >> -(identifier % ',') >> ')');
instance_id = identifier >> ':' >> identifier;
instance_list = qi::skip(qi::blank) //
['#' >> (instance_id | '(' >> -(instance_id % ',') >> ')')];
instance_or_class_list = class_list | instance_list;
instance_or_class_list_filter = instance_or_class_list >> -subscript;
property_filter = qi::char_("!") >> '(' >> ')';
deep_search = "?" >> -('(' >> qi::uint_ >> ')') >>
instance_or_class_list >> -property_filter >> -subscript;
parent = "..";
self = ".";
separator = "/";
query_part = (member | separator | parent | self | deep_search |
instance_or_class_list_filter);
query = query_part >> *(separator >> *query_part);
BOOST_SPIRIT_DEBUG_NODES((start)(identifier)(subscript)(member)(
class_list)(instance_id)(instance_list)(instance_or_class_list)(
instance_or_class_list_filter)(property_filter)(deep_search)(
parent)(self)(separator)(query_part)(query))
}
private:
qi::rule<It, AST::Result()> start;
// i don't know how to map the values in to the Result vector
qi::rule<It> identifier; // -> only a rule build helper
qi::rule<It> subscript; // output
qi::rule<It> member; // output
qi::rule<It> class_list; // output
qi::rule<It> instance_id;
qi::rule<It> instance_list; // output
qi::rule<It> instance_or_class_list;
qi::rule<It> instance_or_class_list_filter;
qi::rule<It> property_filter; // output
qi::rule<It> deep_search; // output
qi::rule<It> parent; // output
qi::rule<It> self; // output
qi::rule<It> separator; // output
qi::rule<It> query; // output
qi::rule<It> query_part; // -> only a rule build helper
};
template <typename P>
bool test_parser(std::string const& input, P const& p) {
auto f(begin(input)), l(end(input));
return parse(f, l, p >> qi::eoi);
}
int main()
{
static const std::string SAMPLE =
"ube/../abc/abc[2]/?(3)#(A:a,B:b)!()[2]/blub[2]/test/?%A";
using It = std::string::const_iterator;
#if 1
Query_parser<It> const p;
for (std::string const& input :
{SAMPLE}) {
It f = input.begin(), l = input.end();
AST::Result data;
if (/*bool ok =*/parse(f, l, p, data)) {
std::cout << "Parse ok\n";
} else {
std::cout << "Parse failed\n";
}
if( f != l )
std::cout << "Remaining unparsed: " << std::quoted( std::string( f, l ) ) << "\n";
}
#endif
#if 1
// my working rule tests to get a feeling what i try to reach
// identifier
qi::rule<It> identifier;
identifier = qi::char_( "a-zA-Z_" ) >> *qi::char_( "a-zA-Z0-9_" );
assert(test_parser("_a_b4", identifier));
// subcription
qi::rule<It> subscription;
subscription = '[' >> qi::uint_ >> ']';
assert(test_parser("[2]", subscription));
assert(test_parser("[45]", subscription));
// member
qi::rule<It> member;
member = identifier >> -subscription;
assert(test_parser("abc", member));
assert(test_parser("abc[2]", member));
// class list
qi::rule<It> class_list;
class_list = '%' >> (identifier | '(' >> -(identifier % ',') >> ')');
assert(test_parser("%A", class_list)); // vector<{{class_name}}> size 1
assert(test_parser("%(A,B,C)",
class_list)); // vector<{{class_name},{},...}> size n
// instance id
qi::rule<It> instance_id;
instance_id = identifier >> ':' >> identifier;
assert(test_parser("_a_b4:blub", instance_id));
// instance list
qi::rule<It> instance_list;
instance_list = qi::skip( qi::blank )['#' >> (instance_id | '(' >> -(instance_id % ',') >> ')')];
assert(test_parser(
"#a:A", instance_list)); // vector<{{class_name, instance_name}}> size 1
assert(test_parser(
"#(a:A,b:B)",
instance_list)); // vector<{{class_name, instance_name},{},...}> size n
// combined class/instance-list
qi::rule<It> instance_or_class_list;
instance_or_class_list = class_list | instance_list;
assert(test_parser("#(a:A,b:B)", instance_or_class_list));
assert(test_parser("%(A,B,C)", instance_or_class_list));
qi::rule<It> instance_or_class_list_filter;
instance_or_class_list_filter = instance_or_class_list >> -subscription;
assert(test_parser("#(a:A,b:B)[2]", instance_or_class_list_filter));
assert(test_parser("%(A,B,C)[5]", instance_or_class_list_filter));
// expression - dummy
qi::rule<It> property_filter;
property_filter = qi::char_( "!" ) >> '(' >> ')';
// deep search
qi::rule<It> deep_search;
deep_search = "?" >> -( '(' >> qi::uint_ >> ')' ) >> instance_or_class_list >> -property_filter >> -subscription;
assert(test_parser("?%ABC", deep_search));
assert(test_parser("?%ABC[2]", deep_search));
assert(test_parser("?%(A,B,C)", deep_search));
assert(test_parser("?%(A,B,C)[2]", deep_search));
assert(test_parser("?#ABC:EFG", deep_search));
assert(test_parser("?#(A:a,B:b,C:c)", deep_search));
assert(test_parser("?(2)%blub!()[2]", deep_search));
// goto parent
qi::rule<It> parent;
parent = "..";
assert(test_parser("..", parent));
// stay
qi::rule<It> self; // um im Root zu suchen
self = ".";
assert(test_parser(".", self));
// member or root
qi::rule<It> child; // or root
child = "/";
assert(test_parser("/", child));
// complete query
qi::rule<It> query;
qi::rule<It> query_part;
query_part = (member | child | parent | self | deep_search |
instance_or_class_list_filter);
query = query_part >> *(child >> *query_part);
assert(test_parser("#(A:a,B:b)[2]", query));
assert(test_parser("abc/..", query));
assert(test_parser("abc/.", query));
assert(test_parser("..", query));
assert(test_parser(".", query));
assert(test_parser("../", query));
assert(test_parser("./", query));
assert(test_parser("abc", query));
assert(test_parser(SAMPLE, query));
assert(test_parser("abc[2]", query));
assert(test_parser("/", query));
assert(test_parser("?(2)%ABC", query));
assert(test_parser("?(2)%(ABC)!()[2]", query));
assert(test_parser("abc[2]", query));
assert(test_parser("abc/?(2)%Class[2]", query));
assert(test_parser("?(2)%ABC", query));
/*
SAMPLE should give a Result Value vector of
{
Member,
Child,
Parent,
Child,
Member,
Child,
Member,
Subcription,
Child
Deep_search,
Instance_list,
Property_filter,
Subscription,
Child
Member,
Subcription,
Child,
Member,
Child,
Deep_search,
Class_list
}
*/
#endif
}
Prints (with all asserts passing):
Parse ok
AST
You will want to map your rules to synthesized AST nodes. Your approach, going by input token, doesn't map nicely on the qi::rule
model because it assumes "global" access to an output token stream. While you can achieve that, what qi::rule
naturally wants to do is synthesize attributes which are returned by value and then be composed into higher level AST nodes.
Using my imagination I'd say you want something more structured. Notably, I see that member
, class_list
and instance_list
can be followed by an optional subscript
. I'd express that logically:
filterable = member | class_list | instance_list;
filter = filterable >> -subscript;
To be honest it looks like the grammar would become more consistent with:
filter = filterable >> property_filter >> -subscript;
Caveat: my ability to guess useful names in AST representation is severely limit by a complete lack of context (e.g. what is -('(' >> qi::uint_ >> ')')
in deep_search
? I'll just call it count
and default it to 1
. This might not make sense with the actual meaning.
As a first shot, this would structurally simply the rules to:
identifier = qi::char_("a-zA-Z_") >> *qi::char_("a-zA-Z0-9_");
member = identifier;
class_list = '%' >> (identifier | '(' >> -(identifier % ',') >> ')');
instance_id = identifier >> ':' >> identifier;
instance_list = '#' >> (instance_id | '(' >> -(instance_id % ',') >> ')');
subscript = '[' >> qi::uint_ >> ']';
property_filter = qi::matches["!()"];
filterable = member | class_list | instance_list;
filtered = filterable >> property_filter >> -subscript;
max_depth = '(' >> qi::uint_ >> ')' | qi::attr(-1);
deep_search = "?" >> max_depth >> filtered;
parent = "..";
self = ".";
part = parent | self | deep_search | filtered;
query = part % '/';
I hope you can appreciate the reduced complexity.
Now let's create matching AST nodes:
using Identifier = std::string;
using Member = Identifier;
using Class_list = std::vector<Identifier>;
using Index = unsigned;
struct Instance_id { Identifier class_name, instance_name; };
using Class_list = std::vector<Identifier>;
using Instance_list = std::vector<Instance_id>;
using Filterable = std::variant<Member, Class_list, Instance_list>;
struct Filter {
Filterable subject;
bool prop_filter;
std::optional<Index> subscript;
};
struct DeepSearch {
std::optional<int> max_depth;
Filter expr;
};
struct Parent {};
struct Self {};
using Part = std::variant<Filter, DeepSearch, Parent, Self>;
using Query = std::vector<Part>;
Now, just matching the rule declarations:
qi::rule<It, AST::Identifier()> identifier;
qi::rule<It, AST::Index()> subscript;
qi::rule<It, AST::Member()> member;
qi::rule<It, AST::Class_list()> class_list;
qi::rule<It, AST::Instance_id()> instance_id;
qi::rule<It, AST::Filterable()> filterable;
qi::rule<It, AST::Filter()> filter;
qi::rule<It, AST::Instance_list()> instance_list;
qi::rule<It, bool()> property_filter;
qi::rule<It, int> max_depth;
qi::rule<It, AST::DeepSearch()> deep_search;
qi::rule<It, AST::Parent()> parent;
qi::rule<It, AST::Self()> self;
qi::rule<It, AST::Part()> part;
qi::rule<It, AST::Query()> query;
And sprinkling a minimal amount of glue:
parent = ".." >> qi::attr(AST::Parent{});
self = "." >> qi::attr(AST::Self{});
Already makes it compile in c++20: Live On Coliru.
Instead adding fusion adaptation for c++17: Live On Coliru
BOOST_FUSION_ADAPT_STRUCT(AST::InstanceId, class_name, instance_name)
BOOST_FUSION_ADAPT_STRUCT(AST::Filter, subject, prop_filter, subscript)
BOOST_FUSION_ADAPT_STRUCT(AST::DeepSearch, max_depth, expr)
BOOST_FUSION_ADAPT_STRUCT(AST::Parent)
BOOST_FUSION_ADAPT_STRUCT(AST::Self)
I'll stick with the Fusion (c++17 compatible) approach.
Debug Output
Because laziness is a virtue, I changed to boost::variant
. I've integrated all the "minor" test cases in the main Qi parsing loop. Some of these tests don't look like they're supposed to match the query
production, so they will croak:
Live On Coliru
//#define BOOST_SPIRIT_DEBUG
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iomanip>
#include <optional>
namespace qi = boost::spirit::qi;
// my AST types
namespace AST {
struct Identifier : std::string {
using std::string::string;
using std::string::operator=;
};
using Member = Identifier;
using Class_list = std::vector<Identifier>;
using Index = unsigned;
struct InstanceId { Identifier class_name, instance_name; };
using Class_list = std::vector<Identifier>;
using Instance_list = std::vector<InstanceId>;
using Filterable = boost::variant<Member, Class_list, Instance_list>;
struct Filter {
Filterable subject;
bool prop_filter;
std::optional<Index> subscript;
};
struct DeepSearch {
int max_depth;
Filter expr;
};
struct Parent {};
struct Self {};
using Part = boost::variant<Filter, DeepSearch, Parent, Self>;
using Query = std::vector<Part>;
} // namespace AST
BOOST_FUSION_ADAPT_STRUCT(AST::InstanceId, class_name, instance_name)
BOOST_FUSION_ADAPT_STRUCT(AST::Filter, subject, prop_filter, subscript)
BOOST_FUSION_ADAPT_STRUCT(AST::DeepSearch, max_depth, expr)
BOOST_FUSION_ADAPT_STRUCT(AST::Parent)
BOOST_FUSION_ADAPT_STRUCT(AST::Self)
namespace AST {
std::ostream& operator<<(std::ostream& os, Parent) { return os << ".."; }
std::ostream& operator<<(std::ostream& os, Self) { return os << "."; }
std::ostream& operator<<(std::ostream& os, InstanceId const& iid) {
return os << iid.class_name << ":" << iid.instance_name;
}
std::ostream& operator<<(std::ostream& os, Filter const& f) {
os << f.subject;
if (f.prop_filter) os << "!()";
if (f.subscript) os << '[' << *f.subscript << ']';
return os;
}
std::ostream& operator<<(std::ostream& os, DeepSearch const& ds) {
os << "?";
if (ds.max_depth != -1)
os << "(" << ds.max_depth << ")";
return os << ds.expr;
}
std::ostream& operator<<(std::ostream& os, Class_list const& cl) {
bool first = true;
os << "%(";
for (auto& id : cl) {
os << (std::exchange(first,false)?"":",") << id;
}
return os << ")";
}
std::ostream& operator<<(std::ostream& os, Query const& q) {
bool first = true;
for (auto& p : q) {
os << (std::exchange(first,false)?"":"/") << p;
}
return os;
}
std::ostream& operator<<(std::ostream& os, Instance_list const& il) {
bool first = true;
os << "#(";
for (auto& iid : il) {
os << (std::exchange(first,false)?"":",") << iid;
}
return os << ")";
}
} // namespace AST
template <typename It> struct QueryParser : qi::grammar<It, AST::Query()> {
QueryParser() : QueryParser::base_type(start) {
start = qi::skip(qi::space)[*query];
identifier = qi::char_("a-zA-Z_") >> *qi::char_("a-zA-Z0-9_");
member = identifier;
class_list = '%' >> (identifier | '(' >> -(identifier % ',') >> ')');
instance_id = identifier >> ':' >> identifier;
instance_list = '#' >> (instance_id | '(' >> -(instance_id % ',') >> ')');
subscript = '[' >> qi::uint_ >> ']';
property_filter = qi::matches["!()"]; // TODO maybe reintroduce a skipper?
filterable = member | class_list | instance_list;
filter = filterable >> property_filter >> -subscript;
max_depth = '(' >> qi::uint_ >> ')' | qi::attr(-1);
deep_search = "?" >> max_depth >> filter;
parent = ".." >> qi::attr(AST::Parent{});
self = "." >> qi::attr(AST::Self{});
part = parent | self | deep_search | filter;
query = part % '/';
BOOST_SPIRIT_DEBUG_NODES((start)(identifier)(subscript)(member)(
class_list)(instance_id)(instance_list)(property_filter)(max_depth)(
filterable)(filter)(deep_search)(parent)(self)(part)(query))
}
private:
qi::rule<It, AST::Query()> start;
qi::rule<It, AST::Identifier()> identifier;
qi::rule<It, AST::Index()> subscript;
qi::rule<It, AST::Member()> member;
qi::rule<It, AST::Class_list()> class_list;
qi::rule<It, AST::InstanceId()> instance_id;
qi::rule<It, AST::Filterable()> filterable;
qi::rule<It, AST::Filter()> filter;
qi::rule<It, AST::Instance_list()> instance_list;
qi::rule<It, bool()> property_filter;
qi::rule<It, int> max_depth;
qi::rule<It, AST::DeepSearch()> deep_search;
qi::rule<It, AST::Parent()> parent;
qi::rule<It, AST::Self()> self;
qi::rule<It, AST::Part()> part;
qi::rule<It, AST::Query()> query;
};
int main()
{
using It = std::string::const_iterator;
QueryParser<It> const p;
for (std::string const input :
{
"ube/../abc/abc[2]/?(3)#(A:a,B:b)!()[2]/blub[2]/test/?%A",
"?(2)%ABC",
"abc/?(2)%Class[2]",
"abc[2]",
"?(2)%(ABC)!()[2]",
"?(2)%ABC",
"/",
"abc[2]",
"abc",
"./",
"../",
".",
"..",
"abc/.",
"abc/..",
"#(A:a,B:b)[2]",
".",
"..",
"?(2)%blub!()[2]",
"?#(A:a,B:b,C:c)",
"?#ABC:EFG",
"?%(A,B,C)[2]",
"?%(A,B,C)",
"?%ABC[2]",
"?%ABC",
"%(A,B,C)[5]",
"#(a:A,b:B)[2]",
"%(A,B,C)",
"#(a:A,b:B)",
"_a_b4:blub",
"%(A,B,C)",
"%A",
"abc[2]",
"abc",
"[45]",
"[2]",
"_a_b4",
}) //
{
It f = input.begin(), l = input.end();
AST::Query parsed_query;
std::cout << "=== " << std::quoted(input) << " ===\n";
if (parse(f, l, p /*>> qi::eoi*/, parsed_query)) {
std::cout << "Parsed: " << parsed_query << "\n";
for (size_t i = 0; i < parsed_query.size(); ++i) {
auto& part = parsed_query[i];
std::cout << " - at #" << i << " part of type " //
<< std::setw(17) << std::left
<< boost::core::demangle(part.type().name()) + ": "
<< part << "\n";
}
} else {
std::cout << "Parse failed\n";
}
if (f != l)
std::cout << "Remaining unparsed: "
<< std::quoted(std::string(f, l)) << "\n";
}
}
Prints
=== "ube/../abc/abc[2]/?(3)#(A:a,B:b)!()[2]/blub[2]/test/?%A" ===
Parsed: ube/../abc/abc[2]/?(3)#(A:a,B:b)!()[2]/blub[2]/test/?%(A)
- at #0 part of type AST::Filter: ube
- at #1 part of type AST::Parent: ..
- at #2 part of type AST::Filter: abc
- at #3 part of type AST::Filter: abc[2]
- at #4 part of type AST::DeepSearch: ?(3)#(A:a,B:b)!()[2]
- at #5 part of type AST::Filter: blub[2]
- at #6 part of type AST::Filter: test
- at #7 part of type AST::DeepSearch: ?%(A)
=== "?(2)%ABC" ===
Parsed: ?(2)%(ABC)
- at #0 part of type AST::DeepSearch: ?(2)%(ABC)
=== "abc/?(2)%Class[2]" ===
Parsed: abc/?(2)%(Class)[2]
- at #0 part of type AST::Filter: abc
- at #1 part of type AST::DeepSearch: ?(2)%(Class)[2]
=== "abc[2]" ===
Parsed: abc[2]
- at #0 part of type AST::Filter: abc[2]
=== "?(2)%(ABC)!()[2]" ===
Parsed: ?(2)%(ABC)!()[2]
- at #0 part of type AST::DeepSearch: ?(2)%(ABC)!()[2]
=== "?(2)%ABC" ===
Parsed: ?(2)%(ABC)
- at #0 part of type AST::DeepSearch: ?(2)%(ABC)
=== "/" ===
Parsed:
Remaining unparsed: "/"
=== "abc[2]" ===
Parsed: abc[2]
- at #0 part of type AST::Filter: abc[2]
=== "abc" ===
Parsed: abc
- at #0 part of type AST::Filter: abc
=== "./" ===
Parsed: .
- at #0 part of type AST::Self: .
Remaining unparsed: "/"
=== "../" ===
Parsed: ..
- at #0 part of type AST::Parent: ..
Remaining unparsed: "/"
=== "." ===
Parsed: .
- at #0 part of type AST::Self: .
=== ".." ===
Parsed: ..
- at #0 part of type AST::Parent: ..
=== "abc/." ===
Parsed: abc/.
- at #0 part of type AST::Filter: abc
- at #1 part of type AST::Self: .
=== "abc/.." ===
Parsed: abc/..
- at #0 part of type AST::Filter: abc
- at #1 part of type AST::Parent: ..
=== "#(A:a,B:b)[2]" ===
Parsed: #(A:a,B:b)[2]
- at #0 part of type AST::Filter: #(A:a,B:b)[2]
=== "." ===
Parsed: .
- at #0 part of type AST::Self: .
=== ".." ===
Parsed: ..
- at #0 part of type AST::Parent: ..
=== "?(2)%blub!()[2]" ===
Parsed: ?(2)%(blub)!()[2]
- at #0 part of type AST::DeepSearch: ?(2)%(blub)!()[2]
=== "?#(A:a,B:b,C:c)" ===
Parsed: ?#(A:a,B:b,C:c)
- at #0 part of type AST::DeepSearch: ?#(A:a,B:b,C:c)
=== "?#ABC:EFG" ===
Parsed: ?#(ABC:EFG)
- at #0 part of type AST::DeepSearch: ?#(ABC:EFG)
=== "?%(A,B,C)[2]" ===
Parsed: ?%(A,B,C)[2]
- at #0 part of type AST::DeepSearch: ?%(A,B,C)[2]
=== "?%(A,B,C)" ===
Parsed: ?%(A,B,C)
- at #0 part of type AST::DeepSearch: ?%(A,B,C)
=== "?%ABC[2]" ===
Parsed: ?%(ABC)[2]
- at #0 part of type AST::DeepSearch: ?%(ABC)[2]
=== "?%ABC" ===
Parsed: ?%(ABC)
- at #0 part of type AST::DeepSearch: ?%(ABC)
=== "%(A,B,C)[5]" ===
Parsed: %(A,B,C)[5]
- at #0 part of type AST::Filter: %(A,B,C)[5]
=== "#(a:A,b:B)[2]" ===
Parsed: #(a:A,b:B)[2]
- at #0 part of type AST::Filter: #(a:A,b:B)[2]
=== "%(A,B,C)" ===
Parsed: %(A,B,C)
- at #0 part of type AST::Filter: %(A,B,C)
=== "#(a:A,b:B)" ===
Parsed: #(a:A,b:B)
- at #0 part of type AST::Filter: #(a:A,b:B)
=== "_a_b4:blub" ===
Parsed: _a_b4
- at #0 part of type AST::Filter: _a_b4
Remaining unparsed: ":blub"
=== "%(A,B,C)" ===
Parsed: %(A,B,C)
- at #0 part of type AST::Filter: %(A,B,C)
=== "%A" ===
Parsed: %(A)
- at #0 part of type AST::Filter: %(A)
=== "abc[2]" ===
Parsed: abc[2]
- at #0 part of type AST::Filter: abc[2]
=== "abc" ===
Parsed: abc
- at #0 part of type AST::Filter: abc
=== "[45]" ===
Parsed:
Remaining unparsed: "[45]"
=== "[2]" ===
Parsed:
Remaining unparsed: "[2]"
=== "_a_b4" ===
Parsed: _a_b4
- at #0 part of type AST::Filter: _a_b4
Bonus
To make all the tests that show remaining unparsed input fail instead, uncomment the >> qi::eoi
expression.