在移植遗留 boost::spirit 代码时遇到困难
Got stuck porting legacy boost::spirit code
我正在将一些遗留代码从 VS2010 和 boost1.53 移植到 VS2017 和 boost1.71。
我在尝试编译时卡住了最后两个小时。
密码是:
#include <string>
#include <vector>
#include <fstream>
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
namespace qi = boost::spirit::qi;
using qi::_1; using qi::_2; using qi::_3; using qi::_4;
enum TYPE { SEND, CHECK, COMMENT };
struct Command
{
TYPE type;
std::string id;
std::string arg1;
std::string arg2;
bool checking;
};
class Parser
{
typedef boost::spirit::istream_iterator It;
typedef std::vector<Command> Commands;
struct deferred_fill
{
template <typename R, typename S, typename T, typename U> struct result { typedef void type; };//Not really sure still necessary
typedef void result_type;//Not really sure still necessary
void operator() (boost::iterator_range<It> const& id, boost::iterator_range<It> const& arg1, bool checking, Command& command) const
{
command.type = TYPE::SEND;
command.id.assign(id.begin(), id.end());
command.arg1.assign(arg1.begin(), arg1.end());
command.checking = checking;
}
};
private:
qi::symbols<char, bool> value;
qi::rule<It> ignore;
qi::rule<It, Command()> send;
qi::rule<It, Commands()> start;
boost::phoenix::function<deferred_fill> fill;
public:
std::vector<Command> commands;
Parser()
{
using namespace qi;
using boost::phoenix::push_back;
value.add("TRUE", true)
("FALSE", false);
send = ("SEND_CONFIRM" >> *blank >> '(' >> *blank >> raw[*~char_(',')] >> ','
>> *blank >> raw[*~char_(',')] >> ','
>> *blank >> value >> *blank >> ')' >> *blank >> ';')[fill(_1, _2, _3, _val)];
ignore = *~char_("\r\n");
start = (send[push_back(_val, _1)] | ignore) % eol;
}
void parse(const std::string& path)
{
std::ifstream in(path, std::ios_base::in);
if (!in) return;
in >> std::noskipws;//No white space skipping
boost::spirit::istream_iterator first(in);
boost::spirit::istream_iterator last;
qi::parse(first, last, start, commands);
}
};
int main(int argc, char* argv[])
{
Parser parser;
parser.parse("file.txt");
return 0;
}
编译器在接下来的方式中抱怨(只复制第一行):
1>z:\externos\boost_1_71_0\boost\phoenix\core\detail\function_eval.hpp(116): error C2039: 'type': no es un miembro de 'boost::result_of<const Parser::deferred_fill (std::vector<Value,std::allocator<char>> &,std::vector<Value,std::allocator<char>> &,boost::iterator_range<Parser::It> &,Command &)>'
1> with
1> [
1> Value=char
1> ]
1>z:\externos\boost_1_71_0\boost\phoenix\core\detail\function_eval.hpp(114): note: vea la declaración de 'boost::result_of<const Parser::deferred_fill (std::vector<Value,std::allocator<char>> &,std::vector<Value,std::allocator<char>> &,boost::iterator_range<Parser::It> &,Command &)>'
1> with
1> [
1> Value=char
1> ]
1>z:\externos\boost_1_71_0\boost\phoenix\core\detail\function_eval.hpp(89): note: vea la referencia a la creación de instancias de plantilla clase de 'boost::phoenix::detail::function_eval::result_impl<F,void (Head,const boost::phoenix::actor<boost::spirit::argument<1>>&,const boost::phoenix::actor<boost::spirit::argument<2>>&,const boost::phoenix::actor<boost::spirit::attribute<0>>&),const boost::phoenix::vector2<Env,Actions> &>' que se está compilando
1> with
1> [
1> F=const boost::proto::exprns_::basic_expr<boost::proto::tagns_::tag::terminal,boost::proto::argsns_::term<Parser::deferred_fill>,0> &,
1> Head=const boost::phoenix::actor<boost::spirit::argument<0>> &,
1> Env=boost::phoenix::vector4<const boost::phoenix::actor<boost::proto::exprns_::basic_expr<boost::phoenix::detail::tag::function_eval,boost::proto::argsns_::list5<boost::proto::exprns_::basic_expr<boost::proto::tagns_::tag::terminal,boost::proto::argsns_::term<Parser::deferred_fill>,0>,boost::phoenix::actor<boost::spirit::argument<0>>,boost::phoenix::actor<boost::spirit::argument<1>>,boost::phoenix::actor<boost::spirit::argument<2>>,boost::phoenix::actor<boost::spirit::attribute<0>>>,5>> *,boost::fusion::vector<std::vector<char,std::allocator<char>>,std::vector<char,std::allocator<char>>,boost::iterator_range<Parser::It>,std::vector<char,std::allocator<char>>,boost::iterator_range<Parser::It>,std::vector<char,std::allocator<char>>,bool,std::vector<char,std::allocator<char>>,std::vector<char,std::allocator<char>>> &,boost::spirit::context<boost::fusion::cons<Command &,boost::fusion::nil_>,boost::fusion::vector<>> &,bool &> &,
1> Actions=const boost::phoenix::default_actions &
1> ]
我猜这个错误与使用 boost::spirit::istream_iterator 而不是 char* 有关,但我不知道如何修复它以使其再次工作。
我运行没主意了,请问谁能看出我的错误在哪里?
噢。你正在做很棒的事情。 Sadly/fortunately太复杂了。
所以让我们先修正,再简化。
错误
就像你说的,
void operator() (boost::iterator_range<It> const& id, boost::iterator_range<It> const& arg1, bool checking, Command& command) const
与实际调用的不匹配:
void Parser::deferred_fill::operator()(T&& ...) const [with T =
{std::vector<char>&, std::vector<char>&,
boost::iterator_range<boost::spirit::basic_istream_iterator<...> >&,
Command&}]
原因不是迭代器(如您所见,boost::spirit__istream_iterator
没问题)。
然而,这是因为您正在获取 其他 事物作为属性。结果 *blank
将属性公开为 vector<char>
。所以你可以 "fix" 通过 omit[]
-ing 那些。让我们将其包装在像 ignore
这样的无属性规则中,这样我们就可以减少混乱。
现在调用是
void Parser::deferred_fill::operator()(T&& ...) const [with T = {boost::iterator_range<It>&, boost::iterator_range<It>&, bool&, Command&}]
所以兼容编译。解析:
SEND_CONFIRM("this is the id part", "this is arg1", TRUE);
有
Parser parser;
parser.parse("file.txt");
std::cout << std::boolalpha;
for (auto& cmd : parser.commands) {
std::cout << '{' << cmd.id << ", "
<< cmd.arg1 << ", "
<< cmd.arg2 << ", "
<< cmd.checking << "}\n";
}
版画
{"this is the id part", "this is arg1", , TRUE}
让我们改进一下
- 这需要船长
- 这需要自动属性传播
- 其他一些风格元素
船长
让我们使用内置功能,而不是 "calling" 明确的船长:
rule<It, Attr(), Skipper> x;
定义一个规则,跳过与 Skipper
类型的解析器匹配的输入序列。您需要实际传入该类型的船长。
- 使用
qi::phrase_parse
代替 qi::parse
- 通过使用
qi::skip()
指令
我一直提倡第二种方法,因为它的界面更友好、更不容易出错。
因此声明船长类型:
qi::rule<It, Command(), qi::blank_type> send;
我们可以将规则简化为:
send = (lit("SEND_CONFIRM") >> '('
>> raw[*~char_(',')] >> ','
>> raw[*~char_(',')] >> ','
>> value >> ')' >> ';')
[fill(_1, _2, _3, _val)];
然后根据 start
规则传递船长:
start = skip(blank) [
(send[push_back(_val, _1)] | ignore) % eol
];
就是这样。仍然编译和匹配相同。
使用词素跳转
还是同一个话题,lexemes
实际上抑制了船长¹,所以你不必raw[]
。这也将暴露的属性更改为 vector<char>
:
void operator() (std::vector<char> const& id, std::vector<char> const& arg1, bool checking, Command& command) const
自动属性传播
Qi 有语义动作,但它的真正优势在于它们是可选的:Boost Spirit: "Semantic actions are evil"?
push_back(_val, _1)
实际上是 *p
、+p
和 p % delim
² 的自动属性传播语义,所以请放弃它:
start = skip(blank) [
(send | ignore) % eol
];
(注意 send|ignore
实际上合成了 optional<Command>
,这对自动传播来说很好)
std::vector
与 std::string
属性兼容,例如,如果我们可以为 arg2
添加占位符,我们就可以匹配 Command
结构布局:
send = lit("SEND_CONFIRM") >> '('
>> attr(SEND) // fill type
>> lexeme[*~char_(',')] >> ','
>> lexeme[*~char_(',')] >> ','
>> attr(std::string()) // fill for arg2
>> value >> ')' >> ';'
;
现在为了能够删除 fill
及其实现,我们必须将 Command
调整为融合序列:
BOOST_FUSION_ADAPT_STRUCT(Command, type, id, arg1, arg2, checking)
样式元素 1
为您的命令类型使用命名空间可以更轻松地使用命令的 operator<<
重载进行 ADL,这样我们就可以 std::cout << cmd;
At this point, it all works in a fraction of the code: Live On Coliru
样式元素 2
如果可以的话,让你的解析器无状态。这意味着它可以是常量,所以你可以:
- 重复使用,无需昂贵的施工
- 优化器还有更多工作要做
- 它更可测试(有状态的东西更难证明幂等)
所以,不是 commands
一个成员,而是 return 他们。当我们这样做的时候,我们可以使 parse
成为一个静态函数
与其对迭代器类型进行硬编码,不如将其灵活地作为模板参数。这样你就不会被 multi_pass_adaptor
和 istream_iterator
的开销所困扰,如果你在 char[]
缓冲区中有一个命令,string
或 string_view
在某些时候.
此外,使用合适的入口点从 qi::grammar
派生您的解析器意味着您可以将其用作解析器表达式(实际上是 a non-terminal,就像 rule<>
) 与任何其他解析器一样。
考虑启用规则调试(参见示例)
完整代码
#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>
#include <fstream>
namespace qi = boost::spirit::qi;
namespace Commands {
enum TYPE { SEND, CHECK, COMMENT };
enum BOOL { FALSE, TRUE };
struct Command {
TYPE type;
std::string id;
std::string arg1;
std::string arg2;
BOOL checking;
};
typedef std::vector<Command> Commands;
// for (debug) output
static inline std::ostream& operator<<(std::ostream& os, TYPE t) {
switch (t) {
case SEND: return os << "SEND";
case CHECK: return os << "CHECK";
case COMMENT: return os << "COMMENT";
}
return os << "(unknown)";
}
static inline std::ostream& operator<<(std::ostream& os, BOOL b) {
return os << (b?"TRUE":"FALSE");
}
using boost::fusion::operator<<;
}
BOOST_FUSION_ADAPT_STRUCT(Commands::Command, type, id, arg1, arg2, checking)
namespace Commands {
template <typename It>
class Parser : public qi::grammar<It, Commands()> {
public:
Commands commands;
Parser() : Parser::base_type(start) {
using namespace qi;
value.add("TRUE", TRUE)
("FALSE", FALSE);
send = lit("SEND_CONFIRM") >> '('
>> attr(SEND) // fill type
>> lexeme[*~char_(',')] >> ','
>> lexeme[*~char_(',')] >> ','
>> attr(std::string()) // fill for arg2
>> value >> ')' >> ';'
;
ignore = +~char_("\r\n");
start = skip(blank) [
(send | ignore) % eol
];
BOOST_SPIRIT_DEBUG_NODES((start)(send)(ignore))
}
private:
qi::symbols<char, BOOL> value;
qi::rule<It> ignore;
qi::rule<It, Command(), qi::blank_type> send;
qi::rule<It, Commands()> start;
};
static Commands parse(std::istream& in) {
using It = boost::spirit::istream_iterator;
static const Parser<It> parser;
It first(in >> std::noskipws), //No white space skipping
last;
Commands commands;
if (!qi::parse(first, last, parser, commands)) {
throw std::runtime_error("command parse error");
}
return commands; // c++11 move semantics
}
}
int main() {
try {
for (auto& cmd : Commands::parse(std::cin))
std::cout << cmd << "\n";
} catch(std::exception const& e) {
std::cout << e.what() << "\n";
}
}
版画
(SEND "this is the id part" "this is arg1" TRUE)
或者确实定义了 BOOST_SPIRIT_DEBUG:
<start>
<try>SEND_CONFIRM("this i</try>
<send>
<try>SEND_CONFIRM("this i</try>
<success>\n</success>
<attributes>[[SEND, [", t, h, i, s, , i, s, , t, h, e, , i, d, , p, a, r, t, "], [", t, h, i, s, , i, s, , a, r, g, 1, "], [], TRUE]]</attributes>
</send>
<send>
<try></try>
<fail/>
</send>
<ignore>
<try></try>
<fail/>
</ignore>
<success>\n</success>
<attributes>[[[SEND, [", t, h, i, s, , i, s, , t, h, e, , i, d, , p, a, r, t, "], [", t, h, i, s, , i, s, , a, r, g, 1, "], [], TRUE]]]</attributes>
</start>
¹ 根据需要预先跳过;参见 Boost spirit skipper issues
²(然后是一些,但我们不要离题)
我正在将一些遗留代码从 VS2010 和 boost1.53 移植到 VS2017 和 boost1.71。
我在尝试编译时卡住了最后两个小时。
密码是:
#include <string>
#include <vector>
#include <fstream>
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
namespace qi = boost::spirit::qi;
using qi::_1; using qi::_2; using qi::_3; using qi::_4;
enum TYPE { SEND, CHECK, COMMENT };
struct Command
{
TYPE type;
std::string id;
std::string arg1;
std::string arg2;
bool checking;
};
class Parser
{
typedef boost::spirit::istream_iterator It;
typedef std::vector<Command> Commands;
struct deferred_fill
{
template <typename R, typename S, typename T, typename U> struct result { typedef void type; };//Not really sure still necessary
typedef void result_type;//Not really sure still necessary
void operator() (boost::iterator_range<It> const& id, boost::iterator_range<It> const& arg1, bool checking, Command& command) const
{
command.type = TYPE::SEND;
command.id.assign(id.begin(), id.end());
command.arg1.assign(arg1.begin(), arg1.end());
command.checking = checking;
}
};
private:
qi::symbols<char, bool> value;
qi::rule<It> ignore;
qi::rule<It, Command()> send;
qi::rule<It, Commands()> start;
boost::phoenix::function<deferred_fill> fill;
public:
std::vector<Command> commands;
Parser()
{
using namespace qi;
using boost::phoenix::push_back;
value.add("TRUE", true)
("FALSE", false);
send = ("SEND_CONFIRM" >> *blank >> '(' >> *blank >> raw[*~char_(',')] >> ','
>> *blank >> raw[*~char_(',')] >> ','
>> *blank >> value >> *blank >> ')' >> *blank >> ';')[fill(_1, _2, _3, _val)];
ignore = *~char_("\r\n");
start = (send[push_back(_val, _1)] | ignore) % eol;
}
void parse(const std::string& path)
{
std::ifstream in(path, std::ios_base::in);
if (!in) return;
in >> std::noskipws;//No white space skipping
boost::spirit::istream_iterator first(in);
boost::spirit::istream_iterator last;
qi::parse(first, last, start, commands);
}
};
int main(int argc, char* argv[])
{
Parser parser;
parser.parse("file.txt");
return 0;
}
编译器在接下来的方式中抱怨(只复制第一行):
1>z:\externos\boost_1_71_0\boost\phoenix\core\detail\function_eval.hpp(116): error C2039: 'type': no es un miembro de 'boost::result_of<const Parser::deferred_fill (std::vector<Value,std::allocator<char>> &,std::vector<Value,std::allocator<char>> &,boost::iterator_range<Parser::It> &,Command &)>'
1> with
1> [
1> Value=char
1> ]
1>z:\externos\boost_1_71_0\boost\phoenix\core\detail\function_eval.hpp(114): note: vea la declaración de 'boost::result_of<const Parser::deferred_fill (std::vector<Value,std::allocator<char>> &,std::vector<Value,std::allocator<char>> &,boost::iterator_range<Parser::It> &,Command &)>'
1> with
1> [
1> Value=char
1> ]
1>z:\externos\boost_1_71_0\boost\phoenix\core\detail\function_eval.hpp(89): note: vea la referencia a la creación de instancias de plantilla clase de 'boost::phoenix::detail::function_eval::result_impl<F,void (Head,const boost::phoenix::actor<boost::spirit::argument<1>>&,const boost::phoenix::actor<boost::spirit::argument<2>>&,const boost::phoenix::actor<boost::spirit::attribute<0>>&),const boost::phoenix::vector2<Env,Actions> &>' que se está compilando
1> with
1> [
1> F=const boost::proto::exprns_::basic_expr<boost::proto::tagns_::tag::terminal,boost::proto::argsns_::term<Parser::deferred_fill>,0> &,
1> Head=const boost::phoenix::actor<boost::spirit::argument<0>> &,
1> Env=boost::phoenix::vector4<const boost::phoenix::actor<boost::proto::exprns_::basic_expr<boost::phoenix::detail::tag::function_eval,boost::proto::argsns_::list5<boost::proto::exprns_::basic_expr<boost::proto::tagns_::tag::terminal,boost::proto::argsns_::term<Parser::deferred_fill>,0>,boost::phoenix::actor<boost::spirit::argument<0>>,boost::phoenix::actor<boost::spirit::argument<1>>,boost::phoenix::actor<boost::spirit::argument<2>>,boost::phoenix::actor<boost::spirit::attribute<0>>>,5>> *,boost::fusion::vector<std::vector<char,std::allocator<char>>,std::vector<char,std::allocator<char>>,boost::iterator_range<Parser::It>,std::vector<char,std::allocator<char>>,boost::iterator_range<Parser::It>,std::vector<char,std::allocator<char>>,bool,std::vector<char,std::allocator<char>>,std::vector<char,std::allocator<char>>> &,boost::spirit::context<boost::fusion::cons<Command &,boost::fusion::nil_>,boost::fusion::vector<>> &,bool &> &,
1> Actions=const boost::phoenix::default_actions &
1> ]
我猜这个错误与使用 boost::spirit::istream_iterator 而不是 char* 有关,但我不知道如何修复它以使其再次工作。
我运行没主意了,请问谁能看出我的错误在哪里?
噢。你正在做很棒的事情。 Sadly/fortunately太复杂了。
所以让我们先修正,再简化。
错误
就像你说的,
void operator() (boost::iterator_range<It> const& id, boost::iterator_range<It> const& arg1, bool checking, Command& command) const
与实际调用的不匹配:
void Parser::deferred_fill::operator()(T&& ...) const [with T = {std::vector<char>&, std::vector<char>&, boost::iterator_range<boost::spirit::basic_istream_iterator<...> >&, Command&}]
原因不是迭代器(如您所见,boost::spirit__istream_iterator
没问题)。
然而,这是因为您正在获取 其他 事物作为属性。结果 *blank
将属性公开为 vector<char>
。所以你可以 "fix" 通过 omit[]
-ing 那些。让我们将其包装在像 ignore
这样的无属性规则中,这样我们就可以减少混乱。
现在调用是
void Parser::deferred_fill::operator()(T&& ...) const [with T = {boost::iterator_range<It>&, boost::iterator_range<It>&, bool&, Command&}]
所以兼容编译。解析:
SEND_CONFIRM("this is the id part", "this is arg1", TRUE);
有
Parser parser;
parser.parse("file.txt");
std::cout << std::boolalpha;
for (auto& cmd : parser.commands) {
std::cout << '{' << cmd.id << ", "
<< cmd.arg1 << ", "
<< cmd.arg2 << ", "
<< cmd.checking << "}\n";
}
版画
{"this is the id part", "this is arg1", , TRUE}
让我们改进一下
- 这需要船长
- 这需要自动属性传播
- 其他一些风格元素
船长
让我们使用内置功能,而不是 "calling" 明确的船长:
rule<It, Attr(), Skipper> x;
定义一个规则,跳过与 Skipper
类型的解析器匹配的输入序列。您需要实际传入该类型的船长。
- 使用
qi::phrase_parse
代替qi::parse
- 通过使用
qi::skip()
指令
我一直提倡第二种方法,因为它的界面更友好、更不容易出错。
因此声明船长类型:
qi::rule<It, Command(), qi::blank_type> send;
我们可以将规则简化为:
send = (lit("SEND_CONFIRM") >> '('
>> raw[*~char_(',')] >> ','
>> raw[*~char_(',')] >> ','
>> value >> ')' >> ';')
[fill(_1, _2, _3, _val)];
然后根据 start
规则传递船长:
start = skip(blank) [
(send[push_back(_val, _1)] | ignore) % eol
];
就是这样。仍然编译和匹配相同。
使用词素跳转
还是同一个话题,lexemes
实际上抑制了船长¹,所以你不必raw[]
。这也将暴露的属性更改为 vector<char>
:
void operator() (std::vector<char> const& id, std::vector<char> const& arg1, bool checking, Command& command) const
自动属性传播
Qi 有语义动作,但它的真正优势在于它们是可选的:Boost Spirit: "Semantic actions are evil"?
push_back(_val, _1)
实际上是*p
、+p
和p % delim
² 的自动属性传播语义,所以请放弃它:start = skip(blank) [ (send | ignore) % eol ];
(注意
send|ignore
实际上合成了optional<Command>
,这对自动传播来说很好)std::vector
与std::string
属性兼容,例如,如果我们可以为arg2
添加占位符,我们就可以匹配Command
结构布局:send = lit("SEND_CONFIRM") >> '(' >> attr(SEND) // fill type >> lexeme[*~char_(',')] >> ',' >> lexeme[*~char_(',')] >> ',' >> attr(std::string()) // fill for arg2 >> value >> ')' >> ';' ;
现在为了能够删除
fill
及其实现,我们必须将Command
调整为融合序列:BOOST_FUSION_ADAPT_STRUCT(Command, type, id, arg1, arg2, checking)
样式元素 1
为您的命令类型使用命名空间可以更轻松地使用命令的 operator<<
重载进行 ADL,这样我们就可以 std::cout << cmd;
At this point, it all works in a fraction of the code: Live On Coliru
样式元素 2
如果可以的话,让你的解析器无状态。这意味着它可以是常量,所以你可以:
- 重复使用,无需昂贵的施工
- 优化器还有更多工作要做
- 它更可测试(有状态的东西更难证明幂等)
所以,不是
commands
一个成员,而是 return 他们。当我们这样做的时候,我们可以使parse
成为一个静态函数与其对迭代器类型进行硬编码,不如将其灵活地作为模板参数。这样你就不会被
multi_pass_adaptor
和istream_iterator
的开销所困扰,如果你在char[]
缓冲区中有一个命令,string
或string_view
在某些时候.此外,使用合适的入口点从
qi::grammar
派生您的解析器意味着您可以将其用作解析器表达式(实际上是 a non-terminal,就像rule<>
) 与任何其他解析器一样。考虑启用规则调试(参见示例)
完整代码
#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>
#include <fstream>
namespace qi = boost::spirit::qi;
namespace Commands {
enum TYPE { SEND, CHECK, COMMENT };
enum BOOL { FALSE, TRUE };
struct Command {
TYPE type;
std::string id;
std::string arg1;
std::string arg2;
BOOL checking;
};
typedef std::vector<Command> Commands;
// for (debug) output
static inline std::ostream& operator<<(std::ostream& os, TYPE t) {
switch (t) {
case SEND: return os << "SEND";
case CHECK: return os << "CHECK";
case COMMENT: return os << "COMMENT";
}
return os << "(unknown)";
}
static inline std::ostream& operator<<(std::ostream& os, BOOL b) {
return os << (b?"TRUE":"FALSE");
}
using boost::fusion::operator<<;
}
BOOST_FUSION_ADAPT_STRUCT(Commands::Command, type, id, arg1, arg2, checking)
namespace Commands {
template <typename It>
class Parser : public qi::grammar<It, Commands()> {
public:
Commands commands;
Parser() : Parser::base_type(start) {
using namespace qi;
value.add("TRUE", TRUE)
("FALSE", FALSE);
send = lit("SEND_CONFIRM") >> '('
>> attr(SEND) // fill type
>> lexeme[*~char_(',')] >> ','
>> lexeme[*~char_(',')] >> ','
>> attr(std::string()) // fill for arg2
>> value >> ')' >> ';'
;
ignore = +~char_("\r\n");
start = skip(blank) [
(send | ignore) % eol
];
BOOST_SPIRIT_DEBUG_NODES((start)(send)(ignore))
}
private:
qi::symbols<char, BOOL> value;
qi::rule<It> ignore;
qi::rule<It, Command(), qi::blank_type> send;
qi::rule<It, Commands()> start;
};
static Commands parse(std::istream& in) {
using It = boost::spirit::istream_iterator;
static const Parser<It> parser;
It first(in >> std::noskipws), //No white space skipping
last;
Commands commands;
if (!qi::parse(first, last, parser, commands)) {
throw std::runtime_error("command parse error");
}
return commands; // c++11 move semantics
}
}
int main() {
try {
for (auto& cmd : Commands::parse(std::cin))
std::cout << cmd << "\n";
} catch(std::exception const& e) {
std::cout << e.what() << "\n";
}
}
版画
(SEND "this is the id part" "this is arg1" TRUE)
或者确实定义了 BOOST_SPIRIT_DEBUG:
<start>
<try>SEND_CONFIRM("this i</try>
<send>
<try>SEND_CONFIRM("this i</try>
<success>\n</success>
<attributes>[[SEND, [", t, h, i, s, , i, s, , t, h, e, , i, d, , p, a, r, t, "], [", t, h, i, s, , i, s, , a, r, g, 1, "], [], TRUE]]</attributes>
</send>
<send>
<try></try>
<fail/>
</send>
<ignore>
<try></try>
<fail/>
</ignore>
<success>\n</success>
<attributes>[[[SEND, [", t, h, i, s, , i, s, , t, h, e, , i, d, , p, a, r, t, "], [", t, h, i, s, , i, s, , a, r, g, 1, "], [], TRUE]]]</attributes>
</start>
¹ 根据需要预先跳过;参见 Boost spirit skipper issues
²(然后是一些,但我们不要离题)