简单c语言子集词法分析器

时间:2024-03-25 15:36:50
  • 概述

词法分析是编译的第一个环节,其输入是高级语言程序,输出是单词串。词法分析器的主要任务是将高级语言程序作为字符串输入,然后依据词法规则将字符串组合成单词,并输出单词串。

为了方便之后的编译环节,通常将输出的单词串表示成二元组的形式(单词种别码,单词符号的属性值)其中种别码通常用整数表示,按开发者意愿将单词种类分类,相同种类单词使用一个种别码,属性值反映单词符号的特性。

本次实验中保留字、运算符、分界符采用一符一种别码的形式,其定义如表1所示。

简单c语言子集词法分析器

为了使程序较为简单,本次实验中的单词符号采用状态图进行识别,整体状态转换图如图1所示,其中最重要的是对于数字与字母识别的状态转换。

简单c语言子集词法分析器

图1 状态转换图

  • 程序中需要注意的问题

①当使用循环读到不属于相同类型的字符时要注意指针回退问题。

②对于注释中含有和注释中相同类型的字符时需要跳,过例如/*  *  */这种问题。

③程序中的空行不算作有效行。

④对于识别出非法标识符或注释要注意记录行号并记录下来。

  • 程序整体实现思路

由于要分析的c语言子程存放在文本文件test.txt中,所以要涉及文件相关操作,那么来从文件中读取字符串使用

 while(!feof(fpr))
{
char ch = fgetc(fpr);
/*
处理字符
*/
}

对于读取的第一个字符是字母那么要继续读取直到读到非字母或数字的字符,代码如下

 while(!feof(fpr))
{
char ch = fgetc(fpr);
if(isLetter(ch)==||(ch=='_'))
{
word[i++]=ch;//word是字符数组,用来将读取的字符拼凑成单词,等待接下来的处理 ch=fgetc(fpr);
while(isLetter(ch)||isNumber(ch))
{
word[i++]=ch;
ch=fgetc(fpr);
} fseek(fpr,-,);
}
}

注意重点来了,当里面while(isLetter(ch)||isNumber(ch))循环跳出来时此时,字符ch里面存放的是非字母字符,好当while(!feof(fpr))循环没有结束时,程序继续执行char  ch = fgetc(fpr)这句,ch里又被重新赋值。发现问题了吗,ch里跳过了一个字符没被分析,对于词法分析来说要分析到每一个字符来说这可是不行的。

来举个例子对于语句max=1;首先读取是m是字母,好继续读取直到遇到非字母字符=跳出里面循环,此时ch=’=’,程序未将文件内容读完,继续char  ch = fgetc(fpr)这句,这时ch=1,ch=’=’的情况没有进行分析。所以我们应该在使用while循环跳出某种情况时要注意指针回退问题,好的来使用这条语句fseek(fpr,-1,1);即将当前fpr指针回退一个。

