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

  • 概述

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

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

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

 

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

 

 

状态转换图

 

  • 程序中需要注意的问题

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

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

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

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

 

  • 程序整体实现思路

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

1 while(!feof(fpr))
2 {
3     char  ch = fgetc(fpr);
4     /*
5     处理字符
6     */
7 }
View Code

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

 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 }                            
View Code

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

来举个例子对于语句max=1;首先读取是m是字母,好继续读取直到遇到非字母字符=跳出里面循环,此时ch=’=’,程序未将文件内容读完,继续char  ch = fgetc(fpr)这句,这时ch=1ch=’=’的情况没有进行分析。所以我们应该在使用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 }       
View Code

 

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

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

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

 

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

整体代码清单如下

  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]='';
 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!='
')
 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]!='
')
 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=='
')
115         {    
116             line++;
117             printf("
");
118             fprintf(fpw,"
");
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
",line);
394 
395     if(errorNum>0)
396     {
397         fprintf(fpw,"
LexicalError(s) on line(s) ");
398         printf("
LexicalError(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 }
View Code
  •  程序中还存在的问题:

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

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

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

 

 

 

 

原文地址:https://www.cnblogs.com/kanjian2016/p/6724033.html