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.hforms.cpp。类比内置过程,你需要在 forms.h 中暴露一个 std::mapstd::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 andor 三个特殊形式。

if 特殊形式最简单,就是选择性地求值某一个分支。if 具有形式 (if 条件 真分支 假分支),只有在 条件 求值得到 #f 的时候才会求值 假分支,否则总是求值 真分支

请测试下面的代码以验证你正确实现了 if

>>> (if '() (print "Yea") (print "Nay"))
"Yea"
()
>>> (if #f (print "Yea") (print "Nay"))
"Nay"
()

接下来是 andand 接受若干个表达式,你需要从前到后依次求值,直到其中一个求值为 #f,然后返回 #f。否则,返回最后一个表达式的值。(and) 返回 #t

or 刚好相反。它也是从前到后依次求值,直到其中一个求值不为 #f,然后返回这个值。否则,返回 #f

andor 具有短路求值特性——一旦结果被确定,后续的表达式都不会被求值。比如下面的例子并不会输出 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.hvalue.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 前提交的,可能获得分数加成。

Last Updated:
Contributors: Guyutongxue