實驗目的
學習和掌握詞法分析程序手工構造狀態圖及其代碼實現方法。
實驗任務
- 閱讀已有編譯器的經典詞法分析源程序;
- 用C或C++語言編寫一門語言的詞法分析器。
實驗內容
- 閱讀已有編譯器的經典詞法分析源程序。
選擇一個編譯器,如:TINY,其它編譯器也可(需自備源代碼)。閱讀詞法分析源程序,理解詞法分析程序的手工構造方法——狀態圖代碼化。尤其要求對相關函數與重要變量的作用與功能進行稍微詳細的描述。若能加上學習心得則更好。TINY語言請參考《編譯原理及實踐》第2.5節(見壓縮包裏附帶的文檔)。
- 確定今後其他實驗中要設計編譯器的語言,如TINY語言,又如更復雜的C-語言(其定義在《編譯原理及實踐》附錄A中)。也可選擇其它語言,不過要有該語言的詳細定義(可仿照C-語言)。一旦選定,不能更改,因爲要在以後繼續實現編譯器的其它部分。鼓勵自己定義一門語言。
- 根據該語言的關鍵詞和識別的詞法單元以及註釋等,確定關鍵字表,畫出所有詞法單元和註釋對應的DFA圖。
- 仿照前面學習的詞法分析器,編寫選定語言的詞法分析器。
- 準備2~3個測試用例,要求包含正例和反例,測試編譯結果。
提示
在充分理解狀態轉換圖代碼化思想的基礎上,思考不同的程序設計語言從詞法角度有什麼區別,可利用增量編程的思想提高編程效率。實驗通過測試後,按規定時間上交源代碼、測試樣例、輸出文件(如有輸出文件)和電子版實驗報告。
狀態轉換圖
代碼實現
#include<iostream>
#include<fstream>
#include<string.h>
#include<assert.h>
using namespace std;
int LINE=1; //用於記錄代碼的行數
bool flag=false; //用於表示當前註釋是否結束
typedef enum
{
ENDFILE,ERROR, //文件讀取完畢和錯誤
IF,ELSE,INT,RETURN,VOID,WHILE, //六關鍵字
ID,NUM, //數字和字母
ADD,SUB,MUL,DIV,L,LE,G,GE,ISE,NT,NTE,FZ,FH,DH,LXK,RXK,LZK,RZK,LDK,RDK,LZS,RZS // + - * / < <= > >= == != = ; , ( ) [ ] { } /* */
}TokenType;
typedef enum
{
START,INNUM,INID,INLT,INGT,INEQ,INNOT,INCOMMENT,INDELETE,READDELETE,DONE
//開始、數字、字符、小於等於、大於等於、賦值或等於、註釋開始,註釋內容開始,註釋內容結束,完成和單個字符直接完成
}TokenState;
bool isnum(char a) //檢測當前字符是否爲數字
{
if(a>='0'&&a<='9')
return true;
return false;
}
bool isalpha(char a) //檢測當前字符是否爲字母
{
if((a>='a'&&a<='z')||(a>='A'&&a<='Z'))
return true;
return false;
}
void PrintToken(TokenType token,const string TokenString) //對於每一種狀態輸出每一樣的詞法分類結果
{
if(TokenString!="\0") //如果是空行,那麼跳過,保持格式
cout<<"\t"<<LINE<<":";
switch(token) //根據當前狀態輸出應當匹配的東西
{
case IF:
case INT:
case ELSE:
case RETURN:
case VOID:
case WHILE:
cout<<"reserved word:"<<TokenString<<endl; //六個預留詞都是單獨的狀態
break;
case ADD: printf("+\n"); break;
case SUB: printf("-\n"); break;
case MUL: printf("*\n"); break;
case DIV: printf("/\n"); break;
case L: printf("<\n"); break;
case LE: printf("<=\n"); break;
case G: printf(">\n"); break;
case GE: printf(">=\n"); break;
case ISE: printf("==\n"); break;
case NT: printf("!\n"); break;
case NTE: printf("!=\n"); break;
case FZ: printf("=\n"); break;
case FH: printf(";\n"); break;
case DH: printf(",\n"); break;
case LXK: printf("(\n"); break;
case RXK: printf(")\n"); break;
case LZK: printf("[\n"); break;
case RZK: printf("]\n"); break;
case LDK: printf("{\n"); break;
case RDK: printf("}\n"); break;
case ENDFILE: if(TokenString!="\0") cout<<"COMMENT:"<<TokenString<<endl;break; //ENDFILE作爲註釋結束的輸出,直接輸出整個字符串
case NUM:
cout<<"NUM, val="<<TokenString<<endl; break;
case ID:
cout<<"ID, name="<<TokenString<<endl; break;
case ERROR: //如果輸入符合文法,那麼不會出現這種狀態
cout<<"ERROR:"<<TokenString<<endl; break;
}
}
TokenType Find(string a) //預留詞也是特殊的ID,因此當匹配到ID時,應當檢測它有沒有可能是預留詞
{
if(a=="if") return IF;
else if(a=="else") return ELSE;
else if(a=="int") return INT;
else if(a=="return") return RETURN;
else if(a=="void") return VOID;
else if(a=="while") return WHILE;
else return ID;
}
void getToken(string tmp) //狀態轉換函數
{
bool save=true; //是否將當前字符存入匹配單詞字符
TokenType currentToken; //當前的字符
TokenState state; //當前的狀態
string tokenString=""; //用於存放待匹配的單詞
if(flag==false) //如果當前還處在註釋內部
state=START;
else
state=INDELETE; //那麼繼續返回註釋狀態
for(int i=0;i<=tmp.length();i++) //按照行爲單位讀入
{
save=true;
switch(state) //判斷當前狀態
{
case START: //開始狀態下,之後狀態參考DFA圖片!
if(isnum(tmp[i]))
state=INNUM;
else if(isalpha(tmp[i]))
state=INID;
else if(tmp[i]=='<')
state=INLT;
else if(tmp[i]=='>')
state=INGT;
else if(tmp[i]=='=')
state=INEQ;
else if(tmp[i]=='!')
state=INNOT;
else if(tmp[i]==' '||tmp[i]=='\t'||tmp[i]=='\n')
save=false;
else if(tmp[i]=='/')
state=INCOMMENT;
else
{
state=DONE;
switch(tmp[i])
{
case '\0': //讀取註釋到了最後,仍然沒有結束,此時返回特殊的狀態
save=false;
currentToken=ENDFILE;
break;
case '+':
currentToken=ADD;
break;
case '-':
currentToken=SUB;
break;
case '*':
currentToken=MUL;
break;
case '(':
currentToken=LXK;
break;
case ')':
currentToken=RXK;
break;
case '[':
currentToken=LZK;
break;
case ']':
currentToken=RZK;
break;
case '{':
currentToken=LDK;
break;
case '}':
currentToken=RDK;
break;
case ';':
currentToken=FH;
break;
case ',':
currentToken=DH;
break;
default:
currentToken=ERROR;
break;
}
}
break;
case INCOMMENT: //將進入註釋狀態的時候
if(tmp[i]!='*')
{
save=true; //如果是連續的/*,那麼進入註釋,否則只有一個/,返回除法
state=DONE;
i--;
currentToken=DIV;
}
else
{
state=INDELETE;
flag=true;
}
break;
case INDELETE:
save=true;
if(tmp[i]=='*')
state=READDELETE; //正式進入註釋,如果有一個*,進入到將要退出註釋的狀態
if(tmp[i]=='\0')
{
state=DONE;
currentToken=ENDFILE; //如果當前行所有都已經結束,那麼直接把所有的字符給到註釋
}
break;
case READDELETE: //將要退出註釋的狀態,此時如果再來一個/,退出註釋,否則返回正式註釋狀態
if(tmp[i]=='/')
{
state=DONE;
flag=false;
currentToken=ENDFILE;
}
else
{
i--;
state=INDELETE;
save=false;
}
break;
case INLT: //<和<=第一個字符都是<,=和==第一個字符都是=,>和>=第一個字符都是>,!和!=第一個字符都是!,下面4種狀態,如果沒有匹配到第二個“=”,需要退回一個字符
state=DONE;
if(tmp[i]=='=')
currentToken=LE;
else
{
save=false;
i--;
currentToken=L;
}
break;
case INGT:
state=DONE;
if(tmp[i]=='=')
currentToken=GE;
else
{
save=false;
i--;
currentToken=G;
}
break;
case INEQ:
state=DONE;
if(tmp[i]=='=')
currentToken=ISE;
else
{
save=false;
i--;
currentToken=FZ;
}
break;
case INNOT:
state=DONE;
if(tmp[i]=='=')
currentToken=NTE;
else
{
save=false;
i--;
currentToken=NT;
}
break;
case INNUM: //數字和字母狀態,只要接下來不能連續匹配到數字和字母,那麼就會停止匹配
if(!isdigit(tmp[i]))
{
i--;
save=false;
state=DONE;
currentToken=NUM;
}
break;
case INID:
if(!isalpha(tmp[i]))
{
i--;
save=false;
state=DONE;
currentToken=ID;
}
break;
case DONE:break;
default:
state=DONE;
currentToken=ERROR;
break;
}
if(save && tmp[i]!='\0')
tokenString+=tmp[i]; //保存好當前的字符,加入匹配字符串
if(currentToken==ID) //如果是ID類型,那麼找一找有沒有可能會是預留類
currentToken=Find(tokenString);
if(state==DONE) //如果匹配完畢
{
PrintToken(currentToken,tokenString); //輸出!
tokenString=""; //匹配字符串歸零,開始下一輪
if(flag==false) //flag表示是否在註釋內,如果當前註釋還沒結束,需要退回到註釋內部狀態
state=START;
else
state=INDELETE;
}
}
LINE++;
}
void read() //從lab2.txt讀取數據
{
fstream ff("lab2.txt",ios::in);
assert(ff.is_open());
string tmp;
cout<<"C MINUS COMPILATION:lab2.txt"<<endl;
while(!ff.eof())
{
getline(ff,tmp);
{
cout<<endl;
cout<<"LINE"<<LINE<<":"<<tmp;
if(!ff.eof())
cout<<endl;
}
if(ff.eof())
cout<<"EOF"<<endl;
getToken(tmp);
}
}
int main(int argc,char* argv[])
{
read();
return 0;
}