【问题标题】:Boost spirit core dump on parsing bracketed expression解析括号表达式时提升精神核心转储
【发布时间】:2021-08-02 03:03:39
【问题描述】:

有一些应该解析终端文字序列的简化语法:id、'' 和 ":action"。 我需要允许括号 '(' ')' 除了提高阅读能力之外什么都不做。 (完整的例子是http://coliru.stacked-crooked.com/a/dca93f5c8f37a889) 我的语法片段:

    start  = expression % eol;
    expression   = (simple_def >> -expression)
    | (qi::lit('(') > expression > ')');

    simple_def = qi::lit('<') [qi::_val = Command::left] 
    | qi::lit('>') [qi::_val = Command::right] 
    | key [qi::_val = Command::id] 
    | qi::lit(":action") [qi::_val = Command::action] 
    ;
    
    key = +qi::char_("a-zA-Z_0-9");

当我尝试解析时:const std::string s = "(a1 &gt; :action)"; 一切都像魅力一样。 但是当我用括号"(a1 (&gt;) :action)" 带来更多的复杂性时,我已经得到了核心转储。仅供参考 - coredump 发生在 coliru,而 msvc 编译示例仅演示失败解析。

所以我的问题是:(1) 括号有什么问题,(2) 如何将括号准确地引入表达式。

附言它是简化的语法,实际上我有更复杂的情况,但这是一个最小的可重现代码。

【问题讨论】:

    标签: c++ boost boost-spirit


    【解决方案1】:

    你应该只处理期望失败:

    terminate called after throwing an instance of 'boost::wrapexcept<boost::spir
    it::qi::expectation_failure<__gnu_cxx::__normal_iterator<char const*, std::__
    cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >
    >'
      what():  boost::spirit::qi::expectation_failure
    Aborted (core dumped)
    

    如果您处理期望失败,程序将不必终止。

    修正语法

    您的“嵌套表达式”规则只接受一个表达式。我认为

    expression = (simple_def >> -expression)
    

    旨在匹配“1 个或多个 `simple_def”。但是,替代分支:

         | ('(' > expression > ')');
    

    不接受相同的:它只是在解析 `)' 后停止。这意味着根据语法,您的输入根本无效。

    我建议通过表达意图来简化。您在语义 typedef 的正确道路上。让我们避免“狡猾”的Line Of Lines(甚至那是什么?):

    using Id     = std::string;
    using Line   = std::vector<Command>;
    using Script = std::vector<Line>;
    

    并始终如一地使用这些 typedef。现在,我们可以在“思考”时表达语法:

        start  = skip(blank)[script];
        script = line % eol;
    
        line   = +simple;
        simple = group | command;
        group  = '(' > line > ')';
    

    看,通过简化我们的心智模型并坚持下去,我们避免了您难以发现的整个问题。

    这是一个快速演示,其中包括错误处理、可选调试输出、测试用例和封装作为语法一部分的跳过程序:Live On Compiler Explorer

    #include <fmt/ranges.h>
    #include <fmt/ostream.h>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    
    namespace qi  = boost::spirit::qi;
    namespace phx = boost::phoenix;
    
    enum class Command { id, left, right, action };
    
    static inline std::ostream& operator<<(std::ostream& os, Command cmd) {
        switch (cmd) {
            case Command::id: return os << "[ID]";
            case Command::left: return os << "[LEFT]";
            case Command::right: return os << "[RIGHT]";
            case Command::action: return os << "[ACTION]";
        }
        return os << "[???]";
    }
    
    using Id     = std::string;
    using Line   = std::vector<Command>;
    using Script = std::vector<Line>;
    
    template <typename It>
    struct ExprGrammar : qi::grammar<It, Script()> {
        ExprGrammar() : ExprGrammar::base_type(start) {
            using namespace qi;
    
            start  = skip(blank)[script];
            script = line % eol;
    
            line   = +simple;
            simple = group | command;
            group  = '(' > line > ')';
    
            command = 
                lit('<')       [ _val = Command::left   ] |
                lit('>')       [ _val = Command::right  ] |
                key            [ _val = Command::id     ] |
                lit(":action") [ _val = Command::action ] ;
    
            key = +char_("a-zA-Z_0-9");
    
            BOOST_SPIRIT_DEBUG_NODES((command)(line)(simple)(group)(script)(key));
        }
    
    private:
        qi::rule<It, Script()>                 start;
        qi::rule<It, Line(), qi::blank_type>   line, simple, group;
        qi::rule<It, Script(), qi::blank_type> script;
    
        qi::rule<It, Command(), qi::blank_type> command;
    
        // lexemes
        qi::rule<It, Id()> key;
    };
    
    int main() {
        using It = std::string::const_iterator;
        ExprGrammar<It> const p;
    
        for (const std::string s : {
                "a1 > :action\na1 (>) :action",
                "(a1 > :action)\n(a1 (>) :action)",
                "a1 (> :action)",
            }) {
    
            It f(begin(s)), l(end(s));
    
            try {
                Script parsed;
                bool ok = qi::parse(f, l, p, parsed);
    
                if (ok) {
                    fmt::print("Parsed {}\n", parsed);
                } else {
                    fmt::print("Parsed failed\n");
                }
    
                if (f != l) {
                    fmt::print("Remaining unparsed: '{}'\n", std::string(f, l));
                }
            } catch (qi::expectation_failure<It> const& ef) {
                fmt::print("{}\n", ef.what()); // TODO add more details :)
            }
        }
    }
    

    打印

    Parsed {{[ID], [RIGHT], [ACTION]}, {[ID], [RIGHT], [ACTION]}}
    Parsed {{[ID], [RIGHT], [ACTION]}, {[ID], [RIGHT], [ACTION]}}
    Parsed {{[ID], [RIGHT], [ACTION]}}
    

    奖金

    但是,我认为使用qi::symbols 命令可以大大简化这一切。实际上,看起来您只是在进行标记(当您说括号不重要时,您确认了这一点)。

        line   = +simple;
        simple = group | command | (omit[key] >> attr(Command::id));
        group  = '(' > line > ')';
        key    = +char_("a-zA-Z_0-9");
    

    现在您根本不需要 Phoenix:Live On Compiler Explorer,正在打印

    ok? true {{[ID], [RIGHT], [ACTION]}, {[ID], [RIGHT], [ACTION]}}
    ok? true {{[ID], [RIGHT], [ACTION]}, {[ID], [RIGHT], [ACTION]}}
    ok? true {{[ID], [RIGHT], [ACTION]}}
    

    更简单?

    既然我观察到您基本上是按行进行标记,为什么不直接跳过括号,并一直简化为:

        script = line % eol;
        line   = *(command | omit[key] >> attr(Command::id));
    

    这就是全部。再次查看Live On Compiler Explorer

    #include <boost/spirit/include/qi.hpp>
    #include <fmt/ostream.h>
    #include <fmt/ranges.h>
    namespace qi = boost::spirit::qi;
    
    enum class Command { id, left, right, action };
    using Id     = std::string;
    using Line   = std::vector<Command>;
    using Script = std::vector<Line>;
    
    static inline std::ostream& operator<<(std::ostream& os, Command cmd) {
        return os << (std::array{"ID", "LEFT", "RIGHT", "ACTION"}.at(int(cmd)));
    }
    
    template <typename It>
    struct ExprGrammar : qi::grammar<It, Script()> {
        ExprGrammar() : ExprGrammar::base_type(start) {
            using namespace qi;
            start = skip(skipper.alias())[line % eol];
            line  = *(command | omit[key] >> attr(Command::id));
            key   = +char_("a-zA-Z_0-9");
    
            BOOST_SPIRIT_DEBUG_NODES((line)(key));
        }
    private:
        using Skipper = qi::rule<It>;
        qi::rule<It, Script()>        start;
        qi::rule<It, Line(), Skipper> line;
    
        Skipper                 skipper = qi::char_(" \t\b\f()");
        qi::rule<It /*, Id()*/> key; // omit attribute for efficiency
        struct cmdsym : qi::symbols<char, Command> {
            cmdsym() { this->add("<", Command::left)
                (">", Command::right)
                (":action", Command::action);
            }
        } command;
    };
    
    int main() {
        using It = std::string::const_iterator;
        ExprGrammar<It> const p;
    
        for (const std::string s : {
                "a1 > :action\na1 (>) :action",
                "(a1 > :action)\n(a1 (>) :action)",
                "a1 (> :action)",
            })
        try {
            It f(begin(s)), l(end(s));
    
            Script parsed;
            bool ok = qi::parse(f, l, p, parsed);
    
            fmt::print("ok? {} {}\n", ok, parsed);
            if (f != l)
                fmt::print(" -- Remaining '{}'\n", std::string(f, l));
        } catch (qi::expectation_failure<It> const& ef) {
            fmt::print("{}\n", ef.what()); // TODO add more details :)
        }
    }
    

    打印

    ok? true {{ID, RIGHT, ACTION}, {ID, RIGHT, ACTION}}
    ok? true {{ID, RIGHT, ACTION}, {ID, RIGHT, ACTION}}
    ok? true {{ID, RIGHT, ACTION}}
    

    注意,我非常巧妙地将 +() 更改为 *(),因此它也可以接受空行。这可能是也可能不是你想要的

    【讨论】:

    • 这是我在 stackoverflow 上得到的最详细的答案,干得好,谢谢!不要认为qi::symbols 对我有用,因为在实际情况下,所有这些字符都返回指向相应函数的指针。由于不清楚的原因,我不能以 qi::_val = px::bind(qi_1, &amp;instance... 的方式编写结果 - 其中 qi::_1 是成员指针。 Nabialec技巧在那里也是多余的。但再一次 - 谢谢!
    • qi::_1怎么可能是成员指针?只有当成员具有特定的静态类型时,这似乎才有意义。和px::bind() does not lazily evaluate the fist argument.。当然,你可以自己解决这个问题:godbolt.org/z/qTx59sjGo 警告:我有一种强烈的感觉,如果你觉得你“需要”这个,你就是把事情复杂化了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-02
    相关资源
    最近更新 更多