linux下lex程序例子,一个Lex/Yacc完整的示例(可使用C++)

作者: 胡彦 2013-4-28

本例子虽小却演示了lex/yacc程序最重要和常用的特征:

* lex/yacc程序组成结构、文件格式。

* 如何在lex/yacc中使用C++和STL库,用extern "C"声明那些lex/yacc生成的、要链接的C函数,如yylex(), yywrap(), yyerror()。

* 重定义YYSTYPE/yylval为复杂类型。

* lex里多状态的定义和使用,用BEGIN宏在初始态和其它状态间切换。

* lex里正则表达式的定义、识别方式。

* lex里用yylval向yacc返回数据。

* yacc里用%token<>方式声明yacc记号。

* yacc里用%type<>方式声明非终结符的类型。

* 在yacc嵌入的C代码动作里,对记号属性($1, $2等)、和非终结符属性($$)的正确引用方法。

* 对yyin/yyout重赋值,以改变yacc默认的输入/输出目标。

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

文件列表:

lex.l:lex程序文件。

yacc.y:yacc程序文件。

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

Makefile:makefile文件。

lex.yy.c:用lex编译lex.l后生成的C文件。

yacc.tab.c:用yacc编译yacc.y后生成的C文件。

yacc.tab.h:用yacc编译yacc.y后生成的C头文件,内含%token、YYSTYPE、yylval等定义,供lex.yy.c和yacc.tab.c使用。

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

README.txt:本说明。

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

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

#ifndef MAIN_HPP

#define MAIN_HPP

#include //使用C++库

#include

#include //printf和FILE要用的

using namespace std;

/*当lex每识别出一个记号后,是通过变量yylval向yacc传递数据的。默认情况下yylval是int类型,也就是只能传递整型数据。

yylval是用YYSTYPE宏定义的,只要重定义YYSTYPE宏,就能重新指定yylval的类型(可参见yacc自动生成的头文件yacc.tab.h)。

在我们的例子里,当识别出标识符后要向yacc传递这个标识符串,yylval定义成整型不太方便(要先强制转换成整型,yacc里再转换回char*)。

这里把YYSTYPE重定义为struct Type,可存放多种信息*/

struct Type//通常这里面每个成员,每次只会使用其中一个,一般是定义成union以节省空间(但这里用了string等复杂类型造成不可以)

{

string m_sId;

int m_nInt;

char m_cOp;

};

#define YYSTYPE Type//把YYSTYPE(即yylval变量)重定义为struct Type类型,这样lex就能向yacc返回更多的数据了

#endif

lex.l: lex程序文件

%{

/*本lex的生成文件是lex.yy.c

lex文件由3段组成,用2个%%行把这3段隔开。

第1段是声明段,包括:

1-C代码部分:include头文件、函数、类型等声明,这些声明会原样拷到生成的.c文件中。

2-状态声明,如%x COMMENT。

3-正则式定义,如digit ([0-9])。

第2段是规则段,是lex文件的主体,包括每个规则(如identifier)是如何匹配的,以及匹配后要执行的C代码动作。

第3段是C函数定义段,如yywrap()的定义,这些C代码会原样拷到生成的.c文件中。该段内容可以为空*/

//第1段:声明段

#include "main.h"//lex和yacc要共用的头文件,里面包含了一些头文件,重定义了YYSTYPE

#include "yacc.tab.h"//用yacc编译yacc.y后生成的C头文件,内含%token、YYSTYPE、yylval等定义(都是C宏),供lex.yy.c和yacc.tab.c使用

extern "C"//为了能够在C++程序里面调用C函数,必须把每一个需要使用的C函数,其声明都包括在extern "C"{}块里面,这样C++链接时才能成功链接它们。extern "C"用来在C++环境下设置C链接类型。

{//yacc.y中也有类似的这段extern "C",可以把它们合并成一段,放到共同的头文件main.h中

int yywrap(void);

int yylex(void);//这个是lex生成的词法分析函数,yacc的yyparse()里会调用它,如果这里不声明,生成的yacc.tab.c在编译时会找不到该函数

}

%}

