I'm generally familiar with using qi::attr to implement a "default value" for a missing entry in parsed input. But I haven't seen how to do this when the default value needs to be pulled from an earlier parse.
I'm trying to parse into the following struct:
struct record_struct {
std::string Name;
uint8_t Distance;
uint8_t TravelDistance;
std::string Comment;
};
From a relatively simple "(text) (number) [(number)] [//comment]" format, where both the second number and the comment are optional. If the second number is not present, it's value should be set to the same as the first number.
What follows is a cut down example of working code that doesn't QUITE do what I want. This version just defaults to 0
rather than the correct value. If possible, I'd like to isolate the parsing of the two integers to a separate parser rule, without giving up using the fusion struct.
Things I've tried that haven't compiled:
- Replacing
qi::attr(0)
withqi::attr(qi::_2)
- Trying to modify after the fact on an attr match with a semantic action `qi::attr(0)[qi::_3 = qi::_2]
The full test code:
#include <string>
#include <cstdint>
#include <boost/spirit/include/qi.hpp>
struct record_struct {
std::string Name;
uint8_t Distance;
uint8_t TravelDistance;
std::string Comment;
};
BOOST_FUSION_ADAPT_STRUCT(
record_struct,
(std::string, Name)
(uint8_t, Distance)
(uint8_t, TravelDistance)
(std::string, Comment)
)
std::ostream &operator<<(std::ostream &o, const record_struct &s) {
o << s.Name << " (" << +s.Distance << ":" << +s.TravelDistance << ") " << s.Comment;
return o;
}
bool test(std::string s) {
std::string::const_iterator iter = s.begin();
std::string::const_iterator end = s.end();
record_struct result;
namespace qi = boost::spirit::qi;
bool parsed = boost::spirit::qi::parse(iter, end, (
+(qi::alnum | '_') >> qi::omit[+qi::space]
>> qi::uint_ >> ((qi::omit[+qi::space] >> qi::uint_) | qi::attr(0))
>> ((qi::omit[+qi::space] >> "//" >> +qi::char_) | qi::attr(""))
), result);
if (parsed) std::cout << "Parsed: " << result << "\n";
else std::cout << "Failed: " << std::string(iter, end) << "\n";
return parsed;
}
int main(int argc, char **argv) {
if (!test("Milan 20 22")) return 1;
if (!test("Paris 8 9 // comment")) return 1;
if (!test("London 5")) return 1;
if (!test("Rome 1 //not a real comment")) return 1;
return 0;
}
Output:
Parsed: Milan (20:22)
Parsed: Paris (8:9) comment
Parsed: London (5:0)
Parsed: Rome (1:0) not a real comment
Output I want to see:
Parsed: Milan (20:22)
Parsed: Paris (8:9) comment
Parsed: London (5:5)
Parsed: Rome (1:1) not a real comment