从零开始构建现代计算机--第六章,01-HACK汇编编译器模块介绍
HACK汇编编译器分成四个模块:
语法分析器(Parser):对输入文件进行语法分析
编码(Code):提供所有汇编命令对应的二进制代码
符号表(Symbol Table):处理符号
主程序:驱动整个编译过程
语法分析器(Parser)
Parser: 将汇编命令分解为其所表达的内存含义(域和符号)。
封装对输入代码的访问操作。功能包括:读取汇编语言命令并对其进行解析:提供“方便访问汇编命令成分(域和符号)”的方案;去掉所有空格和注释。
程序 | 参数 | 返回值 | 功能 |
---|---|---|---|
构造函数/ 初始化函数 | 输入文件/ 输入流 | – | 打开输入文件/输入流,为语法解析作准备 |
hasMoreCommands | – | Boolean | 输入当中还有更多命令吗 |
advance | – | – | 从输入中读取下一条命令,将其当作“当前命令”,仅当hasMoreCommands()为真时,才能调用本程序。最初始的时候,没有“当前命令” |
commandType | – | A_COMMAND, C_COMMAND, L_COMMAND | 返回当前命令的类型: A_COMMAND: 当@Xxx中的Xxx是符号或十进制数字时 C_COMMAND 用于dest=comp;jump L_COMMAND(实际上是伪命令) 当(Xxx)中的Xxx是符号时 |
symbol | – | 字符串 | 返回形如@Xxx或(Xxx)的当前命令的符号或十进制值,仅当commandType()是A_COMMAND或L_COMMAND是才能调用 |
dest | – | 字符串 | 返回当前C-指令的dest助记符(具有8种可能的形式) 仅当commandType()是C_COMMAND时才能调用 |
comp | – | 字符串 | 返回当前C-指令的comp助记符(具有28种可能的形式),仅当commandType()是C_COMMAND时才能调用 |
jump | – | 字符串 | 返回当前C-指令的jump助记符(具有8种可能的形式),仅当commandType()是C_COMMAND时才能调用 |
编码(Code)
将Hack汇编语言助记符翻译成二进制码。
程序 | 参数 | 返回值 | 功能 |
---|---|---|---|
dest | 助记符(字符串) | 3bits | 返回dest助记符的二进制码 |
comp | 助记符(字符串 | 7bits | 返回comp助记符的二进制码 |
jump | 助记符(字符串 | 3bits | 返回jump助记符的二进制码 |
无符号程序的汇编编译器
Parser和Code模块实现较为简单,可以先实现,而符号表的处理较为复杂,可以放到第二阶段再进行。
在第一阶段约定:输入的Prog.asm程序不包含符号,因此:(a)在所有地址命令@Xxx中,Xxx常数是十进制数而不是符号;(b)输入文件不包含标记命令即(Xxx)命令。
实现:
- 打开Prog.hack的输出文件
- 处理汇编指令文件Prog.asm
- 对C指令,将翻译后的指令域的二进制码连接到一个单一的16-位字上,将16-位字写入Prog.hack文件
- 对A-指令@Xxx,将语法分析器返回的二进制常数翻译成对应的二进制表示,然后将得到的16-位字写入Prog.hack文件。
符号表(Symbol Table)
翻译中需要为指令中可能包含的符号确定实际的地址。汇编编译器使用符号表(symbol table)来完成这个任务,符号表用来建立和维持符号与其地址之间的关联。哈希表(hash table)是建立映射的经典数据结构之一。
SymbolTable: 在符号标签(symbolic labels)和数字地址之间建立关联。
程序 | 参数 | 返回值 | 功能 |
---|---|---|---|
Constructor | – | – | 创建空的符号表 |
addEntry | symbol(字符串), address(int) | – | 将(symbol,address)配对加入符号表 |
contains | symbol(字符串) | Boolean | 符号表是否包含了指定的symbol |
GetAddress | symbol(字符串) | int | 返回与symbol关联的地址 |
有符号程序的汇编编译器
汇编程序允许符号在定义之前使用(即goto命令的目的地)。此功能为汇编程序员福音,却使编译器开发变得困难。解决方法之一是,编写“两遍(two-pass)”编译器,从头到尾读取两次代码。
第一遍构建符号表,第二遍用每个符号相关的含义(数字地址)替换该符号,并产生最后的二进制码。
符号表中应该包含并处理Hack语言中的所有三种符号:预定义符号(predefined symbols),标签(labels) 和变量(variables)。
- 初始化 用所有预定义符号和它们预分配的RAM地址对符号进行初始化。
- 第一遍 用数字记录ROM地址–当前命令最终加载的目标地址。每遇到一条指令都自动加1,遇到标签伪指令或注释时不发生变化。
遇到伪指令时,在符号表中加上一个新条目来将Xxx与最终用于存储程序中下一条指令的ROM地址关联起来。在这个阶段程序中所有标记和它们的ROM地址被加入到符号表中。程序的变量放在第二阶段处理。 - 第二遍 对每一行进行语法分析。@Xxx指令中Xxx是符号时,在符号表中查找Xxx。如果在符号表中没有找到该符号,说明它是变量。这时在符号表中添加(Xxx,n),这里n代表下一个可使用的RAM地址。分配的RAM地址是紧邻预定义符号地址之后的连续数字。