现在的位置: 首页 > 综合 > 正文

Flex和Bison的C++可重进入—多线程解决方案

2013年11月14日 ⁄ 综合 ⁄ 共 2599字 ⁄ 字号 评论关闭

目前我们部门用到flex & bison解析器的地方很多,除了编译器 & 汇编器外,还有其他蛮多地方均用到flex & bison解析器,但是没有一个是线程安全的,也就是说在多线程环境下是不能够正常work,因此研究线程安全性就很有必要性。

使用flex(lex)和bison(yacc)可以非常方便的创建词法分析和语法分析器,典型的这类程序都是使用一些全局变量进行信息的传递,这也是这种程序默认的方式,比如:flex解析到一个string,可以通过 yylval传递给bison;再就是flex和bison默认是通过一些全局变量来保存解析的status信息。简单点说, flex和bison默认创建的词法分析和语法分析的C/C++代码都不是线程安全的,都是不可重入的。

本文就讲述如何利用flex & bison创建线程安全的解析器:

1)     
flex
的可重入实施.

使用flex创建C++ Code的词法解析器时,默认就是可重入的(如果创建的是C
Code
词法解析器默认是不可重入的),但是这种可重入是不能直接使用,等到讲述bison的可重入时我们再回来说明这个问题
.
要让flex生成C++ code,我们需在.l文件中加入%option c++,或者在使用的时候加-+参数即可.

 

2)     
bison
的可重入实施.

在编写.y文件的时候加入%pure_parser,表示创建可重入的语法解析程序,这时候无法使用全局变量共享信息,必须通过参数传递。但是默认的解析器的构造函数parse()接受的参数为空,返回值也只是表示分析成败的一个整数,且都不能定制。因此,最好的办法就是在Parser的定义中增加一个数据成员,来保存语法分析的结果,待分析成功结束时,再从此数据成员中取出分析结果即可,
通常这个变量被称为“上下文”(context),这一点可以用指令%parse-param { type name } 来完成。

/* The driver is passed by reference to the parser and to the scanner. This

 * provides a simple but effective pure interface, not relying on global

 * variables. */

%parse-param    { class C_Driver& driver }

Or

#define  YYPARSE_PARAM  parm(表示Parser全局函数带有一个参数为Void *,但是我们不这样用)

这样user可以在解析程序中使用driver or parm这两个变量.这两个变量都是从外面通过参数的形式传给解析程序,那此时bison如何跟flex通信呢?这时候就不会透过全局变量yylval等进行通信了,加入%pure_parser新生成的代码中已经没有yylval这个全局变量了,在解析器的内部实际上调用的是带参数的yylex函数,yylex(semantic_type*
yylval,location_type* yylloc),看到没?是通过参数把yylval 传给flex,从而达到通信的目,另外一个参数location_type* yylloc是一个用于跟踪位置的参数,你可以使用该参数进行位置跟踪,具体使用可以参考下面的附近程序.我们这时候回过头来看flex,flex对C++支持的基本原理是通过继承来完成的。在头文件cwgwin\usr\include\FlexLexer.h(or
\Lib_Toolchain\Include\FlexBison\FlexLexer.h)中定义了两个类FlexLexer和yyFlexLexer。前者是一个抽象类,定义了Flex所生成的词法分析器的接口(interface),比如yylex();注意是没有带参数的,后者则继承自FlexLexer,是词法分析器的实现类,封装了词法分析器用到的状态变量等。因此,在默认的C++模式下,Flex的任务就是根据 ”.l” 源文件自动生成yyFlexLexer中各成员函数的定义。规则的动作代码自然是被合成为yyFlexLexer::yylex()的实现啦,因此在规则动作中我们访问的变量和函数实际都是yyFlexLexer类的成员,而不再是全局变量或全局函数。因为这里的yylex()函数是不带参数,而且是虚方法,而刚才我们看到bison需要的是带参数的yylex(),这怎么办,我们可以采取这样的方法:

首先我们重新定义一个新的类叫C_Scanner,public
yyFlexLexer, 在该类中我们定义另外的lex函数,这时候带有参数,如:

virtual  C_Parser::token_type yylex(

        semantic_type* yylval,

        location_type* yylloc

    );

而它的实现在.l生成的.cpp文件中,而它原型声明是用一个Macro: YY_DECL来表示,因此我们必须重新定义该Macro,从而达到重新定义的目的.(从这里我们可以看出它的灵活性)

#define YY_DECL                          \

    C_Parser::token_type             \

    C_Scanner::yylex(                   \

C_Parser::semantic_type* yylval,        \

    C_Parser::location_type* yylloc     \

    )

#endif

原先没有参数的yylex()中利用的是yylval,yylloc等全局变量,而这时我们是通过参数传递yylval,yylloc等。因此在.l中的C++ Code使用的必须跟参数中定义一样,比如参数中声明的是yylval1,而你在.l中使用yylval就会报错,因为.l中的规则中的C++ Code都会被Copy到yylex函数中,再者yylval已经不是全局变量,因此会报错找不到yylval.

还有一点很重要,那如何让yyparse中调用的是带参数的yylex,而不是没有带参数的,做法是在.y文件中加上以下两行:

#undef yylex

#define yylex driver.m_pLexer->lex

当然driver类必须定义一个C_Scanner的变量.

抱歉!评论已关闭.