題目:編寫識別由下列文法所定義的表達式的預測分析程序。
EàE+T | E-T | T
TàT*F | T/F |F
Fà(E) | i
輸入:每行含一個表達式的文本文件。
輸出:分析成功或不成功信息。
(題目來源:編譯原理實驗(三)--預測(LL(1))分析法的實現)
解答:
(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
對於以上改進的方法。可得:
對於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)文法。
b) 由Select集構造文法LL(1)的預測分析表 (表1)
非終 結符 |
終結符 |
||||||||
i |
+ |
- |
* |
/ |
( |
) |
# |
ε |
|
E |
E >TE’[0] |
|
|
|
|
E->TE’[5] |
|
|
|
E’ |
|
E’-> + T E’[11] |
E’-> - TE’[12] |
|
|
|
E’->ε[16] |
E’->ε[17] |
E’->ε[18] |
T |
T->FT’[20] |
|
|
|
|
T->FT’[25] |
|
|
|
T’ |
|
T’->ε[31] |
T’->ε[32] |
T’->*FT’[33] |
T’->/FT’[34] |
|
T’->ε[36] |
T’->ε[37] |
T’->ε[38] |
F |
F->i[40] |
|
|
|
|
F->(E)[45] |
|
|
|
(2)設計
算法的主要流程圖(如:圖1)。
圖1.流程圖
(3)程序代碼如下
/************************************************************************
* 文件名 :test3.c
* 文件描述:預測分析法實現的語法分析器。分析如下文法:
* E->E+T | E-T | T
* T->T*F | T/F |F
* F->(E) | i
* 輸入:每行含一個表達式的文本文件(#號結束)。
* 輸出:分析成功或不成功信息。
* 創建人:余洪周 2006-12-16
* 版本號:1.0
* 說明 :為了表示的方便采用了如下的所示表示方法:
* A=E' B=T'
* 非終結符:0=E 1=E' 2=T 3=T' 4=F
* 終結符 :0=i 1=+ 2=- 3=* 4=/ 5=( 6=) 7=#
***********************************************************************/
#include
#include
struct struCH{
char ch;
struct struCH *next;
}struCH,*temp,*head,*shift,*top,*base;
/*head指向線性鏈表的頭結點,shift指向動態建成的結點
*top和base分別指向堆棧的頂和底
*/
FILE *fp;
char curchar; /*存放當前待比較的字符*/
char curtocmp; /*存放當前棧頂的字符*/
char ch;
int right, i,j;
int table[5][9]={ /*存儲預測分析表,1為有產生式,0為無*/
{1,0,0,0,0,1,0,0,0},
{0,1,1,0,0,0,1,1,1},
{1,0,0,0,0,1,0,0,0},
{0,1,1,1,1,0,1,1,1},
{1,0,0,0,0,1,0,0,0}};
void main(int argc,char *argv[]){
void puch(char ch);
void pop();
void doforpush(int t);
void identify();
int errnum=0, k=0, countchar=0, rownum;
int m=0;
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)){
ch=getc(fp); /*這里只是讓指針往前移*/
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仍多一個,故減1*/
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;
/* '#''E'進棧 */
base=malloc(sizeof(struCH)); /*初始化堆棧*/
base->next=NULL;
base->ch='#';
temp=malloc(sizeof(struCH));
temp->next=base;
temp->ch='E';
top=temp; /*棧頂指針top指向棧頂*/
/*初始存放待識別的表達式的線性鏈表頭*/
shift=malloc(sizeof(struCH));
shift->next=NULL;
head=shift;
/*讀取一行形成線性鏈表*/
ch=getc(fp);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;
ch=getc(fp);
if(m!=(countchar-1)) putchar(ch); /*不輸出最后一次讀取的文件結束符*/
m++;
}
head=head->next; /*消去第一個空頭結點,並使head指向非空線性鏈表頭*/
shift=head; /*shift指向頭結點,以便后面識別操作*/
putchar('\n');
identify(); /*開始識別一行*/
if(!right) /*錯誤提示:[文件名] Line [行號]:error expression!*/
printf("%s Line %d:\t error expression!\n",argv[1],rownum);
else /*正確提示:[文件名] Line [行號]:right expression!*/
printf("%s Line %d:\t right expression!\n",argv[1],rownum);
putchar('\n');
}/*end for*/
printf("Completed!\n");
fclose(fp); /*關閉文件*/
exit(0); /*正常退出程序*/
}
/*入棧函數*/
void push(char ch){
temp=malloc(sizeof(struCH));
temp->ch=ch;
temp->next=top;
top=temp;
}
/*出棧函數*/
void pop(void){
curtocmp=top->ch;
if(top->ch!='#')
top=top->next;
}
/*根據數組下標計算的值找對應的產生式,並入棧*/
void doforpush(int t){
switch(t){
case 0:push('A');push('T');break;
case 5:push('A');push('T');break;
case 11:push('A');push('T');push('+');break;
case 12:push('A');push('T');push('-');break;
case 20:push('B');push('F');break;
case 25:push('B');push('F');break;
case 33:push('B');push('F');push('*');break;
case 34:push('B');push('F');push('/');break;
case 40:push('i');break;
case 45:push(')');push('E');push('(');
}
}
/*根據curchar,curtocmp轉為數字以判斷是否有產生式*/
void changchartoint()
{
switch(curtocmp) /*非終結符:棧頂*/
{
case 'A':i=1;break;
case 'B':i=3;break;
case 'E':i=0;break;
case 'T':i=2;break;
case 'F':i=4;
}
switch(curchar) /*終結符:待識別的表達式中*/
{
case 'i':j=0;break;
case '+':j=1;break;
case '-':j=2;break;
case '*':j=3;break;
case '/':j=4;break;
case '(':j=5;break;
case ')':j=6;break;
case '#':j=7;
}
}
/*識別算法函數*/
void identify()
{
int t;
for(;;)
{
pop(); /*讀取棧頂的字符存curtocmp中*/
curchar=shift->ch; /*讀取鏈中的一個字符存curchar*/
printf("\t%c-->%c\n",curchar,curtocmp);
if(curtocmp=='#' && curchar=='#') break;
if(curtocmp=='A'||curtocmp=='B'||curtocmp=='E'||curtocmp=='T'||curtocmp=='F'){
if(curtocmp!='#'){ /*[1]未到棧底時,匹配字符*/
changchartoint();
if(table[i][j]){ /*[1.1]有產生式*/
t=10*i+j; /*計算產生式在數組中的位置*/
doforpush(t); /*找對應t的產生式,並入棧*/
continue;
}
else{ /*[1.2]沒有產生式*/
right=0; /*出錯*/
break;
}
}
else{ /*[2]到棧底,當前比較字符為'#'*/
if(curtocmp!=curchar){ /*輸入行中最后一字符非'#'*/
right=0; /*出錯*/
break;
}
else
break; /*正確*/
}
}
else{ /*若當前字符為終結符*/
if(curtocmp!=curchar){
right=0; /*出錯*/
break;
}
else{
shift=shift->next; /*讀取下一個字符*/
continue;
}
}
}/*end for*/
}
(4)調試
1.編譯:在windows平台下,推薦用Turbo C 2編譯連接生成后ana.exe;
2.輸入表達式:在ana.exe程序同一目錄下新建一文本文件(如:exp.txt)。往文本文件中輸入要識別的表達式,表達式以“#”結束,可輸入多行。同一行“#”以后的內容在識別過種中將自動丟棄。如將以下內容存入exp.txt文件中:
i+i#
(i+i)*i-i#
3.運行:打開MSDOS控制台,進入ana.exe程序所在的目錄(如:C:\>)。
輸入:[程序名] [存放表達式的文本文件名],如:ana exp.txt
以上程序執行結果,如圖2所示。
圖2.實例運行結果圖
4.異常操作結果注釋:
a) 文件不存在or打不開時,提示“Can not open file [txtfile],or not exist it!”正常退出。
b) 打開文件成功,提示:Success open file [txtfile]
c) 若為空文件,提示:[txtfile] is a blank file!
d) 先檢測文件中是否含有非法字符,若有,則統計並輸出其個數:[errnum] Unindentify characters in file [txtfile]
e) 程序將分析每行的表達式,輸出每行執行的結果,如上圖。若為錯誤的表達式,則提示:[txtfile] Line [rownum]: error expression!
Tag: Any problem,leave word! ^_^ I will write back as soon as possible!
參考資料
關於題目的來源
* 編譯原理實驗二 遞歸下降語法分析 華南熱帶農業大學 計算機科學與技術系
關於LL(1)分析方法
* 何炎祥,編譯原理,高等教育出版社,2004-8
關於指針線性鏈表代碼實現思想
* 黃記瑤,編譯實驗(三)預測分析,2005-11-9