遞歸下降分析法實現LL(1)文法的語法分析器


   本文將就編譯原理中比較常用的一個表達式文法,通過遞歸下降語法分析法來編寫分析器。文中將為您提供如何通過FIRST、FOLLOW和SELECT集合來判斷LL(1)方法,然后如何用遞歸下降語法分析法分析LL(1)方法的基本遞歸流程,以及如何用C語言來編程實現分析器。
 

題目: 編程識別由下列文法所定義的表達式的遞歸下降語法分析器。

                    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        SELECTEàTE’=FIRST(TE’)=FIRST(T)={ (i }

E’       SELECTE’ à+TE’={ + } 

       SELECTE’ à −TE’={ − }

       SELECTE’ àε={ε)#}

T        SELECTTàFT’={(i}

T’       SELECTT’ à*FT’={ * }  

SELECTT’ à ∕FT’={ ∕ }

SELECTT’ àε={ε+)#}

F        SELECTFà(E) ={ ( }

SELECTFài={ i }

∴   SELECT(E’ à+TE’)∩SELECTE’ à −TE’∩SELECTE’ àε=F

SELECT(T’ à*FT’)∩SELECTT’ à ∕FT’∩SELECTT’ àε=F

SELECTFà(E) ∩SELECTFàiF

由上可知,有相同左部產生式的SELECT集合的交集為空,所以文法是LL1)文法。因此,轉化后的文法可以用遞歸下降分析法作語法分析。

 

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM