如何让Boost Spirit Qi在OR条件下尝试所有规则以解析命令?
解决Boost Spirit Qi解析分支回溯问题
问题描述
需要解析以下SendCmd命令模板:
SendCmd SomeCommand Left_Side = "Some Value"; SendCmd AnotherCmd "Some Literal" = Some_Value; SendCmd AnotherCmd "Some Literal" = Some_Value "Other Literal" = "Something"; SendCmd SomeCommand Just_object_name;
现有代码可成功解析后三种模板,但第一种解析失败。核心问题出在send_cmd_规则的(object_ | gen_asgns_)分支:解析第一个测试用例的Left_Side = "Some Value"时,object_成功匹配Left_Side后,后续无法匹配分号,但Spirit未回溯尝试gen_asgns_分支;调换分支顺序则会导致第四个测试用例失败,使用qi::hold[object_]也无效。
原核心规则代码:
send_cmd_ = no_case["sendcmd"] >> (literal_ | object_) >> (object_ | gen_asgns_) >> ';' ;
运行结果:
Case #1 SendCmd SomeCommand Left_Side = "Some Value"; [INVALID] Case #2 SendCmd AnotherCmd "Some Literal" = Some_Value; [Success] Case #3 SendCmd AnotherCmd "Some Literal" = Some_Value "Other Literal" = "Something"; [Success] Case #4 SendCmd SomeCommand Just_object_name; [Success]
问题分析
Boost Spirit Qi基于PEG(Parsing Expression Grammar),PEG的分支选择是优先匹配第一个成功的分支,一旦某个分支的前缀部分匹配成功,即使后续规则失败,也不会回溯尝试下一个分支。
在原规则中:
- 当解析第一个测试用例的
Left_Side = "Some Value"时,object_先匹配了Left_Side,此时分支已消耗部分输入,后续的';'无法匹配,整个send_cmd_规则失败,不会尝试gen_asgns_分支。 - 若调换分支顺序为
(gen_asgns_ | object_),gen_asgns_是*gen_asgn_(零或多个赋值),会优先匹配零个赋值,导致第四个测试用例的Just_object_name无法被匹配,最终因无法匹配';'而失败。
解决方案
通过预判后续输入来明确分支匹配条件:让object_分支仅在匹配后紧跟分号时才生效,否则尝试gen_asgns_分支。使用qi::lit结合&(正向预测先行断言)实现这一逻辑。
修改后的send_cmd_规则如下:
send_cmd_ = no_case["sendcmd"] >> (literal_ | object_) >> (object_ >> &lit(';') | gen_asgns_) >> ';' ;
完整修改后的核心代码
namespace client { template <typename Itr> struct DML : qi::grammar<Itr, Ast::SendCmd()> { DML() : DML::base_type(start) { using namespace qi; start = skip(space)[send_cmd_]; ident_ = raw[alpha >> *(alnum | '_')]; number_ = double_; literal_ = '"' > *('\'' >> char_ | ~char_('"')) > '"'; object_ = ident_ >> *('[' >> ident_ >> ']'); gen_asgn_ = (literal_ | object_) >> '=' >> (literal_ | number_ | object_); gen_asgns_ = *gen_asgn_; // 修改后的send_cmd_规则,添加正向预测断言 send_cmd_ = no_case["sendcmd"] >> (literal_ | object_) >> (object_ >> &lit(';') | gen_asgns_) >> ';' ; BOOST_SPIRIT_DEBUG_NODES( (ident_)(literal_)(number_)(object_)(gen_asgn_)(send_cmd_) ) } private: qi::rule<Itr, Ast::SendCmd()> start; using Skipper = qi::space_type; qi::rule<Itr, Ast::Literal()> literal_; qi::rule<Itr, Ast::Number()> number_; qi::rule<Itr, Ast::Ident()> ident_; qi::rule<Itr, Ast::GenericAssignment(), Skipper> gen_asgn_; qi::rule<Itr, Ast::GenAssignments(), Skipper> gen_asgns_; qi::rule<Itr, Ast::SendCmd(), Skipper> send_cmd_; qi::rule<Itr, Ast::Object(), Skipper> object_; }; }
修改后运行结果
Case #1 SendCmd SomeCommand Left_Side = "Some Value"; [Success] Case #2 SendCmd AnotherCmd "Some Literal" = Some_Value; [Success] Case #3 SendCmd AnotherCmd "Some Literal" = Some_Value "Other Literal" = "Something"; [Success] Case #4 SendCmd SomeCommand Just_object_name; [Success]
内容的提问来源于stack exchange,提问作者Dylan
相关产品推荐
相关产品推荐

