合抱之木,生于毫末
本节阅读量:这一章只做一件事:让下面这段小语言程序完整走过解释和编译两条路径。
|
|
解释器会直接计算并打印:
|
|
编译器会生成一段能返回 42 的 x86-64 汇编。把汇编交给 cc 后,我们还能得到真正的可执行程序。
这门小语言使用 Lisp 风格语法,(+ 40 2) 表示 40 + 2。第一章还允许单个整数,例如 42、-7;完整规则下一节会展开。
通过这个加法例子,我们会走过一门语言实现里最小、但完整的两条路:
|
|
第一章的目标不是“记住一堆术语”,而是先建立一个感觉:
|
|
你需要先知道什么
你只需要会一点 C++ 基础:
- 能看懂
struct。 - 能看懂函数调用。
- 大概知道指针或对象可以互相引用。
- 知道递归函数会调用自己。
其他概念都会在用到时再解释。本章核心代码只有几百行,但 lexer、parser、解释器和编译器之间的边界已经完整存在。
代码架构
第一章代码很小,但已经按一门语言实现的基本结构分好了层。先看目录,不要一上来就钻进某个函数里:
|
|
这些英文名可以先这样读:
|
|
其中三个名字最重要:
|
|
这一章最关键的分界线是 AST:
|
|
也就是说,lexer 先把文本切成 token,parser 再把 token 组装成 AST。解释器和编译器处理的是“结构问题”:一旦 parser 返回了 AST,后面的代码就不需要再关心源码里有几个空格、括号写在哪里、数字有几位。
本章会用四个命令观察同一个程序的不同阶段:
| 命令 | 观察到什么 |
|---|---|
run |
解释器直接计算出的结果 |
ast |
parser 生成的语法树 |
ir |
编译器生成的中间计算步骤 |
compile |
写入 .s 文件的汇编文本 |
这里要特别区分:run 会把 42 打印到终端,编译后的可执行程序则把 42 作为退出码返回。第 0 章已经演示了怎样用 echo $? 查看它。
第一次读代码,可以按这个顺序:
|
|
这样读会比直接从 main 一路跳函数更稳:先理解每一层负责什么,再看它们怎么连起来。
汇编部分适合分成两步读,不要同时处理“汇编是什么”和“生成器怎样写汇编”:
|
|
第一遍不需要记住整套 x86-64 指令,但要能说明当前栈帧怎样分配空间、怎样用负偏移保存临时变量,以及返回前怎样收回空间。这三件事是读懂第一章编译器所必需的。
读完这一章,你不需要掌握完整的编译原理。只要能回答三个问题,就已经抓住了主线:文本怎样变成 AST,AST 怎样被解释器求值,AST 又怎样经过 IR 变成汇编。