简易词法分析器

当我们写好一段代码之后,我们通常的操作是:编译,链接, 执行。

而在编译阶段,我们的编译器就会进行词法分析这个阶段,来分析有没有词法错误。

而单词符号一般分为五大类:

1.关键字,也称保留字,比如:if,else,sizeof等(c语言中由32个关键字,在这里也就不一一列举);

2.标识符,用来表示各种名字,如:常量名,变量名,函数名等;

3.常数,各种类型的常数,如:23,3.1415,TURE,"ABC"等;

4.运算符,如:+,*,<=等;

5.界符,如:逗号,分号等。

因此,词法分析程序采用二元式的形式来输出。

比如:(1,“if”)这就是说当前遇到了一个关键字"if"。

要达到上面的这种输出模式,那么首先要进行的就是对一段代码我们应该怎样去识别它到底是属于上述5种类型的哪一类。


接下来,我们进行一个简单的分析:

我们可以利用文件指针,再使用getchar()来逐个读取字符来进行判断。

但是,我们这5种类型的单词符号不一定都是一个字符呀!而getchar()只能获取当前指针所指的下一个字符。

所以,我们应该首先定义一个来存放这一串字符的中间数组。并且可以将它作为一个全局变量。

但是,getchar()每次都会自动将文件指针下移,所以当我们对一个单词符号打印完之后,我们应该利用sweek(,,)对指针回退一个字符,后边代码会介绍这个函数。

在这里可能有的人就想不通了,为什么要回退一个字符呢?

我给大家举个例子:有这样的代码:

int x=5;

首先,获取字符'i',判断其为字母后,相继获取'n','t'直到获取到空格,然后与之前定义好的关键字数组相比较,发现他是关键字,并且打印.

此时,文件指针指向了空格,再一次获取到'x',同理当判断到'='的时候,两者不属于同一符号.将x打印.

此时,文件指针指向了'=',这是问题就出现了,当我们要接着判断时,我们获取到的字符是'5',而不是'='(getchar()会自动将文件指针下移动,先移动,再获取),所以程序就会出现纰漏.所以,就需要引入回退.

(1).关键字和表示符判断。

之前我提到过,在c语言中有32个关键字,所以我们可以定义一个数组来存放这些关键字。
当我们用中间数组和这个数组进行比较时,如果有相同的,那么我们就可以确定它就是一个关键字;反之,它就是标识符。
而且,在这里我们要清楚,标识符只能是字母或者下划线开头。
所以,在这里我们需要一个单独判断字母的函数和一个判断关键字的函数。
在这里我还想说的是:我们还可以将这32个关键字按首字母顺序排列,并且利用上排序算法,就可以节约大量的时间。(这个下边代码没有给出,只作为参考)。

(2).常数判断

常数相对来说还是比较好判断的,只要当我们获取到的第一个字符是数字,那么它就是常数。(这里只考虑数字,不考虑TURE等其他常数)
所以,在这里我们需要一个判断数字的函数。

(3)运算符和界符判断

就运算符来说,存在一目,二目(这里不考虑其他的运算符)。
为什么我要把这两个放在一起来说呢。因为,我们可以对这两个判断分别写一个函数,来利用switch语句一一对比。
所以这里我们需要两个函数分别来判断运算符和界符。

最后,我们对这些函数进行一个简单的逻辑拼接就可以实现我们的词法分析器。具体的操作请看下边的代码,我会进行详细解释。
//1.关键字,保留字  main int double等 
//2.标识符 自己定义的变量名,常量名等 
//3.常数
//4.运算符+ - < > * /等 
//5.界符(,;)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#define max 37
char *Position[max]={"auto","break","case","char","const","continue","default","do","double",
      "else","enum","extern","float","for","if","int","long","register","restrict","return",
      "short","signed","sizeof","static","struct","switch","typedef","union","unsigned","void",
      "volatile","while"}; //自定义关键字 
