From b13857759a0c63c465d7bc77bd939539f1ed9212 Mon Sep 17 00:00:00 2001 From: Guyutongxue Date: Sat, 11 May 2024 21:01:09 +0800 Subject: [PATCH 1/4] Update 3.md --- docs/levels/3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/levels/3.md b/docs/levels/3.md index 9c8f5c5..bacf2fc 100644 --- a/docs/levels/3.md +++ b/docs/levels/3.md @@ -87,7 +87,7 @@ using namespace std::literals; // 使用 s 后缀 std::vector 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."); From b34e85ecbaa1866e4d2addfd4ccdb282251942e2 Mon Sep 17 00:00:00 2001 From: Guyutongxue Date: Mon, 13 May 2024 12:14:18 +0800 Subject: [PATCH 2/4] Update 3.md --- docs/levels/3.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/levels/3.md b/docs/levels/3.md index bacf2fc..4b0ef01 100644 --- a/docs/levels/3.md +++ b/docs/levels/3.md @@ -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; +} ``` 程序运行的效果是这样的: From 9c6fec53416b41dae7f0e2bedf51bb2a0cade1b0 Mon Sep 17 00:00:00 2001 From: Guyutongxue Date: Mon, 13 May 2024 12:25:23 +0800 Subject: [PATCH 3/4] Update rjsj_test.md --- docs/appendix/rjsj_test.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/appendix/rjsj_test.md b/docs/appendix/rjsj_test.md index c550c59..9e16712 100644 --- a/docs/appendix/rjsj_test.md +++ b/docs/appendix/rjsj_test.md @@ -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` 宏的展开操作。 From 2c8d86899ca49e8ca3183ae83951338625ad73a8 Mon Sep 17 00:00:00 2001 From: Guyutongxue Date: Sat, 25 May 2024 21:01:59 +0800 Subject: [PATCH 4/4] Update 7p.md --- docs/levels/7p.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/levels/7p.md b/docs/levels/7p.md index 33ef0b6..e33733c 100644 --- a/docs/levels/7p.md +++ b/docs/levels/7p.md @@ -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 的时机可能有所变化。