Frequently asked questions that semantic actions suppress the automatic distribution of attributes. Assuming semantic action will take care of that.
In general, there are two approaches:
either use operator%= instead of operator= to assign a definition to the rule
or use the third (optional) template argument for the rule<> template, which can be set to true for forced propagation semantics.
Simplified sample
Here I simplify, mainly by deleting the semantic action inside the range rule itself. Now we can completely abandon the type ast::range . No more merging required.
Instead, we use the "natural" synthesized attribute numer>>'-'>>number , which is the merging sequence of ints ( fusion::deque<int, int> in this case).
Now all that remains to be done is to make sure that the branches | correspond to compatible types. A simple repeat(1)[] fixes this.
Live on coliru
#include <boost/spirit/home/x3.hpp> #include <iostream> namespace x3 = boost::spirit::x3; namespace ast { using expr = std::vector<int>; struct printer { std::ostream& out; auto operator()(expr const& e) const { std::copy(std::begin(e), std::end(e), std::ostream_iterator<expr::value_type>(out, ", "));; } }; } namespace parser { auto const expand = [](auto& ctx) { using boost::fusion::at_c; for (auto i = at_c<0>(_attr(ctx)); i <= at_c<1>(_attr(ctx)); ++i) x3::_val(ctx).push_back(i); }; auto const number = x3::uint_; auto const range = x3::rule<struct _r, ast::expr> {} = (number >> '-' >> number) [expand]; auto const expr = x3::rule<struct _e, ast::expr> {} = -(range | x3::repeat(1)[number] ) % ','; } template<class Phrase, class Grammar, class Skipper, class AST, class Printer> auto test(Phrase const& phrase, Grammar const& grammar, Skipper const& skipper, AST& data, Printer const& print) { auto first = phrase.begin(); auto last = phrase.end(); auto& out = print.out; auto const ok = phrase_parse(first, last, grammar, skipper, data); if (ok) { out << "OK! Parsed: "; print(data); out << "\n"; } else { out << "Parse failed:\n"; out << "\t on input: " << phrase << "\n"; } if (first != last) out << "\t Remaining unparsed: '" << std::string(first, last) << '\n'; } int main() { std::string numeric_tests[] = { "1,2,3,4,6,7,9,10,11,12", // individually enumerated "1-4,6-7,9-12", // short-hand: using three ranges }; for (auto const& t : numeric_tests) { ast::expr numeric_data; test(t, parser::expr, x3::space, numeric_data, ast::printer{std::cout}); } }
Print
OK! Parsed: 1, 2, 3, 4, 6, 7, 9, 10, 11, 12, OK! Parsed: 1, 2, 3, 4, 6, 7, 9, 10, 11, 12,