開篇
編譯,簡單的說,就是把源程序轉換為可執行程序。從hello world 說程序運行機制 里面簡單的說明了程序運行的過程,以及一個程序是如何一步步變成可執行文件的。在這個過程中,編譯器做了很多重要的工作。對底層該興趣的我,自然的,也就迫切想搞清楚編譯的內部實現,也就是編譯的原理。
這篇文章主要說的是編譯器前端,詞法分析器的原理,最后會給出一個詞法分析器的簡單實現。
介紹
編譯簡單的說,就是把源程序轉化為另一種形式的程序,而其中關鍵的部分就是理解源程序所要表達的意思,才能轉化為另一種源程序。
可以用一個比喻來說明問題:人A和人B想要交談,但是他們都不知道彼此的語言,這就需要一個翻譯C,同時懂得A和B的語言。有了C做中間層,A和B才能正常交流。C的作用就有點像編譯器,它必須能理解源程序所要表達的意思,才能把信息傳遞給另一個。
編譯器也一樣,它的輸入是語言的源文件(一般可以是文本文件)對於輸入的文件,首先要分離出這個輸入文件的每個元素(關鍵字、變量、符號、、)
然后根據語言的文法,分析這些元素的組合是否合法,以及這些組合所表達的意思。
程序設計語言和自然語言不一樣,都是用符號來描述,每個特定的符號表示特定的意思,而且程序設計語言是上下文無關的。上下文無關就是某一個特定語句所要表達的意思和它所處的上下文沒有關系,只有它自身決定。
這篇博文主要說的就是詞法分析,也就是把輸入的符號串整理成特定的詞素。
詞法分析
示例:
比如如下的代碼段:
while(i>=j) i--
詞法分析器設計
分析器的簡單實現
上文主要介紹了詞法分析的一些相關的知識,而對詞法分析器的具體實現還沒有具體提到,為了能更好的理解詞法分析,我寫了一個簡單的詞法分析器。
雖然說是語法分析器,但實現的功能很簡單,只是對輸入的程序把注釋去掉,其中用到了上面關於狀態轉換圖部分的知識。
分析:
一般的程序設計語言, 注釋部分的形式為;
/* 注釋部分、、、、*/
我們的程序總是順序的一個一個字符讀取輸入文件的。我們的目的是把注釋部分去掉,那么對於輸入的字符流,我們只要識別出“/*”就知道后面的部分是注釋部分,直到識別輸入流中出現"*/"為止。
對字符流的處理是一個一個進行的,每讀入一個字符,就判斷,如果字符是“/”,就說明后面 的部分可能是注釋,再看下一個輸入字符,如果是“*”, 就是上面所說的情況:“ /*”那么后面的部分就是注釋部分,然后再用相同的方法找出"*/"就可以了。
這個識別的過程就可以用狀態轉換圖來清晰的表示:
對於讀入的每個符號都要進行判斷,如果是“/”說明后面的部分有可能是注釋,進入狀態1。如果后面的輸入是“*”那么就可以確定以后的內容為注釋內容,如果后面的輸入不是"*",說明后面的內容不是注釋,前面出現的"/"可能是做除號使用,如“5/3”
其實上面的流程圖也就對應了程序實現的邏輯,可以用switch-case 來實現,對於每個輸入,判斷后跳轉到相應的狀態,然后繼續判斷。
下面是程序偽代碼:
while((ch=getchar())!=EOF)
switch(state)
case 1 :if ch=="/",state=2,break;
case 2: if ch=="*",state=3
else state=1;break;
case 3:..........
case 4:..........
詞法分析器
這個程序比較簡單,就不給出源代碼了。接下來是一個簡單的詞法分析器的代碼,可以實現對關鍵字(如 while end if 等),對數字的識別,去掉空格符等。
下面是這個分析器的功能:
1、 待分析的簡單語言的詞法
(1) 關鍵字:
begin if then while do end
所有關鍵字都是小寫。
(2) 運算符和界符:
:= + – * / < <= <> > >= = ; ( ) #
(3) 其他單詞是標識符(ID)和整型常數(NUM),通過以下正規式定義:
ID=letter(letter| digit)*
NUM=digit digit *
(4) 空格由空白、制表符和換行符組成。空格一般用來分隔ID、NUM,運算符、界符和關鍵字,詞法分析階段通常被忽略。
2、 各種單詞符號對應的種別碼
詞法分析程序的功能
輸入:所給文法的源程序字符串。
輸出:二元組(syn,token或sum)構成的序列。
其中:syn為單詞種別碼;
token為存放的單詞自身字符串;
sum為整型常數。
下面是程序源代碼,基於上面的討論,應該比較好了解了。
#include<stdio.h> #include<string.h> #include<iostream.h> char prog[80],token[8]; char ch; int syn,p,m=0,n,row,sum=0; char *rwtab[6]={"begin","if","then","while","do","end"}; void scaner() { /* 共分為三大塊,分別是標示符、數字、符號,對應下面的 if else if 和 else */ for(n=0;n<8;n++) token[n]=NULL; ch=prog[p++]; while(ch==' ') { ch=prog[p]; p++; } if((ch>='a'&&ch<='z')||(ch>='A'&&ch<='Z')) //可能是標示符或者變量名 { m=0; while((ch>='0'&&ch<='9')||(ch>='a'&&ch<='z')||(ch>='A'&&ch<='Z')) { token[m++]=ch; ch=prog[p++]; } token[m++]='\0'; p--; syn=10; for(n=0;n<6;n++) //將識別出來的字符和已定義的標示符作比較, if(strcmp(token,rwtab[n])==0) { syn=n+1; break; } } else if((ch>='0'&&ch<='9')) //數字 { { sum=0; while((ch>='0'&&ch<='9')) { sum=sum*10+ch-'0'; ch=prog[p++]; } } p--; syn=11; if(sum>32767) syn=-1; } else switch(ch) //其他字符 { case'<':m=0;token[m++]=ch; ch=prog[p++]; if(ch=='>') { syn=21; token[m++]=ch; } else if(ch=='=') { syn=22; token[m++]=ch; } else { syn=23; p--; } break; case'>':m=0;token[m++]=ch; ch=prog[p++]; if(ch=='=') { syn=24; token[m++]=ch; } else { syn=20; p--; } break; case':':m=0;token[m++]=ch; ch=prog[p++]; if(ch=='=') { syn=18; token[m++]=ch; } else { syn=17; p--; } break; case'*':syn=13;token[0]=ch;break; case'/':syn=14;token[0]=ch;break; case'+':syn=15;token[0]=ch;break; case'-':syn=16;token[0]=ch;break; case'=':syn=25;token[0]=ch;break; case';':syn=26;token[0]=ch;break; case'(':syn=27;token[0]=ch;break; case')':syn=28;token[0]=ch;break; case'#':syn=0;token[0]=ch;break; case'\n':syn=-2;break; default: syn=-1;break; } } int main() { p=0; row=1; cout<<"Please input string:"<<endl; do { cin.get(ch); prog[p++]=ch; } while(ch!='#'); p=0; do { scaner(); switch(syn) { case 11: cout<<"("<<syn<<","<<sum<<")"<<endl; break; case -1: cout<<"Error in row "<<row<<"!"<<endl; break; case -2: row=row++;break; default: cout<<"("<<syn<<","<<token<<")"<<endl;break; } } while (syn!=0); }
改程序在C-free5上調試通過
下面是程序截圖:
小結
這里主要說的是編譯器中的詞法分析,還介紹了詞法分析的一些相關知識,最后給出了一個很簡單的詞法分析器的實現。
參看資料:編譯原理
如有轉載請注明出處:http://www.cnblogs.com/yanlingyin/
一條魚、尹雁鈴@ 博客園 2012-17
E-mail:yanlingyin@yeah.net