提升Spirit Qi语法用于合成关联二元运算符AST节点?

时间:2013-05-22 02:59:42

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

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

struct binop_t
{
    expression_t lhs, rhs;
};

我想使用“BOOST_FUSION_ADAPT_STRUCT”宏来允许这个结构由boost合成:spirit :: qi :: rule:

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

换句话说,二进制运算AST节点(binop_t)需要两个操作数 - 应该操作的左侧(lhs)和右侧(rhs)表达式。我能够通过使用以下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)
            ;

在这个语法中,“表达式”规则产生一个“程序”,它是一个操作列表“。我们可以从语法规则中看到”表达式“它在语法中使用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;
    }
}

1 个答案:

答案 0 :(得分:3)

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。如果打开调试语句,它将打印出一个“infinte”数量的文本,告诉你解析器出错了。