I would say all the semantic actions are useless here[1].
All of them can be replaced by a well-placed qi::attr
to simply synthesize the desired attribute in-place and you can again use the automatic attribute propagation magic of Spirit.
In my experience, if you start using semantic actions for the simplest things, you're better of without Spirit
Simplify first
So, here's my take at the AST node
type:
struct node {
std::string element, category;
std::vector<node> children;
};
BOOST_FUSION_ADAPT_STRUCT(node, element, category, children)
It's functionally equivalent to yours, but more c++-like, and therefore somewhat more friendly. Now, you didn't give the rule declarations, so "imagined" them as follows:
qi::rule<It, node(), qi::space_type> ABC, ABD, test;
qi::rule<It, std::vector<node>(), qi::space_type> B;
// also for demo purposes:
B = '{' >> -(test % ',') >> '}';
Now, my suggestion of you rules, simplified:
ABC = qi::string("a") >> qi::attr("ABC") >> B >> 'c';
ABD = qi::string("a") >> qi::attr("ABD") >> B >> 'd';
test = ABC | ABD;
Indeed, you need to hold
because /otherwise/ both ABC/ABD will bind to the same internal synthesized attribut node&
:
ABC = qi::hold[ qi::string("a") >> qi::attr("ABC") >> B >> 'c' ];
ABD = qi::hold[ qi::string("a") >> qi::attr("ABD") >> B >> 'd' ];
This is all you wanted/needed.
Test/Demo Program
I constructed a few test cases for the grammar as I "reverse-engi-magined" it and added debug code:
//#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/adapted.hpp>
struct node {
std::string element, category;
std::vector<node> children;
};
BOOST_FUSION_ADAPT_STRUCT(node, element, category, children)
static std::ostream& operator<<(std::ostream& os, node const& n) {
return os << boost::fusion::as_vector(n);
}
static std::ostream& operator<<(std::ostream& os, std::vector<node> const& v) {
os << "{";
for (size_t i = 0; v.size()>1 && i<v.size()-1; ++i)
os << v.at(i) << ", ";
if (!v.empty()) os << v.back();
return os << "}";
}
namespace qi = boost::spirit::qi;
int main() {
using It = std::string::const_iterator;
qi::rule<It, node(), qi::space_type> ABC, ABD, test;
qi::rule<It, std::vector<node>(), qi::space_type> B;
#if 1
ABC = qi::hold[ qi::string("a") >> qi::attr("ABC") >> B >> 'c' ];
ABD = qi::hold[ qi::string("a") >> qi::attr("ABD") >> B >> 'd' ];
#else
ABC = qi::string("a") >> qi::attr("ABC") >> B >> 'c' ;
ABD = qi::string("a") >> qi::attr("ABD") >> B >> 'd' ;
#endif
test = ABC | ABD;
B = '{' >> -(test % ',') >> '}';
for (std::string const s : {
"a{a{}c}c",
"a{a{}d}d",
"a{a{}c}d",
"a{a{}d,a{}c}c",
})
{
std::cout << "\n-- Parsing '" << s << "'\n";
It f = s.begin(), l = s.end();
node parsed;
bool ok = qi::phrase_parse(f, l, test, qi::space, parsed);
if (ok)
std::cout << "Parsed: " << parsed << "\n";
else
std::cout << "Parse failed\n";
if (f!=l)
std::cout << "Remaining unparsed input: '" << std::string(f,l) << "'\n";
}
}
Output (with holds):
-- Parsing 'a{a{}c}c'
Parsed: (a ABC {(a ABC {})})
-- Parsing 'a{a{}d}d'
Parsed: (a ABD {(a ABD {})})
-- Parsing 'a{a{}c}d'
Parsed: (a ABD {(a ABC {})})
-- Parsing 'a{a{}d,a{}c}c'
Parsed: (a ABC {(a ABD {}), (a ABC {})})
[1]. See also Boost Spirit: "Semantic actions are evil"?