好的,这是更改。困难的部分实际上是代码生成内置函数。
解析
第 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
)
第二步:扩展语法
(这都在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 > ')')
;
注意排序。如果您希望能够添加与关键字冲突的函数名称,则需要添加预防措施。
此外,让 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);
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 做作=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
-------------------------