无名,天地之始;有名,万物之母
本节阅读量:第一章里,程序只有整数和加法:
|
|
第二章开始加入“名字”。我们希望能写:
|
|
解释器会直接打印:
|
|
编译器会生成一段能返回 42 的 x86-64 汇编。
这一次,多出来的关键问题是:
|
|
变量名不会自己保存值。解释器求值时,需要从环境里查:
|
|
编译器生成汇编时,需要把名字变成更低层的位置:
|
|
第二章的目标不是引入很多新语法,而是把“名字、绑定、作用域、位置”这条线走清楚。
从第一章延续什么
第一章已经跑通了这条主线:
|
|
第一章已经有栈槽:IR 里的临时变量 t.0、t.1 会被放到 -8(%rbp)、-16(%rbp) 这样的栈槽里。
第二章仍然走同一条路,只是在 AST、解释器和编译器里各加一个概念:
|
|
这里新增的不是“栈槽”本身,而是这一步:
|
|
第一章的这些写法在第二章仍然有效:
|
|
第二章新增:
|
|
注意,单独的 x 在语法上是变量引用,但如果没有任何 let 绑定它,运行时应该报错。
代码变化在哪里
第二章目录结构和第一章一样,仍然是:
|
|
不需要重新讲一遍每个目录。真正变化集中在这几处:
|
|
lexer 在第二章基本不变。它仍然只负责把括号分开、按空白切 token;真正判断 x 是变量、let 是特殊形式的地方在 parser。
cli 也基本不变。run、ast、ir、compile 这些命令仍然把同一条主线串起来。
读完本章后
你应该理解:
- 为什么变量引用需要查找环境。
let的 value 和 body 为什么使用不同的环境。- 同名遮蔽(shadowing)为什么不是修改旧变量,而是建立一个新的、更近的绑定。
- 解释器里的 environment 和编译器里的内部名字、栈槽有什么区别。
- 为什么编译后的汇编里没有用户变量名,只有栈槽地址。
本节目录