合抱之木,生于毫末
本节阅读量:
这一章只做一件事:把下面这段小语言程序从一段文本,变成两个结果。
解释器会直接打印:
编译器会生成一段能返回 42 的 x86-64 汇编。
这门小语言使用 Lisp 风格语法,(+ 40 2) 表示 40 + 2。第一章还允许单个整数,例如 42、-7;完整规则下一节会展开。
通过这个加法例子,我们会走过一门语言实现里最小、最完整的两条路:
1
2
|
源码 -> 语法树 -> 直接算结果
源码 -> 语法树 -> 生成汇编
|
第一章的目标不是“记住一堆术语”,而是先建立一个感觉:
1
2
|
程序不是一上来就能执行。
我们要先把文本读懂,再决定怎么执行它。
|
你需要先知道什么
你只需要会一点 C++ 基础:
- 能看懂
struct。
- 能看懂函数调用。
- 大概知道指针或对象可以互相引用。
- 知道递归函数会调用自己。
其余所有的内容,会在用到时进行讲解。本章代码量不大,核心代码仍然保持在几百行规模。
代码架构
第一章代码很小,但已经按一门语言实现的基本结构分好了层。先看目录,不要一上来就钻进某个函数里:
1
2
3
4
5
6
7
8
9
10
11
12
|
code/01_numbers/src/
cli/main.cpp 命令行入口,负责读文件、选择 run/ast/ir/compile
front/ast.h AST 节点定义:IntExpr、AddExpr
front/ast.cpp AST 节点的构造和 dump
front/lexer.h/.cpp source -> tokens
front/parser.h/.cpp tokens -> AST
interp/interpreter.h/.cpp AST -> integer
compile/ir.h/.cpp AST -> IR
compile/assembly.h/.cpp IR -> x86-64 assembly
|
这些英文名可以先这样读:
1
2
3
4
|
front 把源码读成 AST
interp 沿着 AST 直接算结果
compile 把 AST 变成 IR,再变成汇编
cli 把命令行和这些模块串起来
|
其中三个名字最重要:
1
2
3
|
token 源码小块,例如 "("、"+"、"40"
AST abstract syntax tree,语法树,程序结构
IR intermediate representation,编译器内部的草稿步骤
|
这一章最关键的分界线是 AST:
1
2
3
|
source text -> tokens -> AST --interp--> integer
|
+--compile--> IR --> assembly
|
也就是说,lexer 先把文本切成 token,parser 再把 token 组装成 AST。解释器和编译器处理的是“结构问题”:一旦 parser 返回了 AST,后面的代码就不需要再关心源码里有几个空格、括号写在哪里、数字有几位。
本章会用四个命令观察同一个程序的不同阶段:
1
2
3
4
|
run 看解释器结果
ast 看语法树
ir 看编译器草稿步骤
compile 生成汇编
|
第一次读代码,可以按这个顺序:
1
2
3
4
5
6
7
|
1. front/ast.h 先看程序结构长什么样
2. front/lexer.cpp 再看源码怎么切成 token
3. front/parser.cpp 再看 token 怎么变成 AST
4. interp/interpreter.cpp 看 AST 怎么直接算出结果
5. compile/ir.h/.cpp 看 AST 怎么拆成 IR
6. compile/assembly.cpp 看 IR 怎么变成汇编
7. cli/main.cpp 最后看命令行怎么把这些步骤串起来
|
这样读会比直接从 main 一路跳函数更稳:先理解每一层负责什么,再看它们怎么连起来。
读完这一章,你不需要懂完整编译原理。你只需要看清楚:文本如何变成 AST,AST 如何被解释器求值,AST 又如何一路变成汇编。