i need to convert an old hierarchical query system parser to C++ my first idea was to port it 1:1 to C++ using Spirit
beware: most of the example-code on the bottom is my rule-test code - to adapt the old syntax, the rules are worked out and doing what i want and my test-queries getting parsed by the rules - nice!
but i have no idea how to port that to a qi:grammar based parser that spits out a vector of variants with the descent types i need from the query (with mandatory and partially optional parts)
can anyone give me an advice how to do the mapping? i think i need some BOOST_FUSION_ADAPT_STRUCT adaptors to fill my result-vector
the #if USE_GRAMMAR() code is not compilable - more or less a frame for your tips
// #define BOOST_SPIRIT_DEBUG
#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;
// these are my result-variant types
struct Member{ std::string name; };
struct Subscription{ 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, Subscription, Property_filter, Parent, Self, Child>;
using Result = std::vector<Value>;
// code that should use the qi::grammar parser
// set that true and get a bunch of compile-errors :)
#define USE_GRAMMAR() ( false )
#if USE_GRAMMAR()
template <typename It>
struct Query_parser : qi::grammar<It, Result()>
{
Query_parser() : Query_parser::base_type( start ){
using namespace qi;
start = skip( space )[*query];
// copy of the test rules from bottom
identifier = qi::char_( "a-zA-Z_" ) >> *qi::char_( "a-zA-Z0-9_" );
subscription = '[' >> qi::uint_ >> ']';
member = identifier >> -subscription;
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 >> -subscription;
property_filter = qi::char_( "!" ) >> '(' >> ')';
deep_search = "?" >> -( '(' >> qi::uint_ >> ')' ) >> instance_or_class_list >> -property_filter >> -subscription;
parent = "..";
self = ".";
child = "/";
query_part = ( member | child | parent | self | deep_search | instance_or_class_list_filter );
query = query_part >> *( child >> *query_part );
}
private:
qi::rule<It, Result()> start;
// i don't know how to map the values in to the Result vector
qi::rule<It, char const*> identifier; // -> only a rule build helper
qi::rule<It, char const*> subscription; // output
qi::rule<It, char const*> member; // output
qi::rule<It, char const*> class_list; // output
qi::rule<It, char const*> instance_id;
qi::rule<It, char const*> instance_list; // output
qi::rule<It, char const*> instance_or_class_list;
qi::rule<It, char const*> instance_or_class_list_filter;
qi::rule<It, char const*> property_filter; // output
qi::rule<It, char const*> deep_search; // output
qi::rule<It, char const*> parent; // output
qi::rule<It, char const*> self; // output
qi::rule<It, char const*> child; // output
qi::rule<It, char const*> query; // output
qi::rule<It, char const*> query_part; // -> only a rule build helper
};
#endif
template <typename P>
bool test_parser( char const* input, P const& p, bool full_match = true ){
using boost::spirit::qi::parse;
char const* f( input );
char const* l( f + strlen( f ) );
return parse( f, l, p ) && ( !full_match || ( f == l ) );
}
int main()
{
#if USE_GRAMMAR()
using It = std::string::const_iterator;
Query_parser<It> const p;
for( std::string const& input : { "ube/../abc/abc[2]/?(3)#(A:a,B:b)!()[2]/blub[2]/test/?%A" } ){
It f = input.begin(), l = input.end();
Result data;
bool ok = parse( f, l, p, data );
if( ok ){
std::cout << "Parse o\n";
}
else{
std::cout << "Parse failed\n";
}
if( f != l )
std::cout << "Remaining unparsed: " << std::quoted( std::string( f, l ) ) << "\n";
}
int brk = 1;
#else
// my working rule tests to get a feeling what i try to reach
// identifier
qi::rule<char const*> identifier;
identifier = qi::char_( "a-zA-Z_" ) >> *qi::char_( "a-zA-Z0-9_" );
assert( test_parser( "_a_b4", identifier ) );
// subcription
qi::rule<char const*> subscription;
subscription = '[' >> qi::uint_ >> ']';
assert( test_parser( "[2]", subscription ) );
assert( test_parser( "[45]", subscription ) );
// member
qi::rule<char const*> member;
member = identifier >> -subscription;
assert( test_parser( "abc", member ) );
assert( test_parser( "abc[2]", member ) );
// class list
qi::rule<char const*> 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<char const*> instance_id;
instance_id = identifier >> ':' >> identifier;
assert( test_parser( "_a_b4:blub", instance_id ) );
// instance list
qi::rule<char const*> 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<char const*> 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<char const*> 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<char const*> property_filter;
property_filter = qi::char_( "!" ) >> '(' >> ')';
// deep search
qi::rule<char const*> 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<char const*> parent;
parent = "..";
assert( test_parser( "..", parent ) );
// stay
qi::rule<char const*> self; // um im Root zu suchen
self = ".";
assert( test_parser( ".", self ) );
// member or root
qi::rule<char const*> child; // or root
child = "/";
assert( test_parser( "/", child ) );
// complete query
qi::rule<char const*> query;
qi::rule<char const*> 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( "ube/../abc/abc[2]/?(3)#(A:a,B:b)!()[2]/blub[2]/test/?%A", 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 ) );
/*
"ube/../abc/abc[2]/?(3)#(A:a,B:b)!()[2]/blub[2]/test/?%A"
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
return 0;
}
thanks for any advice
UPDATE
i tried to extend sehe's example replacing the Query with a struct with rooted bool and a path parts vector
# changed Ast::Query
using Parts = std::vector<Part>;
struct Query
{
bool rooted{};
Parts parts;
};
# another adapter
BOOST_FUSION_ADAPT_STRUCT( AST::Query, rooted, parts )
# two new rules
qi::rule<It, AST::Parts()> parts;
qi::rule<It, bool()> rooted;
# rule definition
parts = part % '/';
rooted = qi::matches["/"];
query = rooted >> parts;
and changed the debug printers accordingly but i always get a compilation error somewhere in boost\boost\spirit\home\support\container.hpp
//[customization_container_value_default
template <typename Container, typename Enable/* = void*/>
struct container_value
: detail::remove_value_const<typename Container::value_type>
{};
Live On Coliru (if the execution time doesn't exceed the limit)
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
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
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_ >> ')')
indeep_search
? I'll just call itcount
and default it to1
. 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.
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:
//#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
To make all the tests that show remaining unparsed input fail instead, uncomment the >> qi::eoi
expression.