利用状态图实现词法分析
http://blog.csdn.net/cn854/article/details/697242
實驗一:詞法分析程序
????????????????????????????????????????????????????????????????????????????????????????????? 03070020?曹寧
一.? 實驗目的
基本掌握計算機語言的詞法分析程序的開發方法。
二.? 實驗內容
編制一個能夠分析三種整數、標識符、主要運算符和主要關鍵字的詞法分析程序。
三.? 實驗環境
PC微機
DOS操作系統或 Windows操作系統
Turbo C 程序集成環境或 Visual C++程序集成環境
四.? 實驗內容
1.??? 根據以下的正規式,編制正規文法,畫出狀態圖;
| 標識符 | letter(letter|digit)* letter->A|B|…|Z|a|b|…|z digit->0|1|2|3|4|…|9 |
| 十進制整數 | 0 | (1|2|3|4|5|6|7|8|9)(0|1|2|3|4|5|6|7|8|9)* |
| 八進制整數 | 0(1|2|3|4|5|6|7)(0|1|2|3|4|5|6|7)* |
| 十六進制整數 | 0x(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f)(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f)* |
| 運算符和分隔符 | + ?- ?* ?/ ?>? <? =? (? ) ?; |
| 關鍵字 | if? then? else? while? do ? |
2.??? 根據狀態圖,設計詞法分析函數int nextToken(),完成以下功能:
1)?????????????從文件讀入數據,分析出一個單詞。
2)?????????????返回單詞種別(用整數表示),
3)?????????????返回單詞屬性(不同的屬性可以放在不同的全局變量中)。
3.??? 編寫測試程序,反復調用函數int nextToken(),輸出單詞種別和屬性。
五.? 實驗步驟
1.????? 根據狀態圖,設計詞法分析算法
| 標識符?????????? | |
| 正規式? | id->letter(letter|digit)* letter->A|B|…|Z|a|b|…|z digit->0|1|2|3|4|…|9 |
| 正規文法 | S->aB’|bB’|…|zB’|AB’|BB’|…|ZB’ B’->0B’|1B’|…|9B’ |
| 狀態圖 |
|
?????????????
| 八進制整數????????????? | |
| 正規式? | 0(1|2|3|4|5|6|7)(0|1|2|3|4|5|6|7)* |
| 正規文法 | S->01B|02B|…|07B B->0B|1B|…|7B| |
| 十進制整數????????????? | |
| 正規式? | 0 | (1|2|3|4|5|6|7|8|9)(0|1|2|3|4|5|6|7|8|9)* |
| 正規文法 | S->0|1B|2B|…|9B B->0B|1B|…|9B| |
| 十六進制整數????????????? | |
| 正規式? | 0x(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f)(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f)* |
| 正規文法 | S->0(x|X)(1B’|2B’|…|9B’|aB’|bB’|…|fB’|AB’|…|FB’) B’->0B’|1B’|…|7B’| |
| 識別這三種數字的狀態圖 | |
|
| |
?????????????
| 運算符和分隔符 | |
| 狀態圖 |
|
?
2.????? 采用C語言,設計函數scan( ),實現該算法
程序中的變量和函數聲明
| //對外函數 extern void initLexer();//打開文件,初始化詞法分析器 extern int nextToken();//獲得一個token //對外變量 extern int attr=-1;//是數值的時候存儲數值,是標識符時存儲在名字表中的位置 extern int lineNo=1;//顯示行數 extern char *keyWord[]={ ??????? "if", ??????? "else", ??????? "then", ??????? "while", ??? ????"do" }; //內部函數 static int fail();//換狀態圖 static char getAnChar();//在文件中讀取一個字符,指針下移一位 static void ungetAnChar();//在文件中指針回退一位 static void getNum(int type);//根據type,獲得十進制數值,存儲在attr static int lookup(const char *s);//在符號表中查找ID static int insert(const char *s);//返回再符號表中的下標位置 static int isKeyWord(char word[]);//判斷是否為關鍵字 //內部變量 static char lexBuf[100];//字符緩存,用來存儲當前分析的字 static int state=1, start=1;//當前狀態和狀態表的開始狀態 static int currentPos=0;//文件中當前指針的位置 static int tokenBeginning=0;//進入一個狀態表時指針位置,即換狀態表時的回退位置。 static FILE *fp;//被編譯的文件指針 static int smptableLength=0;//當前符號表的長度 |
?
| int nextToken(){ ? ??? int length=0; ??? char c; ??? int keyWordPos=0;//在關鍵字數組中的下標 ? ??? state=1;start=1; ??? //存儲開始位置 ??? tokenBeginning=currentPos; ??? //狀態圖的實現 ??? while(1){ ? ??????? switch(state){ ??????? case 1: ??????????? c=getAnChar(); ??????????? if(c==' '||c=='/t'||c=='/r'){ ??????????????? tokenBeginning++; ??????????? } ??????????? else if(c=='/n'){ ??????????????? lineNo++; ??????????????? tokenBeginning++; ??????????? } ??????????? else if(isalpha(c)){ ??????????????? state=2; ??????????????? lexBuf[length++]=c; ??????????? } ??????????? else if(c==EOF) return FILEEND; ??????????? else state=fail(); ??????????? break; ??????? case 2: ????????? ??c=getAnChar(); ??????????? if(isdigit(c)||isalpha(c)){ ??????????????? state=2; ??????????????? lexBuf[length++]=c; ??????????? } ??????????? else state=3; ??????????? break; ??????? case 3: ??????????? ungetAnChar(); ??????????? lexBuf[length]='/0'; ?? ?????????keyWordPos=isKeyWord(lexBuf); ??????????? if(keyWordPos!=-1){ ??????????????? //是關鍵字 ??????????????? return IF+keyWordPos; ??????????? } ??????????? //不是關鍵字 ??????????? attr=insert(lexBuf);//把ID在名字表中的數組下標存儲在attr中 ??????????? return ID; ??????? case 4: ??????????? c=getAnChar(); ??????????? if(c=='0') state=5; ??????????? else if(c>='1' && c<='9'){ ??????????????? state=12; ??????????????? lexBuf[length++]=c; ??????????? } ??????????? else state=fail(); ??????????? break; ??????? case 5: ?????????? ?c=getAnChar(); ??????????? if(c=='x') state=6; ??????????? else if(c>='1'&& c<='8'){ ??????????????? state=10; ??????????????? lexBuf[length++]=c; ??????????? } ? ??????????? else state=13; ??????????? break; ??????? case 6: ??????????? c=getAnChar(); ??? ????????if(c>='1' && c<='9' || c>='a'&& c<='f' || c>='A' && c<='B'){ ??????????????? state=7; ??????????????? lexBuf[length++]=c; ??????????? } ??????????? else state=fail(); ??????????? break; ??????? case 7: ??????????? c=getAnChar(); ??????????? if(c>='1' && c<='9' || c>='a'&& c<='f' || c>='A' && c<='B'||c=='0'){ ??????????????? state=7; ??????????????? lexBuf[length++]=c; ??????????? } ??????????? else state=8; ??????????? break; ??????? case 8: ??????????? ungetAnChar(); ??????????? lexBuf[length]='/0'; ??????????? getNum(INT16);//把數值存在attr中 ??????????? return INT16; ??????? case 10: ??????????? c=getAnChar(); ??????????? if(c>='0'&& c<='8'){ ??????????????? state=10; ??????????????? lexBuf[length++]=c; ??????????? } ??????????? else state=11; ???????? ???break; ??????? case 11: ?? ?????????ungetAnChar(); ??????????? lexBuf[length]='/0'; ??????????? getNum(INT8);//把數值存在attr中 ??????????? return INT8; ??????? case 12: ??????????? c=getAnChar(); ??????????? if(c>='0' && c<='9'){ ??????????????? state=12; ? ??????????????lexBuf[length++]=c; ??????????? } ??????????? else state=13; ??????????? break; ??????? case 13: ??????????? ungetAnChar(); ??????????? lexBuf[length]='/0'; ??????????? getNum(INT10);//把數值存在attr中 ??????????? return INT10; ??????? case 14: ?? ?????????c=getAnChar(); ??????????? if(c=='+') state=15; ??????????? else if(c=='-') state=16; ??????????? else if(c=='*') state=17; ??????????? else if(c=='/') state=18; ??????????? else if(c=='>') state=19; ??????????? else if(c=='<') state=20; ???????? ???else if(c=='=') state=21; ??????????? else if(c=='(') state=22; ??????????? else if(c==')') state=23; ??????????? else if(c==';') state=24; ??????????? else state=fail(); ??????????? break; ??????? case 15: ??????????? return ADD; ??????? case 16: ???? ???????return SUB; ??????? case 17: ??????????? return MUL; ??????? case 18: ??????????? return DIV; ??????? case 19: ??????????? return GT; ??????? case 20: ??????????? return LT; ??????? case 21: ??????????? return EQ; ??????? case 22: ??????????? return LBR; ??????? case 23: ??????????? return RBR; ??????? case 24: ??????????? return SEM; ??????? } ??? }???? } |
?
3.????? 編制測試程序(主函數main)。
| void main(){ ???? int token; ???? int oldLine=-1; ???? initLexer();?? ????? ???? while(1){ ???????? token=nextToken(); ???? ????if(token==FILEEND) break; ???????? if(oldLine!=lineNo) { ???????????? printf("___________Line %d____________/n",lineNo); ???????????? oldLine=lineNo; ???????? } ???????? ??????? if(token==ID){//標識符 ??????????? printf("ID:%d,Pos:%d/n",token,attr); ??? ????} ??????? else { ??????????? switch(token){ ??????????????? case INT16:case INT8:case INT10://數字 ??????????????????? printf("%d/t/t%d/n",token,attr); ??????????????????? break; ??????????????? default: printf("%d/n",token);//關鍵字 ??????????????? } ???? ???} ??????? token=nextToken(); ???? } }//*/ |
?
4.????? 調試程序:輸入一組單詞,檢查輸出結果
| ??? 1? 92+data>? 0x3f? 04? while |
|
|
| 1x3 x3 x3 x44?? 00 |
|
|
以上都是一行的沒有出現什么問題,但是當對多行進行詞法分析時,總是出錯,通過打印讀到的字符發現有字符13(ASCII),查資料才知是’/t’,把光標移到當前行的起始位置,我不知道為什么會有這個字符,添加濾掉這個字符的邏輯,才使詞法分析成功。
| while (1) do num=10; num=11; if (1) then caoning=0x11; if (1) than caoning=878; |
|
|
?
5.????? 詞法分析程序的數據結構與算法
符號表的數據結構:采用的是結構數組
| struct ENTYE{ ?? char word[100]; ?? float value; }; int smptableLength=0; struct ENTYE smptable[200]; |
在配以int lookup(const char *s)和int insert(const char *s)對這個符號表操作。
| /*查找s在符號表的位置,沒有返回-1*/ int lookup(const char *s){ ??? int i; ??? for (i=0;i<smptableLength;i++){ ??????? if (strcmp(smptable[i].word,s)==0) return i; ??? } ??? return -1; } /*返回s在符號表中的位置*/ int insert(const char *s){ ?? int i=lookup(s); ?? if(i==-1){ ??????? strcpy(smptable[smptableLength].word,s); ?????? //smptableLength++; ?????? return smptableLength++; ?? } ?? return i; } |
六.? 心得體會
這次詞法分析的實驗本身沒有什么難度,但是在做這實驗之前感覺沒譜,所以踏下心仔細的閱讀Aho的《編譯原理》中前三章,受到很大啟發,尤其是利用switch語句把狀態圖實現的技術,可謂一絕,這也是我學習詞法分析的最大的收獲。
在做語法分析的時候,對詞法分析進行了一些修改,學習了一下不同文件內的函數和變量的使用,在做詞法分析的時候架構沒有做好,比如哪些函數和變量作為內部的,哪些是提供外部使用的接口,比較混亂,也沒有進行大量測試,導致語法分析時受阻重重,最后停下語法分析的編程,對此詞法分析修改,使程序清晰易讀,并進行了大量的測試,再作語法分析時就容易多了。這也是個教訓,以后編程首先得考慮好,再一步一步來。就會省很多麻煩。
?
?
?
http://blog.chinaunix.net/uid-26750235-id-3088397.html
代碼下載: git clone git://git.code.sf.net/p/redy/code redy-code這一章的內容有: 通過一個實例來說明狀態機合并的方法 狀態機合并算法 狀態鏈在合并中的優點
(1)簡介 在這一章里面,你會看到兩個簡單的狀態機: 一個為狀態機用于識別正則式 ? [0-7]+abf 所表于的語言,[0-7] 表于數字0-7中的任意一個,'+'表示重復一次或多次。 另一個狀態機用于識別正則式 [4-9]+acd所表于的語言。同樣[4-9]表示數字4-7中的任意一個。'+'表示重復一次或多次。
(2)狀態機1 根據正則式[0-7]+abf,繪出的狀態圖如下:
我們使用狀態鏈的算法來用程序來構造該狀態機,從狀態中可以看出,該狀態機總共有5個狀態,其中Abegin為開始狀態,A4為終態,由于每個狀態,都只在一個輸入類型下發生狀態轉移,所以每一個狀態,這里采用函數指釷的方法來判斷輸入類型。 在程序開頭,我們對于每一個狀態都進行申明,以使后面引用:
同樣我們也使用狀態鏈的方法來構造狀態機2,狀態機2總共有5個狀態,開始狀態為Bbegin,終態為B4,同構造狀態機1的方法一樣。我這里就直接貼出程序,不進行說明。 狀態申明:
(4)狀態機的合并
a)輸入類型分析 對于狀態機1來說,輸入類型有這么5種
把兩個狀態機的輸入類型進行合并得到下面9種輸入類型
b)合并第一步 狀態機1的開始狀態為Abegin,狀態機2的開始狀態為Bbegin,把兩個狀態合成一個狀態Cbegin,Abegin和Bbegin兩個狀態能發生的狀態轉移,Cbegin同樣能發生。
這時狀態圖為: 其中黃色表示狀態機1,綠色表示狀態機2,黃色表示為新創建的狀態。
因為Abegin,Bbegin能發生的狀態轉移,Cbegin都以發生,即 f(Abegin,D0_7)->A1 f(Begin,D4_9)->B1 所以推得出: f(Cbegin,D0_3)->A1 f(Cbegin,D4_7)->(A1,B1) f(Cbegin,D8_9)->B1 這里我們繪出一個狀態轉換表
| 輸入\狀態 | D0_3 | D4_7 | D8_9 | S_a | S_b | S_c | S_d | S_f | Other |
| Cbegin (Abegin,Begin) | A1 | A1,B1 | B1 | | | | | | |
c)合并第二步
假設狀態S在輸入類型I下的后繼狀態組合為C:
在這一步中,找出標記一個標記過的狀態,進行狀態轉移處理,在一步時,我們只有狀態C1被標記過,狀態C1等價于(A1,B1)的組合,這時我們對狀態C1進行處理,并且取消對C1的標記。 因為 f(A1,D0_7)->A1 f(A1,S_a)->A2 f(B1,D4_9)->B1 f(B1,S_a)->B2 所以 f(C1,D0_3)->A1 f(C1,D4_7)-:>(A1,B1) f(C1,D8_9)->B1 f(C1,S_a)->(A2,B2) 根據上面的轉移公式,我們繼續繪畫狀態轉移表
| 輸入\狀態 | D0_3 | D4_7 | D8_9 | S_a | S_b | S_c | S_d | S_f | Other |
| Cbegin (Abegin,Begin) | A1 | A1,B1 | B1 | | | | | | |
| C1 (A1,B1) | A1 | A1,B1 | B1 | A2,B2 | | | | | |
根據上面的轉換,我們繼續繪制狀態圖
此時狀態圖為:
e)合并第四步 合并第四步,就是得重復第三步,直到沒有標記狀態為止,現在被標記的狀態只有C2,所以我們需要對C2進行處理,并且取消標記C2。按照第三步的方法來處理。 因為 f(A2,S_b)=A3 f(B2,S_c)=B3 所以 f(C2,S_b)=A3 f(C2,S_c)=B3 根據上面公式,這時轉移表為:
| 輸入\狀態 | D0_3 | D4_7 | D8_9 | S_a | S_b | S_c | S_d | S_f | Other |
| Cbegin (Abegin,Begin) | A1 | A1,B1 | B1 | | | | | | |
| C1 (A1,B1) | A1 | A1,B1 | B1 | A2,B2 | | | | | |
| C2 (A2,B2) | | | | | A3 | B3 | | | |
繼續繪制我們的狀態轉換圖,因為狀態C2在S_b,S_c分別轉移到A3,B3。兩個都是單狀態,所以分別畫一條轉移線從C2到A3,B3。
此時狀態圖為:
到現在為些,已經沒有標記過的狀態了,所以狀態機1與狀態機2的合并也算完成了。
(5)合并后的狀態機
狀態C1在D0_3轉移到狀態A1,在D4_7轉移到本身,在D8_9轉移到B1,在S_a轉移到C2
這樣我們就以經完成合并后狀態機模型的狀態鏈,這時把開始狀態改為Cbegin,代入驅動程序,可以對正則式[0-7]+abf和正則式[4-9]+acd表示的語言進行綜合識別了 合并后的狀態機可以在下載的文件夾下面的tutorial/lexical/merge1中找到,對程序編譯,運行可執行文件merge 順便在這里也貼出一個運行后的圖給大家看看運行結果。
(6)總結
a)合并算法總結
b)采用狀態鏈狀態機的優點
在狀態中每一個狀態,不用去關心狀態機的整體結構。這是我們實現狀態機合并的關鍵。
?
總結
以上是生活随笔為你收集整理的利用状态图实现词法分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一个简单词法分析器的实现代码(java实
- 下一篇: 夏威夷