简单c语言子集词法分析器
概述
词法分析是编译的第一个环节,其输入是高级语言程序,输出是单词串。词法分析器的主要任务是将高级语言程序作为字符串输入,然后依据词法规则将字符串组合成单词,并输出单词串。
为了方便之后的编译环节,通常将输出的单词串表示成二元组的形式(单词种别码,单词符号的属性值)其中种别码通常用整数表示,按开发者意愿将单词种类分类,相同种类单词使用一个种别码,属性值反映单词符号的特性。
本次实验中保留字、运算符、分界符采用一符一种别码的形式,其定义如表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语言子集词法分析器的更多相关文章
- 02.从0实现一个JVM语言之词法分析器-Lexer-03月02日更新
从0实现JVM语言之词法分析器-Lexer 本次有较大幅度更新, 老读者如果对前面的一些bug, 错误有疑问可以复盘或者留言. 源码github仓库, 如果这个系列文章对你有帮助, 希望获得你的一个s ...
- linux内核学习之一 简单c语言反汇编
(我是第一次发技术博客的菜鸟,恳请大家指导!!) 一 由简单c程序生成汇编代码 首先给出本次我们要反汇编的简单c语言程序:(够简单吧~) 在linux环境中使用下面的命令条件编译: 生成汇编文件sh ...
- 用简单的语言描述C++ 是什么?
用简单的语言描述C++ 是什么? 答:C++是在C语言的基础上开发的一种面向对象编程语言,应用广泛.C++支持多种编程范式 --面向对象编程.泛型编程和过程化编程. 其编程领域众广,常用于系统开发,引 ...
- 简单的C语言编译器--词法分析器
1. 定义词法单元Tag 首先要将可能出现的词进行分类,可以有不同的分类方式.如多符一类:将所有逗号.分号.括号等都归为一类,或者一符一类,将一个符号归为一类.我这里采用的是一符一类的方式.C代码 ...
- 用C/C++手撕CPlus语言的集成开发环境(1)—— 语言规范 + 词法分析器
序言 之所以叫做CPlus语言,是因为原本是想起名为CMinus的,结果发现GitHub和Gitee上一堆的CMinus的编译器(想必都是开过编译原理课程并且写了个玩具级的语言编译器的大佬们吧).但是 ...
- Markdown:纯文本进行网页排版的简单标记语言
Markdown http://daringfireball.net/projects/markdown/ 2016-08-03 Markdown是一种标记语言,对纯文本使用简单的标记符号进行网页格式 ...
- Linux下简单C语言小程序的反汇编分析
韩洋原创作品转载请注明出处<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 写在开始,本文为因为参加MOO ...
- 简单R语言爬虫
R爬虫实验 R爬虫实验 PeRl 简单的R语言爬虫实验,因为比较懒,在处理javascript翻页上用了取巧的办法. 主要用到的网页相关的R包是: {rvest}. 其余的R包都是常用包. libra ...
- 如何用visual studio2013编写简单C语言程序
vc++6.0 作为经典版本,虽然已经几乎淘汰,但还是有很多的初学者在使用.但当他们使用vs2013时会发现界面和操作和vc++6.0有了极大的不同,不知该如何 操作.随着vs2013的普及,更多人使 ...
随机推荐
- connect函数的用法
无论流式套接字(如TCP)还是数据报(如UDP),均可以使用connect函数.对于流式套接字,使用connect函数后,建立固定地址的连接,之后可以使用send/rev函数进行数据收发.对于数据报, ...
- synchronized和lock比对
前言:在上面的博客说了synchronized的一些用法,下面我们再来看看lock,这个出现频率也是非常高的一个. 1:获取Lock锁的几种方式 前面说了synchronized有锁对象和锁类对象,当 ...
- Jaro-Winkler Distance
发现commons-lang 中有实现: StringUtils.class public static double getJaroWinklerDistance(final CharSequenc ...
- Go语言并发机制初探
Go 语言相比Java等一个很大的优势就是可以方便地编写并发程序.Go 语言内置了 goroutine 机制,使用goroutine可以快速地开发并发程序, 更好的利用多核处理器资源.这篇文章学习 g ...
- python 接口自动化测试--框架整改(五)
代码结构: 目标架构: 1.用例分析器,自动根据接口的参数,类型生成测试用例 2.数据分析器,自动维护接口参数数据,动态数据自动生成,返回结果自动查询判断 3.核心调用引擎,分SOAP和HTTP两种, ...
- Knockoutjs:Component and Custom Elements(翻译文章)
Knockoutjs 的Components 是一种自定义的组件,它以一种强大.简介的方式将你自己的ui代码组织成一种单独的.可重用的模块,自定义的组件(Component)有以下特点: 1.可以替代 ...
- php+apache+mysql的安装
1.LAMP的安装顺序问题,现在是默认安装好了Linux系统,我的版本是Ubuntu 12.04.一般来说比较建议的顺序是Mysql Apache 最后安装PHP,在我实践下来 Apache和Mysq ...
- SolrCloud的介绍
SolrCloud(solr云)是Solr提供的分布式搜索方案. 当你需要大规模,容错,分布式索引和检索能力时使用 SolrCloud. 当索引量很大,搜索请求并发很高时,同样需要使用SolrClou ...
- 【转】如何成为一位优秀的创业CEO
编者按:本文来自 Ryan Allis,是一位来自旧金山的创业者和投资人.在 2003 年创立了 iContact,并任 CEO. 做创业公司的 CEO 可以说是世界上最有挑战性的事情之一.你得让客户 ...
- ie旋转滤镜Matrix
旋转一个元素算是一个比较常见的需求了吧,在支持CSS3的浏览器中可以使用transform很容易地实现,这里有介绍:http://www.css88.com/archives/2168,这里有演示ht ...