• 概述

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

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

本次实验中保留字、运算符、分界符采用一符一种别码的形式,其定义如表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. 考试模块 - ERP数据流

    快速链接:人力资源知识体系索引 本主题主要列出考试中涉及到的所有表. 步骤 操作 相关表 说明 1 考试辅助资料   基础资料,见附表1 2 题库(109130) HXExmGp   3 试题 HXE ...

  2. 普实软件:MES机器数据维护

    概述 机器数据有两个菜单,机器主数据在制造数据模块下,机器MES数据相关的设置在MES模块下,两个菜单查看的内容是一致的,但是机器主数据显示的是普通的机器,可做新增.编辑.删除操作,机器MES数据仅做 ...

  3. Yii里model验证的小技巧

    例如:需要判断,字符长度在12-20之间,最大长度的提示语用tooLong,最小提示语言用tooShort array('pay_order', 'length', 'max' => 20, ' ...

  4. 从数组中每次取一个不同的数组成员 getRandomItem(arr)

    积累些常用的方法, 都是随写的, 不好之处, 望指出. getRandomItem(arr)函数如下: var getRandomItem = function () { var preItem = ...

  5. 解决AJAX在火狐,谷歌都能正常运行,而IE不行的问题

    如图所示: 经过一系列测试,并不是data参数的问题,也不是if...else...判断的问题,居然是console.log()将函数阻拦住了,百度了下说低版本的IE不支持console.log(), ...

  6. SpringBoot之旅 -- 定时任务两种(Spring Schedule 与 Quartz 整合 )实现

    相关文章 Spring Boot 相关文章目录 前言 最近在项目中使用到定时任务,之前一直都是使用Quartz 来实现,最近看Spring 基础发现其实Spring 提供 Spring Schedul ...

  7. Fraction to Recurring Decimal leetcode

    Given two integers representing the numerator and denominator of a fraction, return the fraction in ...

  8. ajax bookstrap美化网页,并实现页面的加载,删除与查看详情

    Bookstrap:美化页面: Bootstrap是Twitter推出的一个开源的用于前端开发的工具包.它由Twitter的设计师Mark Otto和Jacob Thornton合作开发,是一个CSS ...

  9. Omi应用md2site-0.5.0发布-支持动态markdown拉取解析

    写在前面 Md2site是基于Omi的一款Markdown转网站工具,使用简单,生成的文件轻巧,功能强大. 官网:http://alloyteam.github.io/omi/md2site/ Git ...

  10. keepalived工作原理

    keepalived是一个类似于Layer2,4,7交换机制的软件.是Linux集群管理中保证集群高可用的一个服务软件,其功能是用来防止单点故障.   keepalived的工作原理:         ...