词法分析的原理
词法分析是编译程序进行编译时第一个要进行的任务,主要是对源程序进行编译预处理之后,对整个源程序进行分解,分解成一个个单词,这些单词有且只有五类,分别时标识符、关键字(保留字)、常数、运算符、界符。以便为之后的语法分析和语义分析做准备。
词法分析面对的对象是单个的字符,目的是把它们组成有效的单词(字符串);而语法的分析则是利用词法分析的结果作为输入来分析是否符合语法规则并且进行语法制导下的语义分析,最后产生四元组(中间代码),进行优化之后最终生成目标代码。由此可见词法分析是所有后续工作的基础,如果这一步出错,就会对下文造成不可挽回的影响。因此在进行词法分析的时候一定要定义好这五种符号的集合。
符号的分类
第一类:关键字(还可以继续添加补充)
指针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;
}