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;
}
See it live
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).
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;
}
See it live
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 ;).
The problem is that unlike character literals, x3::space
has an attribute. So you don't have an attribute of two separate character sequences separated by whitespace, but rather an attribute of one big character sequence which includes the whitespace.
The omit
directive is what you're after, and with that single addition your 'not working code' works. :-]
// Example program
#include <string>
#include <iostream>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/home/x3.hpp>
namespace x3 = boost::spirit::x3;
struct employee {
std::string name;
std::string location;
};
BOOST_FUSION_ADAPT_STRUCT(employee, name, location)
x3::rule<struct parse_emp_id, employee> const parse_emp = "Employee Parser";
auto parse_emp_def
= +x3::ascii::alnum
>> x3::omit[+x3::space]
>> +x3::ascii::alnum
;
BOOST_SPIRIT_DEFINE(parse_emp)
int main()
{
std::string const input = "Joe Fairbanks";
employee ret;
x3::parse(input.begin(), input.end(), parse_emp, ret);
std::cout << "Name: " << ret.name << "\tLocation: " << ret.location << '\n';
}
Online Demo
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With