1、小型编译程序的设计与实现本实验设计的小型编译程序涉及到编译前端的三个阶段:词法分析、语法分析和语义分析生成中间代码(四元式) ,编译程序的重点放在中间代码生成阶段。编译程序的输出结果包括词法分析后的二元式序列、变量名表;语法分析后的状态栈分析过程显示;语义分析生成中间代码后的四元式程序。整个程序分为三个部分:(1) 词法分析部分(2) 语法分析、语义分析及四元式生成部分(3) 输出显示部分1 词法分析器设计词法分析器的功能是输入源程序,输出单词符号。我们规定输出的单词符号格式为如下的二元式:(单词种别编码,单词自身的值)由于我们规定的程序语句中涉及单词较少,故在词法分析阶段忽略了单词输入错误的
2、检查。1.1 单词符号的内部定义及在编译程序中的定义我们对常量、变量、临时变量、保留关键字(if、while、begin、end 、else、then、do 等) 、关系运算符、逻辑运算符、分号、括号等,规定其内部定义如下:符 号 种别编码 说 明sy_if 0 保留字 ifsy_then 1 保留字 thensy_else 2 保留字 elsesy_while 3 保留字 whilesy_begin 4 保留字 beginsy_do 5 保留字 dosy_end 6 保留字 enda 7 赋值语句semicolon 8 “ ; ”e 9 布尔表达式Jinghao 10 “ # ”S 11 语
3、句L 12 复合语句Tempsy 15 临时变量EA 18 B and(即布尔表达式中的 B)EO 19 B or(即布尔表达式中的 B )Plus 34 “ + ”Times 36 “ * ”Becomes 38 “ := ” 赋值Op_and 39 “ and ”Op_or 40 “ or ”Op_not 41 “ not ”Rop 42 关系运算符Lparent 48 “ ( ”Rparent 49 “ ) ”Ident 56 变量Intconst 57 整常量#include “stdio.h“ /*如果使用 TC 的话,需要配置头文件路径*/#include “string.h“#d
4、efine ACC -2/*/#define sy_if 0#define sy_then 1#define sy_else 2#define sy_while 3#define sy_begin 4#define sy_do 5#define sy_end 6#define a 7#define semicolon 8#define e 9#define jinghao 10#define S 11#define L 12#define tempsy 15#define EA 18 /*E and*/#define E0 19 /E or*/#define plus 34#define ti
5、mes 36#define becomes 38#define op_and 39#define op_or 40#define op_not 41#define rop 42#define lparent 48#define rparent 49#define ident 56#define intconst 571.2 变量及数据结构说明编译程序中涉及到的变量及数据结构说明如下:char ch=0; /*从字符缓冲区读取当前字符*/int count=0; /*词法分析结果缓冲区计数器*/static char spelling10=“; /*存放识别的字*/static char lin
6、e81=“; /*一行字符缓冲区,最多 80 个字符*/char *pline; /*字符缓冲区指针*/static char ntab110010; /*变量名表,共 100 项,每项长度 10*/struct ntabint tc; /*真值*/int fc; /*假值*/ntab2200; /*在布尔表达式 E 中保存有关布尔变量的真、假值*/int label=0; /*指向 ntab2 的指针*/struct rwordschar sp10;int sy; /*保留字表的结构,用来与输入缓冲区中的单词进行匹配*/struct rwords reswords10=“if“,sy_if,
7、“do“,sy_do,“else“,sy_else,“while“,sy_while,“then“,sy_then,“begin“,sy_begin,“end“,sy_end,“and“,op_and,“or“,op_or,“not“,op_not; /*保留字表初始化,大小为 10*/struct aaint sy1; /*存放单词的种别编码*/int pos; /*存放单词自身的值*/buf1000, /*词法分析结果缓冲区*/n, /*读取二元式的当前字符*/n1, /*当前表达式中的字符*/E, /*非终结符*/sstack100, /*算术或布尔表达式加工处理使用的符号栈*/ibuf
8、100, /*算术或布尔表达式使用的缓冲区*/stack1000; /*语法分析加工处理使用的符号栈*/struct aa oth; /*四元式中的空白位置*/struct fourexpchar op10;struct aa arg1;struct aa arg2;int result;fexp200; /*四元式的结构定义*/int ssp=0; /*指向 sstack 栈指针*/struct aa *pbuf=buf; /*指向词法分析缓冲区的指针*/int nlength=0; /*词法分析中记录单词的长度*/int tt1=0; /*变量名表指针*/char *cfile; /*源程
9、序文件,为结束符*/int lnum=0; /*源程序行数记数*/int sign=0; /*sign=1 为赋值语句;=2 为 while 语句;=3 为 if 语句*/*/int newt=0; /*临时变量计数器*/int nxq=100; /*nxq 总是指向下一个将要形成的四元式地址,*/*每次执行 gen()时,地址自动增 1*/int lr; /*扫描 LR 分析表 1 过程中保存的当前状态值*/int lr1; /*扫描 LR 分析表 2 或表 3 所保存的当前状态值*/int sp=0; /*查找 LR 分析表时状态栈的栈顶指针*/int stack1100; /*状态栈 1
10、 定义*/int sp1=0; /*状态栈 1 的栈顶指针*/int num=0; /*算术或布尔表达式缓冲区指针*/struct llint nxq1; /*记录下一条四元式的地址*/ int tc1; /*真值链*/int fc1; /*假值链*/labelmark10; /*记录语句嵌套层次的数组,*/*即记录嵌套中每层的布尔表达式 E 的首地址*/int labeltemp10; /*记录语句嵌套层次的数组,*/*即记录每层 else 之前的四元式地址*/int pointmark=-1, /*labelmark 数组指针*/pointtemp=-1; /*labeltemp 数组指针
11、*/1.3 主函数 main()void main()cfile=fopen(“pas.dat“,“r“); /*打开 C 语言源文件*/readch(); /*从源文件读一个字符*/scan(); /*词法分析*/disp1();disp3();stacksp.pos=0;stacksp.sy1=-1; /*初始化状态栈*/stack1sp1=0; /*初始化状态栈 1*/oth.sy1=-1;printf(“n* 状态栈变化过程以及归约顺序 *n“);readnu(); /*从二元式读入一个符号*/lrparse(); /*语法语义分析产生四元式*/getch();disp2();prin
12、tf(“n 程序运行结束!n“);getch();1.4 词法分析函数说明(1) 读取函数 readline( )、readch( )词法分析包含从源文件读取字符的操作,但频繁的读文件会影响程序执行效率,故实际上是从源程序文件“pas.dat”中读取一行到输入缓冲区,而词法分析过程中每次读取一个字符时则是通过执行 readch()从输入缓冲区获得的;若缓冲区已被读空,则再执行 readline()从 pas.dat 中读取下一行至输入缓冲区。/*从文件读一行到缓冲区*/readline()char ch1;pline=line;ch1=getc(cfile); while(ch1!=n pli
13、ne+;ch1=getc(cfile); *pline=0;pline=line;/*从缓冲区读取一个字符*/readch()if(ch=0)readline();lnum+;ch=*pline;pline+;(2) 扫描函数 scan( )扫描函数 scan()的功能是滤除多余空格并对主要单词进行分析处理,将分析得到的二元式存入二元式结果缓冲区。/*扫描主函数*/scan()int i;while(ch!=) /*是源程序结束符号*/switch(ch)case :break;case a :case b :case c :case d :case e :case f :case g :ca
14、se h :case i :case j :case k :case l :case m :case n :case o :case p :case q :case r :case s :case t :case u :case v :case w :case x :case y :case z : /*保留字和标识符中的字母只能是小写字母*/identifier(); /*识别保留字和标识符*/break;case 0 :case 1 :case 2 :case 3 :case 4 :case 5 :case 6 :case 7 :case 8 :case 9 :number(); /*识别
15、整常数*/break;case ) bufcount.pos=4; /*识别=*/elsebufcount.pos=3; /*识别*/pline-;bufcount.sy1=rop; /*识别关系运算符*/count+;break;case ( :bufcount.sy1=lparent; /*识别(*/count+;break;case ) :bufcount.sy1=rparent; /*识别)*/count+;break;case # :bufcount.sy1=jinghao; /*识别#*/count+;break;case + :bufcount.sy1=plus; /*识别+*/
16、count+;break;case * :bufcount.sy1=times; /*识别*/count+;break;case : :readch();if(ch=)bufcount.sy1=becomes; /*识别:=*/count+;break;case = :bufcount.sy1=rop;bufcount.pos=5; /*识别= ,关系算符*/count+;break;case ; :bufcount.sy1=semicolon; /*识别 ;*/count+;break;readch();bufcount.sy1=-1; /*不可识别的符号*/(3) 变量处理及变量名表 fi
17、nd( )变量处理中首先把以字母开头的字母数字串存到 spelling10数组中,然后进行识别。识别过程是先让它与保留关键字表中的所有关键字进行匹配,若获得成功则说明它为保留关键字,即将其内码值写入二元式结果缓冲区;否则说明其为变量,这时让它与变量名表中的变量进行匹配(变量匹配函数find() ) ,如果成功,则说明该变量已存在并在二元式结果缓冲区中标记为此变量(单词自身值填为该变量在变量名表中的位置) ,否则将该变量登记到变量名表中,再将这个新变量存入二元式缓存数组中。/* 变量匹配,查找变量名表 */find(char spel)int ss1=0;int ii=0;while(ss1=0
18、)stack1sp1=lr1;if(n1.sy1!=tempsy)ssp+;num+;sstackssp.sy1=n1.sy1; /*将变量名压栈*/sstackssp.pos=n1.pos; /*将变量名地址压栈*/n1.sy1=ibufnum.sy1;n1.pos=ibufnum.pos;lrparse1(num);if(lr1=100)case 101: /* EE+E */E.pos=newtemp();gen(“+“,sstackssp-2,sstackssp,E.pos+100);ssp=ssp-2;sstackssp.sy1=tempsy;sstackssp.pos=E.pos;
19、sp1=sp1-3;break;case 102: /* EE*E */E.pos=newtemp();gen(“*“,sstackssp-2,sstackssp,E.pos+100);ssp=ssp-2;sstackssp.sy1=tempsy;sstackssp.pos=E.pos;sp1=sp1-3; /* EE+E 产生式右部长度为 3,故归约后栈指针减 3 */break;case 103: /* E(E) */E.pos=sstackssp-1.pos;ssp=ssp-2;sstackssp.sy1=tempsy;sstackssp.pos=E.pos;sp1=sp1-3;brea
20、k;case 104: /* Ei */E.pos=sstackssp.pos;sp1-;break;n1.sy1=tempsy; /* 归约后为非终结符*/n1.pos=E.pos;lrparse1(num);if(lr1=ACC)ssp=ssp-3;sp1=sp1-3;2.3 布尔表达式处理的语义加工程序根据布尔表达式文法中产生式对应的语义动作,相应的加工处理程序如下:lrparse2(int num)int templabel;lr1=action2stack1sp1change2(n1.sy1);if(lr1=-1)if(sign=2) printf(“nwhile 语句出错!n“);
21、if(sign=3) printf(“nif 语句出错!n“);getch();exit(0);if(lr1=0) /*当前查找 LR 分析表中的状态为移进状态*/sp1+;stack1sp1=lr1;ssp+;sstackssp.sy1=n1.sy1;sstackssp.pos=n1.pos;if(n1.sy1!=tempsy)n1.sy1=ibufnum.sy1;n1.pos=ibufnum.pos;lrparse2(num);if(lr1=100)break;case 3:gen(“j“,sstackssp-2,sstackssp,0);break;case 4:gen(“jif e t
22、hen Selse S 归约n“);sp=sp-6;n.sy1=S; /*归约完 if 后,填 then 后面的无条件转移语句*/fexplabeltemppointtemp.result=nxq;pointtemp-;if(stacksp.sy1=sy_then)gen(“j“,oth,oth,0);backpatch(labelmarkpointmark.fc1,nxq);pointtemp+;labeltemppointtemp=nxq-1;pointmark-;if(stacksp.sy1=sy_do)gen(“j“,oth,oth,labelmarkpointmark.nxq1);b
23、ackpatch(labelmarkpointmark.fc1,nxq);break;case 102: /*Swhile e do S*/printf(“S-while e do S 归约n“);sp=sp-4;n.sy1=S; /*归约完后,填 do S 后面的无条件转移语句*/pointmark-;if(stacksp.sy1=sy_do)gen(“j“,oth,oth,labelmarkpointmark.nxq1);backpatch(labelmarkpointmark.fc1,nxq);fexplabeltemppointtemp.result=nxq;if(stacksp.sy
24、1=sy_then)gen(“j“,oth,oth,0);fexplabelmarkpointmark.fc1.result=nxq;pointtemp+;labeltemppointtemp=nxq-1;break;case 103: /*Sbegin L end*/printf(“S-begin L end 归约n“);sp=sp-3;n.sy1=S;if(stacksp.sy1=sy_then)gen(“j“,oth,oth,0);backpatch(labelmarkpointmark.fc1,nxq);pointtemp+;labeltemppointtemp=nxq-1;if(st
25、acksp.sy1=sy_do)gen(“j“,oth,oth,labelmarkpointmark.nxq1);backpatch(labelmarkpointmark.fc1,nxq);getch();break;case 104: /*Sa*/printf(“S-a 归约n“);sp=sp-1;n.sy1=S;if(stacksp.sy1=sy_then)gen(“j“,oth,oth,0);backpatch(labelmarkpointmark.fc1,nxq);pointtemp+;labeltemppointtemp=nxq-1;if(stacksp.sy1=sy_do)gen(
26、“j“,oth,oth,labelmarkpointmark.nxq1);backpatch(labelmarkpointmark.fc1,nxq);break;case 105: /*LS*/printf(“L-S 归约n“);sp=sp-1;n.sy1=L;break;case 106: /*LS;L*/printf(“L-S;L 归约n“);sp=sp-3;n.sy1=L;break;getch();pbuf-;lrparse();if(lr=ACC) return ACC;3. 上机实验内容实验一 词法分析程序的设计、分析与验证 实验目的:学习设计词法分析程序,验证词法分析的方法 实验
27、准备:阅读与本次实验有关的内容小型语言关于单词的内部定义;小型编译程序中关于词法分析的程序说明。 实验内容:设计、输入、调试一个小型语言的词法分析程序输入 PAS.DAT 源程序,输出单词符号二元式 实验检查:机上检查,提问,要求读懂程序,会分析输出的结果。1. 单词符号的定义,一般来说。不论用什么语言实现编译器,都要使用整型的常量来定义各种单词符号。2. 词法分析程序的基本数据结构:ch、spelling10、struct aaint sy1;int pos;buf10003. 词法分析程序的初始化,用 readline( )把输入流中的字符读到内存的缓冲区中,预置当前字符的位置,并且调用
28、readch( )预读一个字符到 ch 中。4. 扫描下一个字符,需要移动字符指针找到下一个字符并把它赋给 ch,注意维护当前字符所在的位置信息。5. 扫描下一个符号,根据当前的字符 ch 来判断下面的符号可能是什么,然后调用 scan( )处理。 问题:1. 词法分析器的功能是什么?输入是什么?输出是什么?2. 程序语言的单词符号一般分为几类?小型编译程序定义了哪些单词?3. 单词符号的内部表示是什么?单词符号二元式由哪两部分组成?4. 标识符和名字的区别?标识符的自身值用什么表示?常量呢?5. 词法分析程序的结构?会分析输出的结果。6. 输出的变量名表与单词符号二元式结果的关系,起什么作用
29、?7. 小型编译程序如何识别标识符和保留字、数值常量、关系运算符?如何过滤空白字符?8. 解释几个函数:identifier()、number()、find(char spel)。9. 解释几个数据结构:10. 扩充:若源程序可以有注释“/*/” ,那么如何对注释进行过滤处理?实验二 小型编译程序的分析与验证 实验目的:学习并验证语法分析(LR 方法)、语义分析产生中间代码(四元式)的方法 实验准备:阅读与本次实验有关的内容关于小型编译程序的说明;源程序文本。 实验内容:输入、调试小型编译程序 PAS.C,用源程序 PAS.DAT 予以验证 实验检查:机上检查,提问,要求读懂程序,会分析输出的
30、结果。 问题:1. 源程序语句的结构。2. 源程序与输出的四元式程序的对应:源程序的哪部分对应哪些四元式?3. 读懂并会分析四元式程序。4. 序号为 106、111、116 的四元式起什么作用?5. 解释语法分析过程中,输出的状态栈的变化情况,理解移进和归约。6. 会不会手工翻译源程序为四元式。7. 解释几个函数。8. 解释几个数据结构。9. 对各种语言成分的分析。实验三 小型编译程序的扩充(算术表达式扩充) 实验目的:学习 LR 分析表的设计方法和语义加工程序的扩充 实验准备:阅读与本次实验有关的内容关于小型编译程序的说明;源程序文本。算术表达式文法扩充为:EE+E|E-E|E*E|E/E|
31、(E)|i 实验内容:参照算术表达式 LR 分析表的设计方法,设计扩充后的算术表达式的 LR 分析表,并对原语义加工程序进行修改,验证修改的结果 实验检查:机上检查,提问,要求读懂程序,会分析输出的结果。 问题:1. 构造扩充算术表达式文法的 LR(0)项目集规范族,构造其 SLR(1)分析表。2. 指出对程序作出了哪些修改。3. 验证结果。4. 编译程序运行实例实例一 待编译的 pas.dat 源程序如下:while (ab) dobegin if m=n thena:=a+1else while k=h do x:=x+2;m:=n+x*(m+y)end#经编译程序运行后得到的输出结果如下
32、:* 词法分析结果 * /*注释:查单词内部定义和下面的变量名表 */3 0 /* (sy_while, 0) */48 0 /* (”(” , 0 ) */56 0 /* (变量, a) */42 3 /* (rop, “”) */56 1 /* (变量, b) */49 0 /* (”) ” , 0) */5 0 /* (sy_do, 0) */4 0 /* (sy_begin, 0) */0 0 /* (sy_if, 0) */56 2 /* (变量, m) */42 2 /* (rop, “=” ) */56 3 /* (变量, n) */1 0 /* (sy_then, 0) */5
33、6 0 /* (变量, a) */38 0 /* (”:=” , 0) */56 0 /* (变量, a) */34 0 /* (”+” , 0) */57 1 /* (整常量, 1) */2 0 /* (sy_else, 0) */3 0 /* (sy_while, 0) */56 4 /* (变量, k) */42 5 /* (rop, “=”) */56 5 /* (变量, h) */5 0 /* (sy_do, 0) */56 6 /* (变量, x) */38 0 /* (”:=” , 0) */56 6 /* (变量, x) */34 0 /* (”+” , 0) */57 2 /
34、* (整常量, 2) */8 0 /* (”;” , 0) */56 2 /* (变量, m) */38 0 /* (”:=” , 0) */56 3 /* (变量, n) */34 0 /* (”+” , 0) */56 6 /* (变量, x) */36 0 /* (”*” , 0) */48 0 /* (”(” , 0) */56 2 /* (变量, m) */34 0 /* (”+” , 0) */56 7 /* (变量, y) */49 0 /* (”) ” , 0) */6 0 /* (sy_end, 0) */10 0 /* (”#” , 0) */程序共 9 行,产生 43 个
35、二元式!* 变量名表 *0 a1 b2 m3 n4 k5 h6 x7 y* 状态栈分析过程及归约顺序 *stack 栈顶项 n=当前符号 lr=转移到的状态stack 0 =0 n=3 lr=3stack 1 =3 n=9 lr=7stack 2 =7 n=5 lr=11stack 3 =11 n=4 lr=4stack 4 =4 n=0 lr=2stack 5 =2 n=9 lr=6stack 6 =6 n=1 lr=10stack 7 =10 n=7 lr=5stack 8 =5 n=2 lr=104S-a 归约stack 7 =10 n=11 lr=14stack 8 =14 n=2 l
36、r=17stack 9 =17 n=3 lr=3stack 10 =3 n=9 lr=7stack 11 =7 n=5 lr=11stack 12 =11 n=7 lr=5stack 13 =5 n=8 lr=104S-a 归约stack 12 =7 n=11 lr=15stack 13 =15 n=8 lr=102S-while e do S 归约stack 9 =1 n=11 lr=18stack 10 =18 n=8 lr=101S-if e then S else S 归约stack 4 =4 n=11 lr=9stack 5 =9 n=8 lr=13stack 6 =13 n=7 l
37、r=5stack 7 =5 n=6 lr=104S-a 归约stack 6 =13 n=11 lr=9stack 7 =9 n=6 lr=105L-S 归约stack 6 =13 n=12 lr=16stack 7 =16 n=6 lr=106L-S;L 归约stack 4 =4 n=12 lr=8stack 5 =8 n=6 lr=12stack 6 =12 n=10 lr=103S-begin L end 归约stack 3 =11 n=11 lr=15stack 4 =15 n=10 lr=102S-while e do S 归约stack 0 =0 n=11 lr=1stack 1 =
38、1 n=10 lr= - 2* 四元式分析结果 *100 (j, a, b, 102)101 (j , , , 117)102 (j=, m, n, 104)103 (j , , , 107)104 (+, a, 1, T1)105 (:=, T1, , a)106 (j , , , 112)107 (j=, k, h, 109)108 (j , , , 112)109 (+, x, 2, T2)110 (:=, T2, , x)111 (j , , , 107)112 (+, m , y, T3)113 (*, x, T3, T4)114 (+, n, T4, T5)115 (:=, T5,
39、 , m)116 (j , , , 100)*程序运行结束!状态栈分析加工过程:状态栈(STACK) 符 号 栈0 #0 3 #while0 3 7 #while e/*(ab)已由布尔式 LR 分析表加工处理后归约为 e*/0 3 7 11 #while e do0 3 7 11 4 #while e do begin 0 3 7 11 4 2 #while e do begin if0 3 7 11 4 2 6 #while e do begin if e/*m=n 已由布尔式 LR 分析表加工处理后归约为 e*/0 3 7 11 4 2 6 10 #while e do begin if
40、 e then0 3 7 11 4 2 6 10 5 #while e do begin if e then a/*a:=a+1 已由算术式 LR 分析表加工处理后归约为a*/*用 Sa 归约,查 LR 分析表得 GOTO(10,S)=14*/0 3 7 11 4 2 6 10 14 #while e do begin if e then S0 3 7 11 4 2 6 10 14 17 #while e do begin if e then S else0 3 7 11 4 2 6 10 14 17 3 #while e do begin if e then S else while 实例二 待编译的 p1.dat