![69d786031aad748f0daa65a1ececdd36.png](https://i-blog.csdnimg.cn/blog_migrate/30c594455097d4d5168f54b2b2bf0fe1.jpeg)
目前,cliblisp已经实现了Y-combinator,如题图所示。
Y-combinator的具体细节和推导,在Y Combinator - bajdcc - 博客园中有介绍。
实施手动递归的原因
继前一篇完成lisp的大体功能之后,计划将其作为一种胶水语言,并向模拟多线程方向前进,这就势必要将lisp的运行设置成低粒度的、可控的。
原计划将其的eval部分设计成指令式,不过指令的设计有点复杂,因此就退一步,将其改造为半指令式,也就是手动实现栈式调用,实际上这不是以指令为最小单位,而是以方法为最小单位。
cval *cvm::run(ast_node *root) {
mem.save_stack();
auto val = conv(root, global_env); // 将AST转换为cval树
cval *ret = nullptr;
call(eval, val, global_env, &ret); // eval(val, global_env)
// 自己实现调用栈
while (!eval_stack.empty()) {
auto frame = eval_stack.back();
auto r = frame->fun(this, frame); // 以方法为最小单位
if (r == s_ret) {
eval_mem.free(frame);
eval_stack.pop_back();
}
}
assert(ret);
eval_stack.clear(); // 清空方法栈
eval_mem.clear(); // 清空栈帧
eval_tmp.clear(); // 清空变量
return ret;
}
原先的代码只需做一些微小的改进,改造成状态机模式。
手动递归的数据结构
每次调用压入一个栈帧。
栈帧:
struct cframe {
csub fun; // 调用的函数接口
cval *val, *env, **ret; // cval树,当前调用环境,返回值地址
void *arg; // 可选参数
};
将所有的函数接口选用统一的签名。
通用函数接口:
enum status_t {
s_ret,
s_call,
s_error,
};
using csub_t = status_t (*)(cvm *vm, cframe *frame);
返回值说明函数是否是返回还是递归调用,以后会使用s_error来实现异常处理。
参数为cvm和cframe,即this指针和栈帧。
例:手动调归后的eval函数
status_t builtins::call_eval(cvm *vm, cframe *frame) {
auto &val = frame->val;//从栈帧获取参数
auto &env = frame->env;
if (val->val._v.count > 2)
vm->error("eval not support more than one args");
auto op = VM_OP(val);
struct tmp_bag {
bool qexp;
cval *ret;
}; // 栈上保存的临时变量
if (frame->arg == nullptr) {
auto tmp = vm->eval_tmp.alloc<tmp_bag>();//申请临时变量
memset(tmp, 0, sizeof(tmp_bag));
tmp->qexp = op->type == ast_qexpr;
frame->arg = tmp;
if (tmp->qexp)
op->type = ast_sexpr;
return vm->call(cvm::eval, op, env, &tmp->ret);//压栈
} else {
auto tmp = (tmp_bag *) frame->arg;
if (tmp->qexp)
op->type = ast_qexpr;
auto ret = tmp->ret;
vm->eval_tmp.free(tmp);//删除变量
VM_RET(ret);//返回结果
}
}
后续计划
手动递归将eval方法变成了可控的,但条件是每个方法必须不能是阻塞的。
后续将clib2d加入cliblisp做控制台功能,就可以在OpenGL的idle方法中调用cliblisp函数了。