编译器可视化
编译器识别字符片段,把词元组织成层级,遍历树,再生成机器可执行的程序。本组三课依次展示源码到 AST、AST 到字节码、栈虚拟机执行字节码。后两课共享 1 + 2 * 3,第一课用 12 + 3 * 4,让多位数的词法处理可见。
最后的虚拟机不是 CPU 或虚拟化管理器,而是语言虚拟机:取操作码,修改栈,再继续。源码中的 + 进入树结构,ADD 则执行两次弹出和一次压入。若生成代码时把运算符放在操作数前,运行就会栈下溢。
错误也有阶段归属。12 + @ 4 无法形成完整词元序列;12 + * 4 可以分词,却在 primary() 解析失败;先执行 ADD 的字节码格式可读,但运行时缺少操作数。
课程比较
| 课程 | 核心问题 | 变化 | 失败点 |
|---|---|---|---|
| 词法与语法分析 | 优先级从哪里来 | 字符片段成为平面词元,语法调用建立层级 | @ 使词法失败,操作数位置的 * 使语法失败 |
| AST → 代码 | 为何 1 + 2 * 3 先写 MUL | 后序遍历,Fold 先折叠常量 | 名称阻止上层折叠,不妨碍另一独立子树 |
| 虚拟机 | 指令为何必须按该顺序 | 操作码修改栈,热循环触发编译 | 运算符优先导致下溢,冷循环不触发 JIT |
解析器输出与代码生成输入使用同样的运算符层级。代码生成右侧的 IR 是正在写出的程序,虚拟机右侧的 Stack 是程序运行时使用的数据栈,两者不可混读。
建议学习顺序
先在词法与语法分析中运行 12 + 3 * 4,看 1、2 如何留在同一个词素内。再跟踪调用:expr 等待 +,term 先完成 *。切换括号场景,解析器代码不变,Add 成为内部节点,Mul 成为根。最后分别尝试词法、语法错误,区分“能分词”和“语法有效”。
AST → 代码默认以 Naive 处理 1 + 2 * 3,MUL 写出时 + 仍在等待。保持树不变切到 Fold,五条指令变为 LOAD_CONST 7。再用 x + 2 * 3:乘法仍可折叠,加法因未知名称不能折叠。
虚拟机默认用解释器运行同一表达式。三次压入后,MUL 计算 2 * 3 → 6,ADD 再计算 1 + 6,说明代码生成为什么使用后序。随后加载 ADD first,实际触发 Underflow。
理解栈纪律后再看可选的 JIT。倒计时先解释两轮,达到热度后编译,指令逐条取出的显示停止。冷循环的热度低于阈值,不会编译。
观察重点
- 区分源码顺序、词元顺序、树层级和指令顺序。源码先出现
+,树先嵌套*,指令先执行MUL。 - 合法词元不一定出现在合法位置。
12 + * 4中STAR可被识别,却不能开始primary()。 - 看栈高度,不只看顶值。默认峰值为 3,对应首个运算符执行前等待的三个叶子。
- 空栈本身不是错误。初始化可以为空,只有运算符请求缺失操作数才下溢。
- JIT 后剩余计算仍会执行。一个原生步骤表示不再逐条取解释器指令,不代表 O(1) 工作或直接跳过计算。
全部编译器可视化课程
改变表达式或执行引擎,逐步对照 Python 代码。词法步骤故意不点亮解析器代码面板,字符到词元的证据显示在画布上。