Lv.5 特殊形式
在 Lv.3 中,我们已经为解释器添加了 define
特殊形式。但这个只是一个特例,我们现在来添加更多的特殊形式。至少,我们可以创建一个单独的“模块”来管理这些特殊形式。
和内置过程很相似,所有的特殊形式都可以抽象成一个函数:
using SpecialFormType = ValuePtr(const std::vector<ValuePtr>&, EvalEnv&);
比如之前的 define
特殊形式可以写成:
ValuePtr defineForm(const std::vector<ValuePtr>& args, EvalEnv& env) {
if (auto name = args[0]->asSymbol()) {
env.添加变量(*name, args[1]);
} else {
throw LispError("Unimplemented");
}
}
quote
特殊形式
任务 5.1 新建 forms.h
与 forms.cpp
。类比内置过程,你需要在 forms.h
中暴露一个 std::map
或 std::unordered_map
的声明,比如:
extern const std::unordered_map<std::string, SpecialFormType*> SPECIAL_FORMS;
然后在 forms.cpp
中给出定义,比如:
const std::unordered_map<std::string, SpecialFormType*> SPECIAL_FORMS{
{"define", defineForm}
};
在 EvalEnv::eval
中,也做相应的修改:
PairValue* expr = /* ... */;
if (auto name = expr->getCar()->asSymbol()) {
if (SPECIAL_FORMS 中包含 *name) {
SPECIAL_FORMS[*name](expr->getCdr()->toVector(), *this);
}
}
这样就把特殊形式相关的逻辑解耦好了。测试 define
工作正常后,就可以直接在 forms.cpp
中添加更多的特殊形式了。
这一步,我们先添加 quote
。在语法分析器中,我们将 '
替换成了 quote
特殊形式。所谓的 quote
,就是不进行求值,直接返回被引起的表达式,因此实现起来非常简单。
实现 quote
后,你的解释器应当能变得更加复读机了:
>>> '42
42
>>> '(+ 1 2)
(+ 1 2)
>>> ''x
(quote x)
if
与短路求值
任务 5.2 接下来,实现 if
and
和 or
三个特殊形式。
if
特殊形式最简单,就是选择性地求值某一个分支。if
具有形式 (if 条件 真分支 假分支)
,只有在 条件
求值得到 #f
的时候才会求值 假分支
,否则总是求值 真分支
。
请测试下面的代码以验证你正确实现了 if
:
>>> (if '() (print "Yea") (print "Nay"))
"Yea"
()
>>> (if #f (print "Yea") (print "Nay"))
"Nay"
()
接下来是 and
。and
接受若干个表达式,你需要从前到后依次求值,直到其中一个求值为 #f
,然后返回 #f
。否则,返回最后一个表达式的值。(and)
返回 #t
。
or
刚好相反。它也是从前到后依次求值,直到其中一个求值不为 #f
,然后返回这个值。否则,返回 #f
。
and
和 or
具有短路求值特性——一旦结果被确定,后续的表达式都不会被求值。比如下面的例子并不会输出 3
。
>>> (and (print 1) (print 2) #f (print 3))
1
2
#f
>>> (or #f #f (print 1) (print 3))
1
()
lambda
特殊形式
任务 5.3 咳咳,这个应该是 Lv.6 完成的内容,但是我们提前做一下,为了均衡每个部分的内容量。众所周知,Mini-Lisp 的 lambda
特殊形式长成这样:
(lambda (形参列表...)
过程体...)
然后我们要把它求值为一个过程类型的值。所以首先修改 value.h
与 value.cpp
,添加 LambdaValue
类作为 Value
的最后一种可能的实现。
class LambdaValue : public Value {
private:
std::vector<std::string> params;
std::vector<ValuePtr> body;
// [...]
public:
std::string toString() const override; // 如前所述,返回 #<procedure> 即可
};
而且你现在也知道它需要保存的信息有形参名字和过程体。所以我们目前先只保存这些信息。创建好 LambdaValue
的构造函数,在 forms.cpp
中添加 lambda
特殊形式的定义,返回一个 LambdaValue
的指针。
此外,你还需要修改 define
特殊形式的定义,使得其支持 (define (f x) ...)
形式的变量定义。(define (f x...) y...)
等价于 (define f (lambda (x...) y...))
,你可以直接编写返回 LambdaValue
的逻辑,也可以先转换到 lambda
特殊形式后再求值。
测试
这一部分内容比较简单。除了刚刚每个任务的测试以外,还有下面这些可以试一试:
>>> (define false #f)
()
>>> (if false "OK" "Emm")
"Emm"
>>> (and false (print "Don't print"))
#f
>>> (or)
#f
>>> (lambda (x) (+ x x))
#<procedure>
>>> (define (double x) (+ x x))
()
>>> double
#<procedure>
>>> (double 3.14)
Error: Unimplemented
如果你实现了更多的内置过程,你还可以试一试这些:
>>> (if (> 3 2) "Correct" "Bad")
"Correct"
>>> (length '(1 2 3 4))
4
>>> (cdr '(1 . 2))
2
阶段性检查
本阶段仍使用“测试框架” rjsj_test
。以下述方式调用 RJSJ_TEST
宏:
int main() {
RJSJ_TEST(TestCtx, Lv2, Lv3, Lv4, Lv5);
// 若你已完成该功能 * 和 > 内置过程的实现,则可测试 Lv5Extra 测试集:
// RJSJ_TEST(TestCtx, Lv2, Lv3, Lv4, Lv5, Lv5Extra);
}
重新编译并运行后,观察测试结果。
在教学网 大作业 / Lv.5 检查 处,提交上述测试的运行截图(包含运行窗口即可)。在 2024 年 6 月 2 日 23:59:59 前提交的,可能获得分数加成。