flex学习 - 生成扫描器和开始条件

生成扫描器
flex的输出文件是lex.yy.c,其中包含扫描程序yylex(),用于匹配标记的一些表以及一些辅助函数和宏。默认情况下,yylex()的声明如下:
int yylex()
{
... various definitions and the actions in here ...
}
(如果你的环境支持函数原型,那么它将是int yylex(void)。)这个函数可以通过定义YY_DECL宏来改变。例如,你可以使用:
#define YY_DECL float lexscan( a, b ) float a, b;
将扫描函数命名为lexscan,返回一个浮点数,并接受两个浮点数作为参数。注意,如果使用K&R风格/非原型函数声明向扫描程序提供参数,则必须以分号(;)结束定义。
flex默认生成C99函数定义。flex过去有能力生成果实的,传统的函数定义。这是为了支持在旧系统上引导GCC。不幸的是,传统的定义阻止我们使用任何小于int的标准数据类型(如short,char或bool)作为函数参数。此外,传统的定义支持在框架文件中增加了额外的复杂性。由于这个原因,flex的当前版本只生成标准的C99代码,将K&R风格的函数留给历史学家。
每次调用yylex()时,它都会从全局输入文件yyin(默认为stdin)中扫描标记。它继续执行,直到到达文件末尾(此时它返回0),或者其中一个操作执行返回语句。
如果扫描器到达文件的末尾,则后续的调用是未定义的,除非yyin指向一个新的输入文件(在这种情况下,从该文件继续扫描),或者调用yyrestart()。yyrestart()接受一个参数,一个FILE指针(如果将YY_INPUT设置为从yyin以外的源扫描,则该指针可以为NULL),并初始化yyin以便从该文件扫描。从本质上讲,只是将yyin分配给新的输入文件或使用yyrestart()这样做没有区别;后者是为了与以前版本的flex兼容,并且因为它可以用于在扫描过程中切换输入文件。它也可以用来丢弃当前输入缓冲区,通过使用yyin参数调用它;但最好使用YY_FLUSH_BUFFER(参见动作)。注意yyrestart()不会将启动条件重置为INITIAL(参见启动条件)。
如果yylex()由于在其中一个操作中执行return语句而停止扫描,则可能会再次调用扫描器,它将在停止的地方恢复扫描。
默认情况下(为了提高效率),扫描器使用块读取而不是简单的getc()调用从yyin读取字符。它获取输入的方式可以通过定义YY_INPUT宏来控制。YY_INPUT()的调用顺序是YY_INPUT(buf,result,max_size)。它的作用是在字符数组中放置最大大小的字符,但在整数变量结果中result是读取的字符数或常量YY_NULL(在Unix系统上是0),以表示EOF。默认的YY_INPUT从全局文件指针yyin读取。
下面是YY_INPUT的一个示例定义(在输入文件的定义部分):
%{
#define YY_INPUT(buf,result,max_size)
{
int c = getchar();
result = (c == EOF) ? YY_NULL : (buf[0] = c, 1);
}
%}
这个定义将改变输入处理,每次只发生一个字符。
当扫描器从YY_INPUT接收到文件结束指示时,它就检查yywrap()函数。如果yywrap()返回false,则假定该函数已继续执行并将yyin设置为指向另一个输入文件,并继续扫描。如果返回true,则扫描程序终止,并向调用者返回0。注意,在这两种情况下,启动条件保持不变;它不会恢复到INITIAL。
如果您没有提供自己的yywrap()版本,那么您必须使用%option noyywrap(在这种情况下,扫描器的行为就像yywrap()返回1一样),或者您必须链接’-Ifl’以获取函数的默认版本,它总是返回1。
有关从内存缓冲区扫描(例如,扫描字符串),请参见扫描字符串。参见多输入缓冲区。
扫描器将其ECHO输出写入yyout全局变量(默认为stdout),用户可以通过将其赋值给其它一些FILE指针来重新定义它。
第八章开始条件
flex提供了一种条件激活规则的机制。任何模式前缀为’’的规则只有在扫描器处于名为’sc’的启动条件时才会被激活。例如:
[^"]
{ /* eat up the string body ... /
...
}
仅当扫描器遇到STRING开始条件时被激活,且
<INITIAL,STRING,QUOTE>. { /
handle an escape ... */
...
}
仅当当前的起始条件是INITIAL,STRING或QUOTE中的一个时将被激活。
开始条件在输入的定义(第一)部分中声明,使用以’%s’或’%x’开头的无缩进行,后跟者一列的名称。前者声明包容性启动条件,后者声明排他性启动条件。使用BEGIN操作激活启动条件。在执行下一个BEGIN操作前,具有给定启动条件的规则将处于活动状态,而具有其它启动条件的规则将处于非活动状态。如果启动条件是包含的,那么没有启动条件的规则也将是活动的。如果是排他性的,那么只有符合启动条件的规则才会被激活。基于相同独占启动条件的一组规则描述了一个扫描器,它独立于flex输入中的任何其它规则。正因为如此,排他性的启动条件可以很容易的指定”迷你扫描器”,它扫描语法上与其它部分不同的输入部分(例如,注释)。
如果包容性和排他性启动条件之间的区别仍然有点模糊,那么这里有一个简单的示例来说明两者之间的联系。这套规则是:
%s example
%%

<example>foo   do_something();

bar            something_else();

等效于:
%x example
%%

<example>foo   do_something();

<INITIAL,example>bar    something_else();

如果没有<INITIAL,example>限定符,第二个示例中的bar模式在启动条件示例中将不会被激活(即无法匹配)。但是,如果我们只是使用来限定bar,那么它将只在example中有效而不是在INITIAL中有效,而在第一个示例中,它在两个规则中都有效,因为在第一个示例中,示例开始条件是包容性(%s)的开始条件。
还要注意,特殊的启动条件说明符’<*>’匹配每个启动条件。因此,上面的例子也可以写成:
%x example
%%

<example>foo   do_something();

<*>bar    something_else();

默认规则(ECHO任何不匹配的字符)在启动条件下仍然有效。它等效于:
<*>.|\n ECHO;
BEGIN(0)返回到原始状态,只有没有启动条件的规则是活动的。这个状态也可以称为起始条件INITIAL,因此BEGIN(INITIAL)等同于BEGIN(0)。(开始条件名称周围的括号不是必须的,但被认为是良好的风格。)
BEGIN动作也可以在规则部分的开头以缩进代码的形式给出。例如,当yylex()被调用且全局变量enter_special为真时下面的例子将引起扫描器进入到SPECIAL开始条件:
int enter_special;

%x SPECIAL
%%
        if ( enter_special )
            BEGIN(SPECIAL);

<SPECIAL>blahblahblah
...more rules follow...

为了说明开始条件的用法,这里有一个扫描器,它提供了像’123.456’这样的字符串的两种不同的解释。默认情况下,它会将其视为三个标记,整数’123’,点’.’和整数’456’。但是如果字符串在该行前面有字符串’expect-floats’,它会将其视为单个标记,为浮点数’123.456’:
%{
#include <math.h>
%}
%s expect

%%
expect-floats        BEGIN(expect);

<expect>[0-9]+.[0-9]+      {
            printf( "found a float, = %f\n",
                    atof( yytext ) );
            }
<expect>\n           {
            /* that's the end of the line, so
             * we need another "expect-number"
             * before we'll recognize any more
             * numbers
             */
            BEGIN(INITIAL);
            }

[0-9]+      {
            printf( "found an integer, = %d\n",
                    atoi( yytext ) );
            }

"."         printf( "found a dot\n" );

下面是一个扫描器,它识别(并丢弃)C注释,同时保持当前输入行的计数。
%x comment
%%
int line_num = 1;

"/*"         BEGIN(comment);

<comment>[^*\n]*        /* eat anything that's not a '*' */
<comment>"*"+[^*/\n]*   /* eat up '*'s not followed by '/'s */
<comment>\n             ++line_num;
<comment>"*"+"/"        BEGIN(INITIAL);

这个扫描器遇到一些麻烦,以便与每个规则匹配尽可能多的文本。一般来说,当尝试编写高速扫描程序时,尝试在每个规则中尽可能多的匹配,因为这是一个很大的胜利。
注意,开始条件名称实际上是整数值,可以这样存储。因此,上述内容可以以下列方式加以扩展:
%x comment foo
%%
int line_num = 1;
int comment_caller;

"/*"         {
             comment_caller = INITIAL;
             BEGIN(comment);
             }

...

<foo>"/*"    {
             comment_caller = foo;
             BEGIN(comment);
             }

<comment>[^*\n]*        /* eat anything that's not a '*' */
<comment>"*"+[^*/\n]*   /* eat up '*'s not followed by '/'s */
<comment>\n             ++line_num;
<comment>"*"+"/"        BEGIN(comment_caller);

此外,您可以使用整数值的YY_START宏访问当前的启动条件。例如,可以这样写上述注释调用者的赋值:
comment_caller = YY_START;
flex提供YYSTATE作为YY_START的别名(因为这是AT&T lex所使用的)。
由于历史原因,在生成的扫描程序中,启动条件没有自己的名称空间。在生成的扫描程序和生成的头文件中,启动条件名称未被修改。查看option-header。查看option-prefix。
最后,这里有一个如何使用独占开始条件匹配C风格引号字符串的示例,包括扩展转义序列(但不包括检查字符串是否过长):
%x str

%%
        char string_buf[MAX_STR_CONST];
        char *string_buf_ptr;


\"      string_buf_ptr = string_buf; BEGIN(str);

<str>\"        { /* saw closing quote - all done */
        BEGIN(INITIAL);
        *string_buf_ptr = '\0';
        /* return string constant token type and
         * value to parser
         */
        }