/*lex的每个正则式前面可以带有"",例如下面的"\n"。每个状态要先用%x声明才能使用。

当lex开始运行时,默认状态是INITIAL,以后可在C代码里用"BEGIN 状态名;"切换到其它状态(BEGIN是lex/yacc内置的宏)。

这时,只有当lex状态切换到COMMENT后,才会去匹配以开头的正则式,而不匹配其它状态开头的。

也就是说,lex当前处在什么状态,就考虑以该状态开头的正则式,而忽略其它的正则式。

其应用例如,在一段C代码里,同样是串"abc",如果它写在代码段里,会被识别为标识符,如果写在注释里则就不会。所以对串"abc"的识别结果,应根据不同的状态加以区分。

本例子需要忽略掉文本中的行末注释,行末注释的定义是:从某个"//"开始,直到行尾的内容都是注释。其实现方法是:

1-lex启动时默认是INITIAL状态,在这个状态下,串"abc"会识别为标识符,串"123"会识别为整数等。

2-一旦识别到"//",则用BEGIN宏切换到COMMENT状态,在该状态下,abc这样的串、以及其它字符会被忽略。只有识别到换行符\n时,再用BEGIN宏切换到初始态,继续识别其它记号。*/

%x COMMENT

/*非数字由大小写字母、下划线组成*/

nondigit([_A-Za-z])

/*一位数字,可以是0到9*/

digit([0-9])

/*整数由1至多位数字组成*/

integer({digit}+)

/*标识符,以非数字开头,后跟0至多个数字或非数字*/

identifier({nondigit}({nondigit}|{digit})*)

/*一个或一段连续的空白符*/

blank_chars([ \f\r\t\v]+)

/*下面%%后开始第2段:规则段*/

%%

{identifier}{//匹配标识符串,此时串值由yytext保存

yylval.m_sId=yytext;//通过yylval向yacc传递识别出的记号的值,由于yylval已定义为struct Type,这里就可以把yytext赋给其m_sId成员,到了yacc里就可以用$n的方式来引用了

return IDENTIFIER;//向yacc返回: 识别出的记号类型是IDENTIFIER

}

{integer}{//匹配整数串

yylval.m_nInt=atoi(yytext);//把识别出的整数串,转换为整型值,存储到yylval的整型成员里,到了yacc里用$n方式引用

return INTEGER;//向yacc返回: 识别出的记号类型是INTEGER

}

{blank_chars}{//遇空白符时,什么也不做,忽略它们

}

\n{//遇换行符时,忽略之

}

"//"{//遇到串"//",表明要开始一段注释,直到行尾

cout<

BEGIN COMMENT;//用BEGIN宏切换到注释状态,去过滤这段注释,下一次lex将只匹配前面带有的正则式

}

.{//.表示除\n以外的其它字符,注意这个规则要放在最后,因为一旦匹配了.就不会匹配后面的规则了(以其它状态<>开头的规则除外)

yylval.m_cOp=yytext[0];//由于只匹配一个字符,这时它对应yytext[0],把该字符存放到yylval的m_cOp成员里,到了yacc里用$n方式引用

return OPERATOR;//向yacc返回: 识别出的记号类型是OPERATOR

}

\n{//注释状态下的规则,只有当前切换到COMMENT状态才会去匹配

BEGIN INITIAL;//在注释状态下,当遇到换行符时,表明注释结束了,返回初始态

}

.{//在注释状态下,对其它字符都忽略,即:注释在lex(词法分析层)就过滤掉了,不返回给yacc了

}

%%

//第3段:C函数定义段

int yywrap(void)

{

puts("-----the file is end");

return 1;//返回1表示读取全部结束。如果要接着读其它文件,可以这里fopen该文件,文件指针赋给yyin,并返回0

}

yacc.y: yacc程序文件

%{

/*本yacc的生成文件是yacc.tab.c和yacc.tab.h

yacc文件由3段组成,用2个%%行把这3段隔开。

第1段是声明段,包括:

1-C代码部分:include头文件、函数、类型等声明,这些声明会原样拷到生成的.c文件中。

2-记号声明,如%token

3-类型声明,如%type

第2段是规则段,是yacc文件的主体,包括每个产生式是如何匹配的,以及匹配后要执行的C代码动作。

第3段是C函数定义段,如yyerror()的定义,这些C代码会原样拷到生成的.c文件中。该段内容可以为空*/

//第1段:声明段

#include "main.h"//lex和yacc要共用的头文件,里面包含了一些头文件,重定义了YYSTYPE

extern "C"//为了能够在C++程序里面调用C函数,必须把每一个需要使用的C函数,其声明都包括在extern "C"{}块里面,这样C++链接时才能成功链接它们。extern "C"用来在C++环境下设置C链接类型。

{//lex.l中也有类似的这段extern "C",可以把它们合并成一段,放到共同的头文件main.h中

void yyerror(const char *s);

extern int yylex(void);//该函数是在lex.yy.c里定义的,yyparse()里要调用该函数,为了能编译和链接,必须用extern加以声明

}

%}

