• 概述

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

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

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

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

图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所示

图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,->等这种形式的输出,这不利于程序的维护。

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

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

简单c语言子集词法分析器的更多相关文章

  1. 02.从0实现一个JVM语言之词法分析器-Lexer-03月02日更新

    从0实现JVM语言之词法分析器-Lexer 本次有较大幅度更新, 老读者如果对前面的一些bug, 错误有疑问可以复盘或者留言. 源码github仓库, 如果这个系列文章对你有帮助, 希望获得你的一个s ...

  2. linux内核学习之一 简单c语言反汇编

    (我是第一次发技术博客的菜鸟,恳请大家指导!!) 一  由简单c程序生成汇编代码 首先给出本次我们要反汇编的简单c语言程序:(够简单吧~) 在linux环境中使用下面的命令条件编译: 生成汇编文件sh ...

  3. 用简单的语言描述C++ 是什么?

    用简单的语言描述C++ 是什么? 答:C++是在C语言的基础上开发的一种面向对象编程语言,应用广泛.C++支持多种编程范式 --面向对象编程.泛型编程和过程化编程. 其编程领域众广,常用于系统开发,引 ...

  4. 简单的C语言编译器--词法分析器

    1. 定义词法单元Tag   首先要将可能出现的词进行分类,可以有不同的分类方式.如多符一类:将所有逗号.分号.括号等都归为一类,或者一符一类,将一个符号归为一类.我这里采用的是一符一类的方式.C代码 ...

  5. 用C/C++手撕CPlus语言的集成开发环境(1)—— 语言规范 + 词法分析器

    序言 之所以叫做CPlus语言,是因为原本是想起名为CMinus的,结果发现GitHub和Gitee上一堆的CMinus的编译器(想必都是开过编译原理课程并且写了个玩具级的语言编译器的大佬们吧).但是 ...

  6. Markdown:纯文本进行网页排版的简单标记语言

    Markdown http://daringfireball.net/projects/markdown/ 2016-08-03 Markdown是一种标记语言,对纯文本使用简单的标记符号进行网页格式 ...

  7. Linux下简单C语言小程序的反汇编分析

    韩洋原创作品转载请注明出处<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 写在开始,本文为因为参加MOO ...

  8. 简单R语言爬虫

    R爬虫实验 R爬虫实验 PeRl 简单的R语言爬虫实验,因为比较懒,在处理javascript翻页上用了取巧的办法. 主要用到的网页相关的R包是: {rvest}. 其余的R包都是常用包. libra ...

  9. 如何用visual studio2013编写简单C语言程序

    vc++6.0 作为经典版本,虽然已经几乎淘汰,但还是有很多的初学者在使用.但当他们使用vs2013时会发现界面和操作和vc++6.0有了极大的不同,不知该如何 操作.随着vs2013的普及,更多人使 ...

随机推荐

  1. 使用grunt实现web自动化

    1.grunt作用        实现压缩.编译.单元测试等重复性工作 2.需要安装的软件          第一步:从官网获取nodejs的软件包,直接双击进行安装(windows下) 第二步:打开 ...

  2. perl 获取系统时间

    最近需要将字符串转换成时间,找了下资料,实战如下,发现时timelocal费了些时间 strftime也可在 c / c++ / awk / php 中使用,用法基本一致. 这个也不错 $time = ...

  3. 使用Nexus搭建Maven代理仓库

    使用Maven构建和管理项目是非常享受的一件事,我们可以从Maven中央仓库下载所需要的构件(artifact),但实际开发中由于种种原因我们需要在架设一个Maven本地代理仓库,如:不方便访问公网. ...

  4. Struts中数据处理

    对数据操作的3种方法(把数据保存到域中): 方式1:直接获取servletApi 核心类:ServletActionContext提供的静态方法 /** * 方式1:拿到servletApi,执行操作 ...

  5. Instant App 即将到来,Android 集权或将加速分裂

    在境外,Android 的体验将越来越好,在中国,Android 的更新可能将止步于6.0! 话题讨论:Instant App 在中国将何去何从? 以下为谷歌原创文章 2017-03-03 Googl ...

  6. React 入门之路(1)

    React React简介 是由Facebook公司推广的一套框架,已经应用instagram等产品 React就是为了提供应用程序性能而设计的一套框架 在angular中,对dom提供了一些指令,让 ...

  7. Java的内存机制详解

    Java把内存分为两种:一种是栈内存,另一种是堆内存.在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配,当在一段代码块定义一个变量时,Java 就在栈中为这个变量分配内存空间, ...

  8. Java 中的锁——Lock接口

    Java SE5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能.虽然它少了(通过synchronized块或者方法所提供的)隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的操作性. ...

  9. [Open Source] RabbitMQ 安装与使用

    前言 吃多了拉就是队列,吃饱了吐就是栈 使用场景 对操作的实时性要求不高,而需要执行的任务极为耗时:(发送短信,邮件提醒,更新文章阅读计数,记录用户操作日志) 存在异构系统间的整合: 安装 下载 Er ...

  10. 平时自己项目中用到的CSS

    outline  当选中input元素的时候会出现状态线, outline设置成none就没了 input{ outline:none; } contentditable  设置元素内的文本是否可编辑 ...