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