Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
1-rambo committed May 30, 2024
2 parents 684b3ee + 2c8d868 commit b103656
Show file tree
Hide file tree
Showing 3 changed files with 21 additions and 10 deletions.
4 changes: 1 addition & 3 deletions docs/appendix/rjsj_test.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,4 @@ concept TestCtx =

## `RJSJ_TEST_ENABLED`

恒展开为 `1`

若删除 `rjsj_test.hpp` 开头的 `#define RJSJ_TEST_ENABLED 1`,则可通过其它方式(编译器选项、构建系统)控制是否启用 `RJSJ_TEST` 宏的操作。
默认定义为 `1`。可通过编译器选项、构建系统等方式定义 `RJSJ_TEST` 宏为 `0`,以禁用 `RJSJ_TEST` 宏的展开操作。
17 changes: 10 additions & 7 deletions docs/levels/3.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,16 @@ ValuePtr EvalEnv::eval(ValuePtr expr) {

随后,在 main 函数中将这个简单的求值器接在语法分析器的后面:

```cpp{4,5}
auto tokens = Tokenizer::tokenize(line);
Parser parser(std::move(tokens));
auto value = parser.parse();
```cpp{1,7}
EvalEnv env;
auto result = env.eval(std::move(value));
std::cout << result->toString() << std::endl;
while (true) {
// [...]
auto tokens = Tokenizer::tokenize(line);
Parser parser(std::move(tokens));
auto value = parser.parse();
auto result = env.eval(std::move(value));
std::cout << result->toString() << std::endl;
}
```

程序运行的效果是这样的:
Expand Down Expand Up @@ -87,7 +90,7 @@ using namespace std::literals; // 使用 s 后缀
std::vector<ValuePtr> v = expr->toVector();
if (v[0]->asSymbol() == "define"s) {
if (auto name = v[1]->asSymbol()) {
将 (*name, v[2]) 添加到符号表中;
将 ( 符号name , v[2]的值 ) 添加到符号表中;
return 空表;
} else {
throw LispError("Malformed define.");
Expand Down
10 changes: 10 additions & 0 deletions docs/levels/7p.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,13 @@
- 宏;
- 延迟求值(`stream``delay`);
- ……

## 方向 4:实现垃圾回收(GC)

本文档所指导实现的 Mini Lisp 解释器存在内存泄露的问题——一个 Lambda 过程值和它定义时的环境会出现“循环引用”,从而导致双方的 `std::shared_ptr` 总是持有对方且永远无法被释放。由于 Mini Lisp 本身的内存模型在强引用图下是有环的,故天然地无法通过引用计数的方式来保证内存不会泄露(换句话说不要指望通过 `std::weak_ptr` 等手段来解决)。为保证不发生内存泄漏,你可以实现一个基于“标记-清除”算法的垃圾回收(Garbage Collection, GC)机制。

具体来说,你需要:
- 将所有的 `Value``EvalEnv` 的所有权改为“全局”的。或者说,你可以使用单例模式声明两个集合来保存所有的 `Value``EvalEnv`。随后,`LambdaValue` 不再持有 `EvalEnv` 的所有权,`EvalEnv` 也不再持有其中符号们所指代的 `Value` 的所有权。(从而,`ValuePtr` 也应实现为裸指针。)
- 当然,在创建求值环境和新的值的时候,你也应该改为向两个全局集合添加元素,然后在相应的位置用裸指针的形式引用它们。
- REPL 模式下,每次求值完成后,进行“标记-清除”算法。具体而言,“标记”阶段你需要从全局求值环境 `globalEnv` 出发,将所有其符号表持有的 `Value` 标记为“可访问的”。如果遇到了 `LambdaValue`,还需要将其定义时用到的求值环境(以及它的上级环境)标记为“可访问的”,然后递归地追溯并标记这些求值环境用到的值。“清除”阶段你需要将两个全局集合中未被标记为“可访问的”那些求值环境和值删除并释放掉。
- 类似地,在文件模式下也应当实现如上所述的 GC;但是执行 GC 的时机可能有所变化。

0 comments on commit b103656

Please sign in to comment.