char Mid[20]; //中间缓冲区 
void initfunctionMid() //缓冲区清除  每打印一次就对中间数组进行清空
{
 int i;
 for(i=0;i<20;i++)
  Mid[i]='\0';
}
int Judgeletter(char ch)//判断字母
{
 if((ch>='a'&&ch<='z')||(ch>='A'&&ch<='Z'))
  return 1;
 else
  return 0;
} 
int Judgenumber(char ch) //判断数字 
{
 if(ch>='0'&&ch<='9')
  return 1;
 else
  return 0;
} 
int Judgedelimiter(char ch) //判断界符
{
 switch(ch)
 {
  case '.':
  case '(':
  case ')':
  case ',':
  case ';':
  case ':':    //这里自己还可以增加一些界符
  case '{':
  case '}':
  case '[':
  case ']':
  case '"': return 1;
  default:return 0;
 }
}
int Judgeoperator(char ch) //判断运算符 
{
 switch(ch)
 {
  case '+':
  case '-':
  case '*':
  case '/':   //同上,可以自己添加
  case '=':
  case '<':
  case '>':
  case '&':
  case '|':
  case '%': return 1;
  default:return 0;
 }
} 
int Judgekeyword(char *Mid)//判断关键字
{
 int i;
 for(i=0;i<max;i++)
 {
  if(strcmp(Mid,Position[i])==0)  //strcmp()判断两个参数的大小,相等返回0,小于返回-1,大于返回1 
     {
      return 1;
      break;
     }
 }
 if(i=max-1)
  return 0; 
} 
void print(int a)  //输出函数 变量a就是符号类型
{
 printf("(%d,%s)\n",a,Mid); //a为种类(对应1---5行) 
}
void WholeJudge(FILE *fp)  //整体判断 
{
 char ch;
 int i=0;  //Mid下标归0;保证每次输出完之后存入数据从第一个开始 
 int j=0;  //监视判错 
 while(1)
 {
     //判断是变量名还是保留字 
  ch=fgetc(fp);
  if(Judgeletter(ch)==1)
  {
   Mid[i++]=ch;   
   ch=fgetc(fp);
   while(Judgeletter(ch)==1||Judgenumber(ch)==1)
   {
    Mid[i++]=ch;
    ch=fgetc(fp);
   }       
  }
  if(strlen(Mid)!=0)
  {
   if(Judgekeyword(Mid)==1)
    print(1);
   else
    print(2);
  }      
  if(!(ch==' '||ch==10||ch==13||ch==9))   //防止一个变量或保留字后边为空格等导致陷入死循环 
   fseek(fp,-1,SEEK_CUR);   //fp指针回退一个字符   fseek(,,)有三个参数,第一个为文件指针,第二个为位移量,第三个是位移基准.用来移动指针
  initfunctionMid();   //缓冲区清除
  i=0;             //Mid下标归0;保证每次输出完之后存入数据从第一个开始     
  //判断常数
    ch=fgetc(fp);
    if(feof(fp))  //判断文件结束 
   break;
  while(Judgenumber(ch)==1)
    {
     Mid[i++]=ch;
     ch=fgetc(fp);
     if(Judgeletter(ch)==1)//判断数字开头是否后接字母 
     {
      j=1;
      break;
     } 
    }  
    if(j==1)   //判错 
    {
     printf("下边运行出错!标识符不能以数字开头!请修改!"); 
     break;
    }    
  if(strlen(Mid)!=0)//如果中间数组不是空,就打印
   print(3);     
  fseek(fp,-1,SEEK_CUR);
  initfunctionMid(); 
  i=0;
  //判断运算符
     ch=fgetc(fp);
  while(Judgeoperator(ch)==1)
    {
     Mid[i++]=ch;
     ch=fgetc(fp);
    }
  if(i>2)  //运算符数量判错 
  {
   printf("下方运算符出错!请修改!");
   break;
  }   
  if(strlen(Mid)!=0)
   print(4);     
  fseek(fp,-1,SEEK_CUR);
  initfunctionMid(); 
  i=0;
  //判断界符
  ch=fgetc(fp);
  if(Judgedelimiter(ch)==1)
  {
   Mid[i]=ch;
   print(5);
  }
  if(strlen(Mid)==0)    
   fseek(fp,-1,SEEK_CUR);   
  initfunctionMid();  
  i=0; 
  }
}
int main()
{
 char Filename[20];
 FILE *fp;
 char ch;
 printf("请输入文件地址:");
 scanf("%s",Filename); 
 fp=fopen(Filename,"r");  
 ch=fgetc(fp);
 while(ch==' '||ch==10||ch==13||ch==9)//忽略空格,换行,回车和Tab 
 {
  ch=fgetc(fp);
 }
 fseek(fp,-1,SEEK_CUR);//SEEK_END文件末尾  SEEK_CUR文件当前指针位置  
 WholeJudge(fp);
 fclose(fp);  
 return 0; 
}
最重要的是,这段代码执行前,一定要建立一个文本文件,将要处理的代码放进去哦!!!
当然,如果你有兴趣,还可以将我们的词法分析器做的更加完美,在这里我就只给大家提供一下思路。
比如,判错,变量名还可以是下划线开头,三目运算符判断等等。


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值