一,项目介绍
终于来到了编译器部分的最后一个章节——代码生成阶段。本章的目标就是将Jack语言转化为VM语言,完成Jack编译器的构建。
刚刚接触这章的内容时,会比较难上手,最主要的问题就在于,这章的内容看起来和第十章没有什么关系。刚开始做这个项目时,我就很疑惑,第十章输出的不是一个结构化的xml文件吗?这个文件在第十一章根本不需要输出,那么这章的内容从何开始呢?
的确,这个xml文件是不需要输出的,但是第十章的目的并不单纯是输出这个xml文件,它更重要的目的是为了让我们了解如何对jack程序文件进行语法分析,以完成CompilationEngine的构建。所以,我们需要关注的是CompilationEngine的函数结构,这个函数结构才是第十一章内容的基础。
二,操作步骤
总体而言,作者为我们设计的操作顺序是非常合理的。在此,我再提出几点预备步骤,这些步骤并不是必要的,但是通过这些操作,能够使得整个项目的实现更加流畅。
1,先给命令行加上-x选项,如果命令行中出现-x,则表示输出xml文件和VM文件,不加-x,则表示只输出VM文件。这样子就将两个不同“写入文件流”区分开来。
2,构建符号表模块。存储符号表时,我所用的数据结构是Python语言中的二维列表。这一阶段的任务是把每一个遇到的Identifier都加以标注并且输出相关信息。
3,进入输出VM语言的阶段。首先可以使用内置的JackCompiler将Jack语言转换为VM语言,(Windows上的JackCompiler需要自己设置配置文件才能够使用,具体教程在这儿)从简单的文件开始转换,自己认真分析代码的转换过程。例如,最简单的Seven函数的Jack代码和VM代码分别如下。
class Main {
function void main() {
do Output.printInt(1 + (2 * 3));
return;
}
}
function Main.main 0
push constant 1
push constant 2
push constant 3
call Math.multiply 2
add
call Output.printInt 1
pop temp 0
push constant 0
return
之后你便可以对照二者,分析转换规则了,例如第一句function Main.main 0肯定是在读取完了所有的ClassVarDev,知道了函数名之后才写入的,于是,写入语句必然就是在compileStatements之前。照这个步骤,逐步完善你的编译器。
三,注意点
我的建议是,先回过头去复习VM代码和Jack语言,了解高级代码转化为VM代码的具体过程,你可以通过看图11.6,图7.9来了解其中的逻辑。
在写编译器的过程中,注意点非常多,这一方面,书中11.2节阐述的非常清楚,在此我重申几个比较关键的问题:
1,constructor, method和function参数配置不同,method方法会默认带一个this的参数,需要加以区分。而讨论参数时,VM代码中function xxx n与call function m中的n与m也是不同的,前者指的是函数中的局部变量数(local),后者指的是调用函数时引入的参数(argument)。
2,Function和Method的调用方式不同,Function只需调用类名ClassName.Function就可以使用,但是method需要调用具体的类实例如abc.Method才可以调用,如果方法就在类中的话,也可直接使用method()。
3,数组只可能在两个地方出现,一是term中,用于引用,另外是Let语句的左边,用于数组赋值。要注意的是,这两处调用的VM代码是不同的,需要加以区分。
4,constructor是构造函数,在编译时,需要先分析Class中有多少个field变量,然后使用Memory.alloc(size)来给他们分配空间,最后再将其基地址存入this指针中。
上述这些注意点的具体代码都可以通过JackCompiler编译现有文件而得到,我就不再赘述了。
最后,debug的过程是痛苦的,也是无可避免的。如果代码出现问题,可以比对JackCompiler的输出文件与你的编译器输出文件的不同。这个过程能够是你对编译有更深的理解。
JackCompiler.py
#!/usr/bin/python
import CompilationEngine
import SymbolTable
import sys,os
'''
The command line of this module is : JackCompiler.py (-x) sourcename
The first option is -x, which decides whether to run xmlWriter() and to output the constructive xml file\
putting forward by CompilationEngine.
'''
option=sys.argv[1]
if option == '-x':
filename=sys.argv[2]
else:
filename=sys.argv[1]
#clear all the // /* ... notes, create a new file to save the result
readfile = open(filename,'r')
copyfile = open('copyfile','w')
line=readfile.readline()
while line:
while line == '\n' or line.startswith('//'):
line=readfile.readline()
if '//' in line:
line=line[:line.find('//')]
if '/*' in line:
aline=line[:line.find('/*')]
while line.find('*/')<0:
line=readfile.readline()
bline=line[line.find('*/')+2:]
line=aline+bline
copyfile.write(line)
line=readfile.readline()
copyfile.close()
readfile.close()
#Main Function
readCopyFile=open('copyfile','r')
writeXmlFile=open(filename.strip('.jack')+'.xml','w')
writeVmFile=open(filename.strip('.jack')+'.vm','w')
outputCompile=CompilationEngine.Compile(readCopyFile,writeXmlFile,writeVmFile)
outputCompile.compileClass()
readCopyFile.close()
writeXmlFile.close()
writeVm