从零开始编程语言:完整实现指南
Source: Dev.to
你是否曾想过 Python、JavaScript 或 Go 在底层是如何工作的?
我花了几个月时间研究和实现不同的语言设计,并将所有内容汇编成一本全面指南,带你从基础词法分析一直到 JIT 编译。
您将构建的内容
通过本指南,您将创建一个完整的编程语言实现,从一个简单的计算器开始,逐步添加:
- 词法分析器 & 语法解析器 – 将源代码转换为抽象语法树(AST)
- 解释器 – 直接执行 AST(最简方式)
- 字节码虚拟机 – 基于栈的虚拟机,类似 Python 的 CPython
- LLVM 集成 – 生成本机机器代码
- 垃圾回收 – 自动内存管理策略
为什么本指南与众不同
大多数编译器教程只提供片段。本指南提供 完整、可运行的 Go 代码,您可以实际执行并进行修改。
实际性能数据
没有夸大其词。指南包含实际基准测试:
Tree‑Walking Interpreter: 10‑100× slower than native
Bytecode VM: 5‑50× slower than native
JIT Compiled: 1‑5× slower (can match native)
AOT Compiled: Baseline (native speed)
实际案例 – 斐波那契(40)
- C(gcc -O3): 0.5 s
- Python(CPython): 45 s (≈90× slower)
- Python(PyPy JIT): 2.5 s (≈5× slower)
渐进学习路径
指南按照逐步增加的复杂度结构化:
| 周次 | 目标 |
|---|---|
| 第 1 周 | 构建解释器 – 从树遍历解释器开始,这是最简单的执行模型。你将在周末结束时拥有一个可工作的语言。 |
| 第 2 周 | 添加字节码虚拟机 – 编译为字节码并构建基于栈的虚拟机。了解 Python 和 Java 的内部工作原理。 |
| 第 3‑4 周 | 本机代码生成 – 使用 LLVM 生成优化的机器码。学习 Rust 和 Swift 快速的原因。 |
| 更进一步 | 即时编译 (JIT) – 研究 V8 和 HotSpot 如何通过运行时优化实现接近本机的性能。 |
完整可运行示例
指南包括完整的计算器语言实现,包含:
- 词法分析器(标记化)
- 递归下降解析器
- 抽象语法树(AST)生成
- 树遍历解释器
source := `
x = 10
y = 20
z = x + y * 2
`
lexer := NewLexer(source)
parser := NewParser(lexer)
ast := parser.Parse()
interpreter := NewInterpreter()
interpreter.Eval(ast)
fmt.Printf("z = %d\n", interpreter.vars["z"]) // z = 50
这不是伪代码——它是可以直接运行的 Go 代码,你可以在此基础上进行构建。
覆盖内容
编译流水线
- Lexical Analysis – 将源代码拆分为标记
- Syntax Analysis – 构建抽象语法树
- Semantic Analysis – 类型检查和符号解析
- Code Generation – 字节码、LLVM IR 或直接解释
执行模型深度解析
-
Interpreters
- 直接执行 AST
- 实现最简单
- 适用于脚本语言和配置语言
-
Virtual Machines
- 基于栈 vs 基于寄存器的架构
- 字节码设计与指令集
- 函数调用与栈帧
- 控制流实现
-
LLVM Integration
- 生成 LLVM IR
- 类型系统映射
- 优化 passes
- 跨平台本地代码生成
-
JIT Compilation (Advanced)
- Profiling 与热点路径检测
- 运行时代码生成
- 反优化(De‑optimization)策略
- 类型专化
垃圾回收
深入自动内存管理:
- Reference Counting – 立即回收,无法处理循环
- Mark‑and‑Sweep – 能处理循环,存在 stop‑the‑world 暂停
- Copying / Generational – 性能最佳,复杂度最高
每种方法都提供可运行实现和权衡分析。
实际案例洞察
本指南不仅讲理论,还解释实际决策:
- 为什么 Python 使用字节码而不是直接解释?
- JavaScript 如何实现接近原生的性能?
- Go 的编译速度为何如此之快?
- Rust 的借用检查器是如何实现的?
权衡一目了然
| Aspect | Interpreter | Bytecode VM | JIT Compiler | AOT with LLVM |
|---|---|---|---|---|
| Development Complexity | 周末项目 | 1‑2 周 | 数月 | 2‑4 周 |
| Execution Speed | 比原生慢 10‑100 倍 | 比原生慢 5‑50 倍 | 比原生慢 1‑5 倍(可匹配原生) | 原生速度 |
| Startup Time | 即时 | 非常快 | 较慢(需要热身) | 即时(预编译) |
关键要点
完整实现
每个主要组件都包含完整、可工作的代码:
- 带有位置跟踪和错误处理的词法分析器
- 带有运算符优先级的递归下降解析器
- 完整指令集的基于栈的虚拟机
- 带有控制流的 LLVM IR 生成
不含空洞
本指南解决了难点:
- 为 JIT 编译创建可执行内存
- 平台特定的调用约定
- 为什么引用计数无法处理循环引用
- 管理指令指针和调用栈
实用示例
学习实现:
- 变量和赋值
- 具有正确优先级的算术表达式
- 字节码中的控制流(
if/while) - 带有正确栈帧的函数调用
- 类型检查和语义分析
谁适合阅读
如果你符合以下情况,请阅读本篇:
- 想了解编程语言的工作原理
- 正在构建 DSL 或配置语言
- 对编译器设计感兴趣,但被《龙书》吓倒
- 想为语言项目(Rust、Go、Python)做贡献
- 需要为你的应用实现脚本系统
前置条件
- 熟悉 Go(或能够阅读并改写代码)
- 基本了解数据结构(树、栈)
- 对底层实现原理充满好奇
需要计算机科学学位
假设没有任何编译器知识。
学习路径推荐
- 从完整的计算器示例开始 – 立即让它工作。
- 添加控制流 – 使用字节码示例实现
if语句和循环。 - 添加函数 – 使用提供的 call‑frame 实现。
- 探索 LLVM – 当你准备好提升性能时,生成本机代码。
- 学习 GC – 了解自动内存管理。
每一步都建立在前一步之上,你将在每个阶段拥有一个可工作的语言。
您将获得的收益
- 深入了解解释器、编译器和虚拟机的工作原理。
- 实践经验从零构建复杂系统。
- 对语言设计权衡的认识。
- 为参与真实语言项目奠定基础。
- 自信构建领域特定语言。
包含的资源
- “Crafting Interpreters” 作者 Bob Nystrom
- LLVM 教程和文档
- 真实世界的语言实现供学习
- 性能基准测试技术
入门
完整的指南以及所有代码示例均可在 GitHub 上获取:
github.com/codetesla51/how-to-build-a-programming-language
克隆仓库,运行示例,今天就开始构建自己的语言吧。
欢迎反馈
这是一份持续更新的指南。如果你发现问题、有什么疑问,或想贡献改进,请在 GitHub 上打开 issue 或提交 PR。
构建一门编程语言是计算机科学中最有成就感的项目之一。它能够揭开整个软件栈的神秘面纱,并为你提供理解任何代码库的超能力。
- 从小做起。先实现一个计算器。
- 逐步添加功能。
- 打破它们,然后修复。
这就是学习的方式。
祝语言构建愉快!