练习
本节阅读量:这一页适合读完第三章后完成,不必一次全部做完。练习 1~3 检查语言语义,练习 4~8 对照控制流 IR 与汇编,练习 9 做整条路径验证,最后一题需要修改代码。
练习 1:画出条件 AST
画出:
|
|
参考形状:
|
|
再运行 ./mini ast <file> 对照。
练习 2:追踪 condition 和最终结果
先不要运行。对下面每个程序,分别写出:
|
|
|
|
注意区分两件事:谓词表达式产生的是 0 或 1,但整个 if 的结果来自被选中的分支,不一定也是 0 或 1。
练习 3:追踪 environment 和分支
程序:
|
|
写出:
|
|
确认内层 x 不会修改外层绑定。
练习 4:手写 IR
给下面程序写 IR:
|
|
必须包含:
|
|
然后运行 ./mini ir <file> 对照名字和顺序。
练习 5:找出基本块
查看 examples/if.lang 的 IR,把每条操作分别归入:
|
|
再画出块之间的箭头,并标出每条边来自 branch、jump 还是 fallthrough。入口块有两条出边,then0 和 else0 最后都到达 end0。
练习 6:同一个结果栈槽
编译:
|
|
在 IR 中找出两个分支共同写入的结果名,再在汇编里找出它对应的栈槽。
解释为什么栈槽分配器第二次看到同名 dst 时不能再分配一个新位置。
再计算这个程序有几个不同的结果栈槽、原始需要多少字节,以及向上对齐后栈帧会分配多少字节。
练习 7:追踪比较和跳转
对下面程序生成汇编:
|
|
沿执行顺序填写:
|
|
最后回答:lowerer 生成两个分支,是否意味着运行时也会执行两个分支?
练习 8:嵌套 if 的标签
查看下面程序的 IR:
|
|
列出所有 then、else 和 end label,确认内外两层 if 没有使用相同名字。
练习 9:解释器与编译器一致
自己写一个同时包含 let、shadowing、eq? 和 if 的程序,并让正确结果位于 0 到 255 之间。
分别验证:
|
|
解释器输出与可执行程序退出码应该相同。
练习 10(综合):把 not 做成语法糖
给语言增加一个一元形式:
|
|
它的语义是:
|
|
也就是说,(not expr) 等价于:
|
|
这道题的重点不是给后端增加新指令,而是练习怎样把一个表面语法复用到已有语义上。推荐先使用 parser 脱糖:
|
|
这样 AST 后面的解释器、IR 和汇编生成器都不需要新增分支。进阶做法是增加 NotExpr,再在解释器和 lowering 中复用“是否等于 0”的语义;这会涉及更多模块,不必作为第一版答案。
完成后测试:
|
|
修改代码后先重新运行 make,再分别检查 run、ast、ir 和 compile,确认解释器与编译器结果一致。
确认解释器和编译器的结果一致。