我正在遵循这个示例: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/