第十章 编译器(上)
导读
在前面的章节中,我们构建了完整的计算机硬件系统,并学习了从机器语言到高级语言的软件栈。本章将开始构建Jack编译器的前端——将Jack源代码转换为VM代码。
编译器是计算机科学中最重要的软件之一。它将人类可读的高级语言转换为机器可执行的代码。通过本章的学习,你将理解编译器的工作原理,掌握词法分析、语法分析和代码生成的基本技术。
编译器的构建展示了软件工程的许多重要概念:形式语言理论、递归下降解析、符号表管理等。这些知识不仅对构建编译器有用,对理解编程语言、设计DSL(领域特定语言)等也很有帮助。
核心概念详解
10.1 编译器概述
编译器是将源代码转换为目标代码的程序。
编译器的组成
典型的编译器包含以下阶段:
词法分析(Lexical Analysis):将字符流转换为词法单元(token)流
语法分析(Syntax Analysis):将token流转换为抽象语法树(AST)
语义分析(Semantic Analysis):检查语义正确性,构建符号表
中间代码生成:生成中间表示(IR)
代码优化:优化中间代码
代码生成:生成目标代码
Jack编译器的简化
Jack编译器进行了简化:
- 直接生成VM代码,跳过中间表示
- 不做复杂的优化
- 单遍编译,边解析边生成代码
10.2 词法分析
词法分析是编译的第一步,将源代码字符流转换为token流。
Token类型
Jack语言的token包括:
关键字:
class, constructor, function, method, field, static,
var, int, char, boolean, void, true, false, null,
this, let, do, if, else, while, return符号:
{ } ( ) [ ] . , ; + - * / & | ~ < > =整数常量:
- 0-32767之间的十进制数
字符串常量:
- 双引号包围的字符序列
- 例如:"Hello, World!"
标识符:
- 字母或下划线开头
- 后跟字母、数字或下划线
- 例如:myVar, _count, Class1
词法分析器实现
词法分析器通常使用有限状态机实现:
class JackTokenizer:
def __init__(self, input_file):
self.input = input_file
self.current_token = None
self.token_type = None
def has_more_tokens(self):
# 检查是否还有更多token
pass
def advance(self):
# 读取下一个token
pass
def get_token_type(self):
# 返回当前token类型
# KEYWORD, SYMBOL, IDENTIFIER, INT_CONST, STRING_CONST
pass
def get_keyword(self):
# 返回关键字值
pass
def get_symbol(self):
# 返回符号
pass
def get_identifier(self):
# 返回标识符
pass
def get_int_constant(self):
# 返回整数常量
pass
def get_string_constant(self):
# 返回字符串常量
pass10.3 语法分析
语法分析是编译的核心,将token流转换为结构化的表示。
上下文无关文法
Jack语言的语法可以用上下文无关文法(CFG)描述:
class → 'class' className '{' classVarDec* subroutineDec* '}'
classVarDec → ('static' | 'field') type varName (',' varName)* ';'
type → 'int' | 'char' | 'boolean' | className
subroutineDec → ('constructor' | 'function' | 'method')
('void' | type) subroutineName '(' parameterList ')' subroutineBody
parameterList → ((type varName) (',' type varName)*)?
subroutineBody → '{' varDec* statements '}'
varDec → 'var' type varName (',' varName)* ';'
statements → statement*
statement → letStatement | ifStatement | whileStatement |
doStatement | returnStatement
letStatement → 'let' varName ('[' expression ']')? '=' expression ';'
ifStatement → 'if' '(' expression ')' '{' statements '}'
('else' '{' statements '}')?
whileStatement → 'while' '(' expression ')' '{' statements '}'
doStatement → 'do' subroutineCall ';'
returnStatement → 'return' expression? ';'
expression → term (op term)*
term → integerConstant | stringConstant | keywordConstant |
varName | varName '[' expression ']' | subroutineCall |
'(' expression ')' | unaryOp term
subroutineCall → subroutineName '(' expressionList ')' |
(className | varName) '.' subroutineName '(' expressionList ')'
expressionList → (expression (',' expression)*)?递归下降解析
递归下降是一种自顶向下的解析方法:
- 每个非终结符对应一个解析函数
- 函数之间相互调用
- 自然映射到文法规则
class CompilationEngine:
def __init__(self, tokenizer, output_file):
self.tokenizer = tokenizer
self.output = output_file
def compile_class(self):
# class → 'class' className '{' classVarDec* subroutineDec* '}'
self.tokenizer.advance() # 'class'
self.tokenizer.advance() # className
self.tokenizer.advance() # '{'
while self.tokenizer.get_token() in ['static', 'field']:
self.compile_class_var_dec()
while self.tokenizer.get_token() in ['constructor', 'function', 'method']:
self.compile_subroutine()
self.tokenizer.advance() # '}'
def compile_class_var_dec(self):
# classVarDec → ('static' | 'field') type varName (',' varName)* ';'
kind = self.tokenizer.get_token() # 'static' or 'field'
self.tokenizer.advance()
type = self.tokenizer.get_token()
self.tokenizer.advance()
var_name = self.tokenizer.get_token()
self.tokenizer.advance()
# 添加到符号表
while self.tokenizer.get_token() == ',':
self.tokenizer.advance()
var_name = self.tokenizer.get_token()
self.tokenizer.advance()
# 添加到符号表
self.tokenizer.advance() # ';'
def compile_subroutine(self):
# subroutineDec → ...
pass
def compile_expression(self):
# expression → term (op term)*
self.compile_term()
while self.tokenizer.get_token() in ['+', '-', '*', '/', '&', '|', '<', '>', '=']:
op = self.tokenizer.get_token()
self.tokenizer.advance()
self.compile_term()
# 生成VM代码10.4 符号表
符号表存储程序中定义的标识符信息。
符号表结构
class SymbolTable:
def __init__(self):
self.table = {} # name -> (type, kind, index)
self.counts = {'static': 0, 'field': 0, 'var': 0, 'arg': 0}
def define(self, name, type, kind):
index = self.counts[kind]
self.table[name] = (type, kind, index)
self.counts[kind] += 1
def type_of(self, name):
return self.table[name][0]
def kind_of(self, name):
return self.table[name][1]
def index_of(self, name):
return self.table[name][2]
def var_count(self, kind):
return self.counts[kind]
def start_subroutine(self):
# 清除局部符号表
self.table = {k: v for k, v in self.table.items()
if v[1] in ['static', 'field']}
self.counts['var'] = 0
self.counts['arg'] = 0作用域
Jack有两个作用域级别:
- 类作用域:static和field变量
- 子程序作用域:arg和var变量
每个子程序开始时,清除局部符号表,但保留类级别的符号。
10.5 表达式编译
表达式编译是编译器中最复杂的部分之一。
中缀到后缀转换
Jack表达式是中缀表示,需要转换为后缀表示以便VM执行:
中缀:a + b * c
后缀:a b c * +使用调度场算法(shunting-yard algorithm)进行转换。
表达式编译实现
def compile_expression(self):
# expression → term (op term)*
self.compile_term()
while self.tokenizer.get_token() in ['+', '-', '*', '/', '&', '|', '<', '>', '=']:
op = self.tokenizer.get_token()
self.tokenizer.advance()
self.compile_term()
# 生成VM代码
if op == '+':
self.emit('add')
elif op == '-':
self.emit('sub')
elif op == '*':
self.emit('call Math.multiply 2')
elif op == '/':
self.emit('call Math.divide 2')
elif op == '&':
self.emit('and')
elif op == '|':
self.emit('or')
elif op == '<':
self.emit('lt')
elif op == '>':
self.emit('gt')
elif op == '=':
self.emit('eq')项的编译
def compile_term(self):
token = self.tokenizer.get_token()
if token.isdigit():
# 整数常量
self.emit(f'push constant {token}')
self.tokenizer.advance()
elif token.startswith('"'):
# 字符串常量
string = token[1:-1]
self.emit(f'push constant {len(string)}')
self.emit('call String.new 1')
for char in string:
self.emit(f'push constant {ord(char)}')
self.emit('call String.appendChar 2')
self.tokenizer.advance()
elif token in ['true', 'false', 'null', 'this']:
# 关键字常量
if token == 'true':
self.emit('push constant 0')
self.emit('neg')
elif token == 'false' or token == 'null':
self.emit('push constant 0')
elif token == 'this':
self.emit('push pointer 0')
self.tokenizer.advance()
elif token == '(' :
# 括号表达式
self.tokenizer.advance()
self.compile_expression()
self.tokenizer.advance() # ')'
elif token in ['-', '~']:
# 一元运算
op = token
self.tokenizer.advance()
self.compile_term()
if op == '-':
self.emit('neg')
elif op == '~':
self.emit('not')
else:
# 变量或函数调用
name = token
self.tokenizer.advance()
if self.tokenizer.get_token() == '[':
# 数组访问
self.emit_push_var(name) # 数组基地址
self.tokenizer.advance()
self.compile_expression()
self.emit('add')
self.emit('pop pointer 1')
self.emit('push that 0')
self.tokenizer.advance() # ']'
elif self.tokenizer.get_token() == '(':
# 函数调用
self.tokenizer.advance()
self.compile_expression_list()
self.emit(f'call {name} {n_args}')
self.tokenizer.advance() # ')'
elif self.tokenizer.get_token() == '.':
# 方法或函数调用
self.tokenizer.advance()
method_name = self.tokenizer.get_token()
self.tokenizer.advance()
self.tokenizer.advance() # '('
if name in self.class_vars:
# 方法调用
self.emit_push_var(name)
self.compile_expression_list()
self.emit(f'call {class_name}.{method_name} {n_args + 1}')
else:
# 函数调用
self.compile_expression_list()
self.emit(f'call {name}.{method_name} {n_args}')
self.tokenizer.advance() # ')'
else:
# 变量
self.emit_push_var(name)10.6 语句编译
Jack支持五种语句类型。
let语句
def compile_let(self):
# let varName ('[' expression ']')? '=' expression ';'
self.tokenizer.advance() # 'let'
var_name = self.tokenizer.get_token()
self.tokenizer.advance()
if self.tokenizer.get_token() == '[':
# 数组赋值
self.emit_push_var(var_name) # 数组基地址
self.tokenizer.advance()
self.compile_expression()
self.emit('add') # 计算目标地址
self.tokenizer.advance() # ']'
self.tokenizer.advance() # '='
self.compile_expression()
self.emit('pop pointer 1')
self.emit('pop that 0') # 这里需要修正
else:
# 普通赋值
self.tokenizer.advance() # '='
self.compile_expression()
self.emit_pop_var(var_name)
self.tokenizer.advance() # ';'if语句
def compile_if(self):
# if '(' expression ')' '{' statements '}' ('else' '{' statements '}')?
label_else = f"IF_ELSE_{self.label_count}"
label_end = f"IF_END_{self.label_count}"
self.label_count += 1
self.tokenizer.advance() # 'if'
self.tokenizer.advance() # '('
self.compile_expression()
self.tokenizer.advance() # ')'
self.emit('not')
self.emit(f'if-goto {label_else}')
self.tokenizer.advance() # '{'
self.compile_statements()
self.tokenizer.advance() # '}'
self.emit(f'goto {label_end}')
self.emit(f'label {label_else}')
if self.tokenizer.get_token() == 'else':
self.tokenizer.advance() # 'else'
self.tokenizer.advance() # '{'
self.compile_statements()
self.tokenizer.advance() # '}'
self.emit(f'label {label_end}')while语句
def compile_while(self):
# while '(' expression ')' '{' statements '}'
label_start = f"WHILE_START_{self.label_count}"
label_end = f"WHILE_END_{self.label_count}"
self.label_count += 1
self.emit(f'label {label_start}')
self.tokenizer.advance() # 'while'
self.tokenizer.advance() # '('
self.compile_expression()
self.tokenizer.advance() # ')'
self.emit('not')
self.emit(f'if-goto {label_end}')
self.tokenizer.advance() # '{'
self.compile_statements()
self.tokenizer.advance() # '}'
self.emit(f'goto {label_start}')
self.emit(f'label {label_end}')do语句
def compile_do(self):
# do subroutineCall ';'
self.tokenizer.advance() # 'do'
self.compile_subroutine_call()
self.emit('pop temp 0') # 丢弃返回值
self.tokenizer.advance() # ';'return语句
def compile_return(self):
# return expression? ';'
self.tokenizer.advance() # 'return'
if self.tokenizer.get_token() != ';':
self.compile_expression()
else:
# void函数返回this或0
if self.current_subroutine_type == 'constructor':
self.emit('push pointer 0')
else:
self.emit('push constant 0')
self.emit('return')
self.tokenizer.advance() # ';'10.7 子程序编译
子程序(构造函数、函数、方法)的编译需要特殊处理。
构造函数
def compile_constructor(self):
# constructor ClassName new(...)
self.tokenizer.advance() # 'constructor'
class_name = self.class_name
self.tokenizer.advance() # 'new'
self.tokenizer.advance() # '('
self.compile_parameter_list()
self.tokenizer.advance() # ')'
self.emit(f'function {class_name}.new {n_locals}')
# 分配内存
n_fields = self.symbol_table.var_count('field')
self.emit(f'push constant {n_fields}')
self.emit('call Memory.alloc 1')
self.emit('pop pointer 0')
self.tokenizer.advance() # '{'
self.compile_subroutine_body()
self.tokenizer.advance() # '}'方法
def compile_method(self):
# method returnType methodName(...)
self.tokenizer.advance() # 'method'
self.tokenizer.advance() # methodName
self.tokenizer.advance() # '('
self.compile_parameter_list()
self.tokenizer.advance() # ')'
self.emit(f'function {class_name}.{method_name} {n_locals}')
# 设置this指针
self.emit('push argument 0')
self.emit('pop pointer 0')
self.tokenizer.advance() # '{'
self.compile_subroutine_body()
self.tokenizer.advance() # '}'函数
def compile_function(self):
# function returnType functionName(...)
self.tokenizer.advance() # 'function'
self.tokenizer.advance() # functionName
self.tokenizer.advance() # '('
self.compile_parameter_list()
self.tokenizer.advance() # ')'
self.emit(f'function {class_name}.{function_name} {n_locals}')
self.tokenizer.advance() # '{'
self.compile_subroutine_body()
self.tokenizer.advance() # '}'10.8 编译器架构
完整的Jack编译器架构:
JackCompiler
├── JackTokenizer (词法分析)
├── CompilationEngine (语法分析+代码生成)
│ ├── SymbolTable (符号表)
│ └── VMWriter (VM代码输出)
└── JackAnalyzer (可选的XML输出)主程序流程
def compile(jack_file, vm_file):
tokenizer = JackTokenizer(jack_file)
vm_writer = VMWriter(vm_file)
engine = CompilationEngine(tokenizer, vm_writer)
engine.compile_class()
vm_writer.close()重要知识点
知识点1:文法与解析器
文法定义了语言的结构,解析器根据文法识别程序结构。常见的解析方法:
- 递归下降:自顶向下,直观易懂
- LL解析:自顶向下,使用预测表
- LR解析:自底向上,功能强大
知识点2:错误恢复
编译器需要能够处理源代码中的错误:
- 恐慌模式:跳过错误部分,寻找同步点
- 短语级恢复:进行局部修正
- 错误产生式:使用特殊的文法规则
知识点3:中间表示
中间表示(IR)是编译器的内部表示:
- 抽象语法树(AST):树形结构
- 三地址码:类似汇编的线性表示
- 字节码:紧凑的二进制表示
知识点4:属性文法
属性文法扩展了上下文无关文法:
- 为每个文法符号关联属性
- 通过语义规则计算属性值
- 用于类型检查和代码生成
知识点5:编译器优化
编译器优化提高生成代码的质量:
- 常量折叠:在编译时计算常量表达式
- 死代码消除:删除不可达代码
- 循环优化:优化循环结构
- 内联展开:将函数调用替换为函数体
常见误区
误区1:认为编译器很神秘
实际上,编译器的基本原理并不复杂。词法分析和语法分析都有成熟的算法和工具。
误区2:忽视错误处理
编译器必须能够处理错误输入。良好的错误处理机制对于用户体验至关重要。
误区3:不理解符号表的作用
符号表是编译器的核心数据结构。正确管理符号表对于类型检查和代码生成至关重要。
误区4:认为递归下降效率低
虽然递归下降可能不是最快的解析方法,但它简单直观,对于大多数应用足够高效。
误区5:过度追求完美
编译器是一个复杂的系统。在实践中,需要在功能、性能和开发时间之间找到平衡。
实践应用
实践1:实现词法分析器
class JackTokenizer:
KEYWORDS = {'class', 'constructor', 'function', 'method', 'field',
'static', 'var', 'int', 'char', 'boolean', 'void',
'true', 'false', 'null', 'this', 'let', 'do', 'if',
'else', 'while', 'return'}
SYMBOLS = {'{', '}', '(', ')', '[', ']', '.', ',', ';',
'+', '-', '*', '/', '&', '|', '~', '<', '>', '='}
def __init__(self, input_file):
with open(input_file) as f:
self.content = f.read()
self.pos = 0
self.current_token = None
self.token_type = None
def has_more_tokens(self):
self.skip_whitespace_and_comments()
return self.pos < len(self.content)
def advance(self):
self.skip_whitespace_and_comments()
if self.pos >= len(self.content):
return
char = self.content[self.pos]
if char.isdigit():
self.read_int_constant()
elif char == '"':
self.read_string_constant()
elif char.isalpha() or char == '_':
self.read_identifier_or_keyword()
elif char in self.SYMBOLS:
self.current_token = char
self.token_type = 'SYMBOL'
self.pos += 1
else:
raise SyntaxError(f"Unexpected character: {char}")
def skip_whitespace_and_comments(self):
while self.pos < len(self.content):
if self.content[self.pos].isspace():
self.pos += 1
elif self.content[self.pos:self.pos+2] == '//':
while self.pos < len(self.content) and self.content[self.pos] != '\n':
self.pos += 1
elif self.content[self.pos:self.pos+2] == '/*':
self.pos += 2
while self.pos < len(self.content) - 1:
if self.content[self.pos:self.pos+2] == '*/':
self.pos += 2
break
self.pos += 1
else:
break
def read_int_constant(self):
start = self.pos
while self.pos < len(self.content) and self.content[self.pos].isdigit():
self.pos += 1
self.current_token = self.content[start:self.pos]
self.token_type = 'INT_CONST'
def read_string_constant(self):
self.pos += 1 # skip opening quote
start = self.pos
while self.pos < len(self.content) and self.content[self.pos] != '"':
self.pos += 1
self.current_token = self.content[start:self.pos]
self.token_type = 'STRING_CONST'
self.pos += 1 # skip closing quote
def read_identifier_or_keyword(self):
start = self.pos
while self.pos < len(self.content) and (self.content[self.pos].isalnum() or self.content[self.pos] == '_'):
self.pos += 1
token = self.content[start:self.pos]
if token in self.KEYWORDS:
self.current_token = token
self.token_type = 'KEYWORD'
else:
self.current_token = token
self.token_type = 'IDENTIFIER'实践2:实现符号表
class SymbolTable:
def __init__(self):
self.class_scope = {}
self.subroutine_scope = {}
self.counts = {'static': 0, 'field': 0, 'var': 0, 'arg': 0}
def start_subroutine(self):
self.subroutine_scope = {}
self.counts['var'] = 0
self.counts['arg'] = 0
def define(self, name, type, kind):
index = self.counts[kind]
if kind in ['static', 'field']:
self.class_scope[name] = (type, kind, index)
else:
self.subroutine_scope[name] = (type, kind, index)
self.counts[kind] += 1
def var_count(self, kind):
return self.counts[kind]
def type_of(self, name):
if name in self.subroutine_scope:
return self.subroutine_scope[name][0]
elif name in self.class_scope:
return self.class_scope[name][0]
return None
def kind_of(self, name):
if name in self.subroutine_scope:
return self.subroutine_scope[name][1]
elif name in self.class_scope:
return self.class_scope[name][1]
return None
def index_of(self, name):
if name in self.subroutine_scope:
return self.subroutine_scope[name][2]
elif name in self.class_scope:
return self.class_scope[name][2]
return None实践3:实现VM代码输出
class VMWriter:
def __init__(self, output_file):
self.output = open(output_file, 'w')
def write_push(self, segment, index):
self.output.write(f'push {segment} {index}\n')
def write_pop(self, segment, index):
self.output.write(f'pop {segment} {index}\n')
def write_arithmetic(self, command):
self.output.write(f'{command}\n')
def write_label(self, label):
self.output.write(f'label {label}\n')
def write_goto(self, label):
self.output.write(f'goto {label}\n')
def write_if(self, label):
self.output.write(f'if-goto {label}\n')
def write_call(self, name, n_args):
self.output.write(f'call {name} {n_args}\n')
def write_function(self, name, n_locals):
self.output.write(f'function {name} {n_locals}\n')
def write_return(self):
self.output.write('return\n')
def close(self):
self.output.close()实践4:实现表达式编译
def compile_expression(self):
self.compile_term()
while self.tokenizer.token_type == 'SYMBOL' and \
self.tokenizer.current_token in '+-*/&|<>=':
op = self.tokenizer.current_token
self.tokenizer.advance()
self.compile_term()
op_map = {
'+': 'add', '-': 'sub', '&': 'and', '|': 'or',
'<': 'lt', '>': 'gt', '=': 'eq'
}
if op in op_map:
self.vm_writer.write_arithmetic(op_map[op])
elif op == '*':
self.vm_writer.write_call('Math.multiply', 2)
elif op == '/':
self.vm_writer.write_call('Math.divide', 2)实践5:测试编译器
使用提供的测试程序测试编译器:
// Test.jack
class Main {
function void main() {
var int x;
let x = 5 + 3;
do Output.printInt(x);
return;
}
}预期VM输出:
function Main.main 1
push constant 5
push constant 3
add
pop local 0
push local 0
call Output.printInt 1
pop temp 0
push constant 0
return本章小结
本章我们构建了Jack编译器的前端,将Jack源代码转换为VM代码。
核心要点回顾
词法分析:将字符流转换为token流,识别关键字、符号、标识符、常量。
语法分析:使用递归下降方法,根据文法规则解析程序结构。
符号表:存储标识符信息,支持作用域管理。
表达式编译:将中缀表达式转换为后缀表示,生成VM代码。
语句编译:处理let、if、while、do、return等语句。
子程序编译:处理构造函数、方法和函数的编译。
关键技能掌握
- 理解编译器的工作原理
- 掌握词法分析和语法分析的方法
- 能够实现符号表
- 能够生成VM代码
- 理解表达式和语句的编译过程
与后续章节的联系
本章构建的编译器前端是后续章节的基础:
- 第十一章将完善编译器,处理更复杂的语言特性
- 第十二章将实现Jack标准库,为编译后的程序提供运行时支持
学习建议
理解原理:深入理解编译器的工作原理,而不仅仅是实现
动手实践:亲手实现编译器的各个组件
测试验证:使用各种测试程序验证编译器的正确性
循序渐进:从简单特性开始,逐步增加复杂度
通过本章的学习,你已经掌握了编译器前端的核心知识。编译器是连接人类思维和机器执行的桥梁,理解其工作原理对于理解编程语言和计算本质至关重要。