I am trying to learn Boost.Spirit, but I have found a difficulty.
I am trying to parse a string into the following structure:
struct employee {
std::string name;
std::string location;
};
And it seems that when two attributes with the same type are back to back, they collapse (logically) into a std::vector
of that type. Because of that rule, the following parser
+x3::ascii::alnum >>
+x3::space >>
+x3::ascii::alnum
would have the attribute of std::vector<std::string>
.
But I am trying to parse this into that struct
, which means that the ideal attribute for me would be a boost::fusion::tuple<std::string, std::string>
, so I can adapt my struct to it.
The complete version of the not working code (referenced above):
// Example program
#include <iostream>
#include <string>
#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
struct employee {
std::string name;
std::string location;
};
BOOST_FUSION_ADAPT_STRUCT(employee,
(std::string, name),
(std::string, location)
)
namespace x3 = boost::spirit::x3;
x3::rule<struct parse_emp_id, employee> const parse_emp = "Employee Parser";
auto parse_emp_def =
+x3::ascii::alnum >>
+x3::space >>
+x3::ascii::alnum
;
BOOST_SPIRIT_DEFINE(parse_emp);
int main()
{
std::string input = "Joe Fairbanks";
employee ret;
x3::parse(input.begin(), input.end(), parse_emp, ret);
std::cout << "Name: " << ret.name << "\tLocation: " << ret.location << std::endl;
}
This code triggers a static_assert
telling me that my attribute isn't correct:
error: static_assert failed "Attribute does not have the expected size."
With the command of
clang++ -std=c++14 test.cpp
(it also fails under GCC).
What I have tried
I have found a workaround to this problem, but it is messy, and I can't believe that this is the cleanest way:
// Example program
#include <iostream>
#include <string>
#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
struct employee {
std::string name;
std::string location;
};
namespace x3 = boost::spirit::x3;
x3::rule<struct parse_emp_id, employee> const parse_emp = "Employee Parser";
auto parse_emp_def =
x3::eps [
([](auto& ctx) {
x3::_val(ctx) = employee{};
})
]>>
(+x3::ascii::alnum)[
([](auto& ctx) {
x3::_val(ctx).name = x3::_attr(ctx);
})
]>>
+x3::space >>
(+x3::ascii::alnum)[
([](auto& ctx) {
x3::_val(ctx).location = x3::_attr(ctx);
})
]
;
BOOST_SPIRIT_DEFINE(parse_emp);
int main()
{
std::string input = "Joe Fairbanks";
employee ret;
x3::parse(input.begin(), input.end(), parse_emp, ret);
std::cout << "Name: " << ret.name << "\tLocation: " << ret.location << std::endl;
}
I really don't like that solution: it kinda ruins the amazing expressiveness of spirit and makes it super ugly, also if I want to add new fields into the employee
struct, then I have to add an extra lambda, instead of just updating my BOOST_FUSION_ADAPT_STRUCT
, which is much easier.
So the question is: Is there some way to (hopefully) cleanly split two consecutive attributes of the same type from the std::vector
and into a boost::fusion::vector
?
Thank you in advance for getting this far ;).