詞法分析的原理
詞法分析是編譯程序進行編譯時第一個要進行的任務,主要是對源程序進行編譯預處理之後,對整個源程序進行分解,分解成一個個單詞,這些單詞有且只有五類,分別時標識符、關鍵字(保留字)、常數、運算符、界符。以便爲之後的語法分析和語義分析做準備。
詞法分析面對的對象是單個的字符,目的是把它們組成有效的單詞(字符串);而語法的分析則是利用詞法分析的結果作爲輸入來分析是否符合語法規則並且進行語法制導下的語義分析,最後產生四元組(中間代碼),進行優化之後最終生成目標代碼。由此可見詞法分析是所有後續工作的基礎,如果這一步出錯,就會對下文造成不可挽回的影響。因此在進行詞法分析的時候一定要定義好這五種符號的集合。
符號的分類
第一類:關鍵字(還可以繼續添加補充)
指針1 |
關鍵字 |
0 |
BEGIN |
1 |
DO |
2 |
ELSE |
3 |
END |
4 |
IF |
5 |
THEN |
6 |
VAR |
7 |
WHILE |
表1-關鍵字表
第二類:標識符 letter(letter|digit)*無窮集
第三類:常數 (digit)+無窮集
第四類:分界符(還可以繼續添加補充)
指針1 |
分界符 |
0 |
, |
1 |
; |
2 |
。 |
3 |
:= |
4 |
( |
5 |
) |
表2-分界符表
第五類:運算符
i值 |
算術運算符 |
10H |
+ |
11H |
- |
20H |
* |
21H |
/ |
表3-算術運算符表
i值 |
關係運算符 |
00H |
< |
01H |
<= |
02H |
= |
03H |
> |
04H |
>= |
05H |
<> |
表4-關係運算符表
最後輸出的一個結果是一個二元組,所有識別出的單詞都用兩個字節的等長表示,稱爲內部碼。第一個字節爲 t , 第二個字節爲 i 。 t 爲單詞的種類。關鍵字的 t=1;分界符的 t=2;算術運算符的 t=3;關係運算符的 t=4;無符號數的 t=5;標識符的 t=6。i 爲該單詞在各自表中的指 針或內部碼值。表1 爲關鍵字表;表2爲分界符表;表 3 爲算術運算符的 i 值; 表4爲關係運算符的 i 值。
轉態轉換圖
圖1-詞法分析的轉態轉換圖
詞法分析程序的設計
圖2 詞法分析的總流程
詞法分析將從文件中讀入的一個個的字符,根據一定的構詞規則,識別出各類有用的單詞。
當讀入的第一個字符是字母時(IsLetter(char c)),開始識別標識符或關鍵字,邊拼寫邊從緩衝區讀入下一符號,當讀入一個非字母符號時,標識符識別完成,但已經多讀入一個符號,所以調用文件操作函數fseek(fp,-1,1),回退一個字符。
其後查找關鍵字表,判斷拼出的符號串是否爲關鍵字。若是關鍵字,則在文件中寫入這個單詞,以及通過二元組寫出這個單詞的種類和單詞的指針或內部碼(例:begin (1,1))。
否則識別的單詞就是標識符,然後將該標識符與標識符表(mark)中的單詞比較,若該標識符存在標識符表中,就在文件中寫入該標識符,以及通過二元組寫出這個單詞的種類和在標識符表中所佔的位數(例:IsLetter (6,q));若該標識符不存在標識符表中,就在標識符表中插入該標識符,然後在文件中寫入該標識符,以及通過二元組寫出這個單詞的種類和在標識符表中第幾位(例:IsDigit(6,line+1))。
當讀入的第一個字符是數字時,開始識別整數或實數,邊拼寫邊讀入下一個字符,當遇到其他非數字符號時,數字常數拼寫完畢,同時也要調用文件操作函數fseek(fp,-1,1),回退一個字符。
當讀入的第一個字符既不是字母也不是數字時,需要去判斷該字符是分界符、算術運算符還是關係運算符,還要通過一個二元組,輸出它的種類和內部碼值。
實驗結果
輸入輸出數據:
輸入:
program example ;
const
a=1;
b+3;
c :=fasdf;
if(a +3 *c > b)
then c:=3;#
輸出:
字符串 |
(t,i)t:字符串種類;i:內部碼值 |
種類 |
const |
(6,3) |
標識符 |
a |
(6,4) |
標識符 |
= |
(4,02H) |
關係運算符 |
1 |
(5,1) |
無符號數 |
; |
(2,1) |
分界符 |
b |
(6,5) |
標識符 |
+ |
(3,10H) |
算術運算符 |
3 |
(5,2) |
無符號數 |
; |
(2,1) |
分界符 |
c |
(6,6) |
標識符 |
:= |
(2,3) |
分界符 |
fasdf |
(6,7) |
標識符 |
; |
(2,1) |
分界符 |
if |
(1,5) |
關鍵字 |
( |
(2,4) |
分界符 |
a |
(6,4) |
標識符 |
+ |
(3,10H) |
算術運算符 |
3 |
(5,2) |
無符號數 |
* |
(3,20H) |
算術運算符 |
c |
(6,6) |
標識符 |
> |
(4,03H) |
關係運算符 |
b |
(6,5) |
標識符 |
) |
(2,5) |
分界符 |
then |
(1,6) |
關鍵字 |
c |
(6,6) |
標識符 |
:= |
(2,3) |
分界符 |
3 |
(5,2) |
無符號數 |
; |
(2,1) |
分界符 |
附錄:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
char *key[8]={"BEGIN","DO","ELSE","END","IF","THEN","VAR","WHILE"};
char Word[20];
int ch; //存儲識別單詞
int number[1000][100]; //常數表
char mark[100][5];
int line=0,row=0;
int IsLetter(char c) //判斷是否爲字母
{
if((c>='a'&&c<='z')||(c>='A'&&c<='Z'))
{
return 1;
}
else
return 0;
}
int IsDigit(char c) //判斷是否爲數字
{
if(c>='0'&&c<='9'){
return 1;
}
else
return 0;
}
int IsKeyWord(char *Word)
{
int i,m;
for(i=0;i<8;i++)
{
if(m=strcasecmp(Word,key[i])==0) //不區分大小寫比較 strcmp區分大小寫比較
{
return i+1; //判斷爲關鍵字
}
}
if(line!=0) //判斷標識符是否在標識表中
{
int q=0;
while(q<line)
{
if(strcmp(Word,mark[q++])==0) //標識符在標識符表中存在
{
printf("%s\t(6,%d)標識符\n\n",Word,q);
return 1; //標識符
}
}
}
strcpy(mark[line],Word); //標識符在標識表中不存在,將標識符保存到標識符表中
printf("%s\t(6,%d)標識符\n\n",Word,line+1);
line++;
return 1;
}
void scanner(FILE *fp) //掃描函數
{
char Word[20]={'\0'};
char ch;
int i,c;
ch=fgetc(fp); //獲取文件中的字符
if(IsLetter(ch))
{
Word[0]=ch;
ch=fgetc(fp);
i=1;
while(IsDigit(ch)||IsLetter(ch))
{
Word[i]=ch;
i++;
ch=fgetc(fp);
}
Word[i]='\0';
fseek(fp,-1,1);
c=IsKeyWord(Word);
if(c!=1)
{
printf("%s\t(1,%d)關鍵字\n\n",Word,c);
}
}
else if(IsDigit(ch))
{
Word[0]=ch;
ch=fgetc(fp);
i=1;
while(IsDigit(ch)){
Word[i]=ch;
i++;
ch=fgetc(fp);
}
Word[i]='\0';
fseek(fp,-1,1);
int num=atoi(Word); //將字符串型轉換成int型
if(row!=0)
{
int y;
for(y=0;y<1000;y++)
{
int w=number[y][10];
int sum=0;
int d;
for(d=1;d<number[y][0];d++)
{
w=w-1;
sum=sum+number[y][d]*pow(2,w);
}
if(num==sum)
{
printf("%d\t(5,%d)無符號數\n\n",num,y+1);
}
}
}
int z=num,c=num;
int m=0;
do
{
z=z/2;
m++;
}while(z!=0);
for(int n=m;n>0;n--){
number[row][n]=c%2;
c=c/2;
}
number[row][0]=m;
int line=row;
printf("%d\t(5,%d)無符號數\n\n",num,line+1);
row++;
}
else
{
Word[0]=ch;
switch(ch)
{
case',':
printf("%s\t(2,0)分界符\n\n",Word);
break;
case ';':
printf("%s\t(2,1)分界符\n\n",Word);
break;
case'.':
printf("%s\t(2,2)分界符\n\n",Word);
break;
case':':
ch=fgetc(fp);
Word[1]=ch;
if(ch=='=')
{
printf("%s\t(2,3)分界符\n\n",Word);
}
else
{
fseek(fp,-1,1);
printf("%s\t(2,6)分界符\n\n",Word);
}
break;
case')':
printf("%s\t(2,5)分界符\n\n",Word);
break;
case'(':
printf("%s\t(2,4)分界符\n\n",Word);
break;
case'+':
printf("%s\t(3,10H)算術運算符\n\n",Word);
break;
case'-':
printf("%s\t(3,11H)算術運算符\n\n",Word);
break;
case'*':
printf("%s\t(3,20H)算術運算符方\n\n",Word);
break;
case'/':
printf("%s\t(3,21H)算術運算符\n\n",Word);
break;
case'<':
ch=fgetc(fp);
Word[1]=ch;
if(ch=='=')
{
printf("%s\t(4,01H)關係運算符\n\n",Word);
}
else if(ch=='>')
{
printf("%s\t(4,05H)關係運算符\n\n",Word);
}
else
{
fseek(fp,-1,1);
printf("%s\t(4,00H)關係運算符\n\n",Word);
}
break;
case'=':
printf("%s\t(4,02H)關係運算符\n\n",Word);
break;
case'>':
ch=fgetc(fp);
Word[1]=ch;
if(ch=='=')
{
printf("%s\t(4,04H)關係運算符\n\n",Word);
}
else
{
fseek(fp,-1,1);
printf("%s\t(4,03H)關係運算符\n\n",Word);
}
break;
}
}
}
int main()
{
FILE *fp;
fp=fopen("E:\\編譯原理\\test.txt","r");
do{
ch=fgetc(fp);
if(ch=='#')
{
break;
}
else if(ch==' '||ch=='\t'||ch=='\n')
{
}
else{
fseek(fp,-1,1); //fp指向文件的位置後退一個字符
scanner(fp);
}
} while(ch!='#');
return 0;
}