使用flex,当达到EOF并通过YY_INPUT提供输入时,如何保留yytext内容?

时间:2013-01-19 20:32:01

标签: flex-lexer

生成的扫描程序遇到文件结尾时,它会丢失之前规则中yytext[]次调用留下的yymore()内容。如果YY_INPUT()被重新定义,则会发生这种错误行为。

这可能是flex中的一个错误,但似乎缺少一些东西 - 灵活扫描程序定义在重新定义YY_INPUT()时应该提供的其他内容。

我已经在Ubuntu 12.04.1和Windows 7上使用flex 2.5.35进行了测试。在两个系统上,如果要通过显式定义提供要扫描的文本,扫描程序会丢失EOF中的yytext[]内容YY_INPUT()

下面是一个示例灵活扫描程序(flex-test.l),用于读取和打印HTML注释,即使最后一条注释未被终止。当它的输入是通过yy_scan_string()提供时它可以正常工作,但是当它的输入由YY_INPUT()的明确定义提供时失败。在示例代码中,#if用于在yy_scan_string()YY_INPUT()实现之间进行选择。 具体来说,预期的输出:

Begin comment: <!--
More comment:  <!--incomplete
EOF comment:   <!--incomplete
如果使用

构建扫描仪,则会出现

flex --nounistd flex-test.l && gcc -DREDEFINE_YY_INPUT=0 lex.yy.c

但是如果使用

构建扫描仪
flex --nounistd flex-test.l && gcc -DREDEFINE_YY_INPUT=1 lex.yy.c

(将 = 0 更改为 = 1 ),然后显示错误输出:

Begin comment: <!--
More comment:  <!--incomplete
EOF comment:

请注意最后一行输出中没有任何评论文本。

以下是示例代码:

/* A scanner demonstrating bad interaction between yymore() and <<EOF>>
 * when YY_INPUT() is redefined: specifically, yytext[] content is lost. */

%{
#include <stdio.h>

int yywrap(void) { return 1; }

#if REDEFINE_YY_INPUT

  #define MIN(a,b) ((a)<(b) ? (a) : (b))

  const char *source_chars;
  size_t source_length;

  #define set_data(s) (source_chars=(s), source_length=strlen(source_chars))

  size_t get_data(char *buf, size_t request_size) {
    size_t copy_size = MIN(request_size, source_length);
    memcpy(buf, source_chars, copy_size);
    source_chars += copy_size;
    source_length -= copy_size;
    return copy_size;
  }

  #define YY_INPUT(buf,actual,ask) ((actual)=get_data(buf,ask))

#endif

%}

%x COMM

%%

"<!--"          printf("Begin comment: %s\n", yytext); yymore(); BEGIN(COMM);
<COMM>[^-]+     printf("More comment:  %s\n", yytext); yymore();
<COMM>.         printf("More comment:  %s\n", yytext); yymore();
<COMM>--+\ *[>] printf("End comment:   %s\n", yytext); BEGIN(INITIAL);
<COMM><<EOF>>   printf("EOF comment:   %s\n", yytext); BEGIN(INITIAL); return 0;

.               printf("Other:         %s\n", yytext);

<<EOF>>         printf("EOF:           %s\n", yytext); return 0;
%%

int main(int argc, char **argv) {
  char *text = "<!--incomplete";

  #if REDEFINE_YY_INPUT
    set_data(text);
    yylex();
  #else
    YY_BUFFER_STATE state = yy_scan_string(text);
    yylex();
    yy_delete_buffer(state);
  #endif
}

1 个答案:

答案 0 :(得分:2)

此问题一直处于弹性状态,可能是永远的。基本上,当flex从其当前缓冲区获取EOF时,它会处理最后一个令牌,然后重新初始化缓冲区,这会有效地抛出当前令牌,即使它已被yymore()“保存”。 (它实际上用NUL s初始化前两个字符,但这足以销毁它。)然后调用yywrap(),它可以选择提供另一个缓冲区(文件)。

这种行为通常是无害的,因为通常不允许令牌跨越两个不同的输入文件,但有时候选择它会很好。然而,在灵魂存在的四分之一世纪中,任何人都不愿意修复它,这还不够好。

不幸的结果是,在获得yytext之后,您无法使用EOF,因为即使没有更多输入文件,缓冲区重置已经完成。 (yyleng也不正确;它尚未重置,并且还会因触发EOF的NUL而增加。)

yy_scan_string的实现中有一个hack,它将新创建的缓冲区的yy_fill_buffer标志设置为0,这意味着不应该尝试重新填充缓冲区。这可以防止缓冲区重置,但不会保护yyleng,这仍然是不正确的。不过,我会考虑保留yytext纯粹的运气。

如果正在积极维护flex,我建议在弹性手册中添加yytextyyleng<<EOF>>规则中未定义的评论,甚至可能考虑修复yymore()以便它允许跨越输入缓冲区(或记录它没有的事实)。

简而言之,您有两种选择:

1)只需使用yy_scan_stringyy_scan_buffer(包含所有适当的警告),并希望没有人恢复黑客攻击,以便您查看{{1}中的yytext }} 规则。我不知道希望如何面向未来,但没有什么可以迫使你升级。

但你可能会更好:

2)使用您自己的缓冲区来保留累积的令牌字符串。

选项(2)实际上并不是那么昂贵;如果令牌字符串很大,则可能比使用<<EOF>>更好,因为flex实际上并不是为处理大型令牌而设计的。对于可能相当大的评论,您可能会发现维护自己的缓冲区会更快,并且可以更加可预测。