简单c语言子集词法分析器
-
概述
词法分析是编译的第一个环节,其输入是高级语言程序,输出是单词串。词法分析器的主要任务是将高级语言程序作为字符串输入,然后依据词法规则将字符串组合成单词,并输出单词串。
为了方便之后的编译环节,通常将输出的单词串表示成二元组的形式(单词种别码,单词符号的属性值)其中种别码通常用整数表示,按开发者意愿将单词种类分类,相同种类单词使用一个种别码,属性值反映单词符号的特性。
本次实验中保留字、运算符、分界符采用一符一种别码的形式,其定义如表1所示。
为了使程序较为简单,本次实验中的单词符号采用状态图进行识别,整体状态转换图如图1所示,其中最重要的是对于数字与字母识别的状态转换。
图1 状态转换图
-
程序中需要注意的问题
①当使用循环读到不属于相同类型的字符时要注意指针回退问题。
②对于注释中含有和注释中相同类型的字符时需要跳,过例如/* * */这种问题。
③程序中的空行不算作有效行。
④对于识别出非法标识符或注释要注意记录行号并记录下来。
-
程序整体实现思路
由于要分析的c语言子程存放在文本文件test.txt中,所以要涉及文件相关操作,那么来从文件中读取字符串使用
1 while(!feof(fpr)) 2 { 3 char ch = fgetc(fpr); 4 /* 5 处理字符 6 */ 7 }
对于读取的第一个字符是字母那么要继续读取直到读到非字母或数字的字符,代码如下
1 while(!feof(fpr)) 2 { 3 char ch = fgetc(fpr); 4 if(isLetter(ch)==1||(ch=='_')) 5 { 6 word[i++]=ch;//word是字符数组,用来将读取的字符拼凑成单词,等待接下来的处理 7 8 ch=fgetc(fpr); 9 while(isLetter(ch)||isNumber(ch)) 10 { 11 word[i++]=ch; 12 ch=fgetc(fpr); 13 } 14 15 fseek(fpr,-1,1); 16 } 17 }
注意重点来了,当里面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语言来说就是非法的标识符,需要将其错误输出,代码如下
1 while(!feof(fpr)) 2 { 3 char ch = fgetc(fpr); 4 if(isNumber(ch)) 5 { 6 word[i++]=ch; 7 ch=fgetc(fpr); 8 if(isLetter(ch)) 9 { 10 printf("LexicalError,"); 11 fprintf(fpw,"LexicalError,"); 12 13 while(isLetter(ch)) 14 ch=fgetc(fpr); 15 16 clearWord(); //将word数组清空,以便后续使用 17 } 18 19 else 20 { 21 while(isNumber(ch)) 22 { 23 word[i++]=ch; 24 ch=fgetc(fpr); 25 } 26 27 printf("<2,%s>,",word);//是整数,将(2,word)写入output文件 28 fprintf(fpw,"<2,%s>,",word); 29 clearWord(); 30 } 31 32 fseek(fpr,-1,1); 33 }
将字符拼凑成单词以后就要和已知的定义表对比,识别出是关键字还是标识符或者是数字,这部分较为简单,具体步骤可在代码清单中查看。
对于程序中的注释处理,也要注意分为单行和双行两种情况,单行注释较为简单,如果遇到字符’/’则再读一个字符,如果还是’/’那么什么判断也不用做,只需将当前行读完即可。多行注释较为复杂,如果对于注释中也含有字符’*’或’/’的处理较为麻烦,其状态转换图如图2所示
图2 注释处理状态转换图
代码如下
1 else if(ch=='*')//处理多行注释 2 { 3 ch=fgetc(fpr); 4 while(ch!='*'&&(fgetc(fpr)!='/'))//避免注释中出现*,但其后不是/的情况 5 { 6 fseek(fpr,-1,1); 7 fgetc(fpr); 8 if(fgetc(fpr)==EOF)//若到文件末尾还没找到注释*/结束符则判错 9 { 10 printf("LexicalError,"); 11 fprintf(fpw,"LexicalError,"); 12 break; 13 } 14 } 15 ch=fgetc(fpr); 16 }
好像还有点小问题,不过对于处理普通多行注释是可以的。
程序中较为复杂的部分已经说完了,那么对于读取的字符未非数字,字母,‘/’‘/*’开头的,则需进行使用多个判断语句就能识别了。
整体代码清单如下
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 char *list[] = {"bsf","zs","+","-","*","/","%","<","<=",">", 6 ">=","==","!=","&&","||","=","(",")","[","]", 7 "{", "}", ";", ",","void","int","float","char","if","else", 8 "while","do","return"}; 9 int listNum = 33; 10 int line = 1; 11 int errorNum=0; 12 char ch; 13 char word[20]; 14 int errorLine[50]; 15 16 void clearWord() 17 { 18 for(int i=0;i<20;i++) 19 { 20 word[i]='\0'; 21 } 22 } 23 24 int isInList(char *name) 25 { 26 int i; 27 for(i=0; i<listNum; i++) 28 { 29 if(strcmp(name, list[i])==0) 30 { 31 return i; 32 } 33 } 34 return -1; 35 } 36 37 38 int isLetter(char ch) 39 { 40 if((ch>='a'&&ch<='z')||(ch>='A'&&ch<='Z')) 41 return 1; 42 else 43 return 0; 44 } 45 46 int isNumber(char ch) 47 { 48 if(ch>='0'&&ch<='9') 49 return 1; 50 else 51 return 0; 52 } 53 54 void dealNote(FILE *fpr,FILE *fpw)//处理注释 55 { 56 char ch; 57 ch=fgetc(fpr); 58 59 if(ch=='/')//处理单行注释 60 { 61 while(ch!='\n') 62 { 63 ch=fgetc(fpr); 64 } 65 fseek(fpr,-1,1); 66 } 67 else if(ch=='*')//处理多行注释 68 { 69 ch=fgetc(fpr); 70 while(ch!='*'&&(fgetc(fpr)!='/'))//避免注释中出现*,但其后不是/的情况 71 { 72 fseek(fpr,-1,1); 73 fgetc(fpr); 74 if(fgetc(fpr)==EOF)//若到文件末尾还没找到注释*/结束符则判错 75 { 76 printf("LexicalError,"); 77 fprintf(fpw,"LexicalError,"); 78 break; 79 } 80 } 81 ch=fgetc(fpr); 82 } 83 else//否则为/界符 84 { 85 printf("<6,->,");//6 86 fprintf(fpw,"<6,->,"); 87 } 88 89 } 90 91 void dealEmpty(FILE *fpr,FILE *fpw)//处理空行 92 { 93 char buf[1024]; 94 while(!feof(fpr)) 95 { 96 fgets(buf,1024,fpr); 97 if(buf[0]!='\n') 98 { 99 fputs(buf,fpw); 100 } 101 102 } 103 } 104 105 106 void scanner(FILE *fpr,FILE *fpw) 107 { 108 109 while(!feof(fpr)) 110 { 111 int i=0; 112 ch = fgetc(fpr); 113 114 if(ch=='\n') 115 { 116 line++; 117 printf("\n"); 118 fprintf(fpw,"\n"); 119 ch=fgetc(fpr); 120 } 121 122 123 if(ch==' ') 124 { 125 while(ch==' ')//忽略空格 126 { 127 ch=fgetc(fpr); 128 } 129 130 } 131 132 if(isLetter(ch)==1||(ch=='_')) 133 { 134 word[i++]=ch; 135 136 ch=fgetc(fpr); 137 while(isLetter(ch)||isNumber(ch)) 138 { 139 word[i++]=ch; 140 ch=fgetc(fpr); 141 } 142 143 int flag = isInList(word); 144 if(flag !=-1) 145 { 146 printf("<%d,->,",flag+1);//是关键字写入文件output 147 fprintf(fpw,"<%d,->,",flag+1); 148 clearWord(); 149 } 150 else 151 { 152 printf("<1,%s>,",word);//是标识符,写入文件output 153 fprintf(fpw,"<1,%s>,",word); 154 clearWord(); 155 } 156 157 fseek(fpr,-1,1);//识别标识符/关键字完毕,退回一个字符 158 } 159 160 else if(isNumber(ch)) 161 { 162 word[i++]=ch; 163 164 ch=fgetc(fpr); 165 if(ch=='.') 166 { 167 word[i++]=ch; 168 169 ch=fgetc(fpr); 170 while(isNumber(ch)) 171 { 172 word[i++]=ch; 173 ch=fgetc(fpr); 174 } 175 176 printf("<2,%s>,",word);//浮点数,写入文件 177 fprintf(fpw,"<2,%s>,",word); 178 clearWord(); 179 } 180 181 else if(isLetter(ch)) 182 { 183 printf("LexicalError,"); 184 185 errorLine[errorNum++]=line; 186 fprintf(fpw,"LexicalError,"); 187 188 while(isLetter(ch)) 189 ch=fgetc(fpr); 190 191 clearWord(); 192 193 } 194 195 else 196 { 197 while(isNumber(ch)) 198 { 199 word[i++]=ch; 200 ch=fgetc(fpr); 201 } 202 203 printf("<2,%s>,",word);//是整数,将(2,word)写入output文件 204 fprintf(fpw,"<2,%s>,",word); 205 clearWord(); 206 } 207 208 fseek(fpr,-1,1); 209 } 210 211 else 212 { 213 switch(ch) 214 { 215 case '+': 216 printf("<3,->,");//3 217 fprintf(fpw,"<3,->,"); 218 break; 219 case '-': 220 word[i++]=ch; 221 222 ch=fgetc(fpr); 223 if(isNumber(ch)) 224 { 225 while(isNumber(ch)) 226 { 227 word[i++]=ch; 228 ch=fgetc(fpr); 229 } 230 231 printf("<2,%s>,",word);//负数,写入文件 232 fprintf(fpw,"<2,%s>,",word); 233 clearWord(); 234 } 235 236 else 237 { 238 printf("<4,->,"); //4 239 fprintf(fpw,"<4,->,"); 240 } 241 242 fseek(fpr,-1,1); 243 break; 244 case '*': 245 printf("<5,->,"); //5 246 fprintf(fpw,"<5,->,"); 247 break; 248 case '/': 249 dealNote(fpr,fpw); 250 break; 251 case '=': 252 ch=fgetc(fpr); 253 if(ch=='=') 254 { 255 printf("<12,->,"); //12 256 fprintf(fpw,"<12,->,"); 257 } 258 else 259 { 260 fseek(fpr,-1,1); 261 printf("<16,->,");//16 262 fprintf(fpw,"<16,->,"); 263 } 264 break; 265 case '<': 266 ch=fgetc(fpr); 267 if(ch=='=') 268 { 269 printf("<9,->,"); //9 270 fprintf(fpw,"<9,->,"); 271 } 272 else 273 { 274 printf("<8,->,");//8 275 fprintf(fpw,"<8,->,"); 276 fseek(fpr,-1,1); 277 } 278 break; 279 case '>': 280 ch=fgetc(fpr); 281 if(ch=='=') 282 { 283 printf("<11,->,");//11 284 fprintf(fpw,"<11,->,"); 285 } 286 else 287 { 288 printf("<10,->,");//10 289 fprintf(fpw,"<10,->,"); 290 fseek(fpr,-1,1); 291 } 292 break; 293 case '!': 294 ch=fgetc(fpr); 295 if(ch=='=') 296 { 297 printf("<13,->,");//13 298 fprintf(fpw,"<13,->,"); 299 } 300 else 301 { 302 fseek(fpr,-1,1); 303 } 304 break; 305 case '&': 306 ch=fgetc(fpr); 307 if(ch=='&') 308 { 309 printf("<14,->,");//14 310 fprintf(fpw,"<14,->,"); 311 } 312 else 313 { 314 fseek(fpr,-1,1); 315 } 316 break; 317 case '|': 318 ch=fgetc(fpr); 319 if(ch=='|') 320 { 321 printf("<15,->,");//15 322 fprintf(fpw,"<15,->,"); 323 } 324 else 325 { 326 fseek(fpr,-1,1); 327 } 328 break; 329 case '(': 330 printf("<17,->,");//17 331 fprintf(fpw,"<17,->,"); 332 break; 333 case ')': 334 printf("<18,->,");//18 335 fprintf(fpw,"<18,->,"); 336 break; 337 case '[': 338 printf("<19,->,");//19 339 fprintf(fpw,"<19,->,"); 340 break; 341 case ']': 342 printf("<20,->,");//20 343 fprintf(fpw,"<20,->,"); 344 break; 345 case '{': 346 printf("<21,->,");//21 347 fprintf(fpw,"<21,->,"); 348 break; 349 case '}': 350 printf("<22,->,");//22 351 fprintf(fpw,"<22,->,"); 352 break; 353 case ';': 354 printf("<23,->,");//23 355 fprintf(fpw,"<23,->,"); 356 break; 357 case ',': 358 printf("<24,->,");//24 359 fprintf(fpw,"<24,->,"); 360 break; 361 /* default: 362 //错误 363 printf("<error>");*/ 364 } 365 } 366 367 } 368 369 } 370 371 int main() 372 { 373 char Filename[20]; 374 FILE *fpr,*fpw; 375 376 printf("请输入读入文件地址:"); 377 scanf("%s",Filename); 378 fpr=fopen(Filename,"r"); 379 380 FILE *fpr1 = fopen("F:\\test1.txt","w"); 381 dealEmpty(fpr,fpr1); 382 fclose(fpr1);//临时存放处理过空行的代码 383 384 FILE *fpr2 = fopen("F:\\test1.txt","r"); 385 386 printf("请输入写出文件地址:"); 387 scanf("%s",Filename); 388 fpw=fopen(Filename,"w"); 389 390 391 scanner(fpr2,fpw); 392 393 printf("%d\n",line); 394 395 if(errorNum>0) 396 { 397 fprintf(fpw,"\nLexicalError(s) on line(s) "); 398 printf("\nLexicalError(s) on line(s) "); 399 for(int i=0;i<errorNum;i++) 400 { 401 fprintf(fpw,"%d,",errorLine[i]); 402 printf("%d,",errorLine[i]); 403 } 404 } 405 406 407 408 return 0; 409 }
-
程序中还存在的问题:
①过多地方使用硬编码如<1,->等这种形式的输出,这不利于程序的维护。
②程序中主要使用数组这种存储结构,对于读取较多内容的字符不太合适。
③主扫描函数内容过长,可读性不好。