题目: 编程识别由下列文法所定义的表达式的递归下降语法分析器。
EàE+T | E-T | T
TàT*F | T/F |F
Fà(E) | i
输入:每行含一个表达式的文本文件。
输出:分析成功或不成功信息。
解答:
(1)分析
a) ∵E=>E+T=>E+T*F=>E+T*(E)即有E=>E+T*(E)存在左递归。用直接改写法消除左递归,得到如下:
E à TE’
E’ à +TE’ | −TE’|ε
T à FT’
T’ à *FT’ | /FT’|ε
F à (E) | i
b) 对于以上改进的方法。可得:
对于E’: FIRST( E’ )=FIRST(+TE’)∪FIRST(-TE’)∪{ε}={+,−,ε}
对于T’: FIRST( T’ )=FIRST(*FT’)∪FIRST(/FT’)∪{ε}={*,∕,ε}
而且: FIRST( E ) = FIRST( T ) = FIRST( F )=FIRST((E))∪FIRST(i)={(,i }
由此我们容易得出各非终结符的FOLLOW集合如下:
FOLLOW( E )= { ),#}
FOLLOW(E’)= FOLLOW(E)={ ),#}
FOLLOW( T )= FIRST(E’)\ε∪FOLLOW(E’)={+,−,),#}
FOLLOW( T’ ) = FOLLOW( T ) ={+,−,),#}
FOLLOW( F )=FIRST(T’)\ε∪FOLLOW(T’)={*,∕,+,−,),#}
由以上FOLLOW集可以我们可以得出SELECT集如下:
对E SELECT(EàTE’)=FIRST(TE’)=FIRST(T)={ (,i }
对E’ SELECT(E’ à+TE’)={ + }
SELECT(E’ à −TE’)={ − }
SELECT(E’ àε)={ε,),#}
对T SELECT(TàFT’)={(,i}
对T’ SELECT(T’ à*FT’)={ * }
SELECT(T’ à ∕FT’)={ ∕ }
SELECT(T’ àε)={ε,+,−,),#}
对F SELECT(Fà(E) )={ ( }
SELECT(Fài)={ i }
∴ SELECT(E’ à+TE’)∩SELECT(E’ à −TE’)∩SELECT(E’ àε)=F
SELECT(T’ à*FT’)∩SELECT(T’ à ∕FT’)∩SELECT(T’ àε)=F
SELECT(Fà(E) )∩SELECT(Fài)= F
由上可知,有相同左部产生式的SELECT集合的交集为空,所以文法是LL(1)文法。因此,转化后的文法可以用递归下降分析法作语法分析。
(2)设计
这里采用递归下降分析法形象描述递归子程序。程序中将要用到的几个重要数据如下:
一个全局变量ch,存放由文件输入得到的字符。
一个函数宏READ(ch),实现读取文件中的字符。
五个子函数:P(E)、P(E’)、P(T)、P(T’)、P(F)。
程序主要的子函数模块流程图如下:
(3)程序代码如下
/************************************************************************
* 文件名:ana.c
* 文件描述:递归下降语法分析器。分析如下方法:
* E->E+T | E-T | T
* T->T*F | T/F |F
* F->(E) | i
* 输入:每行含一个表达式的文本文件。
* 输出:分析成功或不成功信息。
* 创建人:余洪周 <nickhome@163.com>2006-12-8
* 版本号:1.0
***********************************************************************/
#include
#include
#define READ(ch) ch=getc(fp) /*宏:READ(ch)*/
char ch; /*声明为全局变量*/
int right=0;
FILE *fp;
struct struCH{
char ch;
struct struCH *next;
}struCH,*temp,*head,*shift;
/*head指向字符线性链表的头结点*/
/*shift指向动态建成的结点(游标)*/
void main(int argc,char *argv[]){
void E (); /* P(E) */
void E1(); /* P(E')*/
void T (); /* P(T) */
void T1(); /* P(T')*/
void F (); /* P(F) */
int errnum=0,k=0,m=0,countchar=0,rownum;
int charerr=0; /*开关控制量*/
/************************以只读方式打开文件*********************/
if((fp=fopen(argv[1],"r"))==NULL)
{
printf("\n\tCan not open file %s,or not exist it!\n",argv[1]);
exit(0); /*文件不存在or打不开时,正常退出程序*/
}
else printf("\n\tSuccess open file: %s\n",argv[1]); /*成功打开文件*/
/******************遍历整个文件检测是否有非法字符********************/
/*如果用while(!feof(fp))语言,将会多出一个字符
*所以这里采用以下方法遍历整个文件检测其否有非法字符
*/
/*[1]计算文件中字符数量*/
while(!feof(fp)){
READ(ch); /*这里读取字符只是让文件指针往前移*/
countchar++; /*统计文件中的字符数(包括换行符及文件结束符)*/
}
rewind(fp); /*将fp文件指针重新指向文件头处,以备后面对文件的操作*/
if(countchar==0){ /*空文件*/
printf("\t%s is a blank file!\n",argv[1]);
exit(0); /*正常退出本程序*/
}
/*[2]开始遍历文件*/
while(k<(countchar-1)){ /*加换行符后countchar仍多了一个,不知为何*/
ch=getc(fp);
if(!(ch=='('||ch==')'||ch=='i'||ch=='+'||ch=='-'||ch=='*'||ch=='/'||ch=='#'||ch=='\n')){
charerr=1;errnum++; /*charerror出错标记,errnum统计出错个数*/
}
k++;
}
rewind(fp); /*将fp文件指针重新指向文件头处,以备后面的建链表操作*/
if(charerr==1){ /*文件中有非法字符*/
printf("\n\t%d Unindentify characters in file %s \n",errnum,argv[1]);
exit(0); /*正常退出本程序*/
}
/*******************非空且无非法字符,则进行识别操作*****************/
for(rownum=1;m<(countchar-1);rownum++){ /*识别所有行,rownum记录行号*/
/*初始变量及堆栈和*/
right=1;
/*初始存放待识别的表达式的线性链表头*/
shift=malloc(sizeof(struCH));/**/
shift->next=NULL;
head=shift;
/*读取一行形成线性链表*/
READ(ch);putchar(ch);m++;
while(ch!='\n'&&m<(countchar)){ /*行末or到文件尾。最后会读取文件结束符*/
/*读取ch,读取存入结点,这样每行便形成一个线性链表*/
temp=malloc(sizeof(struCH));
temp->ch=ch;
temp->next=NULL;
shift->next=temp;
shift=shift->next;
READ(ch);
if(m!=(countchar-1)) putchar(ch); /*不输出最后一次读取的文件结束符*/
m++;
}
head=head->next; /*消去第一个空头结点,并使head指向非空线性链表头*/
shift=head; /*shift指向头结点,以便后面识别操作*/
putchar('\n');
E(); /*开始识别一行*/
if(shift->ch=='#'&&right) /*正确提示:[文件名] Line [行号]:right expression!*/
printf("%s Line %d:\t right expression!\n",argv[1],rownum);
else /*错误提示:[文件名] Line [行号]:error expression!*/
printf("%s Line %d:\t error expression!\n",argv[1],rownum);
putchar('\n');
}/*end for*/
printf("Completed!\n");
fclose(fp); /*关闭文件*/
exit(0); /*正常结束程序*/
}
/*以下函数分别对应于子模块程序*/
void E(){
T();
E1();
}
void E1(){
if(shift->ch=='+'||shift->ch=='-'){
shift=shift->next;
T();
E1();
}
else{
if(shift->ch=='#'||shift->ch==')')
return;
else
right=0;
}
}
void T(void){
F();
T1();
}
void T1(void){
if(shift->ch=='*'||shift->ch=='/'){
shift=shift->next;
F();
T1();
}
else{
if(shift->ch!='#'&&shift->ch!=')'&&shift->ch!='+'&&shift->ch!='-')
right=0; /*如果不是'#'or')'or'+'or'+'or'-'则出错*/
}
}
void F(void){
if(shift->ch=='i')
shift=shift->next;
else{
if(shift->ch=='('){
shift=shift->next;
E();
if(shift->ch==')')
shift=shift->next;
else
right=0;
}
else
right=0;
}
}
(4)调试
1.编译:在Windows平台下,用Turbo C 2编译连接生成后ana.exe;
2.输入表达式:在ana.exe程序同一目录下新建一文本文件(如:test.txt)。往文本文件中输入要识别的表达式,表达式以“#”结束,可输入多行。同一行“#”以后的内容在识别过种中将自动丢弃。如将以下内容存入test.txt文件中:
i-i#
#
i#
++i#
(i+i)*i#++
3.运行:打开Dos控制台,进入程序所在的目录(如C:\>)。
输入:[程序名] [存放表达式的txt文件名],如:ana test.txt
以上test.txt文本实例运行的结果如图:
test.txt实例运行结果图
异常结果注释:
a) 文件不存在or打不开时,提示“Can not open file [filename],or not exist it!”正常退出。
b) 打开文件成功,提示:Success open file:[filename]
c) 若为空文件,提示:[filename] is a blank file! 并正常退出程序。
d) 先检测文件中是否含有非法字符,若有,则统计并输出其个数:[errnum] Unindentify characters in file [filename]
e) 程序将分析每行的表达式,输出每行执行的结果,如上图可知。
结束语
文中代码部分也可以用其他语言实现,而思想基本上都是一样的,请读者自行实现。水平有限,时间仓促,有什么不足之处,敬请读者批评指正。
参考资料
关于题目的来源
* 编译原理实验二 递归下降语法分析 华南热带农业大学 计算机科学与技术系
关于LL(1)分析方法
* 何炎祥,编译原理,高等教育出版社,2004-8
关于指针线性链表代码实现思想
* 黄记瑶,编译实验(二)递归下降语法分析,2005-11-9