在规则匹配后使用Spirit qi :: success回调设置字段 [英] Using Spirit qi::success callbacks to set fields after a rule match

查看:49
本文介绍了在规则匹配后使用Spirit qi :: success回调设置字段的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用 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 ).

规则名称不能是其他文字,因此我建议

  1. 要么将其还原为 std :: string (将其从附加类型中剥离)名称拥有的信息)

      struct规则{std :: string名称;//lhs表达rhs;}; 

  2. 直接将 _rule_name 合成为 Term (包括 TermType 纳入其规则) https://godbolt.org/z/Kbb9dP

  3. 保持 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

  1. either reverting it down to std::string (stripping it from the extra type info that Name had)

    struct Rule {
        std::string name; // lhs 
        Expression rhs;
    };
    

  2. or making _rule_name synthesize into Term directly (including the TermType into its rule) https://godbolt.org/z/Kbb9dP

  3. or Keeping the best of both worlds where Term has a conversion constructor that takes Name:

    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:

Live On Compiler Explorer

//#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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