一、系统描述
问题描述
对类 C 语言中 while 循环语句的理解及分析,设计编译器前端中的词法分析、语法分析、语义分析及中间代码生成等编译过程,用简单优先分析法自底向上的分析并通过语法制导翻译的方法将 while 循环语句翻译输出中间代码四元式。
主要任务
通过设计、编制、调试一个 WHILE 循环语句的语法及语义分析程序,加深对语法及语义分析原理的理解,并实现词法分析程序对单词序列的词法检查和分析。
首先写出一个能识别 while 循环语句的文法,通过求出其文法的优先表,观察该文法是否有冲突。如果有,修改文法,解决文法的冲突使它符合简单优先法的要求,然后按照这个文法编写一个集词法分析,语法分析和语义分析为一体的程序,该程序首先可以检查输入语句是否符合词法要求,若符合则继续识别输入的语句是否符合 while 语句的文法,若符合则进行语义分析,输出用四地址代码表示的中间代码。
二、文法及属性文法的描述
属性文法
产生式 | 语义动作 |
---|---|
Aid=E’ | Gen(‘=’, E.addr, ’ ’ , id.lexeme) |
EE’ | E.addr = E’.addr |
ET’ | E.addr = T’.addr |
EE+T’ | E.addr = new Temp(); gen(‘+’,E1.addr,T’.addr,E.addr) |
EE-T’ | E.addr = new Temp(); gen(‘-’,E1.addr,T’.addr,E.addr) |
T’T | T’.addr = T.addr |
TF | T.addr = F.addr |
TT*F | T.addr=new Temp(); gen(‘*’, T1.addr, F.addr, T.addr) |
TT/F | T.addr=new Temp(); gen(‘/’, T1.addr, F.addr, T.addr) |
Fid | F.addr = id.lexeme |
Fn | F.addr = n.num |
F(E’) | F.addr = E’.addr |
While 语句、判断语句的属性文法有两种,一种是将 while 循环、判断语句的 L 属性(具有综合属性和继承属性)的语法制导定义(SDD)改写成语法制导的翻译方案(SDT),新的 SDT 可以和自底向上语法分析器一起完成翻译;另一种为利用拉链回填技术一趟式目标代码生成,利用真假链,将之前的语法制导的翻译方案里的继承属性变为继承属性即可自底向上的完成翻译,两种属性文法如下:
其一:
产生式 | 语义动作 |
---|---|
S while ( { L1 = new(); L2 = new(); C.false = S.next;; C.true=L2; gen(“label”, “ ”, “ ”, L1);};C) { S1.next=L1; gen(“label”. “ ”, “ ”,L2); };S1 { gen(“goto” , “ ”, “ ”, L1} | S while ( { L1 = new(); L2 = new(); C.false = S.next;; C.true=L2; gen(“label”, “ ”, “ ”, L1);};C) { S1.next=L1; gen(“label”. “ ”, “ ”,L2); };S1 { gen(“goto” , “ ”, “ ”, L1} |
Cid>id { if id.lexme > id.lexme goto C.true ;;Goto C.false } / /产生相关四元式 | Cid>id { if id.lexme > id.lexme goto C.true ;;Goto C.false } / /产生相关四元式 |
其二:
产生式 | 语义动作 |
---|---|
S while ( { M1.instr = nextinstr; / /变量 nextinstr 为下一条指令的序号};C) {M2.instr = nextinstr; / /同上 };S1 {backpatch (S1.nextlist, M1.instr); backpatch(C.truelist, M2.instr);; S.nextlist = B.falselist;; gen(“goto” , “ ”, “ ”, M1.instr);} | S while ( { M1.instr = nextinstr; / /变量 nextinstr 为下一条指令的序号};C) {M2.instr = nextinstr; / /同上 };S1 {backpatch (S1.nextlist, M1.instr); backpatch(C.truelist, M2.instr);; S.nextlist = B.falselist;; gen(“goto” , “ ”, “ ”, M1.instr);} |
Cid>id { C.truelist = makelist(nextinstr); ;C.falselist=makelist(nextinstr+1);;Gen(“ if” id.lexme > id.lexme “goto ___(空)“);; Gen(“goto ___(空)”} / /产生相关四元式 | Cid>id { C.truelist = makelist(nextinstr); ;C.falselist=makelist(nextinstr+1);;Gen(“ if” id.lexme > id.lexme “goto ___(空)“);; Gen(“goto ___(空)”} / /产生相关四元式 |
三、语法分析方法描述及语法分析表设计
简单优先法
简单优先分析法的基本思想是对一个文法按一定原则求出该文法所有符号(终结符和非终结符)之间的优先关系,按照这种关系确定规约过程中的句柄,它的规约过程实际上是一种规范规约(最左规约),当一个文法无二义性是,那么它对一个句子的规范规约是唯一的,每次规约时按照当前句型的句柄,当句柄出现在栈顶符号串中时,用句柄规约,直到输入串只剩结束符,文法符号栈中只剩开始符,则语法分析正确。
若要用简单优先法,需按以下规则先构造出文法的优先关系矩阵:
= Y 当且仅当文法 G 中存在产生式规则 A…XY…。
< Y 当且仅当文法 G 中存在产生式规则 A…XB…,且 B+ Y …。
> Y当且仅当文法G中存在产生式规则A…BD…,且B+ …X和D*Y…。
根据规则求出的优先关系矩阵如下:
S | w | ( | C | ) | A | E’ | E | T’ | T | F | + | - | * | / | id | N | > | < | >= | <= | = | # | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
S | > | ||||||||||||||||||||||
w | = | > | |||||||||||||||||||||
( | < | = | = | < | < | < | < | < | > | ||||||||||||||
C | = | > | |||||||||||||||||||||
) | > | = | > | > | > | > | < | > | |||||||||||||||
A | > | ||||||||||||||||||||||
E’ | = | > | |||||||||||||||||||||
E | > | = | = | > | |||||||||||||||||||
T’ | > | > | > | > | |||||||||||||||||||
T | > | > | > | = | = | > | |||||||||||||||||
F | > | > | > | > | > | > | |||||||||||||||||
+ | < | = | < | < | < | < | > | ||||||||||||||||
- | < | = | < | < | < | < | > | ||||||||||||||||
* | < | < | < | > | |||||||||||||||||||
/ | < | < | < | > | |||||||||||||||||||
Id | > | > | > | > | > | = | = | = | = | = | > | ||||||||||||
N | > | > | > | > | > | > | |||||||||||||||||
> | = | > | |||||||||||||||||||||
< | = | > | |||||||||||||||||||||
>= | = | > | |||||||||||||||||||||
<= | = | > | |||||||||||||||||||||
= | < | = | < | < | < | < | < | < | > | ||||||||||||||
# | < | < | < | < | < | < | < | < | < | < | < | < | < | < | < | < | < | < | < | < | < | < | N |
简单优先文法的定义
若一个文法是简单优先文法,必须满足以下条件:
在文法符号集 V 中,任意两个符号之间最多只有一种优先关系成立。
在文法中,任意两个产生式没有相同的右部。
要想用简单优先文法进行语法分析,若文法不满足第一个条件,可以对文法进行修改,使其满足条件,不出现优先关系冲突即可;对于第二个条件,在本次实验中,限制了向文法添加两个空产生式,将现在的语法制导的翻译方案修改为后缀翻译方案(仅在规约时做语义动作即可),从而不得不在产生式中间添加语义动作,并在相关符号出现在分析栈栈顶时,进行相关的动作。
简单优先分析法的操作步骤
首先根据已知优先文法构造相应的优先关系矩阵,并将文法的产生式保存,设置符号栈,算法步骤如下:
- 将输入符号串 a2…an#依次逐个存入符号栈 S 中,直到遇到栈顶符号 ai 的优先性 > 下一个待输入符号 aj 为止。
- 栈顶当前符号 ai 为句柄尾,由此向左在栈中找句柄的头符号 ak,即找到 ak-1<ak 为止。
- 由句柄 ak… ai 在文法的产生式中查找右部为 ak…ai 的产生式,若找到则用相应左部代替句柄,若找不到则为出错,这时可断定输入串不是该文法的句子。
- 重复上述(1)、(2)、(3)步骤直到规约完输入符号串,栈中只剩文法的开始符号为止。
四、中间代码
中间代码形式的描述
中间代码通过四元式给出。一个四元式有四个字段,分别称为 op、arg1、arg2、result.字段 op 包含一个运算符的内部编码。运算对象和运算结果有时指用户自己定义的变量,有时指编译器生成的临时变量。
四元式表达中有一些特例:
- 形如 x=minus y 的单目运算符指令和赋值指令 x=y 不使用 arg2。对于像 x=y 这样的赋值语句,op 是=,而对大部分其他运算而言,赋值运算符是隐含表示的。
- 条件或非条件转移指令将目标表号放入 result 字段。
- 像 param 这样的运算既不使用 arg2,也不使用 result。
- 中间代码序列的结构设计
通过词法分析及语法分析之后,在增量翻译的过程中,将相关动作产生的结果放到指令数组中,然后对指令进行分析。程序最后的应产生类似如下代码:
while( a<b) a=(a+b)/c
<, a, b, t0)
if, t0, goto,103)
else,,, 106)
+, a, b, t1)
/, t1, c, t2)
=, t2,, a)
五、编译器前端的概要设计及部分流程图
简要分析
通过程序,在 lexer.txt 文本中输入 while 循环语句。对文本进行扫描,通过词法分析类中的函数 lexer()对输入的 while 语句进行词法分析,这里词法分析主要识别 while 关键字,整数,变量,+,-,*,/等运算符号,每个都有相关的标志号,并在定义的数据结构中保存标识符、关键字、运算符和数字等。
将已计算好的文法符号优先关系矩阵放至程序中(二维数组形式)。
对已进行词法分析的 while 语句得到的数据结构传递给语法分析程序,进行语法分析过程。这里用到的语法分析方法是简单优先法,该语法分析过程是规范规约的过程。
从词法分析过程传递过来的数据结构中逐个读取单词,并根据自福建的优先关系确定句柄并规约,在规约的过程中保存规约得到的指令数组以便进行中间代码生成,在规约的过程中如果出现非法的字符或规约不能接受的字符,则保存相应的错误并中断程序的运行。
- 将语法分析所得到的指令数组通过四元式输出函数 gen()将 while 循环语句表示为四元式的形式,输出到 parserout.txt 文件中。
- 概要设计
- 概要流程
编译器前端由词法分析器,语法分析器,中间代码生成器组成,整个翻译过程为词法分析器从源程序文件中读取源文件进行词法分析,接着将词法分析结果词法单元作为语法分析器的输入进行语法分析,语法分析通过一栈一流,实现对词法单元序列的分析后,将分析过程中产生的四元式指令按一定格式输出到文件中。
概要流程图
词法分析
词法分析器读入组成源程序的字符流,并将它们组织成有意义的词素序列。对于每个词素,词法分析器产生如下形式的词法单元:
<token-name,attribute-value>
词法分析将整个源程序组织成若干个以上形式的词法单元结构传送给语法分析器。本次实验中用一个结构体存储以上结构,并将所有结构体存入 vector 数组保存并传递给语法分析程序。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8SLEyhO0-1654590625948)(https://www.writebug.com/myres/static/uploads/2022/6/7/539efc61e98ce09d4451c9ea0a6bb9e2.writebug)]
词法流程图
语法分析
语法分析要处理的是词法分析的输出即词法单元序列。该部分主要用到一个符号栈,用来保存语法分析过程中处理的符号等;还有一个输入流,用来保存词法单元的输入流序列,后者可用之前 vector 词法单元数组来代替。本次课程设计语法分析所用的是简单优先分析法,其主要分析流程如以下流程图所示:
语法分析流程图
语义分析
在此次程序中语义分析主要是将语义分析结果转化成四元式表示的中间代码的过程。所用到的方法是语法制导的翻译方案,即在文法的产生体中嵌入了程序片段的一个上下文无关文法。这些程序片即为语义动作。
在语法分析过程中,随着分析的步步进展,根据每个产生式的语义动作进行翻译。属性文法每个符号有属性,所以每个符号入栈时,必须连属性一起入栈(本程序中将属性和符号构造成一个结构体,这样仅需一个栈即可),这样栈符号就由文法符号及存放该符号属性域所组成。由于属性类型不同,属性域存放的内容就要根据属性的类型来定。有的可直接存放属性值,也有的存放指向该属性值的指针。
例如当规约 left+right 时用到产生式 E=E+T’产生式的动作 { E.addr = new Temp(); gen(‘+’,E1.addr,T’.addr,E.addr)},将符号栈中的‘left’,‘+’,‘right’,弹出并存入符号数组中,通过将符号数组传递给相应产生式左部命名的动作处理函数,相关函数就会根据符号数组中符号的属性产生相应四元式,并存入指令数组中。当符号栈中仅剩开始符号时,语法分析结束,语义分析最后通过拉链回填技术对四元式指令数组进行完善,最后将指令数组中的指令按一定格式顺序输出即可。
六、源程序执行结果(测试方法和测试结果)
执行结果
软件测试方法
从测试是否针对系统的内部结构和具体实现算法的角度来看,可分为白盒测试和黑盒测试;黑盒测试也称功能测试或数据驱动测试,它是在已知产品所应具有的功能,通过测试来检测每个功能是否都能正常使用,在测试时,把程序看作一个不能打开的黑盆子,在完全不考虑程序内部结构和内部特性的情况下,测试者在程序接口进行测试,它只检查程序功能是否按照需求规格说明书的规定正常使用,程序是否能适当地接收输入数锯而产生正确的输出信息,并且保持外部信息(如数据库或文件)的完整性。白盒测试也称结构测试或逻辑驱动测试,它是知道产品内部工作过程,可通过测试来检测产品内部动作是否按照规格说明书的规定正常进行,按照程序内部的结构测试程序,检验程序中的每条通路是否都有能按预定要求正确工作。
- 鉴于本次设计特点在本次程序中我们主要采用黑盒测试。
- 结果
- 测试用例是对程序是否能够正确运行,能够处理规定错误和异常的检测,所以测试用例的设计直接影响对程序的评估。
测试用例 1:
while(a<=b) nice=(a+b)/3-2*b
符合设计文法,程序运行结果如下:
程序结果同预计结果相同。
测试用例二
while(a<=b) nice=a+b/3-2*b
符合设计文法,程序运行结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dcapFxRQ-1654590625950)(https://www.writebug.com/myres/static/uploads/2022/6/7/a465cf466832eec2204f3cb4ec863712.writebug)]
程序运行结果,与预计结果相同,与测试用例一相比,运算顺序此次变成了 b/3 优先,而测试用例一的运算顺序为括号内优先,这符合事实上的运算过程。
测试用例三
while(a<=b) nice=a++/3-2*b
符合不符合设计文法,程序运行结果如下:
本次分析过程中由两个连续的‘+‘,本次文法的优先关系矩阵中没有此项,所以语法分析失败。
七、实践总结(本设计的评价、特点、不足、收获与体会等)
评价:
这次程序设计中的思想都是经过长久思考而来。在编写程序之前,通过对简单优先分析法、语法制导的翻译过程、拉链回填等知识的学习和思考,让我对编译中的自底向上的优先法和如何进行翻译成四元式的过程有了更加深刻的理解。本次实验虽然满足了实验的基本要求,但整个文法较为简单,不能对一些复杂的语句进行分析。
特点:
我个人认为我这次实验程序的特点是通过对翻译方法的学习,实现了对 >,<,>=,<=等的处理,对复杂的赋值表达式也能进行正确的翻译,并通过拉链回填技术将四元式的跳转指令完善。
不足:
由于个人能力和时间问题,没有实现通过扫描输入文法自动生成优先关系矩阵和一些复杂的语句分析(如多条赋值语句和 while 语句的嵌套),优先关系矩阵的构造还是通过自己手算,效率低,准确率不高。也因为简单优先文法的限制,一写较为复杂的关系表达式不能分析。
收获与体会:
通过本次实验,我对编译原理这门课程的基本思想又有了新的认识以及更加深刻的学习。通过对编译中的词法分析、简单优先分析法、语法知道的翻译和拉链回填技术的加深学习,对这些知识中的思想有了进一步的认识,巩固平时所学的知识,真正左到了学以指用,也锻炼了自己的编程水平。
编译原理时一门非常专业的学科,对于现阶段的我,仅能学习了解它的基本原理,实现一些简单的文法分析程序, 学好这门课程对以后的发展具有深远的影响。课程设计是对学习成果的检验,它锻炼了我们的独立思考能力、解决问题的能力,让我们更加明白思路的重要性,只有清晰的思路,才能编写出逻辑性、健壮性强,完善合理的程序。
总之,这次的课程设计强化了我对编译知识的理解记忆,增强了我独立思考和动手实践的能力,收获颇丰。
深学习,对这些知识中的思想有了进一步的认识,巩固平时所学的知识,真正左到了学以指用,也锻炼了自己的编程水平。
编译原理时一门非常专业的学科,对于现阶段的我,仅能学习了解它的基本原理,实现一些简单的文法分析程序, 学好这门课程对以后的发展具有深远的影响。课程设计是对学习成果的检验,它锻炼了我们的独立思考能力、解决问题的能力,让我们更加明白思路的重要性,只有清晰的思路,才能编写出逻辑性、健壮性强,完善合理的程序。
总之,这次的课程设计强化了我对编译知识的理解记忆,增强了我独立思考和动手实践的能力,收获颇丰。