结合二元算子AST节点的Boost Spirit Qi语法

Boost Spirit Qi Grammar for Synthesizing associative Binary Operator AST nodes?

本文关键字:节点 Boost Spirit 语法 Qi AST 二元 结合      更新时间:2023-10-16

我试图解析形式为"1.0 + 2.0 + 3.0 +…"的表达式到AST.我有以下AST节点的二进制操作(完整的,最小的代码示例是在最后):

struct binop_t
{
    expression_t lhs, rhs;
};

我想使用"BOOST_FUSION_ADAPT_STRUCT"宏来允许这个结构通过boost:spirit::qi::规则来合成:

BOOST_FUSION_ADAPT_STRUCT
(
    client::ast::binop_t,
    (client::ast::expression_t, lhs)
    (client::ast::expression_t, rhs)
)

换句话说,二进制操作AST节点(binop_t)需要两个操作数——应该操作的左侧(lhs)和右侧(rhs)表达式。I am能够通过使用以下qi::grammar:

将形式为"1.0+(2.0+(3.0+4.0))"的表达式解析为此AST节点
qi::rule<Iterator, ast::literal_t(), ascii::space_type> literal;
qi::rule<Iterator, ast::binop_t(), ascii::space_type> binop;
qi::rule<Iterator, ast::expression_t(), ascii::space_type> primary_expr;
qi::rule<Iterator, ast::expression_t(), ascii::space_type> expr;
expr = binop.alias();
binop = primary_expr > qi::lit('+') > primary_expr;
primary_expr = (qi::lit('(') > expr > qi::lit(')')) 
             | literal
             ;
literal = qi::double_;

然而,我正在努力理解如何修改这个语法,以便它可以解析这样的表达式没有使用括号(例如。"1 + 2 + 3 + 4 +……")。

我已经查看了"calc4.cpp"Boost Spirit示例,并注意到它仅使用以下AST节点进行二进制操作(如添加):

struct operation
{
    optoken operator_;
    operand operand_;
};

这个例子和我试图做的事情之间的区别是,这个例子定义了用于合成二进制操作节点的语法,纯粹是根据一组一元操作。一元操作列表被合成到一个称为"program"的AST节点:

struct program
{
    operand first;
    std::list<operation> rest;
};

在示例中使用以下语法综合了整个内容:

    qi::rule<Iterator, ast::program(), ascii::space_type> expression;
    qi::rule<Iterator, ast::program(), ascii::space_type> term;
    qi::rule<Iterator, ast::operand(), ascii::space_type> factor;
        expression =
            term
            >> *(   (char_('+') >> term)
                |   (char_('-') >> term)
                )
            ;
        term =
            factor
            >> *(   (char_('*') >> factor)
                |   (char_('/') >> factor)
                )
            ;
        factor =
                uint_
            |   '(' >> expression >> ')'
            |   (char_('-') >> factor)
            |   (char_('+') >> factor)
            ;

在这个语法中,"表达式"规则产生一个"程序",它是一个操作列表。从"expression"的语法规则可以看出,它在语法中使用了Kleene星号:

*((char_('+') >> term)

这就是语法如何能够解析关联二进制操作链,例如"1+2+3+4+…"。该语法的属性是list,它与"program"AST节点的定义相匹配。计算器"eval"函数然后简单地遍历"program"中的操作列表,从左到右将这些操作应用于操作数:

    int operator()(program const& x) const
    {
        int state = boost::apply_visitor(*this, x.first);
        BOOST_FOREACH(operation const& oper, x.rest)
        {
            state = (*this)(oper, state);
        }
        return state;
    }

我也看过"mini-c"Boost Spirit的例子,它有一个非常相似的AST设计,其中没有二进制操作符AST节点(只有一个接受单个操作数的"操作符"节点)。

下面是到目前为止我已经实现的程序的完整的最小代码示例。回顾一下,我的问题是:我如何修改这个程序,使它能够从"1+2+3+4+…"这样的表达式合成binop_t AST节点树?不带的输入文本中括号的用法:

#include <boost/variant.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <iostream>
#include <string>
#include <exception>
using boost::variant;
using boost::recursive_wrapper;
namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
namespace phoenix = boost::phoenix;
namespace client { namespace ast {
    struct literal_t;
    struct binop_t;
    typedef variant< recursive_wrapper<literal_t>,
                     recursive_wrapper<binop_t>
                   > expression_t;
    struct literal_t
    {
        double value;       
    };
    struct binop_t
    {
        expression_t lhs, rhs;
    };
}} // ns
BOOST_FUSION_ADAPT_STRUCT
(
    client::ast::literal_t,
    (double, value)
)
BOOST_FUSION_ADAPT_STRUCT
(
    client::ast::binop_t,
    (client::ast::expression_t, lhs)
    (client::ast::expression_t, rhs)
)
namespace client {
    template <typename Iterator>
    struct grammar_t : qi::grammar<Iterator, ast::expression_t(), ascii::space_type>
    {
        qi::rule<Iterator, ast::literal_t(), ascii::space_type> literal;
        qi::rule<Iterator, ast::binop_t(), ascii::space_type> binop;
        qi::rule<Iterator, ast::expression_t(), ascii::space_type> primary_expr;
        qi::rule<Iterator, ast::expression_t(), ascii::space_type> expr;
        grammar_t() : grammar_t::base_type(expr)
        {
            expr = binop.alias();
            binop = primary_expr > qi::lit('+') > primary_expr;
            primary_expr = (qi::lit('(') > expr > qi::lit(')')) 
                         | literal;
            literal = qi::double_;
            expr.name("expr");
            binop.name("binop");
            literal.name("literal");
            qi::debug(expr);
            qi::debug(binop);
            qi::debug(literal);
        }
    };
} // ns
int main()
{
    try
    {
        string input = "0.1 + 1.2 ";
        std::string::const_iterator begin = input.begin();
        std::string::const_iterator end = input.end();  
        typedef std::string::const_iterator iterator_type;
        client::grammar_t<iterator_type> g;
        client::ast::expression_t ast;
        bool status;    
        status = qi::phrase_parse(begin, end, g, ascii::space, ast);
        EXPECT_TRUE(status);
        EXPECT_TRUE(begin == end);
    } catch (std::exception& e)
    {
        cout << e.what() << endl;
    }
}

freenode上##spirit IRC频道上的VeXocide解决了这个问题(http://codepad.org/wufmFufE)。答案是按如下方式修改语法:

    expr = binop.alias();
    binop = primary_expr >> qi::lit('+') >> (binop | primary_expr);
    primary_expr = (qi::lit('(') >> expr >> qi::lit(')'))
                 | literal;            
    literal = qi::double_;

这个语法创建了一个正确的递归,能够合成我正在寻找的解析树。

对于遇到同样问题的人的提示:如果没有Spirit调试语句,如果提供左递归语法,Boost Spirit将由于堆栈溢出而导致Seg Fault。如果您打开调试语句,它将打印出"无限"数量的文本,告诉您解析器中出现了错误。