C++ 如何使用Boost Spirit x3编写具有两个后置操作数语法的二进制运算符?

C++ 如何使用Boost Spirit x3编写具有两个后置操作数语法的二进制运算符?,c++,boost,boost-spirit-x3,C++,Boost,Boost Spirit X3,我举这个例子: 我试图完成的是编写一个像min{x}{y}那样解析和生成的规则。大部分代码都使用表达式语法,比如x+y,但现在我想将这两个操作数放置并解析为运算符的rhs 我在expression_def.hpp文件中添加了以下代码: ... x3::symbols<ast::optoken> additive_op; x3::symbols<ast::optoken> multiplicative_op; x3::symbols<a

我举这个例子:

我试图完成的是编写一个像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)
    ;
。。。
x3::符号加法运算;
x3::符号乘法运算;
x3::符号二进制post_op;
x3::符号一元运算;
x3::符号关键词;
...
binarypost_op.add
(“min”,ast::op_divide)//暂时使用伪操作
;
...
结构二进制POST_expr_类;
结构一元表达式类;
...
typedefx3::规则
二进制Post_expr_类型;
...
binarypost_expr_type const binarypost_expr=“binarypost_expr”;
... 
自动常数乘法表达式=
二进制邮政编码
>>*(乘法运算>二进制后运算)
;
auto const binarypost_expr_def=//查看链接操作
('{'>一元表达式>'}')
>>*(二进制post_op>('{'>一元表达式>'}'))
;
自动常量表达式=
初级出口
|(一元运算>主运算)
;
这个很好用。但是它只能解析像{x}min{y}这样的东西。我希望能够解析min{x}{y}。我尝试了许多组合,例如:


binarypost_op>>('{'>One-ary_-expr>'}')>('{'>One-ary_-expr>'}'))等。但我似乎不知道写这篇文章的正确方法是什么?有什么建议/意见吗?

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

解析
步骤1:扩展AST 总是从AST开始。我们需要可以作为函数调用的操作数:

struct function_call_class;
typedef x3::rule<function_call_class, ast::function_call>    function_call_type;
function_call_type const function_call = "function_call";
在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;
};
步骤2:扩展语法 (这都在
表达式中\u def.hpp

让我们使用泛型,使用符号表解析函数名标记:

x3::symbols<ast::funtoken> functions;
现在声明函数调用的规则:

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

  • 使用一些随机的人为输入运行它:

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

为什么要用有趣的牙套?这使得语法毫无理由地不一致,难以整合。此外,预期的优先规则是什么?您会设想一个具有两个以上参数的前缀运算符吗?为什么不同时
min(x,y)
min(x,y,z)
,和
max(a)
?一致性和灵活性。我可以介绍所有操作符都需要大括号,这将使它更加一致,但是,现在只需要一个操作符。我正在为将来更宏伟的计划使用的支架。至于优先级,因为它也可能是一个自定义函数,我希望它接近我放置它的当前位置-就在一元函数的上方。谢谢你的评论和兴趣。我觉得在这个级别上很容易出错。我将看看是否可以使它作为一个函数调用工作,比如
min(a,b)
Added code+Makefile在我只做了一些小的更改,因为我无法编译“bool compiler::operator()(ast::function\u call const&x)const”函数。1.auto const function_call_def=functions>>'{'>>expression%'}{'>>'}';2开关(x.fun){case ast::fun_min:program.op(fun_min);break;case ast::fun_max:program.op(fun_max);break;case ast::fun_frac:program.op(op_div);break;默认值:BOOST_ASSERT(0);返回false;}返回true;3.case-fun_-min:--stack_-ptr;如果(!bool(stack_ptr[-1])非常感谢。我将告诉你我使用花括号的原因。在latex中格式化时,您编写的除法操作如下:\frac{x}{y}。我希望我的解析器具有相同的格式,然后能够使用相同的解析器进行计算。现在我可以执行以下操作:a=\frac{44}{11}我可以得到期望的结果=4;关于将参数固定为2,我看到整个vm只在堆栈的两个索引上运行-1和0,所以我认为整个事情可能需要更改以容纳更多的参数。感谢您的帮助和努力。这是一个堆栈。索引是偶然的。事实上,局部变量也在堆栈上,而y绝对不是SP-1/SP-2。堆栈机器非常有名,用途广泛,不需要调整更多参数(查看RPN表达式以获得精心设计的示例,这里没有问题)。需要的是临时变量的良好范例。唯一需要的是使用frameptr的地址转换(已经存在)但具有向下偏移量。当前机制仅限于命名和声明的变量,不允许codegen dynamic Temps。如果要解析latex,只需使用该语法。从完全不同的语法开始,然后“颠覆”它不是很有用。当您写注释时“//的虚拟操作用法
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;
}
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 
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
-------------------------