如何使用 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
-------------------------
我正在关注这个例子: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 -------------------------