2

我正在关注这个例子:https ://github.com/boostorg/spirit/blob/develop/example/x3/calc/calc9/expression_def.hpp

我想要完成的是编写一个像 min{x}{y} 这样解析和生成的规则。大多数代码都使用像 x + y 这样的表达式语法,但现在我想将两个操作数放置并解析为运算符的 rhs。

我在 expression_def.hpp 文件中添加了以下代码:

    ...
    x3::symbols<ast::optoken> additive_op;
    x3::symbols<ast::optoken> multiplicative_op;
    x3::symbols<ast::optoken> binarypost_op;
    x3::symbols<ast::optoken> unary_op;
    x3::symbols<> keywords;
    ...

    binarypost_op.add
        ("min", ast::op_divide) // Dummy operation usage for now
        ;
    ...
    struct binarypost_expr_class;
    struct unary_expr_class; 
    ...
    typedef x3::rule<binarypost_expr_class, ast::expression> 
    binarypost_expr_type;
    ...
    binarypost_expr_type const binarypost_expr = "binarypost_expr";
    ... 

    auto const multiplicative_expr_def =
    binarypost_expr
    >> *(multiplicative_op > binarypost_expr)
    ;
    auto const binarypost_expr_def =           // See the chaining operation
    ('{' > unary_expr > '}')
    >> *(binarypost_op > ('{' > unary_expr > '}'))
    ;
    auto const unary_expr_def =
        primary_expr
    |   (unary_op > primary_expr)
    ;

这工作正常。但它只能解析像 {x} min {y} 这样的东西。我希望能够解析 min {x} {y}。我尝试了许多组合,例如:

binarypost_op >> ('{' > unary_expr > '}') > ('{' > unary_expr > '}') 等等。但我似乎无法弄清楚写这个的正确方法是什么?有什么建议/意见吗?

4

1 回答 1

2

好的,这是更改。困难的部分实际上是代码生成内置函数。

解析


第 1 步:扩展 AST

始终从 AST 开始。我们想要可以是函数调用的操作数:

在 ast.hpp 中:

struct function_call;  // ADDED LINE

// ...

struct operand :
    x3::variant<
        nil
      , unsigned int
      , variable
      , x3::forward_ast<unary>
      , x3::forward_ast<expression>
      , x3::forward_ast<function_call> // ADDED LINE
    >
{
    using base_type::base_type;
    using base_type::operator=;
};

// ...

enum funtoken
{
    fun_min,
    fun_max,
};

// ...

struct function_call : x3::position_tagged
{
    funtoken fun;
    std::list<operand> args;
};

在 ast_adapted.hpp 中:

BOOST_FUSION_ADAPT_STRUCT(client::ast::function_call,
    fun, args
)

第二步:扩展语法

(这都在expression_def.hpp

让我们通用,因此使用符号表解析函数名称标记:

x3::symbols<ast::funtoken> functions;

我们必须在其中初始化add_keywords

functions.add
    ("min", ast::fun_min)
    ("max", ast::fun_max)
    ;

现在为函数调用声明一个规则:

struct function_call_class;
typedef x3::rule<function_call_class, ast::function_call>    function_call_type;
function_call_type const function_call = "function_call";

这都是繁文缛节。“有趣的事情”是规则定义:

auto const function_call_def =
        functions
    >>  '(' >> expression % ',' >> ')'
    ;

出色地。这太令人沮丧了。让我们整合到我们的主要表达规则中:

auto const primary_expr_def =
        uint_
    |   bool_
    |   function_call
    |   (!keywords >> identifier)
    |   ('(' > expression > ')')
    ;

注意排序。如果您希望能够添加与关键字冲突的函数名称,则需要添加预防措施。

此外,让 AST 注释适用于我们的节点:

struct function_call_class : x3::annotate_on_success {};

代码生成

很容易找到在哪里添加对新 AST 节点的支持:

在 compiler.hpp 中:

 bool operator()(ast::function_call const& x) const;

现在是困难的部分。

一般 n 元真正需要的是累加器。由于我们没有寄存器,因此这需要是临时的(本地)。但是,由于 VM 实现没有这些,我将实现限制为仅固定的二进制函数调用。

请注意,VM 已经支持函数调用。函数可以有局部变量。因此,如果您对变量参数内置函数进行代码生成,则可以实现左折叠递归解决方案。

在 compiler.cpp 中:

bool compiler::operator()(ast::function_call const& x) const
{
    auto choice = [&](int opcode) {
        BOOST_ASSERT(x.args.size() == 2); // TODO FIXME hardcoded binary builtin
        auto it = x.args.begin();

        auto& a = *it++;
        if (!boost::apply_visitor(*this, a))
            return false;

        auto& b = *it++;
        if (!boost::apply_visitor(*this, b))
            return false;
        program.op(opcode); // the binary fold operation

        program.op(op_jump_if, 0);
        size_t const branch = program.size()-1;

        if (!boost::apply_visitor(*this, a))
            return false;
        program.op(op_jump, 0);
        std::size_t continue_ = program.size()-1;

        program[branch] = int(program.size()-branch);
        if (!boost::apply_visitor(*this, b))
            return false;

        program[continue_] = int(program.size()-continue_);
        return true;
    };

    switch (x.fun) {
        case ast::fun_min: return choice(op_lt);
        case ast::fun_max: return choice(op_gt);
        default: BOOST_ASSERT(0); return false;
    }
    return true;
}

我刚刚从周围的代码中获得了关于如何生成跳转标签的灵感。


试一试

  • 一个简单的例子是:var x = min(1,3);

    Assembler----------------
    
    local       x, @0
    start:
          op_stk_adj  1
          op_int      1
          op_int      3
          op_lt
          op_jump_if  13
          op_int      1
          op_jump     15
    13:
          op_int      3
    15:
          op_store    x
    end:
    -------------------------
    Results------------------
    
        x: 1
    -------------------------
    
  • 使用一些随机的人为输入运行它:

    ./test <<< "var a=$(($RANDOM % 100)); var 
    

    b=$(($RANDOM % 100)); var 做作=min(max(27,2*a), 100+b);"

    打印例如:

    Assembler----------------
    
    local       a, @0
    local       b, @1
    local       contrived, @2
    start:
          op_stk_adj  3
          op_int      31
          op_store    a
          op_int      71
          op_store    b
          op_int      27
          op_int      2
          op_load     a
          op_mul
          op_gt
          op_jump_if  24
          op_int      27
          op_jump     29
    24:
          op_int      2
          op_load     a
          op_mul
    29:
          op_int      100
          op_load     b
          op_add
          op_lt
          op_jump_if  58
          op_int      27
          op_int      2
          op_load     a
          op_mul
          op_gt
          op_jump_if  51
          op_int      27
          op_jump     56
    51:
          op_int      2
          op_load     a
          op_mul
    56:
          op_jump     63
    58:
          op_int      100
          op_load     b
          op_add
    63:
          op_store    contrived
    end:
    -------------------------
    Results------------------
    
        a: 31
        b: 71
        contrived: 62
    -------------------------
    
于 2017-05-26T15:26:42.590 回答