一个Lex/Yacc完整的示例(可使用C++)

来源:互联网 发布:wps两列对比重复数据 编辑:程序博客网 时间:2024/05/16 17:50

作者: 胡彦 2013-4-28
代码下载地址:http://pan.baidu.com/share/link?shareid=579088&uk=253544182
本框架是一个lex/yacc完整的示例,包括详细的注释,用于学习lex/yacc程序基本的搭建方法,在linux/cygwin下敲入make就可以编译和执行。大部分框架已经搭好了,你只要稍加扩展就可以成为一个计算器之类的程序,用于《编译原理》的课程设计,或者对照理解其它lex/yacc项目的代码。
本例子虽小却演示了lex/yacc程序最重要和常用的特征:

[plain] view plain copy
  1. * lex/yacc程序组成结构、文件格式。  
  2. * 如何在lex/yacc中使用C++和STL库,用extern "C"声明那些lex/yacc生成的、要链接的C函数,如yylex(), yywrap(), yyerror()。  
  3. * 重定义YYSTYPE/yylval为复杂类型。  
  4. * lex里多状态的定义和使用,用BEGIN宏在初始态和其它状态间切换。  
  5. * lex里正则表达式的定义、识别方式。  
  6. * lex里用yylval向yacc返回数据。  
  7. * yacc里用%token<>方式声明yacc记号。  
  8. * yacc里用%type<>方式声明非终结符的类型。  
  9. * 在yacc嵌入的C代码动作里,对记号属性($1, $2等)、和非终结符属性($$)的正确引用方法。  
  10. * 对yyin/yyout重赋值,以改变yacc默认的输入/输出目标。  

本例子功能是,对当前目录下的file.txt文件,解析出其中的标识符、数字、其它符号,显示在屏幕上。linux调试环境是Ubuntu 10.04。

文件列表:

[plain] view plain copy
  1. lex.l:      lex程序文件。  
  2. yacc.y:     yacc程序文件。  
  3. main.h:     lex.l和yacc.y共同使用的头文件。  
  4. Makefile:       makefile文件。  
  5. lex.yy.c:       用lex编译lex.l后生成的C文件。  
  6. yacc.tab.c: 用yacc编译yacc.y后生成的C文件。  
  7. yacc.tab.h: 用yacc编译yacc.y后生成的C头文件,内含%token、YYSTYPE、yylval等定义,供lex.yy.c和yacc.tab.c使用。  
  8. file.txt:       被解析的文本示例。  
  9. README.txt: 本说明。  


下面列出主要的代码文件:

 main.h: lex.l和yacc.y共同使用的头文件 

[cpp] view plain copy
  1. #ifndef MAIN_HPP  
  2. #define MAIN_HPP  
  3.   
  4. #include <iostream>//使用C++库  
  5. #include <string>  
  6. #include <stdio.h>//printf和FILE要用的  
  7.   
  8. using namespace std;  
  9.   
  10. /*当lex每识别出一个记号后,是通过变量yylval向yacc传递数据的。默认情况下yylval是int类型,也就是只能传递整型数据。 
  11. yylval是用YYSTYPE宏定义的,只要重定义YYSTYPE宏,就能重新指定yylval的类型(可参见yacc自动生成的头文件yacc.tab.h)。 
  12. 在我们的例子里,当识别出标识符后要向yacc传递这个标识符串,yylval定义成整型不太方便(要先强制转换成整型,yacc里再转换回char*)。 
  13. 这里把YYSTYPE重定义为struct Type,可存放多种信息*/  
  14. struct Type//通常这里面每个成员,每次只会使用其中一个,一般是定义成union以节省空间(但这里用了string等复杂类型造成不可以)  
  15. {  
  16.     string m_sId;  
  17.     int m_nInt;  
  18.     char m_cOp;  
  19. };  
  20.   
  21. #define YYSTYPE Type//把YYSTYPE(即yylval变量)重定义为struct Type类型,这样lex就能向yacc返回更多的数据了  
  22.   
  23. #endif  


