如何使用 Boost Spirit x3 编写具有两个 post 操作数语法的二元运算符?

How to write binary operator with two post operands syntax with Boost Spirit x3?

我正在关注这个例子: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 > '}') 等。但我似乎不能弄清楚什么是正确的写法?有什么建议/意见吗?

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

正在解析


第 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
)

第 2 步:扩展语法

(这都在expression_def.hpp)

让我们变得通用,所以使用符号 table:

解析函数名称标记
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";

这些都是繁文缛节。 "interesting thing" 是规则定义:

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

嗯。太令人印象深刻了。让我们整合到我们的主要表达规则中:

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

Note the ordering. If you want to be able to add function names that collide with a keyword, you'll need to add precautions.

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

struct function_call_class : x3::annotate_on_success {};

代码生成

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

在compiler.hpp中:

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

困难的部分来了。

What's really required for general n-ary is an accumulator. Since we don't have registers, this would need to be a temporary (local). However, since the VM implementation doesn't have these, I've limited the implementation to a fixed binary function call only.

Note that the VM already has support for function calls. Functions can have locals. So, if you code-gen a variable-argument built-in function you can implement a left-fold recursive solution.

在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 contrived=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
    -------------------------