flex学习 - 可重入的C扫描器

flex能够生成可重入的C扫描程序。生成的扫描器是可移植的,并且可以安全的在一个或多个独立的控制线程中使用。可重入扫描器最常见的用途是在多线程应用程序中。任何线程都可以创建和执行可重入的flex扫描器,而不需要与其它线程同步。
17.1 可重入的使用
但是可重入扫描器还有其它用途。例如,你可以同时扫描两个或更多的文件来实现在标记级别的差异(即,而不是在字符级别):
/* Example of maintaining more than one active scanner. */

do {
    int tok1, tok2;

    tok1 = yylex( scanner_1 );
    tok2 = yylex( scanner_2 );

    if( tok1 != tok2 )
        printf("Files are different.");
 } while ( tok1 && tok2 );

可重入扫描程序的另一个用途是递归。(注意,也可以使用不可重入的扫描程序和缓冲状态创建递归扫描程序。参见多输入缓冲区。)
下面的简单扫描器通过调用自身的另一个实例来支持’eval’命令。
/* Example of recursive invocation. */

%option reentrant

%%
"eval(".+")"  {
                  yyscan_t scanner;
                  YY_BUFFER_STATE buf;

                  yylex_init( &scanner );
                  yytext[yyleng-1] = ' ';

                  buf = yy_scan_string( yytext + 5, scanner );
                  yylex( scanner );

                  yy_delete_buffer(buf,scanner);
                  yylex_destroy( scanner );
             }
...
%%

17.2 可重入API概述
可重入扫描器的API和不可重入的扫描器不同。这里是一个API的快速概要:
1、%option reentrant必须被指定
2、所有的函数必须添加一个参数:yyscanner
3、所有的全局变量通过它们的等效宏替换。(我们告诉你这是因为在调试器件它是重要的)
4、yylex必须分别在yylex_init之后和yylex_destroy之前调用
5、访问方法(get/set函数)提供访问公共flex变量的方法
6、用户指定的数据可以被存储在yyextra中
17.3 可重入示例
首先,一个可重入扫描器的例子如下:
/* This scanner prints "//" comments. */

%option reentrant stack noyywrap
%x COMMENT

%%

"//"                 yy_push_state( COMMENT, yyscanner);
.|\n

<COMMENT>\n          yy_pop_state( yyscanner );
<COMMENT>[^\n]+      fprintf( yyout, "%s\n", yytext);

%%

int main ( int argc, char * argv[] )
{
    yyscan_t scanner;

    yylex_init ( &scanner );
    yylex ( scanner );
    yylex_destroy ( scanner );
return 0;
}