lex.l: lex程序文件

[cpp] view plain copy
  1. %{  
  2. /*本lex的生成文件是lex.yy.c 
  3. lex文件由3段组成,用2个%%行把这3段隔开。 
  4. 第1段是声明段,包括: 
  5. 1-C代码部分:include头文件、函数、类型等声明,这些声明会原样拷到生成的.c文件中。 
  6. 2-状态声明,如%x COMMENT。 
  7. 3-正则式定义,如digit ([0-9])。 
  8. 第2段是规则段,是lex文件的主体,包括每个规则(如identifier)是如何匹配的,以及匹配后要执行的C代码动作。 
  9. 第3段是C函数定义段,如yywrap()的定义,这些C代码会原样拷到生成的.c文件中。该段内容可以为空*/  
  10.   
  11. //第1段:声明段  
  12. #include "main.h"//lex和yacc要共用的头文件,里面包含了一些头文件,重定义了YYSTYPE  
  13. #include "yacc.tab.h"//用yacc编译yacc.y后生成的C头文件,内含%token、YYSTYPE、yylval等定义(都是C宏),供lex.yy.c和yacc.tab.c使用  
  14.   
  15. extern "C"//为了能够在C++程序里面调用C函数,必须把每一个需要使用的C函数,其声明都包括在extern "C"{}块里面,这样C++链接时才能成功链接它们。extern "C"用来在C++环境下设置C链接类型。  
  16. {   //yacc.y中也有类似的这段extern "C",可以把它们合并成一段,放到共同的头文件main.h中  
  17.     int yywrap(void);  
  18.     int yylex(void);//这个是lex生成的词法分析函数,yacc的yyparse()里会调用它,如果这里不声明,生成的yacc.tab.c在编译时会找不到该函数  
  19. }  
  20. %}  
  21.   
  22. /*lex的每个正则式前面可以带有"<状态>",例如下面的"<COMMENT>\n"。每个状态要先用%x声明才能使用。 
  23. 当lex开始运行时,默认状态是INITIAL,以后可在C代码里用"BEGIN 状态名;"切换到其它状态(BEGIN是lex/yacc内置的宏)。 
  24. 这时,只有当lex状态切换到COMMENT后,才会去匹配以<COMMENT>开头的正则式,而不匹配其它状态开头的。 
  25. 也就是说,lex当前处在什么状态,就考虑以该状态开头的正则式,而忽略其它的正则式。 
  26. 其应用例如,在一段C代码里,同样是串"abc",如果它写在代码段里,会被识别为标识符,如果写在注释里则就不会。所以对串"abc"的识别结果,应根据不同的状态加以区分。 
  27. 本例子需要忽略掉文本中的行末注释,行末注释的定义是:从某个"//"开始,直到行尾的内容都是注释。其实现方法是: 
  28. 1-lex启动时默认是INITIAL状态,在这个状态下,串"abc"会识别为标识符,串"123"会识别为整数等。 
  29. 2-一旦识别到"//",则用BEGIN宏切换到COMMENT状态,在该状态下,abc这样的串、以及其它字符会被忽略。只有识别到换行符\n时,再用BEGIN宏切换到初始态,继续识别其它记号。*/  
  30. %x COMMENT  
  31.   
  32. /*非数字由大小写字母、下划线组成*/  
  33. nondigit    ([_A-Za-z])  
  34.   
  35. /*一位数字,可以是0到9*/  
  36. digit       ([0-9])  
  37.   
  38. /*整数由1至多位数字组成*/  
  39. integer     ({digit}+)  
  40.   
  41. /*标识符,以非数字开头,后跟0至多个数字或非数字*/  
  42. identifier  ({nondigit}({nondigit}|{digit})*)  
  43.   
  44. /*一个或一段连续的空白符*/  
  45. blank_chars ([ \f\r\t\v]+)  
  46.   
  47. /*下面%%后开始第2段:规则段*/  
  48. %%  
  49.   
  50. {identifier}    {   //匹配标识符串,此时串值由yytext保存  
  51.             yylval.m_sId=yytext;//通过yylval向yacc传递识别出的记号的值,由于yylval已定义为struct Type,这里就可以把yytext赋给其m_sId成员,到了yacc里就可以用$n的方式来引用了  
  52.             return IDENTIFIER;  //向yacc返回: 识别出的记号类型是IDENTIFIER  
  53.         }  
  54.   
  55. {integer}       {   //匹配整数串  
  56.             yylval.m_nInt=atoi(yytext);//把识别出的整数串,转换为整型值,存储到yylval的整型成员里,到了yacc里用$n方式引用  
  57.             return INTEGER;//向yacc返回: 识别出的记号类型是INTEGER  
  58.         }  
  59.   
  60. {blank_chars}   {   //遇空白符时,什么也不做,忽略它们  
  61.         }  
  62.                   
  63. \n      {   //遇换行符时,忽略之  
  64.         }  
  65. "//"        {   //遇到串"//",表明要开始一段注释,直到行尾  
  66.             cout<<"(comment)"<<endl;//提示遇到了注释  
  67.             BEGIN COMMENT;//用BEGIN宏切换到注释状态,去过滤这段注释,下一次lex将只匹配前面带有<COMMENT>的正则式  
  68.         }  
  69.   
  70. .       {   //.表示除\n以外的其它字符,注意这个规则要放在最后,因为一旦匹配了.就不会匹配后面的规则了(以其它状态<>开头的规则除外)  
  71.             yylval.m_cOp=yytext[0];//由于只匹配一个字符,这时它对应yytext[0],把该字符存放到yylval的m_cOp成员里,到了yacc里用$n方式引用  
  72.             return OPERATOR;//向yacc返回: 识别出的记号类型是OPERATOR  
  73.         }  
  74.   
  75. <COMMENT>\n   {   //注释状态下的规则,只有当前切换到COMMENT状态才会去匹配  
  76.             BEGIN INITIAL;//在注释状态下,当遇到换行符时,表明注释结束了,返回初始态  
  77.         }  
  78.   
  79. <COMMENT>.    {   //在注释状态下,对其它字符都忽略,即:注释在lex(词法分析层)就过滤掉了,不返回给yacc了  
  80.         }  
  81.   
  82. %%  
  83.   
  84. //第3段:C函数定义段  
  85. int yywrap(void)  
  86. {  
  87.     puts("-----the file is end");  
  88.     return 1;//返回1表示读取全部结束。如果要接着读其它文件,可以这里fopen该文件,文件指针赋给yyin,并返回0  
  89. }  


