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> 0x |
|
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語句把狀態圖實現的技術,可謂一絕,這也是我學習詞法分析的最大的收穫。
在做語法分析的時候,對詞法分析進行了一些修改,學習了一下不同文件內的函數和變量的使用,在做詞法分析的時候架構沒有做好,比如哪些函數和變量作爲內部的,哪些是提供外部使用的接口,比較混亂,也沒有進行大量測試,導致語法分析時受阻重重,最後停下語法分析的編程,對此詞法分析修改,使程序清晰易讀,並進行了大量的測試,再作語法分析時就容易多了。這也是個教訓,以後編程首先得考慮好,再一步一步來。就會省很多麻煩。