17.4 可重入API的详细描述
以下是使用flex可重入C API需要做或知道的事情:
1、可重入扫描器的声明
%option reentrant(-reentrant)必须被指定
注意%option reentrant是在上面的示例中指定的(参见可重入示例)。如果没有指定这个选项,flex会很高兴的生成一个不可重入的扫描器,而不会由任何的抱怨。如果不想使用可重入扫描程序,可以显示指定%option noreentrant,尽管这不是必须的。默认产生一个非可重入的扫描器。
2、额外的参数
所有的函数添加一个参数:yyscanner
注意,对yy_push_state和yy_pop_state的调用都有一个参数yyscanner,该参数在不可重入的扫描器中不存在。这里的yy_push_state和yy_pop_state在可重入扫描器中被声明:
static void yy_push_state ( int new_state , yyscan_t yyscanner ) ;
static void yy_pop_state ( yyscan_t yyscanner ) ;
注意,参数yyscanner出现在两个函数的声明中。实时上,可重入扫描器中的所有flex函数都有这个附加参数。它始终是参数列表中的最后一个参数,它始终是类型yyscan_t(类型定义为void ),并且始终命名为yyscanner。正如您可能已经猜想的那样,yyscanner是一个指针,指向封装了扫描器当前状态的不透明数据结构。关于函数声明的列表,查看可重入函数章节。注意,预处理的宏,像BEGIN,ECHO,和REJECT,不需要这个参数。
3、通过宏替换全局变量
在传统的flex中所有的全局变量通过等效的宏替换。
注意,在上面的例子中,yyout和yytext不是纯变量。这些宏将被展开为它们等效的lvalue。所有flex系列的全局变量已经通过它们等效的宏替换。典型的,yytext,yyleng,yylineno,yyin,yyout,yyextra,yylval,和yylloc是宏。你可以安全的在操作中使用这些宏,就好像它们是普通的变量一样。我们告诉你这些只是为了让你不希望外部链接到这些变量。目前,每个宏都扩展为内部结构体的成员,例如:
#define yytext (((struct yyguts_t
)yyscanner)->yytext_r)
关于yytext和friends,需要记住的重要一点是,yytext不是可重入扫描器中的全局变量,您不能从操作外部或其它函数直接访问它。你必须使用访问器方法,例如,yyget_text来完成此操作(见下文)。
4、初始化和销毁函数
yylex必须分别在yylex_init之后和yylex_destory之前调用。
int yylex_init ( yyscan_t * ptr_yy_globals ) ;
int yylex_init_extra ( YY_EXTRA_TYPE user_defined, yyscan_t * ptr_yy_globals ) ;
int yylex ( yyscan_t yyscanner ) ;
int yylex_destroy ( yyscan_t yyscanner ) ;
函数yylex_init必须在任何其它函数调用前调用。yylex_init的参数是一个通过yylex_init填充的未初始化指针地址,复写任何之前的内容。这个函数yylex_init_extra可以被替代使用,它的第一个参数是YY_EXTRA_TYPE类型的变量。查看下面的yyextra章节更详细的信息。
在ptr_yy_globals中存储的值应该随后被传递给yylex和yylex_destroy。flex不会保存传递给yylex_init的参数,因此只要在所有对扫描器调用期间(包括对yylex_destroy的调用)仍然在作用域中,将本地指针的地址传递给yylex_init是安全的。
现在你应当对yylex函数很熟悉了。可重入版本接受一个参数,即yylex_init返回的值(通过参数)。其它的,它的行为与非可重入的yylex版本相似。
yylex_init和yylex_init_extra都以返回0表示成功,非0值表示失败,一些错误码通过下面的值设置:
(1)ENOMEM内存分配错误。查看内存管理
(2)EINVAL无效的参数
yylex_destroy这个函数应当在扫描器使用的资源释放时调用。yylex_destroy被调用后yyscanner的内容不应当被继续使用。可以确认,如果你计划恢复使用它没有需要销毁一个扫描器。flex扫描器(包括可重入和非可重入)可以通过调用yyrestart重新开始。
下面的示例创建一个扫描器,使用它,然后当完成后销毁它:

int main (){
   yyscan_t scanner;
   int tok;
   yylex_init(&scanner);
   while ((tok=yylex(scanner)) > 0)
      printf("tok=%d  yytext=%s\n", tok, yyget_text(scanner));
   yylex_destroy(scanner);
   return 0;
}

5、可重入扫描器访问变量
访问方法(get/set函数)提供了访问公共flex变量的方法。
你构建的许多扫描器将是更大项目的一部分。项目的某些部分将需要访问flex的值,例如yytext。在不可重入的扫描器中,这些值是全局的,因此访问它们没有问题。然而,在可重入的扫描器中,没有全局的flex值。你不能直接的访问它们。替代的,你必须使用访问器方法(get/set函数)访问flex值。每个访问器方法被命名为yyget_NAME或yyset_NAME,这里的NAME是flex变量的名字。例如:
/* Set the last character of yytext to NULL. */

void chop ( yyscan_t scanner ){
    int len = yyget_leng( scanner );
    yyget_text( scanner )[len - 1] = '\0';
}

