Yes, x3 makes it a lot easier to compose rules.
Mainly because parser expressions don't have the tendency to keep references to temporaries when assigned to variables, like they used to in the age of Qi1.
Limitation: declaring parsers with external linkage is a lot more ... complicated in X3, requiring the dance with the macros that you show BOOST_SPIRIT_{DECLARE,DEFINE}
.
Your Sample
That won't fly because the macro is meant to be used at namespace scope. The good news is you may not need it, because there is no need to declare the rule separately from the definition unless you are dealing with recursively required rules.
As an aside x3::rule<char>
is likely a mistake. char
is a tag type in that declaration, and that's not a good tag type. If you wanted an attribute type instead, that needs to be the second template argument.
auto SomeFunction(std::string &str)
{
return x3::rule<struct _tag, std::string> {"dynamic"}
= '[' >> x3::lit(str) >> ']';
}
In fact I very frequently make little factories at my declaration site:
template <typename Attr>
auto compose = [](auto p1, auto p2) {
return rule<struct _, Attr> {"compose"}
= nocase [
lexeme [ "property:" << as_parser(p1) ]
>> '='
lexeme [ "value:" << as_parser(p2) ]
];
};
That's a bit contrived but should give you ideas. Use it like compose<int>("number", x3::int_)
or compose<std::string>("name", +x3::graph)
Some Inspirational Examples
Understanding the List Operator (%) in Boost.Spirit showing an ad-hoc as<>[]
facility:
namespace {
template <typename T>
struct as_type {
template <typename Expr>
auto operator[](Expr&& expr) const {
return x3::rule<struct _, T>{"as"} = x3::as_parser(std::forward<Expr>(expr));
}
};
template <typename T> static const as_type<T> as = {};
}
Avoid throwing expectation_failure when expectation parser fails which dynamically composes a symbols lookup:
x3::symbols<char> const keyword = []{
x3::symbols<char> kw;
kw += "for","begin","end","function","while","break","switch";
return kw;
}();
Dynamically switching symbol tables in x3 which is a very complete example with many parser factories:
// (case insensitive) keyword handling
static auto kw = [](auto p) { return x3::lexeme[p >> !(x3::graph - x3::char_("/=,()"))]; };
static auto ikw = [](auto p) { return x3::no_case [kw(p)]; };
static auto qualifier = [](auto p) { return x3::lexeme['/' >> ikw(p)]; };
And even shows how to override as_spirit_parser
for your own types:
// Options and CiOptions
namespace util {
template <typename Tag>
auto as_spirit_parser(Options<Tag> const& o, bool to_lower = false) {
x3::symbols<typename Options<Tag>::type> p;
int n = 0;
for (std::string el : o._options) {
if (to_lower) boost::to_lower(el);
p.add(el, n++);
}
return kw(p);
}
template <typename Tag>
auto as_spirit_parser(IcOptions<Tag> const& o) {
return x3::no_case [ as_spirit_parser(o, true) ];
}
}
And pretty elegant way to write member-wise propagation helpers with auto-generated semantic actions:
auto set = [](auto member, auto p) {
auto propagate = [member](auto& ctx) {
traits::move_to(_attr(ctx), _val(ctx).*(member));
};
return as_parser(p)[propagate];
};
using T = ast::ShowSymbolsCommand;;
return qualifier("all") >> set(&T::all, attr(true))
| qualifier("full") >> set(&T::full, attr(true))
| qualifier("out") >> set(&T::out, '=' >> Filespec)
| qualifier("type") >> set(&T::types, '=' >> SymbolTypes)
| set(&T::wildcard, Wildcard);
I strongly suggest you per-use these examples to get a sense of just how powerful X3 composing is. Only when you /really/ require it would I consider recreating something like qi::lazy
in X3
1 or in fact anything Proto-based, like Phoenix too
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…