词法分析器实验报告班级:2011211312姓名:郭金龙学号:2011211445一、问题描述设计并实现C语言的词法分析程序,要求如下。(1)、可以识别出用C语言编写的源程序中的每个单词符号,并以记号的形式输出每个单词符号。(2)、可以识别并读取源程序中的注释。(3)、可以统计源程序汇总的语句行数、单词个数和字符个数,其中标点和空格不计算为单词,并输出统计结果(4)、检查源程序中存在的错误,并可以报告错误所在的行列位置。(5)、发现源程序中存在的错误后,进行适当的恢复,使词法分析可以继续进行,通过一次词法分析处理,可以检查并报告源程序中存在的所有错误。实验要求:方法1:采用C/C++作为实现语言,手工编写词法分析程序。方法2:通过编写LEX源程序,利用LEX软件工具自动生成词法分析程序二、算法思想及实现首先从主题上来看是一个大概的字符/字符串匹配过程,然后在这其中细化到分类包括标识符,关键字,标点符号,数字等等。这其中在计算一下几个方面的时候比较简单:(1)行数:每次读到回车的时候行数加一(2)单词数:在读标识符和关键字的时候每读到一个计数器加一(3)字符数:除了标点符号和空格以外,每读到一个字符都计数器加一剩下的在判断各种符号的时候以及是否是关键字和标识符的时候,在程序中严格遵守书上给的流程图如下并且输出形式也严格按照书上给的表格如下:三、设计描述主要说说这其中几个比较关键的地方(1)首先要判断是否读到文件末尾,如果是的话要跳出并关闭文件,否则会导致内存泄露(2)考虑需要超前判断的几个字符包括,=,=等等,这下如果读到第一个字符而下一个字符不是预想中的字符时候要指针回退一个字符,但是要考虑到如果这个字符是在文件末尾的话会出现循环的情况,这也是其中的一个问题(3)关键字的判断上,首先要定义一个关键字数组或者容器,然后通过token里面临时存储的字符串来进行匹配,最后如果匹配成功或者失败要有相应的措施并且每次匹配后一定要清空token。四、源程序#includeiostream#includefstream#includestringusingnamespacestd;stringkeyword[13]={begin,end,if,then,else,while,write,read,do,call,const,char,until};intmain(){inti=0;//循环计数器introl=1;//计算行数intword_num=0;//计算单词个数intcharacter=0;//计算字符个数inttemp[100];//标记出错的行数interr=0;charc;stringtoken;ifstreaminfile(d:\\1.txt,ios::in);while(!infile.eof()){infile.get(c);if(infile.eof())break;if(c=='\n'){rol++;}elseif(c==''){character++;infile.get(c);if(c=='='){character++;coutrelop,LEendl;}elseif(c==''){character++;coutrelop,NEendl;}else{coutrelop,LTendl;if(!infile.eof())infile.seekg(-1,ios::cur);//指针向前一个字符}continue;}elseif(c==''){character++;infile.get(c);if(c=='='){character++;coutrelop,GEendl;}else{coutrelop,GTendl;if(!infile.eof())infile.seekg(-1,ios::cur);//指针向前一位}continue;}elseif(c==':'){infile.get(c);if(c=='='){coutagsin-op,--endl;}else{cout:,--endl;if(!infile.eof())infile.seekg(-1,ios::cur);}continue;}elseif(c=='='){character++;coutrelop,EQendl;continue;}elseif(c=='+'){character++;cout+,--endl;continue;}elseif(c=='-'){character++;cout-,--endl;continue;}elseif(c=='*'){character++;cout*,--endl;continue;}elseif(c=='/'){character++;cout/,--endl;continue;}elseif(c=='('){character++;cout(,--endl;continue;}elseif(c==')'){character++;cout),--endl;continue;}elseif(c==','){cout,,--endl;continue;}elseif(c==';'){cout;,--endl;continue;}elseif('0'=c&&c='9')//判断常数{while('0'=c&&c='9'){if(infile.eof())break;character++;//计算数字字符个数token.push_back(c);infile.get(c);}if(!infile.eof())infile.seekg(-1,ios::cur);if((c=='_'||(c='z'&&c='a')||(c='Z'&&c='A'))&&c!='e')//判断错误出现的位置{temp[err]=rol;err++;}coutnum,常数表入口指针endl;token.clear();continue;}elseif(('a'=c&&c='z')||('A'=c&&c='Z')){while(('a'=c&&c='z')||('A'=c&&c='Z')||('0'=c&&c='9')){if(infile.eof())break;character++;//计算标识符中的字符个数token.push_back(c);infile.get(c);}if(!infile.eof())infile.seekg(-1,ios::cur);for(i=0;i13;i++){if(keyword[i]==token){word_num++;coutkeyword[i]--endl;//关键字token.clear();break;}}if(i==13){word_num++;coutid,指向标识符token在符号表中的入口指针endl;//标识符token.clear();}continue;}else{;}}infile.close();cout程序共有rol行endl;cout程序共有word_num个单词endl;cout程序共有character个字符endl;for(intj=0;jerr;j++)cout第temp[j]行有错误!endl;system(pause);}五、测试结果注:这里主要测试两种结果其中一个是有错误的一个是没有错误的,这样可以对比一下六、用户使用说明用户需要在vs2010或vs2012下运行,注意不能用dev-c++,同时需要将文件名为1.txt的文件放在d盘根目录下面,虽然要求读取c文件,但里面装上.c的代码效果还是一样的,其他没有特殊要求七、心得体会这次试验中碰到了很多问题,主要说这其中最主要的问题,就是在读取文件中的字符的时候碰到文件末尾的问题,其实也是最关键的边界问题,当读取到或者的时候如果下一个符号是文件末尾,并且我们还需要超前读取字符,然后如果再需要指针回退,这时候就出现了一个死循环的问题,很久不能解决之后发现了一个新问题,就是将读取的一个字符放入定义的charc中,而这个时候有两种写法,一种是infilec;另一种是infile.get(c);我原来以为这两种是一样的,但是发现这两种在用的时候得到的功能却不一样,网上没有查到这两者的具体区别,但根据程序调试中的问题来看,我个人认为可能是这样的,infile.get(c)是指针指在当前字符,当执行这个语句的时候指针向前一个字符并且将那个字符存到c中,而在回退的时候指针再指向前一个字符,而infilec是指针指向当前的字符同时执行这个语句的时候把当前的字符输出,然后在把指针后移一位,而在回退以后在执行这条语句c里面存储的就是没有回退的那个字符,而也正是这二者的区别导致了边界死循环的问题。这次实验还是学到了很多,希望以后能够碰到并解决更多问题