ChinaUnix首页 > 精华文章 > C/C++ > 正文

[原创] 利用flex & bison (lex & yacc)创建可重入(线程安全)的词法分析和语法解析器


http://www.chinaunix.net 作者:linux_prog  发表于:2008-11-14 23:35:16
发表评论】 【查看原文】 【C/C++讨论区】【关闭

利用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版本




原文链接:http://bbs.chinaunix.net/viewthread.php?tid=985564
转载请注明作者名及原文出处