通过设计、编制、调试一个具体的词法分析程序,实现对高级程序设计语言源程序进行扫描过程中将其分解为各种单词的词法分析方法;加深对课堂教学的理解;提高词法分析方法的实践能力。
编制一个读单词过程,源程序保存在文本文件中,读取该文件,识别出各个具有独立意义的单词,即基本保留字、标识符、常数、运算符、分界符五大类。依次输出各个单词的内部单词种别及单词符号自身值,遇到错误时可显示“Eorror”,然后跳过错误部分继续显示。
单词及其内部编码如下表要求:
单词类别 | 单词自身值 | 内部编码 |
---|---|---|
保留字 | int、for、while、do、return、break、continue | 1 |
标识符 | 除保留字外的以字母开头,后跟字母、数字的字符序列 | 2 |
常 数 | 无符号整型数 | 3 |
运算符 | +、-、*、/、=、>、<、>=、<=、!= | 4 |
分界符 | ,、;、{、}、(、) | 5 |
大致的思路就是将文本一个字符一个字符地提取出来,分别对字符进行判断,并将符合条件的字符提取出来,贴上标签。
结构体中包含数组words用来存储字符串,整型变量label作为标签,用来记录为个字符串的类型。
typedef struct chars
{
char words[20]; //字符串
int label; //标签
}chars;
FILE* f; //表示打开的文件
chars ptr[MAX]; // 存储所有的单词和符号及其标签
char ch; // 表示当前从文件中提取出来的字符
int k = 0; // 作为结构体数组ptr的索引,每当有内容存入到ptr中时,k就会加1
// 运算符
char operators[10] = {'+','-','*','/','=','>','<','!',':'};
//保留字,也叫作关键字
char keywords[7][10] = {"int","for","while","do","return","break","continue"};
//分界符
char delimiters[] = {',',';','{','}','(',')'};
构建一个名为extracttext的子函数,用fgetc() 方法将文件中的信息一个字节一个字节地提取出来,并将其赋值给ch,然后对ch进行判断。因为是一个字符一个字符进行,所以需要循环,使用while循环,调用C语言自带的函数feof(),该函数的作用是判断文件是否结束,若文件已经读取完毕,返回非零值,否则返回零。所以,当feof(f) 返回非零值时,即:文件f结束时,跳出循环。
void extracttext(){
//int i = 0,j = 0;
ch = fgetc(f);
while(!feof(f))
{
if(ischar()==1){ // 判断字符是不是字母
get_words();
}
else if(isnumber()==1){ // 判断字符是不是数字
get_numbers();
}
else if(isoperators()==1){ // 判断字符是不是运算符
get_operators();
}
else if(isdelimiters()==1){ // 判断字符是不是分界符
get_delimiters();
}
else{
ch = fgetc(f); // 以上都不符合的话就跳到下一个字符
}
}
}
判断ch是否为字符,若不是字符,则返回 -1 ,若是,则返回 1。
int ischar()
{
if(ch>='a'&&ch<='z'||ch>='A'&&ch<='Z')
return 1;
return -1;
}
如果ch是字符,就需要分两种情况进行讨论。第一种情况是单个字符,第二种情况是一个字符串。
所以在get_words()中,使用一个while循环来提取字符串。当ch为空格,回车或大空格时,说明这个单词已经结束了,所以将其作为循环结束的标志。但同时,如果ch为运算符和分解符时,也标志着单词的结束,故在while循环开始的时候就会有一个判断,如果ch为分界符或运算符,则break,跳出循环,否者将出ch存入到ptr中。这样就可以把字符和字符串分开。
当循环跳出时,如果存入的是一个字符串,也会有两种情况。第一种情况是关键字,第二种情况是一个标识符。需要进一步地去判断。最后就是k进行自加。
void get_words(){
int j = 0;
ptr[k].words[j] = 0;
while(ch!=' '&&ch!='\n'&&ch!='\t'){
if(isdelimiters() == 1 || isoperators() == 1){
break;
}
else{
ptr[k].words[j] = ch;
j = j + 1;
}
ch = fgetc(f);
}
if(iskeywords()==1){
ptr[k].label = 1;
}
else{
ptr[k].label = 2;
}
k = k + 1;
}
判断当前提取出来的字符串是否为关键字,若不是,则返回 -1,若是,则返回1。
int iskeywords()
{
int i;
for(i=0;i<11;i++)
if(strcmp(keywords[i],ptr[k].words) == 0)
return 1;
return -1;
}
判断ch是否为运算符,若不是运算符,则返回 -1,若是,则返回1。
int isoperators()
{
int i;
for(i=0;i<12;i++)
if(ch == operators[i])
return 1;
return -1;
}
判断ch是否为分界符,若不是分界符,则返回 -1,若是,则返回1。
int isdelimiters()
{
int i=0;
for(i=0;i<6;i++)
if(ch == delimiters[i])
return 1;
return -1;
}
判断ch是否为数字,若不是数字,则返回 -1,若是,则返回1。
int isnumber()
{
if(ch>='0'&&ch<='9')
return 1;
return -1;
}
如果ch是数字,也是有两种情况,个位数和多位数。具体的思路与子函数get_words() 基本相同,唯一不同的一点是在C语言中数字后面跟字母是不合法的需要跳过,并进行报错。所以在子函数get_numbers()中定义一个i,让其等于k,作为结构体ptr的标志,循环一开始就要对ch进行判断,看其是否为字母,若是字母则输出错误提示信息,并让i加1。循环结束时,用i和k进行比较,若相等,则说明没有报错,则k自增1;若不相等,则k不进行自增,这样下一个字符就可以把错误信息给覆盖掉了。
void get_numbers(){
int j = 0,i = 0;
i = k;
while(ch!=' '&&ch!='\n'&&ch!='\t'){
if(ischar() == 1){
printf("error:This is an illegal name.\n");
i = i + 1;
}
else if(isdelimiters() == 1 || isoperators() == 1){
break;
}
else if(isnumber() == 1){
ptr[k].words[j] = ch;
j = j + 1;
ptr[k].label = 3;
}
ch = fgetc(f);
}
if(i == k)
k = k + 1;
}
如果ch是运算符,可以直接将ch存入到ptr中,并将标签定为4。但是还需要考虑大于等于,小于等于和不等于的情况,所以,调用子函数isoperators()对下一个ch进行判断,如果也是运算符,则存入ptr中。
void get_operators(){
ptr[k].words[0] = ch;
ptr[k].label = 4;
ch = fgetc(f);
if(isoperators() == 1){
ptr[k].words[1] = ch;
ch = fgetc(f);
}
k = k + 1;
}
如果ch是分界符,可以直接将ch存入到ptr中,并将标签定为5。
void get_delimiters(){
ptr[k].words[0] = ch;
ptr[k].label = 5;
k = k + 1;
ch = fgetc(f);
}