yacc.y: yacc程序文件

[cpp] view plain copy
  1. %{  
  2. /*本yacc的生成文件是yacc.tab.c和yacc.tab.h 
  3. yacc文件由3段组成,用2个%%行把这3段隔开。 
  4. 第1段是声明段,包括: 
  5. 1-C代码部分:include头文件、函数、类型等声明,这些声明会原样拷到生成的.c文件中。 
  6. 2-记号声明,如%token 
  7. 3-类型声明,如%type 
  8. 第2段是规则段,是yacc文件的主体,包括每个产生式是如何匹配的,以及匹配后要执行的C代码动作。 
  9. 第3段是C函数定义段,如yyerror()的定义,这些C代码会原样拷到生成的.c文件中。该段内容可以为空*/  
  10.   
  11. //第1段:声明段  
  12. #include "main.h"//lex和yacc要共用的头文件,里面包含了一些头文件,重定义了YYSTYPE  
  13.   
  14. extern "C"//为了能够在C++程序里面调用C函数,必须把每一个需要使用的C函数,其声明都包括在extern "C"{}块里面,这样C++链接时才能成功链接它们。extern "C"用来在C++环境下设置C链接类型。  
  15. {   //lex.l中也有类似的这段extern "C",可以把它们合并成一段,放到共同的头文件main.h中  
  16.     void yyerror(const char *s);  
  17.     extern int yylex(void);//该函数是在lex.yy.c里定义的,yyparse()里要调用该函数,为了能编译和链接,必须用extern加以声明  
  18. }  
  19.   
  20. %}  
  21.   
  22. /*lex里要return的记号的声明 
  23. 用token后加一对<member>来定义记号,旨在用于简化书写方式。 
  24. 假定某个产生式中第1个终结符是记号OPERATOR,则引用OPERATOR属性的方式: 
  25. 1-如果记号OPERATOR是以普通方式定义的,如%token OPERATOR,则在动作中要写$1.m_cOp,以指明使用YYSTYPE的哪个成员 
  26. 2-用%token<m_cOp>OPERATOR方式定义后,只需要写$1,yacc会自动替换为$1.m_cOp 
  27. 另外用<>定义记号后,非终结符如file, tokenlist,必须用%type<member>来定义(否则会报错),以指明它们的属性对应YYSTYPE中哪个成员,这时对该非终结符的引用,如
    .member*/  
  28. %token<m_nInt>INTEGER  
  29. %token<m_sId>IDENTIFIER  
  30. %token<m_cOp>OPERATOR  
  31. %type<m_sId>file  
  32. %type<m_sId>tokenlist  
  33.   
  34. %%  
  35.   
  36. file:   //文件,由记号流组成  
  37.     tokenlist   //这里仅显示记号流中的ID  
  38.     {  
  39.         cout<<"all id:"<<$1<<endl;    //$1是非终结符tokenlist的属性,由于该终结符是用%type<m_sId>定义的,即约定对其用YYSTYPE的m_sId属性,$1相当于$1.m_sId,其值已经在下层产生式中赋值(tokenlist IDENTIFIER)  
  40.     };  
  41. tokenlist://记号流,或者为空,或者由若干数字、标识符、及其它符号组成  
  42.     {  
  43.     }  
  44.     | tokenlist INTEGER  
  45.     {  
  46.         cout<<"int: "<<$2<<endl;//$2是记号INTEGER的属性,由于该记号是用%token<m_nInt>定义的,即约定对其用YYSTYPE的m_nInt属性,$2会被替换为yylval.m_nInt,已在lex里赋值  
  47.     }  
  48.     | tokenlist IDENTIFIER  
  49.     {  
  50.         $$+=" " + $2;//
    tokenlist
    相当于$$.m_sId,这里把识别到的标识符串保存在tokenlist属性中,到上层产生式里可以拿出为用  
  51.         cout<<"id: "<<$2<<endl;//$2是记号IDENTIFIER的属性,由于该记号是用%token<m_sId>定义的,即约定对其用YYSTYPE的m_sId属性,$2会被替换为yylval.m_sId,已在lex里赋值  
  52.     }  
  53.     | tokenlist OPERATOR  
  54.     {  
  55.         cout<<"op: "<<$2<<endl;//$2是记号OPERATOR的属性,由于该记号是用%token<m_cOp>定义的,即约定对其用YYSTYPE的m_cOp属性,$2会被替换为yylval.m_cOp,已在lex里赋值  
  56.     };  
  57.   
  58. %%  
  59.   
  60. void yyerror(const char *s) //当yacc遇到语法错误时,会回调yyerror函数,并且把错误信息放在参数s中  
  61. {  
  62.     cerr<<s<<endl;//直接输出错误信息  
  63. }  
  64.   
  65. int main()//程序主函数,这个函数也可以放到其它.c, .cpp文件里  
  66. {  
  67.     const char* sFile="file.txt";//打开要读取的文本文件  
  68.     FILE* fp=fopen(sFile, "r");  
  69.     if(fp==NULL)  
  70.     {  
  71.         printf("cannot open %s\n", sFile);  
  72.         return -1;  
  73.     }  
  74.     extern FILE* yyin;  //yyin和yyout都是FILE*类型  
  75.     yyin=fp;//yacc会从yyin读取输入,yyin默认是标准输入,这里改为磁盘文件。yacc默认向yyout输出,可修改yyout改变输出目的  
  76.   
  77.     printf("-----begin parsing %s\n", sFile);  
  78.     yyparse();//使yacc开始读取输入和解析,它会调用lex的yylex()读取记号  
  79.     puts("-----end parsing");  
  80.   
  81.     fclose(fp);  
  82.   
  83.     return 0;  
  84. }  


