在提升精神中使用语义动作来设置字段
Using semantic actions in boost spirit to set fields
假设您有一个使用 boost spirit 的解析器来设置除 id 字段之外的此字段。是否可以使用语义动作来生成和设置 id 字段?或者有没有更好的方法来实现这个。
struct employee
{
std::string id;
int age;
std::string surname;
std::string forename;
double salary;
};
BOOST_FUSION_ADAPT_STRUCT(
client::employee,
(int, age)
(std::string, id)
(std::string, surname)
(std::string, forename)
(double, salary)
)
是的,这是可能的。
一个陷阱是语义动作的存在通常会抑制自动属性传播。由于您希望同时拥有两者,因此您需要使用 %=
而不是 =
来分配解析器表达式(请参阅 docs)。
或者,您可以动态生成一个值并使用您显示的改编。
概念验证:SA + 改编
这里我只是将 id
从改编中排除。另请注意,自 c++11 以来,您不需要重复类型:
BOOST_FUSION_ADAPT_STRUCT(
client::employee, age, /*id, */ surname, forename, salary)
我更喜欢用一些 phoenix 函数助手来编写 SA:
auto _id = px::function { std::mem_fn(&client::employee::id) };
auto _gen = px::function { client::employee::generate_id };
start %= skip(space) [
age >> name >> name >> salary >> eps
[ _id(_val) = _gen() ]
];
#include <boost/fusion/adapted.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <iomanip>
#include <iomanip>
namespace qi = boost::spirit::qi;
namespace px = boost::phoenix;
namespace fus = boost::fusion;
namespace client {
struct employee {
std::string id;
int age;
std::string surname;
std::string forename;
double salary;
static std::string generate_id() {
static int _next{0};
return "autoid#" + std::to_string(_next++);
}
};
using fus::operator<<;
}
BOOST_FUSION_ADAPT_STRUCT(
client::employee, age, /*id, */ surname, forename, salary)
template <typename It>
struct Parser : qi::grammar<It, client::employee()> {
Parser() : Parser::base_type(start) {
using namespace qi;
name = +graph;
age = uint_ >> eps(_val < 110 && _val > 16);
salary = double_;
auto _id = px::function { std::mem_fn(&client::employee::id) };
auto _gen = px::function { client::employee::generate_id };
start %= skip(space) [
age >> name >> name >> salary >> eps
[ _id(_val) = _gen() ]
];
BOOST_SPIRIT_DEBUG_NODES((start)(name)(age)(salary))
}
private:
qi::rule<It, client::employee()> start;
qi::rule<It, unsigned()> age;
qi::rule<It, std::string()> name;
qi::rule<It, double()> salary;
};
static auto qview(auto f, auto l) {
return std::quoted(
std::string_view(std::addressof(*f), std::distance(f, l)));
}
int main() {
Parser<std::string::const_iterator> p;
std::cout << fus::tuple_delimiter(',');
for (std::string const& input: {
//age surname forename salary
"55 Astley Rick 7232.88",
"23 Black Rebecca 0.00",
"77 Waters Roger 24815.07",
})
{
auto f = begin(input), l = end(input);
client::employee emp;
if (parse(f, l, p, emp)) {
std::cout << "Parsed: " << emp.id << " " << emp << "\n";
} else {
std::cout << "Parse failed\n";
}
if (f != l) {
std::cout << "Remaining unput: " << qview(f,l) << "\n";
}
}
}
版画
Parsed: autoid#0 (55,Astley,Rick,7232.88)
Parsed: autoid#1 (23,Black,Rebecca,0)
Parsed: autoid#2 (77,Waters,Roger,24815.1)
备选方案:内联生成
你会保持完全改编:
BOOST_FUSION_ADAPT_STRUCT(
client::employee, age, id, surname, forename, salary)
并在正确的位置使用 qi::attr()
重新拼写规则:
auto _gen = px::function { client::employee::generate_id };
start %= skip(space) [
age >> attr(_gen()) >> name >> name >> salary
];
Live On Coliru(省略未更改列表的其余部分)
打印(再次):
Parsed: autoid#0 (55,autoid#0,Astley,Rick,7232.88)
Parsed: autoid#1 (23,autoid#1,Black,Rebecca,0)
Parsed: autoid#2 (77,autoid#2,Waters,Roger,24815.1)
结论
回想起来,我认为替代方案更有吸引力。
假设您有一个使用 boost spirit 的解析器来设置除 id 字段之外的此字段。是否可以使用语义动作来生成和设置 id 字段?或者有没有更好的方法来实现这个。
struct employee
{
std::string id;
int age;
std::string surname;
std::string forename;
double salary;
};
BOOST_FUSION_ADAPT_STRUCT(
client::employee,
(int, age)
(std::string, id)
(std::string, surname)
(std::string, forename)
(double, salary)
)
是的,这是可能的。
一个陷阱是语义动作的存在通常会抑制自动属性传播。由于您希望同时拥有两者,因此您需要使用 %=
而不是 =
来分配解析器表达式(请参阅 docs)。
或者,您可以动态生成一个值并使用您显示的改编。
概念验证:SA + 改编
这里我只是将 id
从改编中排除。另请注意,自 c++11 以来,您不需要重复类型:
BOOST_FUSION_ADAPT_STRUCT(
client::employee, age, /*id, */ surname, forename, salary)
我更喜欢用一些 phoenix 函数助手来编写 SA:
auto _id = px::function { std::mem_fn(&client::employee::id) };
auto _gen = px::function { client::employee::generate_id };
start %= skip(space) [
age >> name >> name >> salary >> eps
[ _id(_val) = _gen() ]
];
#include <boost/fusion/adapted.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <iomanip>
#include <iomanip>
namespace qi = boost::spirit::qi;
namespace px = boost::phoenix;
namespace fus = boost::fusion;
namespace client {
struct employee {
std::string id;
int age;
std::string surname;
std::string forename;
double salary;
static std::string generate_id() {
static int _next{0};
return "autoid#" + std::to_string(_next++);
}
};
using fus::operator<<;
}
BOOST_FUSION_ADAPT_STRUCT(
client::employee, age, /*id, */ surname, forename, salary)
template <typename It>
struct Parser : qi::grammar<It, client::employee()> {
Parser() : Parser::base_type(start) {
using namespace qi;
name = +graph;
age = uint_ >> eps(_val < 110 && _val > 16);
salary = double_;
auto _id = px::function { std::mem_fn(&client::employee::id) };
auto _gen = px::function { client::employee::generate_id };
start %= skip(space) [
age >> name >> name >> salary >> eps
[ _id(_val) = _gen() ]
];
BOOST_SPIRIT_DEBUG_NODES((start)(name)(age)(salary))
}
private:
qi::rule<It, client::employee()> start;
qi::rule<It, unsigned()> age;
qi::rule<It, std::string()> name;
qi::rule<It, double()> salary;
};
static auto qview(auto f, auto l) {
return std::quoted(
std::string_view(std::addressof(*f), std::distance(f, l)));
}
int main() {
Parser<std::string::const_iterator> p;
std::cout << fus::tuple_delimiter(',');
for (std::string const& input: {
//age surname forename salary
"55 Astley Rick 7232.88",
"23 Black Rebecca 0.00",
"77 Waters Roger 24815.07",
})
{
auto f = begin(input), l = end(input);
client::employee emp;
if (parse(f, l, p, emp)) {
std::cout << "Parsed: " << emp.id << " " << emp << "\n";
} else {
std::cout << "Parse failed\n";
}
if (f != l) {
std::cout << "Remaining unput: " << qview(f,l) << "\n";
}
}
}
版画
Parsed: autoid#0 (55,Astley,Rick,7232.88)
Parsed: autoid#1 (23,Black,Rebecca,0)
Parsed: autoid#2 (77,Waters,Roger,24815.1)
备选方案:内联生成
你会保持完全改编:
BOOST_FUSION_ADAPT_STRUCT(
client::employee, age, id, surname, forename, salary)
并在正确的位置使用 qi::attr()
重新拼写规则:
auto _gen = px::function { client::employee::generate_id };
start %= skip(space) [
age >> attr(_gen()) >> name >> name >> salary
];
Live On Coliru(省略未更改列表的其余部分)
打印(再次):
Parsed: autoid#0 (55,autoid#0,Astley,Rick,7232.88)
Parsed: autoid#1 (23,autoid#1,Black,Rebecca,0)
Parsed: autoid#2 (77,autoid#2,Waters,Roger,24815.1)
结论
回想起来,我认为替代方案更有吸引力。