I am trying to parse the following struct:
struct Selector {
std::string element;
std::string id;
std::vector<std::string> classes;
};
This struct is used to parse selectors in the form element#id.class1.class2.classn. These selectors always start with 1 or no elements, could contain 1 or no ids, and could contain 0 to n classes.
This gets even more complicated though, because classes and id can appear in any order, so the following selectors are all valid: element#id.class1, .class1#id.class2.class3, #id.class1.class2, .class1.class2#id. For this reason, I have not been able to use hold[], or at<T>() approaches described here, and I also have not been able to use BOOST_FUSION_ADAPT_STRUCT.
The only way that I have been able to synthesize this struct, is with the following rules:
auto element = [](auto& ctx){x3::_val(ctx).element = x3::_attr(ctx);};
auto id = [](auto& ctx){x3::_val(ctx).id = x3::_attr(ctx);};
auto empty = [](auto& ctx){x3::_val(ctx) = "";};
auto classes = [](auto& ctx){x3::_val(ctx).classes.insert(x3::_val(ctx).classes.end(), x3::_attr(ctx).begin(), x3::_attr(ctx).end());};
auto elementRule = x3::rule<class EmptyIdClass, std::string>() = +x3::char_("a-zA-Z") | x3::attr("");
auto idRule = x3::rule<class EmptyIdClass, std::string>() = ("#" >> +x3::char_("a-zA-Z")) | x3::attr("");
auto classesRule = x3::rule<class ClassesClass, std::vector<std::string>>() = *("." >> +x3::char_("a-zA-Z"));
auto selectorRule = x3::rule<class TestClass, Selector>() = elementRule[element] >> classesRule[classes] >> idRule[id] >> classesRule[classes];
What would be the best way to parse this struct? Is it possible to synthesize this selector struct naturally, using BOOST_FUSION_ADAPT_STRUCT, and without semantic actions?
It seems like everytime I think I am am getting the hang of Spirit X3, I stumble upon a new challenge. In this particular case, I learned about issues with backtracking, about an issue with using at<T>() that was introduced in Boost 1.70 here, and I also learned that hold[] is not supported by X3.
I've written similar answers before:
I don't think you can directly fusion-adapt. Although if you are very motivated (e.g. you already have the adapted structs) you could make some generic helpers off that.
To be fair, a little bit of restructuring in your code seems pretty nice to me, already. Here's my effort to make it more elegant/convenient. I'll introduce a helper macro just like BOOST_FUSION_ADAPT_XXX, but not requiring any Boost Fusion.
Let's Start With The AST
As always, I like to start with the basics. Understanding the goal is half the battle:
Note that I fixed the optionality of some parts to reflect real life.
Magic Sauce (see below)
We'll dig into this later. Suffice it to say it generates the semantic actions that you had to tediously write.
Main dish
Now, we can simplify the parser rules a lot, and run the tests:
See it Live On Wandbox, printing:
The Magic
Now, how did I generate those actions? Using a little bit of Boost Preprocessor:
Now, you might see that it defines static const variables named like the Ast types.
The real magic is
Propagators::Prop<F>which has a bit of dispatch to allow for container attributes and members. Otherwise it just relays tox3::traits::move_to:BONUS
A lot of the complexity in the propagator type is from handling container attributes. However, you don't actually need any of that:
Is more than enough, and the propagation helper can be simplified to:
As you can see evaporating the tag dispatch has a beneficial effect.
See the simplified version Live On Wandbox again.
FULL LISTING
For posterity on this site:
test.cpp
propagate.hpp
as.hpp