问题
I'm having some trouble using the alternative parser, I need the parser to rollback changes if the first option failed. I tried using hold[] but I get the error that it "could not deduce template argument"
I'm trying to parse an AST, and I'm storing it in:
struct node;
BOOST_FUSION_DEFINE_STRUCT(
, node,
(std::string, element)
(std::string, category)
(std::vector<node>, children)
)
'element' is the element parsed, 'category' it's classification, and then I have a vector to store his children
So, to exemplify I have several cases similar to:
ABC=
(qi::char_('a')[at_c<0>(qi::_val) = "a", at_c<1>(qi::_val) = "ABC"]
>> B[push_back(at_c<2>(qi::_val), qi::_1)]
>> qi::char_('c'))
;
ABD=
(qi::char_('a')[at_c<0>(qi::_val) = "a", at_c<1>(qi::_val) = "ABD"]
>> B[push_back(at_c<2>(qi::_val), qi::_1)]
>> qi::char_('d')
)
;
test = ABC | ABD;
B is some rule that returns a node. If I run this I get B repeated for the second alternative, since it was pushed in the first. As I said I tried:
test = hold[ABC]| ABD;
but I get that error. The solution I've come to is to store the result of rule B in a local variable and then push it only at the end of the rule (meaning the rule matches). But that results in very complicated semantic actions, any alternative?
回答1:
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:
Live On Coliru - with
qi::hold[]
Live On Coliru - without
qi::hold[]
//#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"?
来源:https://stackoverflow.com/questions/33482421/rolling-back-changes-in-alternative-parsers-in-qi-spirit