上面的代码可以像这样从一个动作中调用:
%%
.+\n { chop( yyscanner );}
你可能会发现%option header-file对于生成所有访问器函数的原型特别有用。查看option-header章节。
6、额外的数据
用户指定的数据可以被存储在yyextra中。
在可重入的扫描器中,使用全局变量的程序的不同部分之间进行通信或维护状态是不明智的。但是,你可能需要访问外部数据或从扫描程序操作中调用外部函数。同样,你可能需要将信息传递给扫描器(例如,打开的文件描述符或数据库连接)。在不可重入的扫描程序中,唯一的方法是使用全局变量。flex允许你在扫描器中存储任何的”extra”数据。这些数据可以通过访问器方法yyget_extra和yyset_extra从扫描程序外部访问,也可以通过扫描程序内部的快捷宏yyextra访问。它们的定义如下:
#define YY_EXTRA_TYPE void*
YY_EXTRA_TYPE yyget_extra ( yyscan_t scanner );
void yyset_extra ( YY_EXTRA_TYPE arbitrary_data , yyscan_t scanner);
此外,还提供了yylex_init的一个额外形式,yylex_init_extra。提供这个函数是为了可以从第一个yyalloc中访问yyextra的值,用于分配扫描器本身。
默认情况下,YY_EXTRA_TYPE被定义为void 类型。你可以使用%option extra-type=”your_type”在扫描器中重新定义这个类型:
/
An example of overriding YY_EXTRA_TYPE. */

%{
#include <sys/stat.h>
#include <unistd.h>
%}
%option reentrant
%option extra-type="struct stat *"
%%

__filesize__     printf( "%ld", yyextra->st_size  );
__lastmod__      printf( "%ld", yyextra->st_mtime );
%%
void scan_file( char* filename )
{
    yyscan_t scanner;
    struct stat buf;
    FILE *in;

    in = fopen( filename, "r" );
    stat( filename, &buf );

    yylex_init_extra( buf, &scanner );
    yyset_in( in, scanner );
    yylex( scanner );
    yylex_destroy( scanner );
    fclose( in );
}

7、关于yyscan_t
yyscan_t被定义为:
typedef void* yyscan_t;
它通过yylex_init()初始化指向一个内部结构体。你应当不会直接访问这个值。特别是,永远不要试图释放它(使用yylex_destory()替代。)
17.5 在可重入C扫描器中的函数和宏变量
下面的函数在可重入扫描器中是有效的:

char *yyget_text ( yyscan_t scanner );
int yyget_leng ( yyscan_t scanner );
FILE *yyget_in ( yyscan_t scanner );
FILE *yyget_out ( yyscan_t scanner );
int yyget_lineno ( yyscan_t scanner );
YY_EXTRA_TYPE yyget_extra ( yyscan_t scanner );
int  yyget_debug ( yyscan_t scanner );

void yyset_debug ( int flag, yyscan_t scanner );
void yyset_in  ( FILE * in_str , yyscan_t scanner );
void yyset_out  ( FILE * out_str , yyscan_t scanner );
void yyset_lineno ( int line_number , yyscan_t scanner );
void yyset_extra ( YY_EXTRA_TYPE user_defined , yyscan_t scanner );

对于yytext和yyleng没有’set’函数。这是有意为之的。
下面的缩写宏在可重入扫描器中的动作是有效的:
yytext
yyleng
yyin
yyout
yylineno
yyextra
yy_flex_debug
在可重入的C扫描器中,对yylineno的支持总是存在的(即,你可以访问yylineno),但是除非%option yylineno被启用,否则该值永远不会被flex修改。这是为了允许用于独立于flex维护行数。
当%option bison-bridge(‘--bison-bridge’)被指定时下面的函数和宏是有效的:
YYSTYPE * yyget_lval ( yyscan_t scanner );
void yyset_lval ( YYSTYPE * yylvalp , yyscan_t scanner );
yylval
当%option bison-locations(‘--bison-locations’)被指定时下面的函数和宏是有效的:
YYLTYPE *yyget_lloc ( yyscan_t scanner );
void yyset_lloc ( YYLTYPE * yyllocp , yyscan_t scanner );
yylloc
支持yylval的前提是YYSTYPE是一个有效的类型。支持yylloc的前提是YYSLYPE是一个有效的类型。典型的,这些类型通过bison产生,且被包括在flex输入的第一段中。

posted @ 2025-03-19 23:28  xiaobing3314  阅读(110)  评论(0)    收藏  举报