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

标签 c++ boost 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 中)

让我们变得通用,所以使用符号表解析函数名称标记:

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 > ')')
    ;

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 节点的支持:

在编译器.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.

在编译器.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=$(($随机% 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
    -------------------------
    

关于c++ - 如何使用 Boost Spirit x3 编写具有两个后操作数语法的二元运算符?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44194944/

相关文章:

c++ - 编译时生成的表

c++ - 如何使用另一个垫子图像替换垫子中的值

boost-spirit - Boost Spirit X3 量产准备好了吗?

c++ - 提升精神 x3 int32 | double_ 无法解析 double

c++ - 如何绕过贪婪的路?

c++ - 如何将在 OS X 中开发的 Qt 应用程序部署到 Windows?

C++11 初始化语法问题(使用 gcc 4.5/4.6)

c++ - boost::asio 多线程异步接受阻塞读/写服务器

c++ - boost::copy_graph 的vertex_copy 是如何工作的?

c++ - boost operator totally_ordered 由 less_than_comparable 和 equality_comparable 组成