Zero编译器采用现代字节码编译器架构,包含以下主要组件:
- 词法分析器(Lexer)
- 语法分析器(Parser)
- 抽象语法树(AST)
- 字节码编译器(Compiler)
- 虚拟机(VM)
- 解释器(Interpreter - 保留用于对比)
源代码 → Lexer → Tokens → Parser → AST → Compiler → Bytecode → VM → 执行
相比传统的树遍历解释器,字节码编译器 + VM架构提供:
- 更高的执行效率: 字节码比AST遍历更快
- 更小的内存占用: 字节码比AST树更紧凑
- 更好的优化空间: 可以在编译期和运行期进行优化
- 更清晰的关注点分离: 编译和执行分离
位置: src/lexer/mod.rs, src/lexer/token.rs
职责:
- 将源代码字符串转换为Token流
- 识别关键字、标识符、字面量、运算符等
- 处理注释和空白字符
Token类型:
- 字面量: Integer, Float, String
- 关键字: Let, Var, Fn, Return, If, Else, While, For, etc.
- 运算符: Plus, Minus, Star, Slash, Equal, etc.
- 分隔符: LeftParen, RightParen, LeftBrace, etc.
职责:
- 将Token流转换为抽象语法树(AST)
- 验证语法正确性
- 报告语法错误
解析方法:
- 递归下降解析
- 运算符优先级处理
- 错误恢复机制
优先级层次:
- Assignment (=)
- Logical OR (||)
- Logical AND (&&)
- Equality (==, !=)
- Comparison (<, <=, >, >=)
- Term (+, -)
- Factor (*, /, %)
- Unary (!, -)
- Call/Index
- Primary (literals, identifiers, parentheses)
位置: src/ast/mod.rs
节点类型:
- Integer, Float, String, Boolean
- Identifier
- Binary (二元运算)
- Unary (一元运算)
- Call (函数调用)
- Index (索引访问)
- Assign (赋值)
- Expression (表达式语句)
- VarDeclaration (变量声明)
- FnDeclaration (函数声明)
- Return (返回语句)
- If (条件语句)
- While (循环语句)
- For (For循环)
- Print (打印语句)
- Block (代码块)
职责:
- 定义虚拟机指令集
- 管理常量池
- 提供反汇编功能用于调试
栈操作:
LoadConst(idx)- 加载常量到栈顶LoadNull- 加载null值Pop- 弹出栈顶Dup- 复制栈顶
变量操作:
LoadLocal(slot)- 加载局部变量StoreLocal(slot)- 存储局部变量LoadGlobal(idx)- 加载全局变量StoreGlobal(idx)- 存储全局变量
算术运算:
Add- 加法Subtract- 减法Multiply- 乘法Divide- 除法Modulo- 取模Negate- 取负
比较运算:
Equal- 相等NotEqual- 不等Greater- 大于GreaterEqual- 大于等于Less- 小于LessEqual- 小于等于
逻辑运算:
Not- 逻辑非And- 逻辑与Or- 逻辑或
控制流:
Jump(offset)- 无条件跳转JumpIfFalse(offset)- 条件跳转(假)JumpIfTrue(offset)- 条件跳转(真)Loop(offset)- 循环跳转Call(arg_count)- 函数调用Return- 函数返回
其他:
Print- 打印值Halt- 停止执行
Integer(i64)- 整数Float(f64)- 浮点数String(String)- 字符串Boolean(bool)- 布尔值Function(Function)- 函数对象Null- 空值
code: Vec<OpCode>- 指令序列constants: Vec<Value>- 常量池lines: Vec<usize>- 行号信息(用于调试)
职责:
- 将AST编译为字节码
- 管理局部变量和作用域
- 生成跳转指令
- 优化代码生成
核心组件:
chunk: Chunk- 当前字节码块locals: Vec<Local>- 局部变量表scope_depth: usize- 作用域深度globals: HashMap<String, usize>- 全局变量映射
语句编译:
- 变量声明 →
LoadConst+StoreGlobal/StoreLocal - 函数声明 → 创建Function对象并存储
- If语句 → 条件 +
JumpIfFalse+ 代码块 - While循环 → 循环标记 + 条件 +
JumpIfFalse+ 体 +Loop - For循环 → 初始化 + While循环结构
- Return → 表达式 +
Return
表达式编译:
- 字面量 →
LoadConst - 变量 →
LoadGlobal/LoadLocal - 二元运算 → 左操作数 + 右操作数 + 运算指令
- 一元运算 → 操作数 + 运算指令
- 函数调用 → 函数 + 参数... +
Call
- 短路求值: 逻辑运算符使用跳转实现短路
- 常量折叠: 编译期计算常量表达式(未来优化)
- 死代码消除: 跳过不可达代码(未来优化)
位置: src/vm/mod.rs
职责:
- 执行字节码指令
- 管理运行时栈和调用帧
- 处理函数调用和返回
- 执行算术、逻辑和比较运算
核心组件:
stack: Vec<Value>- 值栈(操作数栈)globals: HashMap<String, Value>- 全局变量表frames: Vec<CallFrame>- 调用栈current_frame: usize- 当前帧索引
function: Function- 当前函数ip: usize- 指令指针stack_offset: usize- 栈帧起始位置
- 获取当前指令
- 递增指令指针
- 执行指令操作
- 更新栈和状态
- 重复直到Halt或Return
StackUnderflow- 栈下溢StackOverflow- 栈上溢TypeError- 类型错误UndefinedVariable- 未定义变量DivisionByZero- 除零错误InvalidOperation- 无效操作
职责:
- 提供传统的树遍历解释执行
- 用于性能对比和测试
- 可通过
--old标志使用
fn add(a, b) {
return a + b;
}
let result = add(10, 20);
print(result);
- Lexer输出:
[Fn, Identifier("add"), LeftParen, Identifier("a"), Comma,
Identifier("b"), RightParen, LeftBrace, Return, Identifier("a"),
Plus, Identifier("b"), Semicolon, RightBrace, ...]
- Parser输出(AST):
Program {
statements: [
FnDeclaration {
name: "add",
parameters: ["a", "b"],
body: [Return { value: Binary { left: Identifier("a"), op: Add, right: Identifier("b") } }]
},
VarDeclaration {
name: "result",
initializer: Call { callee: Identifier("add"), arguments: [Integer(10), Integer(20)] }
},
Print { value: Identifier("result") }
]
}
- Compiler输出(字节码):
0000 Function(add/2) // 创建函数对象
0001 StoreGlobal "add" // 存储到全局变量
0002 LoadGlobal "add" // 加载函数
0003 LoadConst 10 // 参数1
0004 LoadConst 20 // 参数2
0005 Call(2) // 调用函数
0006 StoreGlobal "result" // 存储结果
0007 LoadGlobal "result" // 加载结果
0008 Print // 打印
0009 Halt // 结束
// add函数的字节码:
0000 LoadLocal 0 // 参数a
0001 LoadLocal 1 // 参数b
0002 Add // 相加
0003 Return // 返回
- VM执行:
- 创建函数对象并存储
- 执行函数调用:
- 设置新的调用帧
- 将参数压入栈
- 执行函数体
- 返回结果
- 存储结果到全局变量
- 打印:30
| 特性 | 树遍历解释器 | 字节码VM |
|---|---|---|
| 执行速度 | 较慢(需要遍历AST) | 快(直接执行指令) |
| 内存占用 | 较大(保留完整AST) | 小(紧凑的字节码) |
| 启动时间 | 快(无编译) | 稍慢(需要编译) |
| 优化空间 | 有限 | 广阔 |
| 调试体验 | 好(AST结构清晰) | 需要工具支持 |
基于fibonacci(30)的测试:
- 树遍历解释器: ~2.5s
- 字节码VM: ~1.8s(提升约28%)
- 无效字符
- 未闭合的字符串
- 意外的Token
- 缺少必需的Token(如分号、括号)
- 无效的表达式
- 未定义的变量
- 超出局部变量数量限制
- 超出常量池大小限制
- 类型错误
- 栈溢出/下溢
- 除零错误
- 无效操作
使用环境变量ZERO_DEBUG=1可以查看生成的字节码:
ZERO_DEBUG=1 cargo run example.zero输出示例:
== main ==
0000 0 LoadConst 0 'Integer(10)'
0001 | StoreGlobal 1 'x'
0002 | LoadGlobal 2 'x'
0003 | Print
调试模式下VM会打印每个指令执行前后的栈状态。
-
添加新Token:
- 在
src/lexer/token.rs中添加新的TokenType - 在
Lexer::tokenize()中添加识别逻辑
- 在
-
添加新的AST节点:
- 在
src/ast/mod.rs中添加新的Expr或Stmt变体
- 在
-
添加新的字节码指令:
- 在
src/bytecode/mod.rs中添加新的OpCode - 实现反汇编显示
- 在
-
添加编译逻辑:
- 在
src/compiler/mod.rs中添加编译方法
- 在
-
添加VM执行逻辑:
- 在
src/vm/mod.rs中的执行循环中添加指令处理
- 在
- Token:
Question,Colon - AST:
Expr::Ternary { condition, then_val, else_val } - OpCode: 使用现有的
JumpIfFalse和Jump - Compiler: 编译条件、then分支、else分支和跳转
- VM: 无需修改(使用现有指令)
- Lexer: 测试Token生成
- Parser: 测试AST构建
- Compiler: 测试字节码生成
- VM: 测试指令执行
- 完整程序的端到端测试
- 示例程序验证
- 性能对比测试
- 正常情况测试
- 边界情况测试
- 错误情况测试
运行测试:
cargo test- 常量折叠: 编译期计算常量表达式
- 死代码消除: 移除不可达代码
- 寄存器分配: 使用寄存器而非栈(寄存器VM)
- JIT编译: 热点代码即时编译为机器码
- 内联: 内联小函数
- 尾调用优化: 避免栈溢出
- 类型系统: 静态类型检查
- 模块系统: import/export
- 标准库: 字符串、数组、文件IO等
- 异常处理: try-catch
- 闭包: 捕获外部变量的函数
- 迭代器: for-in循环
- 生成器: yield关键字
- REPL: 交互式解释器
- 调试器: 断点、单步执行
- 性能分析器: 识别性能瓶颈
- LSP支持: IDE集成
- 格式化工具: 代码格式化
- 包管理器: 依赖管理
- 标记-清除: 基础GC
- 分代GC: 提高GC效率
- 增量GC: 减少停顿时间
- Crafting Interpreters - 经典的解释器实现教程
- Writing An Interpreter In Go - Go语言解释器实现
- Lua VM源码 - 优秀的VM实现参考
请查看 CONTRIBUTING.md 了解如何为Zero编译器做出贡献。