Makefile: makefile文件

[cpp] view plain copy
  1. LEX=flex  
  2. YACC=bison  
  3. CC=g++  
  4. OBJECT=main #生成的目标文件  
  5.   
  6. $(OBJECT): lex.yy.o  yacc.tab.o  
  7.     $(CC) lex.yy.o yacc.tab.o -o $(OBJECT)  
  8.     @./$(OBJECT) #编译后立刻运行  
  9.   
  10. lex.yy.o: lex.yy.c  yacc.tab.h  main.h  
  11.     $(CC) -c lex.yy.c  
  12.   
  13. yacc.tab.o: yacc.tab.c  main.h  
  14.     $(CC) -c yacc.tab.c  
  15.   
  16. yacc.tab.c  yacc.tab.h: yacc.y  
  17. # bison使用-d参数编译.y文件  
  18.     $(YACC) -d yacc.y  
  19.   
  20. lex.yy.c: lex.l  
  21.     $(LEX) lex.l  
  22.   
  23. clean:  
  24.     @rm -f $(OBJECT)  *.o  


 file.txt: 被解析的文本示例

[plain] view plain copy
  1. abc defghi  
  2. //this line is comment, abc 123 !@#$  
  3. 123 45678   //comment until line end  
  4. !   @   #   $  


使用方法:
1-把lex_yacc_example.rar解压到linux/cygwin下。
2-命令行进入lex_yacc_example目录。
3-敲入make,这时会自动执行以下操作:
(1) 自动调用flex编译.l文件,生成lex.yy.c文件。
(2) 自动调用bison编译.y文件,生成yacc.tab.c和yacc.tab.h文件。
(3) 自动调用g++编译、链接出可执行文件main。
(4) 自动执行main。
运行结果如下所示:

