利用flex & bison (lex & yacc)創建可重入(線程安全)的詞法分析和語法解析器

 利用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;
}

   有了可重入和參數傳遞基礎,我們就可以很方便的設計自己的線程安全詞法和語法分析程序,所有的信息可以通過
傳遞參數搞定。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章