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");
}
}
任务 5.1 quote 特殊形式
新建 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)
任务 5.2 if 与短路求值
接下来,实现 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
()
任务 5.3 lambda 特殊形式
咳咳,这个应该是 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);
}
重新编译并运行后,观察测试结果。
在 2025 年 6 月 1 日 23:59:59 前在教学网 大作业 / Lv.5 检查 处,提交上述测试的运行截图(需要包含部分代码与运行窗口)。