一、前言
词法分析和语法分析是编译原理中必要的部分,是需要花费一定时间去学习理解的,本文简单介绍了使用c语言如何编写c语言的词法分析器。(ps:完整代码的链接在文末)
二、什么是词法分析器
定义
词法分析器的功能输入源程序,按照构词规则分解成一系列单词符号。单词是语言中具有独立意义的最小单位,包括关键字、标识符、运算符、界符和常量等。
(1) 关键字:是由程序语言定义的具有固定意义的标识符。例如begin,end,if,while都是保留字。这些字通常不用作一般标识符。
(2) 标识符:用来表示各种名字,如变量名,数组名,过程名等等。
(3) 常数 :常数的类型一般有整型、实型、布尔型、文字型等。
(4) 运算符:如+、-、*、/等等。
(5) 界符 :如逗号、分号、括号、等等。
输出
有了对词法分析器的定义,我们编写的词法分析器的输出理所当然应当是如下的形式:
(单词,单词属性,id(种别码))
如:(if,关键字,3)
id通常情况下可以自己定义,例如无符号整数的id可以设为1,只要能区别不同的属性即可。(ps:如果在编写时有对应的种别码表,照着写就完事儿了。)
种别码表示例
单词符号 | 种别码 |
---|---|
NUM | 0 |
Letter | 1 |
main | 2 |
三、实现过程
如何实现
- 读到空格则略过,读下一个字符;若读到的是字母,就再接着读,直到读到的既不是字母也不是数字也不是下划线,并将读到的写入到token数组;
- 若读到的是数字,直到读到的不是数字或小数点,将读到的写入到token数组;
- 若读到的是<|>|=,则再读入下一位,若为=,则该运算符为<=|>=|==,若为其他字符,则返回<|>|=的种别码;
- 若读到的是/,则读下一位,若为*,则说明之后为多行注释,一直读入直到读入*,并判断下一位是否为/,若是则注释结束,不是继续往下一位读入;若读入\n,则行数加一,若读入的字符与以上都不匹配,则报错,并输出出错行数;
- 若读到/时,下一位又读到/,即读到的是单行注释,此时判断下一位是否为\n,若是,则注释结束,不是则继续读入下一位。
部分代码
--建立分析时的缓冲空间
char ch =' '; //存放读入当前的输入字符
int Line_NO; //纪录行号
--建立关键字表
struct keywords{ //关键字
char lexptr[MAXBUF];
int token;
};
struct keywords symtable[MAX];
char str[MAX][10]={"int","char","float","main","double","case","for","if","auto","else","do","while","void","static","return","break","struct","const","union","switch","typedef","enum"}; //记为3~24
--初始化关键字表
void init(){ //关键字表初始化
int j;
for(j=0; j<MAX; j++){
strcpy(symtable[j].lexptr,str[j]);
symtable[j].token=j+3;
}
}
--Iskeyword函数分析关键字
int Iskeyword(char * is_res){ //对关键字进行搜索
int i;
for(i=0;i<MAX;i++){
if((strcmp(symtable[i].lexptr,is_res))==0)
break;
}
if(i<MAX)
return symtable[i].token;
else
return 0;
}
--IsLetter函数分析字母
int IsLetter(char c){ //判断是否为字母
if(((c>='a')&&(c<='z'))||((c>='A')&&(c<='Z')))
return 1;
else
return 0;
}
--IsDigit函数分析数字
int IsDigit(char c){ //判断是否为数字
if(c>='0'&&c<='9')
return 1;
else
return 0;
}
--碰到空格、tab跳过
if(ch==' '||ch=='\t'){}
else if(ch=='\n')
Line_NO++;
--忽略大小写
if((ch<='A')&&(ch>='Z'))
ch=ch+32;
--字符的处理,包括注释的去除,非法字符
switch(ch){ //符号
case'(' :fprintf(fpout,"%s\t\t%d\t\t分界符\n","(",26);break;
case')' :fprintf(fpout,"%s\t\t%d\t\t分界符\n",")",27);break;
case'[' :fprintf(fpout,"%s\t\t%d\t\t分界符\n","[",28);break;
case']' :fprintf(fpout,"%s\t\t%d\t\t分界符\n","]",29);break;
case';' :fprintf(fpout,"%s\t\t%d\t\t分界符\n",";",30);break;
case'.' :fprintf(fpout,"%s\t\t%d\t\t分界符\n",".",31);break;
case',' :fprintf(fpout,"%s\t\t%d\t\t分界符\n",",",32);break;
case':' :fprintf(fpout,"%s\t\t%d\t\t分界符\n",":",33);break;
case'{' :fprintf(fpout,"%s\t\t%d\t\t分界符\n","{",34);break;
case'}' :fprintf(fpout,"%s\t\t%d\t\t分界符\n","}",35);break;
case'"' :fprintf(fpout,"%s\t\t%d\t\t分界符\n","\"",36);break;
case'+' :fprintf(fpout,"%s\t\t%d\t\t运算符\n","+",37);break;
case'-' :fprintf(fpout,"%s\t\t%d\t\t运算符\n","-",38);break;
case'*' :fprintf(fpout,"%s\t\t%d\t\t运算符\n","*",39);break;
case'=' :fprintf(fpout,"%s\t\t%d\t\t运算符\n","=",40);break;
case'>' :{
ch=fgetc(fpin);
if(ch=='=')
fprintf(fpout,"%s\t\t%d\t\t运算符\n",">=",41);
else{
fprintf(fpout,"%s\t\t%d\t\t运算符\n",">",42);
fseek(fpin,-1L,SEEK_CUR);
}
}break;
case'<' :{
ch=fgetc(fpin);
if(ch=='=')
fprintf(fpout,"%s\t\t%d\t\t运算符\n","<=",43);
else{
fprintf(fpout,"%s\t\t%d\t\t运算符\n","<",44);
fseek(fpin,-1L,SEEK_CUR);}
}break;
case'%' :{
ch=fgetc(fpin);
if(ch=='d'||ch=='f'||ch=='s'||ch=='x')
fprintf(fpout,"%s\t\t%d\t\t输出格式符\n","\%",45);
else
fprintf(fpout,"%s\t\t%d\t\t运算符\n","\%",46);
}break;
case'/' :{
ch=fgetc(fpin);//出现在/ /之间的全部作为注释部分处理
if(ch=='*'){
while(ch!='/'&&ch!=EOF)
ch=fgetc(fpin);
if(ch==EOF)
fprintf(fpout,"缺少一个'/'");
}
else if(ch=='/'){
while(ch!='\n'&&ch!=EOF)
ch=fgetc(fpin);
}
else{
fprintf(fpout,"%s\t\t%d\t\t运算符\n","/",47);
fseek(fpin,-1L,SEEK_CUR);
}
}break;
default :fprintf(fpout,"在第%d行无法识别的字符\t%c\n",Line_NO,ch); //非法字符
}
四、测试
测试用例
测试文件s.txt
int main(void)
{
int a1,b;
a1=103;
b=2;
if(a1>=b){/*多行
注释*/
a1=a1*b;//单行注释
}
printf("%d",a1);
return 0;
}
输出结果
输出文件r.txt
int 3 关键字
main 6 关键字
( 26 分界符
void 15 关键字
) 27 分界符
{ 34 分界符
int 3 关键字
a1 1 标识符
, 32 分界符
b 1 标识符
; 30 分界符
a1 1 标识符
= 40 运算符
103 2 无符号整数
; 30 分界符
b 1 标识符
= 40 运算符
2 2 无符号整数
; 30 分界符
if 10 关键字
( 26 分界符
a1 1 标识符
>= 41 运算符
b 1 标识符
) 27 分界符
{ 34 分界符
a1 1 标识符
= 40 运算符
a1 1 标识符
* 39 运算符
b 1 标识符
; 30 分界符
} 35 分界符
printf 1 标识符
( 26 分界符
" 36 分界符
% 45 输出格式符
" 36 分界符
, 32 分界符
a1 1 标识符
) 27 分界符
; 30 分界符
return 17 关键字
0 2 无符号整数
; 30 分界符
} 35 分界符
完整代码: https://download.csdn.net/download/yiwanxianyutang/20089286?spm=1001.2014.3001.5501