张东昌的zcc编译器
设计并实现一款基本的c语言编译器。从词法分析开始,逐步完成c语言文法的设计,文法分析,语法制导翻译,生成中间代码并最终生成可运行的汇编代码。并且在过程中完成符号表的管理和错误管理。
采用c++编写,使用makefile对项目进行管理,开发环境为ubuntu。
项目的主要结构如下:
其中,源码的结构如下(源码的头文件在include下对应的目录下
对于资源文件中
词法分析,顾名思义就是对源程序进行符合相应语言词法规则的单词进行分析和切分。对于c语言的词法分析器,其输入是c语言的源代码,其输出是一个个连续的token,而token是这样的二元组:(A, B),其中A表示该token所属的类型,比如是id(变量、函数名等)或者是value(如整数,浮点数、char等),或者是其它的c语言的关键字、操作符、分隔符号等。而B代表是该token附带属性,如如果是id,则B是该id在符号表中的入口地址,如果是value,那么B则是该value的具体的值。
为了方便,将每一个关键字、操作符、分隔符等符号进行编码,(可使用宏定义)如if 为1, else为2等。
对于词法分析过程,主要使用的是有穷状态自动机进行匹配处理,从开始状态出发,通过识别不同的字符进入相应的状态,从而实现分析过程。在这其中需要注意的是,当遇到id时,需要将其插入到符号表中进行管理,方便后续的翻译。
词法分析可以识别c语言程序的单词、关键字等拼写错误。对于此时,如果遇到了错误(读入当前字符但是无法和任何状态进行匹配)该如何处理:典型的处理方法是抛弃读入的字符直到读入可以正确匹配的字符后正常进行匹配。在这过程可能涉及到错误报告等内容。
语法分析是根据已有的文法,判断读入的token串是否是该文法的一个句子。语法分析的方法主要包括自顶向下的语法分析和自顶向下的分析两种。
自顶向下的语法分析是从文法的开始符号出发,一步步推导出符合当前符号的文法,其主要包括递归下降法和LL1分析方法,其中前者是将文法的每一个非终结符号都写成一个处理子程序,通过递归调用最终实现语法的分析,好处是准确性高,但是缺点是随着文法的复杂程度提高代码量也随之升高,同事其对文法也有要求,必须满足LL1文法。而后者需要求出first集合和follow集合,并且据此得出LL1的预测分析表,并根据此表,利用堆栈实现词法分析。以上的处理方法都需要判断文法,如果文法不满足LL1文法的条件,则其复杂度超过了处理能力。
自底向上的语法是从输入的token开始,利用goto表和action表、分析栈,通过移入和规约,一步步得到文法的开始符号的分析方法。其主要包括算符优先算法,LLR(0),SLR(1),LR(1)和LALR(1)等几种分析方法。算符优先算法根据符号的优先级决定移入和规约。对于LR(0)其移入和规约过程中不会向前看任何信息,直接根据文法规则执行动作,这就使其无法处理移入规约等冲突,其分析能力甚至无法分析算数表达式。而SLR(1)则是在规约时会向前看follow集合来决定是否执行规约动作,因此其分析能力超过了LR(0),但是由于其仅仅在规约过程中使用了follow集合,其仍然后产生冲突。而LR(1)则对每一个文法单元都含有一个展望符,执行任何动作都需要对展望符进行判断。LR(1)分析能力最强,但是由于其具有许多‘心’相同而展望符不同的状态,因此其分析的代价要更高一些。而LALR(1)则是合并了LR(1)中‘心’相同的并且合并不会产生冲突的状态以此来降低复杂度,但是合并状态后可能会造成冲突,因此其分析能力要低于LR(1)。
我使用的方法(注:这里用到的算法可以到书中查阅,这里只会给出难点重点)
我使用的是LR(1)进行语法分析,因为其分析能力最强,当然难度也更大一些,其主要步骤如下:
【1】构造c语言文法
从c程序整体出发,逐步增加c语言语法,其中的几个要点如下:
如int a, b, c,d = 1…;这样的的句型该如何构造文法,我采用了这样的方法:
value_declare_define_list -> ID
value_declare_define_list -> value_declare_define_list , ID
value_declare_define_list -> array
value_declare_define_list -> value_declare_define_list , array
value_declare_define_list -> ID = expression
value_declare_define_list -> value_declare_define_list , ID = expression
可以看到ID可以规约为value_declare_define_list,同时value_declare_define_list,ID也可以规约为value_declare_define_list,这样在自底向上移入规约过程中就可以处理这种不定数目的重复单元的语句。
我们的需要的是含有空产生式的文法,因为在后期语法制导翻译的过程中需要使用这样的文法符号用于标记回填动作。
在后期进行翻译过程中,对大部分的产生式,在执行规约动作的同时会采取相应的翻译动作,因此,文法应该具备显示标记该执行什么动作的功能,我采用了如下的方法:
value_declare_define_list -> ID = expression $action action_value_define
如上述的文法,在处理文法的同时,读到 $action 标记后,则下一次读入的字符串是该产生式的处理标记。
如开始符号为S,则在文法中需要加入这样一个文法
S`->S
加入这一个文法是因为后面的LR(1)语法分析过程中dfa的状态转移需要一个状态来标记文法识别结束。
【2】求first集合
在构造LR(1)文法分析表之前,我们的程序需要能够求得特定串的first集合的能力,因为LR(1)在进行引入规约的每一个语法单元都有一个展望符号,而这个展望符号就是根据小圆点的位置后的串求得的。其中求串的first集合依赖于求符号的first集合,具体算法在这里不再详细讲解了。
【3】构造dfa
LR的dfa就是从开始状态出发,当读入不同的符号会进入不同的状态,其中,读入的符号包括终结符和非终结符号,不同的状态是每一个LR(1)的item,每一个item具有这样的结构:
[A->a.Bc, m],其中A->aBc是文法中的产生式,小圆点加在B之前说明已经读入a,等待读入B,而m是该item的展望符号。
在构造dfa过程中,有两个关键步骤:
其一是求文法的闭包,这个完整构造任意dfa的关键。其具体步骤如下:对于开始状态,将[S`->.S,#]加入该状态,对于任意一个加在小圆点后面的非终结符号,将其加入闭包,如S->A,则将[S->.A,?]加入状态中,而?需要根据first集合求得。
其二是go函数。所谓go函数就是当读入某一个符号,从状态A进入状态B的过程。如上述的文法,初始状态读入S,则进入新的状态2,该状态中[S`->S.],然后根据闭包算法算出该状态的闭包即可。
使用上述两个方法则可以成功构造lr的dfa。
【4】根据dfa构造goto表和action表
即根据小圆点位置和小圆点后面的符号来决定是否移入还是规约或者ac,具体算法略。
【5】编写主控程序
主控程序需要使用一个状态栈,一个符号栈,根据goto表和action表,分析从词法分析器中读入的token,来决定是否规约还是移入。其中的一个难点是如何处理空产生式,我的处理方法如下:
当语法分析中遇到错误的时候,尝试用当前符号和空字符来重新查询aciton表获取action,若仍然出错,则报错,否则则认为当前需要插入一个空状态,从而纠正分析状态。具体的程序如下:
语法制导翻译就是在进行语义分析的同事进行语法分析。当使用某一个产生式进行规约时,则使用挂钩程序调用该产生式所对应的语义动作函数执行相应的动作。而其中的翻译模式又分为S属性翻译和L属性翻译
如条件语句这种需要的进行跳转的语句进行翻译的时候,在翻译的当时我们无法获知应该跳转到哪一个地方,此时有两种方法,其一是扫描第二遍来填写信息,因为需要扫描两遍,因此这种方法的效率比较低。其二就是回填技术,回填就是在遇到jmp X时,此时不填写X,而在后续相应的位置对其进行填写,此时就要对文法进行特殊的修改,加入空的非终结符来指示在哪一步需要记录位置,哪一步需要填写位置,哪一步需要合并等,如我对for语句修改后的回填文法如下
for_statement-> for F1 ( expression_assignment ; bool_expression ; F2 expression_assignment ) F3 statement $action action_FOR
for_statement -> for F1 ( expression_assignment ; bool_expression ; F2 expression_unary ) F3 statement $action action_FOR
F1 -> null $action action_F1_FLAG
F2 -> null $action action_F2_FLAG
F3 -> null $action action_F3_FLAG
在解决回填问题中,书中使用了两个链表,在回填结束后将两个链表合并到一起。我采取了另外一种方法,除了for循环之外的可以使用,方法如下:引入两个回填栈,其一是when false to jump,其二是when true to jump,另引入一个回填次数的堆栈。每次将false跳转的压入第一个堆栈中,true跳转压入第二个堆栈中。每一次进入一个新的回填,都需要想回填次数堆栈push一个0,每次向回填堆栈压入需要回填的地址时,回填次数堆栈需要增加1,在开始回填时根据回填堆栈top数据决定回填次数。回填结束将其pop。
对于for循环的回填,其具有特殊性,如下面的语句
for (i = 0; i < 10; ++i) {
do something;
}
其翻译的三地址代码应该是这样的
0. i = 0
1. goto 3
2. i = i + 1
3. if i < 10 goto 5
4. goto 9
5. //do
6. // some
7. //things
8. goto 2
如果直接翻译直接回填直接存储的话,无法解决第一次不++i,而第二次以及以后++i这个翻译问题。因此可以创建一个临时的三地址码序列(或者书上的链表),由此可以实现不按照源代码顺序的三地址码的翻译。对于上述的代码,可以首先将i<10翻译到临时三地址序列中,然后翻译goto3, i = i + 1,然后将临时三地址码序列合并到原来主码序列中即可。
符号表是管理编译过程中所有变量,过程,数组等信息的表,其以id为组织依据,并附加了相应的属性值,其在编译过程起到了至关重要的作用。需要注意的是,符号表中的插入动作要远少于查询动作,因此符号表的查询效率很重要,因此一般可使用hash技术来解决各种id的搜索问题。
在实现符号表中我主要利用了vector,对于变量,过程,数组我是用了不同的vector存储他们的信息,而对于变量、过程、数组又各自含有自己的属性结构来存储相应的属性信息。
实现了词法分析
LR(1)文法分析,并可以识别大部分c语言文法。
在语义分析部分,使用回填技术,完成了对声明语句、赋值语句、算数表达式、布尔表达式、顺序语句、条件判断语句、while循环语句和for语句的翻译,同时也可以对简单数组进行处理,并据此生成3地址码。
汇编代码生成部分,只能对语句进行简单的汇编指令的翻译。
对于如下源程序:
因为太长,放在最后的附录。
首先在课程中我学习了编译技术的思想,系统的了解了编译系统的具体结构和编译流程,以及编译过程中的优化技术。更具体来说,我掌握了自底向上和自顶像下的文法翻译过程,掌握的递归下降、LL1和LR(1)等具体的分析过程和实现方式,以及语法制导的翻译过程。
其次,在实践中意识到了编程之前的良好的结构设计可以有效的提高编码效率,比如我在实现文法分析和语义分析的时候,总需要更改前一部分的代码,如果之前可以设计好了就会少了很多麻烦。同时代码应该尽量封装与细节无关的接口,以及预留后期更改的余地。
并且,在实验过程中,多次遇到困难,如主控程序中对空状态处理,回填中的某些问题等,我都独立的思考并且最终解决了这些问题,这让我感到很兴奋。
另外,此次实验中的大量代码实践也提高了我对大量代码的组织和掌控能力,同时也在一定程度上提高了我的编程能力。
要合理分配时间,我的语法分析部分很早完成了,但是中间有一段时间耽搁了,导致最后一周赶着写实验,最终生成汇编代码部分没有成功完成,这一点很遗憾。
虽然都说编程语言是工具,重要的是算法思想等蕴含在背后的东西,但是通过这次实验我体会到了语言虽然是工具但是能够熟练运用才能谈其他的东西。
其中前面的数字代表是源代码中所在的行数,后面是使用该产生式进行规约。
1 value_type -> int
1 ID -> identity
3 value_type -> int
3 ID -> identity
3 VALUE ->
3 _F -> VALUE
3 _T -> _F
3 expression -> _T
3 value_declare_define_list -> ID = expression
4 value_declare_define -> value_type value_declare_define_list ;
4 content_alone -> value_declare_define
4 content -> content_alone
4 value_type -> int
4 ID -> identity
4 VALUE ->
4 _F -> VALUE
4 _T -> _F
4 expression -> _T
4 value_declare_define_list -> ID = expression
5 value_declare_define -> value_type value_declare_define_list ;
5 content_many -> content value_declare_define
5 content -> content_many
5 value_type -> int
5 ID -> identity
5 VALUE ->
5 _F -> VALUE
5 _T -> _F
5 expression -> _T
5 value_declare_define_list -> ID = expression
6 value_declare_define -> value_type value_declare_define_list ;
6 content_many -> content value_declare_define
6 content -> content_many
6 value_type -> int
6 ID -> identity
6 VALUE ->
6 _F -> VALUE
6 _T -> _F
6 expression -> _T
6 value_declare_define_list -> ID = expression
7 value_declare_define -> value_type value_declare_define_list ;
7 content_many -> content value_declare_define
7 content -> content_many
7 ID -> identity
7 ID -> identity
7 _F -> ID
7 _T -> _F
7 expression -> _T
7 ID -> identity
7 _F -> ID
7 _T -> _F
7 expression -> expression + _T
7 expression_assignment -> ID = expression
8 content_many -> content expression_assignment ;
8 content -> content_many
8 ID -> identity
8 ID -> identity
8 _F -> ID
8 _T -> _F
8 expression -> _T
8 ID -> identity
8 _F -> ID
8 _T -> _F
8 expression -> expression + _T
8 expression_assignment -> ID = expression
9 content_many -> content expression_assignment ;
9 content -> content_many
9 ID -> identity
9 ID -> identity
9 _F -> ID
9 _T -> _F
9 expression -> _T
9 ID -> identity
9 _F -> ID
9 _T -> _F
9 expression -> expression - _T
9 expression_assignment -> ID = expression
10 content_many -> content expression_assignment ;
10 content -> content_many
10 M1 -> #
10 ID -> identity
10 _F -> ID
10 _T -> _F
10 expression -> _T
10 _C -> expression
10 _B -> _C
10 VALUE ->
10 _F -> VALUE
10 _T -> _F
10 expression -> _T
10 _C -> expression
10 _B -> _B < _C
10 _A -> _B
10 bool_expression -> _A
10 M2 -> #
11 ID -> identity
11 ID -> identity
11 _F -> ID
11 _T -> _F
11 expression -> _T
11 VALUE ->
11 _F -> VALUE
11 _T -> _F
11 expression -> expression + _T
11 expression_assignment -> ID = expression
12 content_alone -> expression_assignment ;
12 content -> content_alone
12 ID -> identity
12 ID -> identity
12 _F -> ID
12 _T -> _F
12 expression -> _T
12 VALUE ->
12 _F -> VALUE
12 _T -> _F
12 expression -> expression + _T
12 expression_assignment -> ID = expression
13 content_many -> content expression_assignment ;
13 content -> content_many
14 statement -> { content }
14 while_statement -> while M1 ( bool_expression ) M2 statement
14 content_many -> content while_statement
14 content -> content_many
14 SS -> #
14 ID -> identity
14 _F -> ID
14 _T -> _F
14 expression -> _T
14 _C -> expression
14 _B -> _C
14 ID -> identity
14 _F -> ID
14 _T -> _F
14 expression -> _T
14 _C -> expression
14 _B -> _B > _C
14 _A -> _B
14 bool_expression -> _A
15 ID -> identity
15 ID -> identity
15 _F -> ID
15 _T -> _F
15 expression -> _T
15 expression_assignment -> ID = expression
16 content_alone -> expression_assignment ;
16 content -> content_alone
16 statement -> { content }
16 IF -> if SS ( bool_expression ) statement
16 SS -> #
16 ID -> identity
16 _F -> ID
16 _T -> _F
16 expression -> _T
16 _C -> expression
16 _B -> _C
16 ID -> identity
16 _F -> ID
16 _T -> _F
16 expression -> _T
16 _C -> expression
16 _B -> _B < _C
16 _A -> _B
16 bool_expression -> _A
17 ID -> identity
17 ID -> identity
17 _F -> ID
17 _T -> _F
17 expression -> _T
17 expression_assignment -> ID = expression
18 content_alone -> expression_assignment ;
18 content -> content_alone
18 statement -> { content }
18 IF -> if SS ( bool_expression ) statement
19 ID -> identity
19 ID -> identity
19 _F -> ID
19 _T -> _F
19 expression -> _T
19 VALUE ->
19 _F -> VALUE
19 _T -> _F
19 expression -> expression + _T
19 expression_assignment -> ID = expression
20 content_alone -> expression_assignment ;
20 content -> content_alone
21 statement -> { content }
21 ELSE -> else statement
21 if_statement -> IF ELSE
21 content_alone -> if_statement
21 statement -> content_alone
21 ELSE -> else statement
21 if_statement -> IF ELSE
21 content_many -> content if_statement
21 content -> content_many
21 F1 -> #
21 ID -> identity
21 VALUE ->
21 _F -> VALUE
21 _T -> _F
21 expression -> _T
21 expression_assignment -> ID = expression
21 ID -> identity
21 _F -> ID
21 _T -> _F
21 expression -> _T
21 _C -> expression
21 _B -> _C
21 VALUE ->
21 _F -> VALUE
21 _T -> _F
21 expression -> _T
21 _C -> expression
21 _B -> _B < _C
21 _A -> _B
21 bool_expression -> _A
21 F2 -> #
21 ID -> identity
21 ID -> identity
21 _F -> ID
21 _T -> _F
21 expression -> _T
21 VALUE ->
21 _F -> VALUE
21 _T -> _F
21 expression -> expression + _T
21 expression_assignment -> ID = expression
21 F3 -> #
22 ID -> identity
22 ID -> identity
22 _F -> ID
22 _T -> _F
22 expression -> _T
22 VALUE ->
22 _F -> VALUE
22 _T -> _F
22 expression -> expression + _T
22 expression_assignment -> ID = expression
23 content_alone -> expression_assignment ;
23 content -> content_alone
24 statement -> { content }
24 for_statement -> for F1 ( expression_assignment ; bool_expression ; F2 expression_assignment ) F3 statement
24 content_many -> content for_statement
24 content -> content_many
24 VALUE ->
25 content_many -> content return VALUE ;
25 content -> content_many
25 function_define -> value_type ID ( ) { content }
25 program -> function_define
FUCK ACCEPT