[plain] view plain copy
  1. bison -d yacc.y  
  2. g++ -c lex.yy.c  
  3. g++ -c yacc.tab.c  
  4. g++ lex.yy.o yacc.tab.o -o main           
  5. -----begin parsing file.txt  
  6. id: abc  
  7. id: defghi  
  8. (comment)  
  9. int: 123  
  10. int: 45678  
  11. (comment)  
  12. op: !  
  13. op: @  
  14. op: #  
  15. op: $  
  16. -----the file is end  
  17. all id: abc defghi  
  18. -----end parsing  


参考资料:《Lex和Yacc从入门到精通(6)-解析C-C++包含文件》http://blog.csdn.net/pandaxcl/article/details/1321552

[END]

阅读全文
'); })();
0 0
原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 夭山念什么 夭山 一个夭一个山读什么 一个夭一个山 夭折 夭折是什么意思 夭折孩子与父母的因果缘分 宝宝夭折是替父母挡灾么 岁城璃心为什么夭折了 少年武斗之时 花火.夭折 夭气 夭气预报 周口夭气预报 查询扬中30夭气预扳 金坛夭气预报 六枝特区毛口乡夭气预报 吉人自有夭相 相柳番外小夭怀孕 相柳有多爱小夭 东央西告 央告的意思 央组词 央部首 央的部首 苏婠央龙凌熙免费阅读 苏婠央龙凌熙全文目录 央的组词 纱央莉 纳兰雪央作品 央组词语 央字组词 南梨央奈 央组词什么 一小央泽 左央事件 小园梨央 央字部首 央词语 贺兰央央作品 若莱莱央在线中文字幕