<str>\n        {
        /* error - unterminated string constant */
        /* generate error message */
        }

<str>\\[0-7]{1,3} {
        /* octal escape sequence */
        int result;

        (void) sscanf( yytext + 1, "%o", &result );

        if ( result > 0xff )
                /* error, constant is out-of-bounds */

        *string_buf_ptr++ = result;
        }

<str>\\[0-9]+ {
        /* generate error - bad escape sequence; something
         * like '\48' or '\0777777'
         */
        }

<str>\\n  *string_buf_ptr++ = '\n';
<str>\\t  *string_buf_ptr++ = '\t';
<str>\\r  *string_buf_ptr++ = '\r';
<str>\\b  *string_buf_ptr++ = '\b';
<str>\\f  *string_buf_ptr++ = '\f';

<str>\\(.|\n)  *string_buf_ptr++ = yytext[1];

<str>[^\\\n\"]+        {
        char *yptr = yytext;

        while ( *yptr )
                *string_buf_ptr++ = *yptr++;
        }

通常,例如在上面的一些示例中,您最终会以相同的开始条件开头编写一大堆规则。flex通过引入启动条件作用域的概念使这一点更简单、更清晰。启动条件范围以:
{
其中是一个或多个启动条件的列表。在开始条件范围内,每个规则都会自动应用前缀,直到’}’匹配初始的’{’。举个例子:
{
"\n" return '\n';
"\r" return '\r';
"\f" return '\f';
"\0" return '\0';
}
等效于:
"\n" return '\n';
"\r" return '\r';
"\f" return '\f';
"\0" return '\0';
开始条件范围可以嵌套。
以下程序可用于操作启动条件堆栈:
函数:void yy_push_state(int new_state)
将当前启动条件压入启动条件堆栈的顶部,并切换到新状态,就像您使用了BEGIN新状态一样(请记住,启动条件名称也是整数)。
函数:void yy_pop_state()
弹出堆栈的顶部,并通过BEGIN切换到它。
函数:int yy_top_state()
返回堆栈的顶部,而不改变堆栈的内容。
开始条件堆栈是动态增长的,因此没有内置的大小限制。如果内存耗尽,程序执行将终止。
为了使用启动条件堆栈,扫描器必须包含%option堆栈指令(请参看扫描器选项章节)。

posted @ 2025-03-09 20:57  xiaobing3314  阅读(42)  评论(0)    收藏  举报