练习

本节阅读量:

这一页适合读完第三章后完成。先手算和手写,再用 mini 检查。

练习 1:画出条件 AST

画出:

1
(if (eq? (+ 1 2) 3) 10 20)

参考形状:

1
2
3
4
If
  condition = ...
  then      = ...
  else      = ...

再运行 ./mini ast <file> 对照。

练习 2:追踪 condition 和最终结果

先不要运行。对下面每个程序,分别写出:

1
2
3
condition 的结果
被选择的分支
整个 if 的最终结果
1
2
3
4
(if 0 10 20)
(if -7 10 20)
(if (eq? (+ 20 22) (+ 40 2)) 1 99)
(if (eq? (eq? 1 2) 0) 42 7)

注意区分两件事:谓词表达式产生的是 01,但整个 if 的结果来自被选中的分支,不一定也是 01

练习 3:追踪 environment 和分支

程序:

1
(let x 0 (if (eq? x 0) (let x 42 x) x))

写出:

1
2
3
4
5
进入外层 let 后的环境
condition 的结果
被选择的分支
进入内层 let 后的环境
最终结果

确认内层 x 不会修改外层绑定。

练习 4:手写 IR

给下面程序写 IR:

1
(if (eq? 1 2) 3 4)

必须包含:

1
2
3
4
5
eq? 操作
branch
then、else、end label
then 后的 jump
两个分支共享的结果名

然后运行 ./mini ir <file> 对照名字和顺序。

练习 5:找出基本块

查看 examples/if.lang 的 IR,把每条操作分别归入:

1
2
3
4
入口块
then0
else0
end0

再画出块之间的箭头。入口块有两条出边,then0 和 else0 最后都到达 end0。

练习 6:同一个结果栈槽

编译:

1
(if 1 10 20)

在 IR 中找出两个分支共同写入的结果名,再在汇编里找出它对应的栈槽。

解释为什么栈槽分配器第二次看到同名 dst 时不能再分配一个新位置。

练习 7:嵌套 if 的标签

查看下面程序的 IR:

1
(if 1 (if 0 1 2) 3)

列出所有 then、else 和 end label,确认内外两层 if 没有使用相同名字。

练习 8:解释器与编译器一致

自己写一个同时包含 let、shadowing、eq?if 的程序,并让正确结果位于 0255 之间。

分别验证:

1
2
3
4
5
6
./mini run <file>
./mini ir <file>
./mini compile <file> -o out.s
cc out.s -o out
./out
echo $?

解释器输出与可执行程序退出码应该相同。

练习 9:把 not 做成语法糖

给语言增加一个一元形式:

1
(not expr)

它的语义是:

1
2
3
先求值 expr
如果结果是 0,not 返回 1
如果结果不是 0,not 返回 0

也就是说,(not expr) 等价于:

1
(eq? expr 0)

这道题的重点不是给后端增加新指令,而是练习怎样把一个表面语法复用到已有语义上。你可以选择其中一种实现方式:

1
2
在 parser 里把 (not expr) 直接构造成已有的 eq? 结构
或者增加 NotExpr,再在解释器和 lowering 中把它转成“是否等于 0”

完成后测试:

1
2
3
(not 0)
(not 42)
(if (not (eq? 1 2)) 10 20)

确认解释器和编译器的结果一致。


3.8 本章总结

上一节

专栏首页

下一节