在规则匹配后使用Spirit qi :: success回调设置字段 [英] Using Spirit qi::success callbacks to set fields after a rule match
问题描述
我正在尝试使用 qi :: on_success
回调(此处)设置一个匹配规则时的字段.下面的代码与代码略有不同,尽管我对规则/ast类的一些细微更改使它无法识别<代码> _rule_name .我的意图在下面的代码中进行了注释.如果 _literal
规则匹配,我想将字段 term_type
设置为 TermType :: literal
,或将其设置为 Term :: rule_name 如果
_rule_name
规则匹配,则为.
//#定义BOOST_SPIRIT_DEBUG#include< boost/spirit/include/qi.hpp>#include< boost/fusion/adapted.hpp>#include< iomanip>命名空间qi = boost :: spirit :: qi;命名空间Ast {枚举类TermType {文字,rule_name};struct Term {std :: string数据;TermType term_type;};使用List = std :: list< Term> ;;使用Expression = std :: list< List> ;;struct Rule {术语名称;//lhs表达rhs;};使用语法= std :: list< Rule> ;;}BOOST_FUSION_ADAPT_STRUCT(Ast :: Term,数据)BOOST_FUSION_ADAPT_STRUCT(Ast :: Rule,name,rhs)命名空间解析器{template< typename Iterator>struct BNF:qi :: grammar< Iterator,Ast :: Syntax()>{BNF():BNF :: base_type(start){使用命名空间qi;_blank =空白;_skipper =空白|(eol>>!skip(_blank.alias())[_ rule]);开始=跳过(_skipper.alias())[_ rule%+ eol];_rule = _rule_name>>" :: =">>_表达;_expression = _list%'|';_list = +(_ literal | _rule_name);_literal ='"'>>*(_ character-'"')>>"|"'">>*(_字符-'")>>"'" ;;_character = alnum |char _("\"'|!#$%&()* +,./:;> =<?@] \\ ^ _`{}〜[-));_rule_name ='<'>>(alpha>> *(alum | char _('-')))>>'>';BOOST_SPIRIT_DEBUG_NODES((_rule)(_ expression)(_ list)(_ literal)(_特点)(_rule_name))}/* qi :: on_success(_term,setTermTypeHandler());setTermTypeHandler(){如果术语是字面的term.symbol_type = TermType :: literal别的term.term_type = TermType ::规则名称}*/私人的:使用Skipper = qi :: rule< Iterator> ;;船长_skipper,_blank;qi :: rule<迭代器,Ast :: Syntax()>开始;qi :: rule<迭代器,Ast :: Rule(),Skipper>_规则;qi :: rule<迭代器,Ast :: Expression(),Skipper>_表达;qi :: rule<迭代器,Ast :: List(),Skipper>_列表;//词素qi :: rule<迭代器,Ast :: Term()>_文字;qi :: rule<迭代器,Ast :: Term()>_rule_name;//qi :: rule<迭代器,std :: string()>_文字;qi :: rule< Iterator,char()>_特点;};}int main(){解析器:: BNF< std :: string :: const_iterator>const解析器;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"表示)";自动it = input.begin(),itEnd = input.end();Ast :: Syntax语法;如果(parse(it,itEnd,解析器,语法)){for(auto& rule:语法){std :: cout<<rule.name.data<<":: =" ;;std :: string sep;为(自动& list:rule.rhs){std :: cout<<九月;for(auto& term:list){std :: cout<<term.data;}sep ="|" ;;};std :: cout<<"\ n";}} 别的 {std :: cout<<失败\ n";}如果(它!= itEnd)std :: cout<<"剩余:"<<std :: quoted(std :: string(it,itEnd))<<"\ n";}
由于您的结构 Term
已成为由元组(std :: string,TermType)模拟的区分名称/文字的联合,所以我这样可以使 _literal
和 _rule_name
都只创建一个字符串,并在TermType上附加 qi :: attr
.
所以
struct术语{std :: string数据;TermType term_type;};
适应两个成员
BOOST_FUSION_ADAPT_STRUCT(Ast :: Term,数据,term_type)
声明相关规则:
qi :: rule<迭代器,Ast :: Term()>_学期;qi :: rule<迭代器,std :: string()>_文字;qi :: rule<迭代器,std :: string()>_rule_name;
被初始化为
_list = + _term;_term = _literal>>attr(Ast :: TermType :: literal)|_rule_name>>attr(Ast :: TermType :: rule_name);_literal ='"'>>*(_ character-'"')>>"|"'">>*(_字符-'")>>"'" ;;_character = alnum |char _("\"'|!#$%&()* +,./:;> =<?@] \\ ^ _`{}〜[-));_rule_name ='<'>>(alpha>> *(alum | char _('-')))>>'>';
这与我的信条一致,您应该尝试避免进行语义动作( Boost Spirit:语义动作是邪恶的"?),并将复杂性降至最低.
成功成功
我认为在这里使用 on_success
的想法是不明智的,因为它适用于与上下文无关的操作(例如,将源位置绑定到每个AST节点,无论类型如何)./p>
在这种情况下,您明确地要添加不同信息(变体判别器),因此最好将其注入到解析器表达式的特定分支中适用于.
旁注?
您似乎可以通过推广以下类型来为自己做些复杂的事情:将 Rule :: name
更改为 Term
(而不是 std :: string
,以前是 Name
).
规则名称不能是其他文字,因此我建议
-
要么将其还原为
std :: string
(将其从附加类型中剥离)名称
拥有的信息)struct规则{std :: string名称;//lhs表达rhs;};
-
或直接将
_rule_name
合成为Term
(包括TermType
纳入其规则) https://godbolt.org/z/Kbb9dP -
或保持
的构造方法Term
发生转换的两全其美具有Name
:显式术语(其他):数据(std :: move(other)),term_type(TermType :: rule_name){}
使用ADT精简编程
请注意,丢失 Name
扫盲类型并不是没有代价的,因为输出变得非常错误.我建议最后一种方法(上面的项目3),为您自己的变体仿真添加自定义 operator<<
:
friend std :: ostream&运算符<((std :: ostream& os,术语const& term){switch(term.term_type){case TermType :: rule_name:return os<<名称(term.data);case TermType :: literal:return os<<std :: quoted(term.data);默认值:return os<<?";}}
现在,您可以享受自己的变体类型并再次更正输出:
//#定义BOOST_SPIRIT_DEBUG#include< boost/spirit/include/qi.hpp>#include< boost/fusion/adapted.hpp>#include< iomanip>命名空间qi = boost :: spirit :: qi;命名空间Ast {结构名称:std :: string {使用std :: string :: string;使用std :: string :: operator =;显式名称(std :: string s):std :: string(std :: move(s)){}朋友std :: ostream&运算符<((std :: ostream& os,名称const& n){返回os<<'<'<<n.c_str()<<'>';}};枚举类TermType {文字,rule_name};struct Term {std :: string数据;TermType term_type;Term()=默认值;显式术语(其他名称):数据(std :: move(other)),term_type(TermType :: rule_name){}朋友std :: ostream&运算符<((std :: ostream& os,术语const& term){switch(term.term_type){case TermType :: rule_name:return os<<名称(term.data);case TermType :: literal:return os<<std :: quoted(term.data);默认值:return os<<?";}}};使用List = std :: list< Term> ;;使用Expression = std :: list< List> ;;struct Rule {名字//lhs表达rhs;};使用语法= std :: list< Rule> ;;}BOOST_FUSION_ADAPT_STRUCT(Ast :: Term,数据,term_type)BOOST_FUSION_ADAPT_STRUCT(Ast :: Rule,name,rhs)命名空间解析器{模板< typename Iterator>struct BNF:qi :: grammar< Iterator,Ast :: Syntax()>{BNF():BNF :: base_type(开始){使用命名空间qi;//关闭clang格式_blank =空白;_skipper =空白|(eol>>!skip(_blank.alias())[_rule]);开始=跳过(_skipper.alias())[_rule%+ eol];_rule = _rule_name>>" :: =">>_表达;_expression = _list%'|';_list = + _term;_term = _literal>>attr(Ast :: TermType :: literal)|_rule_name;_literal ='"'>>*(_ character-'"')>>"|"'">>*(_字符-'")>>"'" ;;_character = alnum |char _("\"'|!#$%&()* +,./:;> =<?@] \\ ^ _`{}〜[-));_rule_name ='<'>>qi :: raw [(alpha> * *(alum | char _('-')))]>>'>';//开启clang格式BOOST_SPIRIT_DEBUG_NODES((_rule] [_ expression] [_ list] [_ literal] [_ character] [_ rule_name))}私人的:使用Skipper = qi :: rule< Iterator> ;;船长_skipper,_blank;qi :: rule<迭代器,Ast :: Syntax()>开始;qi :: rule<迭代器,Ast :: Rule(),Skipper>_规则;qi :: rule<迭代器,Ast :: Expression(),Skipper>_表达;qi :: rule<迭代器,Ast :: List(),Skipper>_列表;//词素qi :: rule<迭代器,Ast :: Term()>_学期;qi :: rule<迭代器,std :: string()>_文字;qi :: rule<迭代器,Ast :: Name()>_rule_name;qi :: rule< Iterator,char()>_特点;};}int main(){解析器:: BNF< std :: string :: const_iterator>const解析器;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"表示)";自动it = input.begin(),itEnd = input.end();Ast :: Syntax语法;如果(parse(it,itEnd,解析器,语法)){for(auto& rule:语法){std :: cout<<rule.name<<":: =" ;;std :: string sep;为(自动& list:rule.rhs){std :: cout<<std :: exchange(sep," |");for(auto& term:list){std :: cout<<学期;}};std :: cout<<"\ n";}} 别的 {std :: cout<<失败\ n";}如果(它!= itEnd)std :: cout<<"剩余:"<<std :: quoted(std :: string(it,itEnd))<<"\ n";}
打印
< code>:: =< letter>< digit>|<字母<数字><代码>< letter>:: ="a";|"b"|"c"表示|"d"为|"e"|"f"表示|"g"|"h"|"i"< digit>:: ="0";|"1"表示|"2"表示|"3"表示|"4"表示
I am trying to use qi::on_success
callback (here) to set a field when a rule is matched. The code below is slightly adapted from this code though my slight changes to the rules/ast class has made it no to recognize _rule_name
. My intention is commented in the code below. I want to set the field term_type
to TermType::literal
if the _literal
rule is matched or to Term::rule_name
if _rule_name
rule is matched.
//#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;
_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);
_literal = '"' >> *(_character - '"') >> '"'
| "'" >> *(_character - "'") >> "'";
_character = alnum | char_("\"'| !#$%&()*+,./:;>=<?@]\\^_`{}~[-");
_rule_name = '<' >> (alpha >> *(alnum | char_('-'))) >> '>';
BOOST_SPIRIT_DEBUG_NODES(
(_rule)(_expression)(_list)(_literal)
(_character)
(_rule_name))
}
/*qi::on_success(_term, setTermTypeHandler());
setTermTypeHandler(){
if term is literal
term.symbol_type = TermType::literal
else
term.term_type = TermType::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;
// lexemes
qi::rule<Iterator, Ast::Term()> _literal;
qi::rule<Iterator, Ast::Term()> _rule_name;
// qi::rule<Iterator, std::string()> _literal;
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; }
sep = " | ";
};
std::cout << "\n";
}
} else {
std::cout << "Failed\n";
}
if (it != itEnd)
std::cout << "Remaining: " << std::quoted(std::string(it, itEnd)) << "\n";
}
Since your struct Term
has become a discriminated union of Name/Literal emulated by a tuple (std::string, TermType) I would make it so that both _literal
and _rule_name
just create a string, and append the TermType with qi::attr
.
So,
struct Term {
std::string data;
TermType term_type;
};
Adapting both members
BOOST_FUSION_ADAPT_STRUCT(Ast::Term, data, term_type)
Declaring relevant rules:
qi::rule<Iterator, Ast::Term()> _term;
qi::rule<Iterator, std::string()> _literal;
qi::rule<Iterator, std::string()> _rule_name;
which are initialized as
_list = +_term;
_term = _literal >> attr(Ast::TermType::literal)
| _rule_name >> attr(Ast::TermType::rule_name);
_literal = '"' >> *(_character - '"') >> '"'
| "'" >> *(_character - "'") >> "'";
_character = alnum | char_("\"'| !#$%&()*+,./:;>=<?@]\\^_`{}~[-");
_rule_name = '<' >> (alpha >> *(alnum | char_('-'))) >> '>';
This keeps with my creed that you should try to avoid semantic actions (Boost Spirit: "Semantic actions are evil"?) and keeps the complexity to a minimum.
on_success
I think the idea to use on_success
was ill-advised here because it works well for non-context-dependent actions (like binding source location to each AST node, regardless of the type).
In this case you explicitly want to add different information (the variant discrimator), so you're better served injecting that into the particular branch of the parser expression it applies to.
Sidenotes?
You seem to have complicated things for yourself by promoting the type of
Rule::name
to Term
(instead of std::string
, where it used to be Name
).
The name of a rule cannot be any other literal, so I'd suggest
either reverting it down to
std::string
(stripping it from the extra type info thatName
had)struct Rule { std::string name; // lhs Expression rhs; };
or making
_rule_name
synthesize intoTerm
directly (including theTermType
into its rule) https://godbolt.org/z/Kbb9dPor Keeping the best of both worlds where
Term
has a conversion constructor that takesName
:explicit Term(Name other) : data(std::move(other)) , term_type(TermType::rule_name) { }
Literate Programming with ADTs
Note that the loss of the Name
literate type was NOT without cost, because
the output became very wrong. I'd suggest the
last approach (bullet 3. above) adding a custom operator<<
for your own variant emulation:
friend std::ostream& operator<<(std::ostream& os, Term const& term) {
switch(term.term_type) {
case TermType::rule_name: return os << Name(term.data);
case TermType::literal: return os << std::quoted(term.data);
default: return os << "?";
}
}
Now you can enjoy your own variant type and correct output again:
//#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/adapted.hpp>
#include <iomanip>
namespace qi = boost::spirit::qi;
namespace Ast {
struct Name : std::string {
using std::string::string;
using std::string::operator=;
explicit Name(std::string s) : std::string(std::move(s)) {}
friend std::ostream& operator<<(std::ostream& os, Name const& n) {
return os << '<' << n.c_str() << '>';
}
};
enum class TermType { literal, rule_name };
struct Term {
std::string data;
TermType term_type;
Term() = default;
explicit Term(Name other)
: data(std::move(other))
, term_type(TermType::rule_name)
{ }
friend std::ostream& operator<<(std::ostream& os, Term const& term) {
switch(term.term_type) {
case TermType::rule_name: return os << Name(term.data);
case TermType::literal: return os << std::quoted(term.data);
default: return os << "?";
}
}
};
using List = std::list<Term>;
using Expression = std::list<List>;
struct Rule {
Name name; // lhs
Expression rhs;
};
using Syntax = std::list<Rule>;
}
BOOST_FUSION_ADAPT_STRUCT(Ast::Term, data, term_type)
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;
// clang-format off
_blank = blank;
_skipper = blank | (eol >> !skip(_blank.alias()) [ _rule ]);
start = skip(_skipper.alias()) [ _rule % +eol ];
_rule = _rule_name >> "::=" >> _expression;
_expression = _list % '|';
_list = +_term;
_term = _literal >> attr(Ast::TermType::literal)
| _rule_name;
_literal = '"' >> *(_character - '"') >> '"'
| "'" >> *(_character - "'") >> "'";
_character = alnum | char_("\"'| !#$%&()*+,./:;>=<?@]\\^_`{}~[-");
_rule_name = '<' >> qi::raw[ (alpha >> *(alnum | char_('-'))) ] >> '>';
// clang-format on
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;
// lexemes
qi::rule<Iterator, Ast::Term()> _term;
qi::rule<Iterator, std::string()> _literal;
qi::rule<Iterator, Ast::Name()> _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 << " ::= ";
std::string sep;
for (auto &list : rule.rhs) {
std::cout << std::exchange(sep, " | ");
for (auto &term: list) { std::cout << term; }
};
std::cout << "\n";
}
} else {
std::cout << "Failed\n";
}
if (it != itEnd)
std::cout << "Remaining: " << std::quoted(std::string(it, itEnd)) << "\n";
}
Prints
<code> ::= <letter><digit> | <letter><digit><code>
<letter> ::= "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i"
<digit> ::= "0" | "1" | "2" | "3" | "4"
这篇关于在规则匹配后使用Spirit qi :: success回调设置字段的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!