/*lex里要return的记号的声明

用token后加一对来定义记号,旨在用于简化书写方式。

假定某个产生式中第1个终结符是记号OPERATOR,则引用OPERATOR属性的方式:

1-如果记号OPERATOR是以普通方式定义的,如%token OPERATOR,则在动作中要写$1.m_cOp,以指明使用YYSTYPE的哪个成员

2-用%tokenOPERATOR方式定义后,只需要写$1,yacc会自动替换为$1.m_cOp

另外用<>定义记号后,非终结符如file, tokenlist,必须用%type来定义(否则会报错),以指明它们的属性对应YYSTYPE中哪个成员,这时对该非终结符的引用,如$$,会自动替换为$$.member*/

%tokenINTEGER

%tokenIDENTIFIER

%tokenOPERATOR

%typefile

%typetokenlist

%%

file://文件,由记号流组成

tokenlist//这里仅显示记号流中的ID

{

cout<定义的,即约定对其用YYSTYPE的m_sId属性,$1相当于$1.m_sId,其值已经在下层产生式中赋值(tokenlist IDENTIFIER)

};

tokenlist://记号流,或者为空,或者由若干数字、标识符、及其它符号组成

{

}

| tokenlist INTEGER

{

cout<定义的,即约定对其用YYSTYPE的m_nInt属性,$2会被替换为yylval.m_nInt,已在lex里赋值

}

| tokenlist IDENTIFIER

{

$$+=" " + $2;//$$是非终结符tokenlist的属性,由于该终结符是用%type定义的,即约定对其用YYSTYPE的m_sId属性,$$相当于$$.m_sId,这里把识别到的标识符串保存在tokenlist属性中,到上层产生式里可以拿出为用

cout<定义的,即约定对其用YYSTYPE的m_sId属性,$2会被替换为yylval.m_sId,已在lex里赋值

}

| tokenlist OPERATOR

{

cout<定义的,即约定对其用YYSTYPE的m_cOp属性,$2会被替换为yylval.m_cOp,已在lex里赋值

};

%%

void yyerror(const char *s)//当yacc遇到语法错误时,会回调yyerror函数,并且把错误信息放在参数s中

{

cerr<

}

int main()//程序主函数,这个函数也可以放到其它.c, .cpp文件里

{

const char* sFile="file.txt";//打开要读取的文本文件

FILE* fp=fopen(sFile, "r");

if(fp==NULL)

{

printf("cannot open %s\n", sFile);

return -1;

}

extern FILE* yyin;//yyin和yyout都是FILE*类型

yyin=fp;//yacc会从yyin读取输入,yyin默认是标准输入,这里改为磁盘文件。yacc默认向yyout输出,可修改yyout改变输出目的

printf("-----begin parsing %s\n", sFile);

yyparse();//使yacc开始读取输入和解析,它会调用lex的yylex()读取记号

puts("-----end parsing");

fclose(fp);

return 0;

}

Makefile: makefile文件

LEX=flex

YACC=bison

CC=g++

OBJECT=main #生成的目标文件

$(OBJECT): lex.yy.o yacc.tab.o

$(CC) lex.yy.o yacc.tab.o -o $(OBJECT)

@./$(OBJECT) #编译后立刻运行

lex.yy.o: lex.yy.c yacc.tab.h main.h

$(CC) -c lex.yy.c

yacc.tab.o: yacc.tab.c main.h

$(CC) -c yacc.tab.c

yacc.tab.c yacc.tab.h: yacc.y

# bison使用-d参数编译.y文件

$(YACC) -d yacc.y

lex.yy.c: lex.l

$(LEX) lex.l

clean:

@rm -f $(OBJECT) *.o

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

abc defghi

//this line is comment, abc 123 !@#$

12345678//comment until line end

!@#$

使用方法:

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。

运行结果如下所示:

bison -d yacc.y

g++ -c lex.yy.c

g++ -c yacc.tab.c

g++ lex.yy.o yacc.tab.o -o main

-----begin parsing file.txt

id: abc

id: defghi

(comment)

int: 123

int: 45678

(comment)

op: !

op: @

op: #

op: $

-----the file is end

all id: abc defghi

-----end parsing

[END]

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值