【问题标题】:Parsing BNF Grammar using Boost Spirit with some custom bnf rules使用带有一些自定义 bnf 规则的 Boost Spirit 解析 BNF 语法
【发布时间】:2021-07-16 18:12:54
【问题描述】:

假设我有如下所示的 BNF 语法。现在,“列表”将对应于“|”之前的所有术语象征。但是,我想读取每个“列表”的最后一个数字作为“列表”的属性。

<code> ::= <code> <line> 12 2 | <line> 24 4 
<line> ::= <ifte> 13 23 | <loop> 24 34 | <action> 15 3 
<ifte> ::= if <cond> {<code>} else {<code>} 12

此外,最后一个数字(列表属性)可以是可选的;我想为了让这更容易,我可能不得不使用一些符号将数字括起来以便于解析,例如 >.

来自herecode 编译但它不解析上面的语法:

//#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/adapted.hpp>
/*#include <fmt/ranges.h>
#include <fmt/ostream.h>*/
#include <iomanip>

namespace AST {

    struct Name : std::string {
        using std::string::string;
        using std::string::operator=;

        friend std::ostream &operator<<(std::ostream &os, Name const &n) {
            return os << '<' << n.c_str() << '>';
        }
    };

    using Term = boost::variant<Name, std::string>;

    struct List {
        std::vector<Term> terms;
        int number;
    };

    using Expression = std::vector<List>;

    struct Rule {
        Name name; //rhs
        Expression rhs;
    };

    using Syntax = std::vector<Rule>;
}
BOOST_FUSION_ADAPT_STRUCT(AST::List, terms, number)
BOOST_FUSION_ADAPT_STRUCT(AST::Rule, name, rhs)

namespace Parser {

    namespace qi = boost::spirit::qi;
    template<typename Iterator>
    class BNF : public qi::grammar<Iterator, AST::Syntax()> {
    public:
        BNF() : BNF::base_type(start) {
            start       = qi::skip(blank)[rule % +qi::eol];
            _rule_name  = qi::hold[qi::char_('<') >> (qi::alpha >> *(qi::alnum | qi::char_('-'))) >> qi::char_('>')];
            _list       = +term >> qi::uint_;
            term        = _literal | _rule_name;
            _literal    = qi::hold['"' >> *(character - '"') >> '"']
                        | qi::hold["'" >> *(character - "'") >> "'"]
                        | qi::hold[+(qi::graph - qi::char_("<|>") - "::=")];
            character   = qi::alnum | qi::char_("\"'| !#$%&()*+,./:;>=<?@]\\^_`{}~[-");
            _expression = _list % '|';

            rule = _rule_name >> "::=" >> _expression;

            BOOST_SPIRIT_DEBUG_NODES((rule)(_expression)(_list)(term)(_literal)(
                character)(_rule_name))
        }

    private:
        qi::rule<Iterator> blank;
        qi::rule<Iterator, AST::Syntax()>     start;
        qi::rule<Iterator, AST::Rule(),       qi::rule<Iterator>> rule;
        qi::rule<Iterator, AST::Expression(), qi::rule<Iterator>> _expression;
        qi::rule<Iterator, AST::List(),       qi::rule<Iterator>> _list;
        qi::rule<Iterator, AST::Term()>       term;
        qi::rule<Iterator, AST::Name()>       _rule_name;
        qi::rule<Iterator, std::string()>     _literal;
        qi::rule<Iterator, char()>            character;
    };
}

int main() {
    Parser::BNF<std::string::const_iterator> const  parser;
}

如何修复/修改上面的代码链接以满足我的需要。