同理对于读取的第一个字符是数字,处理情况同上,但是如果继续读取的字符中出现了字母,对于c语言来说就是非法的标识符,需要将其错误输出,代码如下

 while(!feof(fpr))
{
char ch = fgetc(fpr);
if(isNumber(ch))
{
word[i++]=ch;
ch=fgetc(fpr);
if(isLetter(ch))
{
printf("LexicalError,");
fprintf(fpw,"LexicalError,"); while(isLetter(ch))
ch=fgetc(fpr); clearWord(); //将word数组清空,以便后续使用
} else
{
while(isNumber(ch))
{
word[i++]=ch;
ch=fgetc(fpr);
} printf("<2,%s>,",word);//是整数,将(2,word)写入output文件
fprintf(fpw,"<2,%s>,",word);
clearWord();
} fseek(fpr,-,);
}

将字符拼凑成单词以后就要和已知的定义表对比,识别出是关键字还是标识符或者是数字,这部分较为简单,具体步骤可在代码清单中查看。

对于程序中的注释处理,也要注意分为单行和双行两种情况,单行注释较为简单,如果遇到字符’/’则再读一个字符,如果还是’/’那么什么判断也不用做,只需将当前行读完即可。多行注释较为复杂,如果对于注释中也含有字符’*’或’/’的处理较为麻烦,其状态转换图如图2所示

简单c语言子集词法分析器

图2 注释处理状态转换图

代码如下

 else if(ch=='*')//处理多行注释
{
ch=fgetc(fpr);
while(ch!='*'&&(fgetc(fpr)!='/'))//避免注释中出现*,但其后不是/的情况
{
fseek(fpr,-,);
fgetc(fpr);
if(fgetc(fpr)==EOF)//若到文件末尾还没找到注释*/结束符则判错
{
printf("LexicalError,");
fprintf(fpw,"LexicalError,");
break;
}
}
ch=fgetc(fpr);
}

好像还有点小问题,不过对于处理普通多行注释是可以的。

程序中较为复杂的部分已经说完了,那么对于读取的字符未非数字,字母,‘/’‘/*’开头的,则需进行使用多个判断语句就能识别了。

整体代码清单如下

 #include <stdio.h>
#include <stdlib.h>
#include <string.h> char *list[] = {"bsf","zs","+","-","*","/","%","<","<=",">",
">=","==","!=","&&","||","=","(",")","[","]",
"{", "}", ";", ",","void","int","float","char","if","else",
"while","do","return"};
int listNum = ;
int line = ;
int errorNum=;
char ch;
char word[];
int errorLine[]; void clearWord()
{
for(int i=;i<;i++)
{
word[i]='\0';
}
} int isInList(char *name)
{
int i;
for(i=; i<listNum; i++)
{
if(strcmp(name, list[i])==)
{
return i;
}
}
return -;
} int isLetter(char ch)
{
if((ch>='a'&&ch<='z')||(ch>='A'&&ch<='Z'))
return ;
else
return ;
} int isNumber(char ch)
{
if(ch>=''&&ch<='')
return ;
else
return ;
} void dealNote(FILE *fpr,FILE *fpw)//处理注释
{
char ch;
ch=fgetc(fpr); if(ch=='/')//处理单行注释
{
while(ch!='\n')
{
ch=fgetc(fpr);
}
fseek(fpr,-,);
}
else if(ch=='*')//处理多行注释
{
ch=fgetc(fpr);
while(ch!='*'&&(fgetc(fpr)!='/'))//避免注释中出现*,但其后不是/的情况
{
fseek(fpr,-,);
fgetc(fpr);
if(fgetc(fpr)==EOF)//若到文件末尾还没找到注释*/结束符则判错
{
printf("LexicalError,");
fprintf(fpw,"LexicalError,");
break;
}
}
ch=fgetc(fpr);
}
else//否则为/界符
{
printf("<6,->,");//
fprintf(fpw,"<6,->,");
} } void dealEmpty(FILE *fpr,FILE *fpw)//处理空行
{
char buf[];
while(!feof(fpr))
{
fgets(buf,,fpr);
if(buf[]!='\n')
{
fputs(buf,fpw);
} }
} void scanner(FILE *fpr,FILE *fpw)
{ while(!feof(fpr))
{
int i=;
ch = fgetc(fpr); if(ch=='\n')
{
line++;
printf("\n");
fprintf(fpw,"\n");
ch=fgetc(fpr);
} if(ch==' ')
{
while(ch==' ')//忽略空格
{
ch=fgetc(fpr);
} } if(isLetter(ch)==||(ch=='_'))
{
word[i++]=ch; ch=fgetc(fpr);
while(isLetter(ch)||isNumber(ch))
{
word[i++]=ch;
ch=fgetc(fpr);
} int flag = isInList(word);
if(flag !=-)
{
printf("<%d,->,",flag+);//是关键字写入文件output
fprintf(fpw,"<%d,->,",flag+);
clearWord();
}
else
{
printf("<1,%s>,",word);//是标识符,写入文件output
fprintf(fpw,"<1,%s>,",word);
clearWord();
} fseek(fpr,-,);//识别标识符/关键字完毕,退回一个字符
} else if(isNumber(ch))
{
word[i++]=ch; ch=fgetc(fpr);
if(ch=='.')
{
word[i++]=ch; ch=fgetc(fpr);
while(isNumber(ch))
{
word[i++]=ch;
ch=fgetc(fpr);
} printf("<2,%s>,",word);//浮点数,写入文件
fprintf(fpw,"<2,%s>,",word);
clearWord();
} else if(isLetter(ch))
{
printf("LexicalError,"); errorLine[errorNum++]=line;
fprintf(fpw,"LexicalError,"); while(isLetter(ch))
ch=fgetc(fpr); clearWord(); } else
{
while(isNumber(ch))
{
word[i++]=ch;
ch=fgetc(fpr);
} printf("<2,%s>,",word);//是整数,将(2,word)写入output文件
fprintf(fpw,"<2,%s>,",word);
clearWord();
} fseek(fpr,-,);
} else
{
switch(ch)
{
case '+':
printf("<3,->,");//
fprintf(fpw,"<3,->,");
break;
case '-':
word[i++]=ch; ch=fgetc(fpr);
if(isNumber(ch))
{
while(isNumber(ch))
{
word[i++]=ch;
ch=fgetc(fpr);
} printf("<2,%s>,",word);//负数,写入文件
fprintf(fpw,"<2,%s>,",word);
clearWord();
} else
{
printf("<4,->,"); //
fprintf(fpw,"<4,->,");
} fseek(fpr,-,);
break;
case '*':
printf("<5,->,"); //
fprintf(fpw,"<5,->,");
break;
case '/':
dealNote(fpr,fpw);
break;
case '=':
ch=fgetc(fpr);
if(ch=='=')
{
printf("<12,->,"); //
fprintf(fpw,"<12,->,");
}
else
{
fseek(fpr,-,);
printf("<16,->,");//
fprintf(fpw,"<16,->,");
}
break;
case '<':
ch=fgetc(fpr);
if(ch=='=')
{
printf("<9,->,"); //
fprintf(fpw,"<9,->,");
}
else
{
printf("<8,->,");//
fprintf(fpw,"<8,->,");
fseek(fpr,-,);
}
break;
case '>':
ch=fgetc(fpr);
if(ch=='=')
{
printf("<11,->,");//
fprintf(fpw,"<11,->,");
}
else
{
printf("<10,->,");//
fprintf(fpw,"<10,->,");
fseek(fpr,-,);
}
break;
case '!':
ch=fgetc(fpr);
if(ch=='=')
{
printf("<13,->,");//
fprintf(fpw,"<13,->,");
}
else
{
fseek(fpr,-,);
}
break;
case '&':
ch=fgetc(fpr);
if(ch=='&')
{
printf("<14,->,");//
fprintf(fpw,"<14,->,");
}
else
{
fseek(fpr,-,);
}
break;
case '|':
ch=fgetc(fpr);
if(ch=='|')
{
printf("<15,->,");//
fprintf(fpw,"<15,->,");
}
else
{
fseek(fpr,-,);
}
break;
case '(':
printf("<17,->,");//
fprintf(fpw,"<17,->,");
break;
case ')':
printf("<18,->,");//
fprintf(fpw,"<18,->,");
break;
case '[':
printf("<19,->,");//
fprintf(fpw,"<19,->,");
break;
case ']':
printf("<20,->,");//
fprintf(fpw,"<20,->,");
break;
case '{':
printf("<21,->,");//
fprintf(fpw,"<21,->,");
break;
case '}':
printf("<22,->,");//
fprintf(fpw,"<22,->,");
break;
case ';':
printf("<23,->,");//
fprintf(fpw,"<23,->,");
break;
case ',':
printf("<24,->,");//
fprintf(fpw,"<24,->,");
break;
/* default:
//错误
printf("<error>");*/
}
} } } int main()
{
char Filename[];
FILE *fpr,*fpw; printf("请输入读入文件地址:");
scanf("%s",Filename);
fpr=fopen(Filename,"r"); FILE *fpr1 = fopen("F:\\test1.txt","w");
dealEmpty(fpr,fpr1);
fclose(fpr1);//临时存放处理过空行的代码 FILE *fpr2 = fopen("F:\\test1.txt","r"); printf("请输入写出文件地址:");
scanf("%s",Filename);
fpw=fopen(Filename,"w"); scanner(fpr2,fpw); printf("%d\n",line); if(errorNum>)
{
fprintf(fpw,"\nLexicalError(s) on line(s) ");
printf("\nLexicalError(s) on line(s) ");
for(int i=;i<errorNum;i++)
{
fprintf(fpw,"%d,",errorLine[i]);
printf("%d,",errorLine[i]);
}
} return ;
}
  • 程序中还存在的问题:

①过多地方使用硬编码如<1,->等这种形式的输出,这不利于程序的维护。

②程序中主要使用数组这种存储结构,对于读取较多内容的字符不太合适。

③主扫描函数内容过长,可读性不好。