I have a parser for string literals, and I'd like to attach a semantic action to the parser that will manipulate the parsed value. It seems that boost::spirit::x3::_val()
returns a reference to the parsed value when given the context, but for some reason the parsed string always enters the body of the semantic action as just an empty string, which obviously makes it difficult to read from it. It is the right string though, I've made sure by checking the addresses. Anyone know how I could have a reference to the parsed value within the semantic action attached to the parser? This here is the parser I currently use:
x3::lexeme[quote > *("\\\"" >> x3::attr('\"') | ~x3::char_(quote)) > quote]
And I'd like to add the semantic action to the end of it. Thank you in advance!
EDIT: it seems that whenever I attach any semantic action in general to the parser, the value is nullified. I suppose the question now is how could I access the value before that happens? I just need to be able to manipulate the parsed string before it is given to the AST.
In X3, semantic actions are much simpler. They're unary callables that take just the context.
Then you use free functions to extract information from the context:
x3::_val(ctx)
is like qi::_val
x3::_attr(ctx)
is like qi::_0
(or qi::_1
for simple parsers)x3::_pass(ctx)
is like qi::_pass
So, to get your semantic action, you could do:
auto qstring
= x3::rule<struct rule_type, std::string> {"qstring"}
= x3::lexeme[quote > *("\\" >> x3::char_(quote) | ~x3::char_(quote)) > quote]
;
Now to make a very odd string rule that reverses the text (after de-escaping) and requires the number of characters to be an odd-number:
auto odd_reverse = [](auto& ctx) {
auto& attr = x3::_attr(ctx);
auto& val = x3::_val(ctx);
x3::traits::move_to(attr, val);
std::reverse(val.begin(), val.end());
x3::_pass(ctx) = val.size() % 2 == 0;
};
auto odd_string
= x3::rule<struct odd_type, std::string> {"odd_string"}
= qstring [ odd_reverse ]
;
#include <boost/spirit/home/x3.hpp>
#include <iostream>
#include <iomanip>
int main() {
namespace x3 = boost::spirit::x3;
auto constexpr quote = '"';
auto qstring
= x3::rule<struct rule_type, std::string> {"qstring"}
= x3::lexeme[quote > *("\\" >> x3::char_(quote) | ~x3::char_(quote)) > quote]
;
auto odd_reverse = [](auto& ctx) {
auto& attr = x3::_attr(ctx);
auto& val = x3::_val(ctx);
x3::traits::move_to(attr, val);
std::reverse(val.begin(), val.end());
x3::_pass(ctx) = val.size() % 2 == 0;
};
auto odd_string
= x3::rule<struct odd_type, std::string> {"odd_string"}
= qstring [ odd_reverse ]
;
for (std::string const input : {
R"("test \"hello\" world")",
R"("test \"hello\" world!")",
}) {
std::string output;
auto f = begin(input), l = end(input);
if (x3::phrase_parse(f, l, odd_string, x3::blank, output)) {
std::cout << "[" << output << "]\n";
} else {
std::cout << "Failed\n";
}
if (f != l) {
std::cout << "Remaining unparsed: " << std::quoted(std::string(f,l)) << "\n";
}
}
}
Printing
[dlrow "olleh" tset]
Failed
Remaining unparsed: "\"test \\\"hello\\\" world!\""
To the added question:
EDIT: it seems that whenever I attach any semantic action in general to the parser, the value is nullified. I suppose the question now is how could I access the value before that happens? I just need to be able to manipulate the parsed string before it is given to the AST.
Yes, if you attach an action, automatic attribute propagation is inhibited. This is the same in Qi, where you could assign rules with %=
instead of =
to force automatic attribute propagation.
To get the same effect in X3, use the third template argument to x3::rule
: x3::rule<X, T, true>
to indicate you want automatic propagation.
Really, try not to fight the system. In practice, the automatic transformation system is way more sophisticated than I am willing to re-discover on my own, so I usually post-process the whole AST or at most apply some minor tweaks in an action. See also Boost Spirit: "Semantic actions are evil"?