使用Spirit qi::success回调在规则匹配后设置字段
我明白你想通过qi::on_success回调在匹配_literal或_rule_name时设置Term的term_type字段。问题出在你没有正确为每个规则绑定对应的回调,而且当前的规则定义没有确保词法单元不受skipper干扰。下面是修正后的完整代码,以及关键修改的解释:
完整修正代码
// #define BOOST_SPIRIT_DEBUG #include <boost/spirit/include/qi.hpp> #include <boost/fusion/adapted.hpp> #include <iomanip> namespace qi = boost::spirit::qi; namespace Ast { enum class TermType { literal, rule_name }; struct Term { std::string data; TermType term_type; }; using List = std::list<Term>; using Expression = std::list<List>; struct Rule { Term name; // lhs Expression rhs; }; using Syntax = std::list<Rule>; } BOOST_FUSION_ADAPT_STRUCT(Ast::Term, data) BOOST_FUSION_ADAPT_STRUCT(Ast::Rule, name, rhs) namespace Parser { template<typename Iterator> struct BNF : qi::grammar<Iterator, Ast::Syntax()> { BNF() : BNF::base_type(start) { using namespace qi; using namespace Ast; _blank = blank; _skipper = blank | (eol >> !skip(_blank.alias())[_rule]); start = skip(_skipper.alias())[_rule % +eol]; _rule = _rule_name >> "::=" >> _expression; _expression = _list % '|'; _list = +(_literal | _rule_name); // 用lexeme包裹词法单元,避免skipper干扰内部匹配 _literal = lexeme[ '"' >> *(_character - '"') >> '"' | "'" >> *(_character - "'") >> "'" ]; _rule_name = lexeme[ '<' >> (alpha >> *(alnum | char_('-'))) >> '>' ]; _character = alnum | char_("\"'| !#$%&()*+,./:;>=<?@]\\^_`{}~[-"); // 为_literal绑定成功回调,设置term_type为literal qi::on_success(_literal, [](auto& ctx) { _val(ctx).term_type = TermType::literal; }); // 为_rule_name绑定成功回调,设置term_type为rule_name qi::on_success(_rule_name, [](auto& ctx) { _val(ctx).term_type = TermType::rule_name; }); BOOST_SPIRIT_DEBUG_NODES( (_rule)(_expression)(_list)(_literal) (_character) (_rule_name)) } private: using Skipper = qi::rule<Iterator>; Skipper _skipper, _blank; qi::rule<Iterator, Ast::Syntax()> start; qi::rule<Iterator, Ast::Rule(), Skipper> _rule; qi::rule<Iterator, Ast::Expression(), Skipper> _expression; qi::rule<Iterator, Ast::List(), Skipper> _list; // 词法单元规则 qi::rule<Iterator, Ast::Term()> _literal; qi::rule<Iterator, Ast::Term()> _rule_name; qi::rule<Iterator, char()> _character; }; } int main() { Parser::BNF<std::string::const_iterator> const parser; std::string const input = R"(<code> ::= <letter><digit> | <letter><digit><code> <letter> ::= "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" <digit> ::= "0" | "1" | "2" | "3" | "4" )"; auto it = input.begin(), itEnd = input.end(); Ast::Syntax syntax; if (parse(it, itEnd, parser, syntax)) { for (auto &rule : syntax) { std::cout << rule.name.data << " ::= "; std::string sep; for (auto &list : rule.rhs) { std::cout << sep; for (auto &term: list) { std::cout << term.data; // 取消注释可以验证term_type是否正确设置 // std::cout << "(" << (term.term_type == Ast::TermType::literal ? "literal" : "rule_name") << ")"; } sep = " | "; }; std::cout << "\n"; } } else { std::cout << "Failed\n"; } if (it != itEnd) std::cout << "Remaining: " << std::quoted(std::string(it, itEnd)) << "\n"; }
关键修改说明
给词法单元添加
lexeme:_literal和_rule_name属于独立词法单元,用lexeme包裹后会临时禁用skipper,避免匹配过程中空白字符干扰引号、尖括号内部的内容解析。绑定
on_success回调:
分别为两个规则设置lambda回调,在匹配成功后通过_val(ctx)获取当前规则返回的Ast::Term对象,直接修改其term_type字段,实现你想要的区分逻辑。简化Fusion适配:
你原来的BOOST_FUSION_ADAPT_STRUCT(Ast::Term, data)只适配data字段是合理的,因为term_type由我们手动设置,Spirit会自动将匹配到的字符串赋值给data。
可选替代方案(无需回调)
如果你觉得回调写法繁琐,也可以直接在规则中构造Ast::Term对象,同时赋值data和term_type:
// 需要额外包含boost/phoenix.hpp #include <boost/phoenix.hpp> // 替换原来的_literal、_rule_name定义和on_success代码 _literal = lexeme[ ( '"' >> *(_character - '"') >> '"' | "'" >> *(_character - "'") >> "'" ) [ _val = boost::phoenix::construct<Ast::Term>(_1, Ast::TermType::literal) ] ]; _rule_name = lexeme[ ( '<' >> (alpha >> *(alnum | char_('-'))) >> '>' ) [ _val = boost::phoenix::construct<Ast::Term>(_1, Ast::TermType::rule_name) ] ];
这种方式把赋值逻辑直接嵌入规则,更直观,不过需要依赖Boost Phoenix库。
内容的提问来源于stack exchange,提问作者user2987773
相关产品推荐
相关产品推荐