【问题讨论】:

    标签: c++ boost-spirit boost-spirit-qi bnf


    【解决方案1】:

    我认为不清楚您要支持哪种输入语法。

    例如,

    1. 如果列表属性可以是可选的,这是否意味着除了&lt;code&gt; &lt;line&gt; 12 2,这也是一个没有属性的有效列表:&lt;code&gt; &lt;line&gt; 12 2?您将如何避免将 12 解析为属性?
    2. 您的输入使用 {} 中的名称 - 您显示的解析器实现显然不支持这些名称。你需要支持吗?怎么样?

    让我们同时解决它们

    广告 2.:修正您的输入

    假设您真的不希望 {} 具有魔法含义,而是将它们作为语法中的文字。就像“if”和“else”一样,它们需要是文字,所以:

    <ifte> ::= 'if' <cond> '{' <code> '}' 'else' '{' <code> '}' 23
    

    <ifte> ::= "if" <cond> "{" <code> "}" "else" "{" <code> "}" 23
    

    这修复了您的示例:Live On Compiler Explorer

    code ::= <code><line> 34 | <line> 34
    line ::= <ifte> 23 | <loop> 34 | <action> 23
    ifte ::= if<cond>{<code>}else{<code>} 23
    Remaining: "
    "
    

    广告 1.:可选属性

    让我们表达我们的意图:

    using ListAttribute = int;
    
    struct List {
        std::list<Term> terms;
        ListAttribute attribute;
    };
    

    然后在语法中添加一个词位规则(无skipper):

    qi::rule<Iterator, Ast::ListAttribute()> _attribute;
    

    然后我们实现如下:

    _attribute  = lexeme [ "<<" >> qi::uint_ >> ">>" ] 
                | qi::attr(0);
    _list       = +_term >> _attribute;
    

    现在它只会将 识别为列表属性:

    Live On Compiler Explorer

    std::string const input =
        "<code> ::= <code> <line> | <line>\n"
        "<line> ::= <ifte> | <loop> | <action>\n"
        "<ifte> ::= 'if' <cond> '{' <code> '}' 'else' '{' <code> '}'\n"
    
        "<code> ::= <code> <line> <<34>> | <line> <<34>>\n"
        "<line> ::= <ifte> <<23>> | <loop> <<34>> | <action> <<23>>\n"
        "<ifte> ::= 'if' <cond> '{' <code> '}' 'else' '{' <code> '}' <<23>>\n"
    
        // and the disambiguated example from the question
        "<code> ::= <code> <line> '34' | <line> '12' <<2>>\n"
    ;
    

    打印

    code ::= <code><line> 0 | <line> 0
    line ::= <ifte> 0 | <loop> 0 | <action> 0
    ifte ::= if<cond>{<code>}else{<code>} 0
    code ::= <code><line> 34 | <line> 34
    line ::= <ifte> 23 | <loop> 34 | <action> 23
    ifte ::= if<cond>{<code>}else{<code>} 23
    code ::= <code><line>34 0 | <line>12 2
    Remaining: "
    "
    

    总结/奖金

    我刚刚意识到您不需要区分 12 212(缺少属性),因为 12 无论如何都不是有效的输入标记(文字/名称以 &lt;"' 之一开头) ,所以这里是:

    Live On Compiler Explorer

    //#define BOOST_SPIRIT_DEBUG
    #include <boost/spirit/include/qi.hpp>
    #include <boost/fusion/adapted.hpp>
    #include <fmt/ranges.h>
    #include <fmt/ostream.h>
    #include <iomanip>
    namespace qi = boost::spirit::qi;
    
    namespace Ast {
        struct Name : std::string {
            using std::string::string;
            using std::string::operator=;
    
            friend std::ostream& operator<<(std::ostream& os, Name const& n) {
                return os << '<' << n.c_str() << '>';
            }
        };
    
        using Term = boost::variant<Name, std::string>;
    
        using ListAttribute = int;
    
        struct List {
            std::list<Term> terms;
            ListAttribute attribute;
    
            friend std::ostream& operator<<(std::ostream& os, List const& l) {
                for (auto& t : l.terms)
                    os << t;
                return os << " " << l.attribute;
            }
        };
    
        using Expression = std::list<List>;
    
        struct Rule {
            Name name; // lhs
            Expression rhs;
        };
    
        using Syntax = std::list<Rule>;
    }
    
    BOOST_FUSION_ADAPT_STRUCT(Ast::List, terms, attribute)
    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;
                start = skip(blank) [ _rule % +eol ];
    
                _rule       = _rule_name >> "::=" >> _expression;
                _expression = _list % '|';
                _attribute  = uint_ | qi::attr(0);
                _list       = +_term >> _attribute;
                _term       = _literal | _rule_name ;
                _literal    = '"' >> *(_character - '"') >> '"'
                            | "'" >> *(_character - "'") >> "'";
                _character  = alnum | char_("\"'| !#$%&()*+,./:;>=<?@]\\^_`{}~[-");
                _rule_name  = '<' >> (alpha >> *(alnum | char_('-'))) >> '>';
    
                BOOST_SPIRIT_DEBUG_NODES(
                    (_rule)(_expression)(_list)(_attribute)(_term)
                    (_literal)(_character)
                    (_rule_name))
            }
    
          private:
            qi::rule<Iterator, Ast::Syntax()>     start;
            qi::rule<Iterator, Ast::Rule(),       qi::blank_type> _rule;
            qi::rule<Iterator, Ast::Expression(), qi::blank_type> _expression;
            qi::rule<Iterator, Ast::List(),       qi::blank_type> _list;
            // lexemes
            qi::rule<Iterator, Ast::ListAttribute()> _attribute;
            qi::rule<Iterator, Ast::Term()>          _term;
            qi::rule<Iterator, Ast::Name()>          _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 =
            "<code> ::= <code> <line> | <line>\n"
            "<line> ::= <ifte> | <loop> | <action>\n"
            "<ifte> ::= 'if' <cond> '{' <code> '}' 'else' '{' <code> '}'\n"
    
            "<code> ::= <code> <line> 34 | <line> 34\n"
            "<line> ::= <ifte> 23 | <loop> 34 | <action> 23\n"
            "<ifte> ::= 'if' <cond> '{' <code> '}' 'else' '{' <code> '}' 23\n"
    
            // and the disambiguated example from the question
            "<code> ::= <code> <line> '34' | <line> '12' 2\n"
        ;
    
        auto it = input.begin(), itEnd = input.end();
    
        Ast::Syntax syntax;
        if (parse(it, itEnd, parser, syntax)) {
            for (auto& rule : syntax)
                fmt::print("{} ::= {}\n", rule.name, fmt::join(rule.rhs, " | "));
        } else {
            std::cout << "Failed\n";
        }
    
        if (it != itEnd)
            std::cout << "Remaining: " << std::quoted(std::string(it, itEnd)) << "\n";
    }
    

    打印

    code ::= <code><line> 0 | <line> 0
    line ::= <ifte> 0 | <loop> 0 | <action> 0
    ifte ::= if<cond>{<code>}else{<code>} 0
    code ::= <code><line> 34 | <line> 34
    line ::= <ifte> 23 | <loop> 34 | <action> 23
    ifte ::= if<cond>{<code>}else{<code>} 23
    code ::= <code><line>34 0 | <line>12 2
    Remaining: "
    "
    

    【讨论】:

    • 我之所以说也许我必须为属性使用诸如&lt;&lt;number&gt;&gt; 之类的封闭符号是因为文字可以没有单引号或双引号;这就是为什么字面量有第三条规则来满足这一点。
    • @r360 我完全错过了。因为您说“来自 here 的代码”,我认为它是指我在上一个答案中最终得到的代码。教训:下次只需包含代码(我编辑了你的问题来做到这一点,但我仍然错过了代码不同)。
    • 我再看看
    • blank 是怎么回事?你永远不会初始化它。如果您只是不想要任何船长,为什么不直接删除它?
    • 所以,我只是设法仔细检查了所有编辑,发现除了统一化规则 (blank)、重新排序和不一致地重命名规则之外,您没有其他更改,可能混淆了 qi::hold[] 和不合逻辑的决定在规则名称中包含“”。哦,删除测试代码,所以我们实际上看不到目标或有效的方法。哦,由于在qi::skip 中使用了复制构造的临时文件,这一切都会立即出现段错误。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-26
    • 2012-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-21
    相关资源
    最近更新 更多