編譯原理中一個實用的詞法分析程序
要進行程序的詞法分析,就要先看看自動機的定義:


那么,一個自動機到底起什么作用呢?用人話說,就是判斷計算機程序里面的每一個單詞是否輸入正確,以及這個單詞屬于什么類型。
本文在上述理論基礎(chǔ)上,借用一個網(wǎng)絡(luò)上的實用詞法分析程序,對編譯中的詞法分析原理進行一些解讀。
假設(shè)輸入如下一段待編譯的程序:
main()
{
int a=-5,b=4,j;
if(a>=b)
j=a-b;
else j=b-a;
}
圖3
對照這個程序,我們來理解圖1圖2的內(nèi)容。
假設(shè)我們現(xiàn)在碰到了這個程序的第一個字母m,那么這個m就是圖1中的初態(tài)S0,因為m后面既可以跟字母、數(shù)字,又可以跟符號什么的,而后面跟隨的字符類型不同,就可能決定了m開頭的這個字符串屬于不同的類型集合,比如m1、m=可能是一個變量,而ma可能是一個函數(shù)名,也可能是一個字符串等等,圖1中的K就代表不同類型的集合(變量、數(shù)字、字符串、保留字等等),那么,圖2的意思就是,由開始的字符出發(fā),當把這一串字符都讀入以后,然后判斷讀入的這串字符屬于什么類型(進入終態(tài)),當然,在逐個讀入字符的過程中,這個字符串有可能進入不同的狀態(tài),比如,i->變量;in->變量;int->關(guān)鍵字,讀到空格鍵結(jié)束。這個分析過程叫詞法分析。
圖3的程序經(jīng)過詞法分析以后要求輸出如下:
(2,”main”)?(5,”(”)?(5,”)”)
(5,”{”)?(1,”int”)?(2,”a”)
(4,”=”)?(3,”-5”)?(5,”,”)
(2,”b”)?(4,”=”)?(3,”4”)
(5,”,”)?(2,”j”)?(5,”;”)
(1,”if”)?(5,”(”)?(2,”a”)
(4,”>=”)?(2,”b”)?(5,”)”)
(2,”j”)?(4,”=”)?(2,”a”)
(4,”-”)?(2,”b”)?(5,”;”)
(1,”else”)?(2,”j”)?(4,”=”)
(2,”b”)?(4,”-”)?(2,”a”)
(5,”;”)?(5,”}”)
一、?實驗理論依據(jù)
(一)識別各種單詞符號
程序語言的單詞符號一般分為五種類型:
1:關(guān)鍵字(保留字/ 基本字)if 、while 、begin…
2:標識符:常量名、變量名、函數(shù)名…
3:常數(shù):34 、56.78 、true 、‘a(chǎn)’ 、…
4:運算符:+ 、- 、* 、/ 、〈 、and 、or 、….
5:界限符:, ; ( ) { } /*…
識別單詞:掌握單詞的構(gòu)成規(guī)則很重要
標識符的識別:字母| 下劃線+( 字母/ 數(shù)字/ 下劃線)
關(guān)鍵字的識別:與標識符相同,最后查表
常數(shù)的識別
界符和算符的識別
大多數(shù)程序設(shè)計語言的單詞符號都可以用轉(zhuǎn)換圖來識別,如圖4

圖4
詞法分析器輸出的單詞符號常常表示為二元式:(單詞種別,單詞符號的屬性值)
單詞種別通常用整數(shù)編碼,如1 代表關(guān)鍵字,2 代表標識符等
(二)超前搜索方法
詞法分析時,常常會用到超前搜索方法。
如當前待分析字符串為“a>+” ,當前字符為“>” ,此時,分析器倒底是將其分析為大于關(guān)系運算符還是大于等于關(guān)系運算符呢?
顯然,只有知道下一個字符是什么才能下結(jié)論。于是分析器讀入下一個字符’+’ ,這時可知應(yīng)將’>’ 解釋為大于運算符。但此時,超前讀了一個字符’+’ ,所以要回退一個字符,詞法分析器才能正常運行。又比如:‘+’ 分析為正號還是加法符號
(三)預(yù)處理
預(yù)處理工作包括對空白符、跳格符、回車符和換行符等編輯性字符的處理,及刪除注解等。由一個預(yù)處理子程序來完成。
二、詞法分析器的設(shè)計
1、 設(shè)計方法:
2、 寫出該語言的詞法規(guī)則。
3、 把詞法規(guī)則轉(zhuǎn)換為相應(yīng)的狀態(tài)轉(zhuǎn)換圖。
4、 把各轉(zhuǎn)換圖的初態(tài)連在一起,構(gòu)成識別該語言的自動機
5、 設(shè)計掃描器
6、 把掃描器作為語法分析的一個過程,當語法分析需要一個單詞時,就調(diào)用掃描器。
掃描器從初態(tài)出發(fā),當識別一個單詞后便進入終態(tài),送出二元式
#include <stdio.h>
#include <string.h>
FILE *fp;
char cbuffer;
char *key[8]={"if","else","for","while","do","return","break","continue"};
int atype,id=4;
int isalpha( char c) 判斷讀入的字符是否是字母
{
if((c<='z')&&(c>='a')||(c<='Z')&&(c>='A'))
return 1;
else return 0;
}
int isdigit(char c)
{
if(c>='0'&&c<='9')
return 1;
else return 0;
}
int search(char searchchar[ ],int wordtype) /*判斷單詞是保留字還是標識符*/
{ 保留字有8個
int i=0;
int p;
switch (wordtype)
{
case 1:for (i=0;i<=7;i++)
{
if (strcmp(key[i],searchchar)==0)
{ p=i+1; break; } /*是保留字則p為非0且不重復(fù)的整數(shù)*/
else p=0; /*不是保留字則用于返回的p=0*/
}
return(p);
}
}
char alphaprocess(char buffer)
{ int atype; /*保留字數(shù)組中的位置*/
int i=-1;
char alphatp[20];
while ((isalpha(buffer))||(isdigit(buffer))||buffer=='_')
{
alphatp[++i]=buffer;
buffer=fgetc(fp);
} /*讀一個完整的單詞放入alphatp數(shù)組中*/
alphatp[i+1]='\0';
atype=search(alphatp,1);/*對此單詞調(diào)用search函數(shù)判斷類型*/
if(atype!=0)
{ printf("(%d,\"%s\")\n",atype-1,alphatp); id=1; } 是保留字則輸出類型1
else
{ printf("(2,\"%s\")\n",alphatp); id=2; } 是函數(shù)名、標識符等則輸出類型2
return (buffer);
}
char digitprocess(char buffer) 數(shù)字只能全部由0-9構(gòu)成
{
int i=-1;
char digittp[20];
while ((isdigit(buffer))||buffer=='.')?/*1 判斷小數(shù)*/
{
digittp[++i]=buffer;
buffer=fgetc(fp);
}
digittp[i+1]='\0';
printf("(3,\"%s\")\n",digittp); 是數(shù)字則輸出類型3
id=3;
return(buffer);
}
char otherprocess(char buffer)
{
char ch[20];
ch[0]=buffer;
ch[1]='\0';
if(ch[0]==','||ch[0]==';'||ch[0]=='{'||ch[0]=='}'||ch[0]=='('||ch[0]==')')
{ printf("(5,\"%s\")\n",ch); 是界符則輸出類型5
buffer=fgetc(fp);
id=5;
return(buffer);}
if(ch[0]=='/')
{
buffer=fgetc(fp);
if(buffer=='*') /*2 區(qū)分注釋符號和除號*/
{
ch[1]=buffer;
buffer=fgetc(fp);
while(buffer!='*')
{
buffer=fgetc(fp);
}
ch[2]=buffer;
buffer=fgetc(fp);
if(buffer=='/')
{
ch[3]=buffer;
ch[4]='\0';
}
printf("(5,\"%s\")\n",ch);
}
else{
printf("(4,\"%s\")\n",ch); 是運算符輸出類型4
id=4;
return(buffer);
}
buffer=fgetc(fp);
id=5;
return(buffer);
}
if(ch[0]=='*')
{ printf("(4,\"%s\")\n",ch);
buffer=fgetc(fp);
id=4;
return(buffer);
}
if(ch[0]=='='||ch[0]=='!'||ch[0]=='<'||ch[0]=='>')
{ buffer=fgetc(fp);
if(buffer=='=')
{ ch[1]=buffer;
ch[2]='\0';
printf("(4,\"%s\")\n",ch);
}
else
{ printf("(4,\"%s\")\n",ch);
id=4;
return(buffer);
}
buffer=fgetc(fp);
id=4;
return(buffer);
}
if(ch[0]=='+'||ch[0]=='-')
{ if(id==4) /*在當前符號以前是運算符,則此時為正負號*/
{ buffer=fgetc(fp);
ch[1]=buffer;
ch[2]='\0';
printf("(3,\"%s\")\n",ch);
id=3;
buffer=fgetc(fp);
return(buffer);
}
ch[1]='\0';
printf("(4,\"%s\")\n",ch);
buffer=fgetc(fp);
id=4;
return(buffer);
}
if(ch[0]=='#')?/*3 識別頭文件*/
{
char t[20];
int i=0;
t[0]=ch[0];
buffer=fgetc(fp);
while((isalpha(buffer))||buffer==' '||buffer=='<'||buffer=='>')
{
t[++i]=buffer;
buffer=fgetc(fp);
}
t[i+1]='\0';
printf("(6,\"%s\")\n",t);
id=6;
return (buffer);
}
if(ch[0]=='\\')?/*4 識別轉(zhuǎn)義符號*/
{
buffer=fgetc(fp);
ch[1]=buffer;
printf("(6,\"%s\")\n",ch); 轉(zhuǎn)義符號輸出類型6
buffer=fgetc(fp);
return(buffer);
}
if(ch[0]=='|'||ch[0]=='&')?/*5 判斷或與*/
{
buffer=fgetc(fp);
if(ch[0]==buffer)
{
ch[1]=buffer;
}
ch[2]='\0';
printf("(4,\"%s\")\n",ch);
buffer=fgetc(fp);
return(buffer);
}
if(ch[0]=='"'||ch[0]=='\'')?/*6 判斷雙引號和單引號*/
{
printf("(5,\"%s\")\n",ch);
id=5;
buffer=fgetc(fp);
return(buffer);
}
}
int main()
{
if ((fp=fopen("example.c","r"))==NULL) /*只讀方式打開一個文件*/
printf("error");
else
{
cbuffer = fgetc(fp); /*fgetc( )函數(shù):從磁盤文件讀取一個字符*/
while (cbuffer!=EOF)
{
if(cbuffer==' '||cbuffer=='\n') /*掠過空格和回車符*/
cbuffer=fgetc(fp);
else
if(isalpha(cbuffer))
cbuffer=alphaprocess(cbuffer);
else
if (isdigit(cbuffer))
cbuffer=digitprocess(cbuffer);
else cbuffer=otherprocess(cbuffer);
}
}
return 0;
}
程序運行結(jié)果:

縱觀整個詞法分析程序,難度并不大,就是判斷保留字、標識符、數(shù)字、運算符和界符,程序里面還有判斷轉(zhuǎn)義字符和注釋符的功能,這么一個簡單的程序,卻可以讓我們真正了解實際的編譯程序大概是怎么構(gòu)成的,在此基礎(chǔ)上,就可以開發(fā)出新的語言,等等。