利用flex & bison (lex & yacc)创建可重入(线程安全)的词法分析和语法解析器
希望读者能有lex yacc基础。
来自:http://www.loveopensource.com/?p=29
使用flex(lex)和bison(yacc)可以非常方便的创建词法分析和语法分析器,典型的这类程序都是
使用一些全局变量进行信息的传递,这也是程序默认的方式,比如:flex解析到一个string,可以通过
yylval传递给bison;再就是flex和bison默认是通过一些全局变量来保存解析的status信息。简单点说,
flex和bison默认创建的词法分析和语法分析的C代码都不是线程安全的,都是不可重入的。
随着多线程编码在linux上的广泛使用,我们对线程安全的词法分析和语法分析的需求也越来越强烈。
本节讨论一下实施方法。
(1) flex的可重入实施
使用flex创建C++的词法解析器时,默认就是可重入的。
需求加入:
%option c++
flex test.lex时,会自动生成lex.yy.cc文件。
root@horde1:~/test/Reentrant Parser#locate FlexLexer.h
/usr/include/FlexLexer.h
可以查看一下FlexLexer.h
里面定义了2个类:FlexLexer和yyFlexLexer
说明见:
http://dinosaur.compilertools.net/flex/flex_19.html#SEC19
范例:
%{
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
%}
%option yylineno
%option noyywrap
%option c++
%%
insert { return CMD_INSERT; }
delete { return CMD_DELETE; }
update { return CMD_UPDATE; }
select { return CMD_SELECT; }
[ \n\t\r]+ { ; }
. { return CMD_ERROR; }
%%
我自己做了集成,把要解析的命令传给自己的class:
#ifndef _DO_H_
#define _DO_H_
#include <iostream>
#include <string>
#include <FlexLexer.h>
using namespace std;
class myLexer: public yyFlexLexer
{
public:
// set parse command
myLexer(const char *sql)
{
snprintf(content, sizeof(content), sql);
parseP = content;
}
// flex通过调用这个方法取得要解析的字符串的
virtual int LexerInput(char* buf, int max_size)
{
if(parseP == NULL) return 0;
if(*parseP == '\0') return 0;
strncpy(buf, parseP, max_size - 1);
buf[max_size - 1] = '\0';
int len = strlen(buf);
parseP += len;
return len;
}
//错误函数
virtual void LexerError(const char* msg)
{
cout<<"error:"<< msg << " at line:" << lineno() <<endl;
}
virtual ~myLexer() {}
private:
char content[4096];
char *parseP;
};
#endif
调用方法:
const char *sql = "update insert select delete update insert select delete";
const char *sql2 = "delete select insert update delete select insert update";
myLexer *myl = new myLexer(sql);
myLexer *myl2 = new myLexer(sql2);
int cmd, cmd2;
while((cmd = myl->yylex()) != 0 && (cmd2 = myl2->yylex()) != 0)
{
cout<< "cmd1 is:" << cmd << " cmd2 is:" << cmd2 << endl;
}
delete myl;
delete myl2;
return 0;
}
用2个词法解析器同时进行解析,发现没有问题,说明是可重入的。
(2) bison的可重入实施
官方文档有:http://dinosaur.compilertools.net/bison/bison_6.html#SEC56
注意点: 必须加入%pure_parser
表示创建可重入的语法解析程序。
这时候无法使用全局变量共享信息,必须通过参数传递。
#define YYPARSE_PARAM parm
#define YYLEX_PARAM parm
表示,会传递一个参数给yyparse()和yylex函数,因为默认的yyparse和yylex是不接受参数的。
#define yyerror(msg) my_yyerror(msg, YYPARSE_PARAM) //把这个参数也传递给yyerror
贴出全部程序:
%{
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string>
#include "do.h"
using namespace std;
%}
%union
{
int cmd_type;
}
%{
#define YYPARSE_PARAM parm
#define YYLEX_PARAM parm
#define yyerror(msg) my_yyerror(msg, YYPARSE_PARAM)
int yylex(YYSTYPE *lvalp, void *parm)
{
myLexer *l = (myLexer *)parm;
return l->yylex();
}
int my_yyerror(char *s, void *parm)
{
myLexer *l = (myLexer *)parm;
cout<<"bison got error\n";
l->LexerError(s);
return 1;
}
%}
%token CMD_INSERT
%token CMD_DELETE
%token CMD_UPDATE
%token CMD_SELECT
%token CMD_ERROR
%pure_parser
%start sql
%%
sql:
|sql command;
command: CMD_INSERT { cout<<"meet a insert\n"; }
| CMD_DELETE { cout<<"meet a delete\n"; }
| CMD_UPDATE { cout<<"meet a update\n"; }
| CMD_SELECT { cout<<"meet a select\n"; }
%%
调用方法:
#include <string>
#include <FlexLexer.h>
#include "do.h"
using namespace std;
int main(int argc, char **argv)
{
const char *sql = "update insert select delete update insert select delete";
const char *sql2 = "delete select insert update delete select insert update";
myLexer *myl = new myLexer(sql);
//进行语法解析
yyparse((void *)myl);
delete myl;
return 0;
}
有了可重入和参数传递基础,我们就可以很方便的设计自己的线程安全词法和语法分析程序,所有的信息可以通过
传递参数搞定。
Parser.rar
converse 回复于:2007-09-03 14:43:28
原创?
linux_prog 回复于:2007-09-03 15:48:18
yes.
cugb_cat 回复于:2007-09-03 15:51:29
引用:原帖由 converse 于 2007-9-3 14:43 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7301222&ptid=985564]
原创?
版主是不是想加精?:mrgreen:
nully 回复于:2007-09-03 16:52:48
一直想不通bison有%pure_parser选项支持可重入,而flex生成C代码却不去持
shit
zhaoli_824 回复于:2008-01-31 14:03:20
引用:
while((cmd = myl->yylex()) != 0 && (cmd2 = myl2->yylex()) != 0)
{
cout<< "cmd1 is:" << cmd << " cmd2 is:" << cmd2 << endl;
}
这也能叫两个分析器同时工作? 楼主至少也应该创建两个线程才能说是可重入的.
并且两个分析器都要在死循环里工作才能说是同时在工作.
我刚刚入门在用pargen 2, 还是谢谢楼主的方法, 我试过了再来回复.
yecheng_110 回复于:2008-01-31 14:39:43
flex & bison好像可以闭源使用
不过有限制
不知道楼主清楚不?
zszyj 回复于:2008-01-31 15:43:54
引用:原帖由 linux_prog 于 2007-9-3 14:18 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7301026&ptid=985564]
利用flex & bison (lex & yacc)创建可重入(线程安全)的词法分析和语法解析器
希望读者能有lex yacc基础。
来自:http://www.loveopensource.com/?p=29
使用flex(lex)和bison(yacc)可以非常方便的创建词 ...
虽然我早就经使用它的C++版本实现了楼主所说的可重入,不过楼主的认真研究精神还是值得支持的.
dirtysalt 回复于:2008-11-14 23:35:16
可以看看作者在bison部分的yylex接口
int yylex(YYSTYPE *lvalp, void *parm)
{
myLexer *l = (myLexer *)parm;
return l->yylex();
}
这个程序能够正常运行,仅仅是在l->yylex()里面不使用任何牵涉到任何yylval的东西,因为为了可重入在这里原来的yylval已经是lvalp指定了,而
l里面并没有任何保存lvalp的信息。
我感觉现在flex和bison在产生C++类层面上之后,交互已经相当少了,感觉貌似flex和bison要分道扬镳,看看yyFlexLexer产生接口
yyFlexLexer(istream *in,istream* out);
void yylex()
void switch_stream(istream *in,istream *out);
int yylex(istream *in,istream *out);
没有一项可以指定原来的YYSTYPE *yylval和YYLTYPE *yylloc这个和bison交互的对象
而看看bison产生类所使用的lex接口,parser里面直接调用parser::lex_()接口,而lex_()直接调用全局的yylex()
int yylex (semantic_value_type& yylval, location_type& yylloc, type1 arg1, ...)
还是使用全局的yylex???
个人感觉如果使用C++类来完成两者的结合基本上很困难。但是依然有办法可以创建可重入的,就是使用yylex中的%reentrant这个选项,使用了yyscan_t保存状态,里面可以设置yylval和yylloc这两个对象。
如果一定需要在类上面的接合,推荐使用ANTLR2.7.7版本,高版本3.x.x还没有出C++,但是出了Java版本
|