yyllocp->first_line returns 重入 Bison 解析器第二次迭代中的未初始化值

yyllocp->first_line returns uninitialized value in second iteration of a reEntrant Bison parser

我有一个可重入解析器,它从字符串中获取输入并具有维护上下文的结构。 使用要解析的不同输入字符串调用函数。该函数的相关代码为:

void parseMyString(inputToBeParsed) {

 //LEXICAL COMPONENT - INITIATE LEX PROCESSING
   yyscan_t scanner;    
   YY_BUFFER_STATE  buffer;
   yylex_init_extra(&parseSupportStruct, &scanner );
   //yylex_init(&scanner);

   buffer = yy_scan_buffer(inputToBeParsed, i+2, scanner);

   if (buffer == NULL) {
       strcpy(errorStrings,"YY_BUFFER_STATE returned NULL pointer\n");
       return (-1);
   }


//BISON PART - THE ACTUAL PARSER
yyparse(scanner, &parseSupportStruct);

...

yylex_destroy(scanner);
...
}

我的 .l 选项是:

 %option noinput nounput noyywrap 8bit nodefault                                 
 %option yylineno
 %option reentrant bison-bridge bison-locations                                  
 %option extra-type="parseSupportStructType *"

.y 中的相关行是:

  %define api.pure full
  %locations
  %param { yyscan_t scanner }
  %parse-param { parseSupportStructType* parseSupportStruct}
  %code {
    int yylex(YYSTYPE* yylvalp, YYLTYPE* yyllocp, yyscan_t scanner);
    void yyerror(YYLTYPE* yyllocp, yyscan_t unused, parseSupportStructType* parseSupportStruct,  const char* msg);
    char *yyget_text (yyscan_t);
    char *strcpy(char *, const char *);
  }
  %union {
     int numval;
     char *strval;
     double floatval; 
  }

在我的解析器中,在某些规则中,我尝试访问 yyllocp->first_line。 在第一次调用 parseMyString(...) 时,我得到了正确的值。 第二次,我得到了一些未初始化的值。我是否需要在每次调用 parseMyString 时初始化 yyllocp->first_line?如何以及在哪里? 我知道我已经给出了部分经过编辑的代码来解释这种情况。很乐意提供更多详细信息。

使用 valgrind 我已经尽我所能消除了内存泄漏,但一些第三方库问题超出了我的控制范围。

flex 或 bison 中的任何内容都不会保持 yylloc 的值。

Bison 解析器(推送解析器除外)将初始化该变量。 (如果您接受默认位置类型——也就是说,您不接受 #define YYLTYPE——yylloc 将被初始化为 {1, 1, 1, 1}。否则,它将被零初始化,无论意味着它是什么类型。)Bison 还生成代码,根据非终端的第一个和最后一个子节点的位置计算非终端的位置。 Flex 生成的代码根本不涉及位置对象。

Flex 扫描仪会自动维护 yylineno 如果您要求启用此功能

%option yylineno

Flex 通常可以比您更有效地做到这一点,并且它可以处理所有极端情况(yylessyymoreinput()REJECT)。所以如果你想跟踪行号,我强烈建议让 flex 来做。

但是 flex 的 yylineno 支持存在一个重要问题。在可重入扫描器中,行号存储在每个弹性缓冲区中,而不是扫描器状态对象中。这几乎肯定是存储它的正确位置,恕我直言,因为如果您使用多个缓冲区,它们可能代表多个输入流,通常您会想要引用其文件中的行号。但是 yy_scan_buffer 没有初始化这个字段。 (因此 yy_scan_stringyy_scan_bytes 也没有,它们只是 yy_scan_buffer 的包装。)

因此,如果您正在使用 yy_scan_* 接口之一,您应该在 yy_scan_* 之后立即调用 yyset_lineno 来重置 yylineno。在您的情况下,这将是:

buffer = yy_scan_buffer(inputToBeParsed, i+2, scanner);
yyset_lineno(1, scanner);

获得 yylineno 后,维护 yylloc 对象就很容易了。 Flex 有一个钩子,可以让你在模式的任何动作执行之前注入代码(即使动作是空的),这个钩子可以用来自动维护 yylloc。在 中,我提供了该技术的一个简单示例(这取决于 yylineno 由 flex 生成的扫描器维护):

#define YY_USER_ACTION                                             \
  yylloc->first_line = yylloc->last_line;                          \
  yylloc->first_column = yylloc->last_column;                      \
  if (yylloc->last_line == yylineno)                               \
    yylloc->last_column += yyleng;                                 \
  else {                                                           \
    yylloc->last_line = yylineno;                                  \
    yylloc->last_column = yytext + yyleng - strrchr(yytext, '\n'); \
  }

如该答案中的注释所示,以上内容并不完全通用,但在许多情况下都适用:

This YY_USER_ACTION macro should work for any scanner which does not use yyless(), yymore(), input() or REJECT. Correctly coping with these features is not too difficult but it seemed out of scope here.

您无法在操作之前处理 yyless()yymore()REJECT(因为在操作之前无法知道它们是否会被执行),因此更可靠的位置- 使用这些功能的应用程序中的跟踪器必须包含修复 yylloc():

的代码
  • 对于yyless(),上面设置last_linelast_column的代码可以在yyless()调用后重新执行,因为flex扫描仪将修复 yylengyylineno.

  • 对于REJECT,无法在REJECT后插入代码。处理它的唯一方法是保留 yylloc 的备份并在 REJECT 宏之前立即恢复它。 (我强烈建议不要使用 REJECT。它的效率极低,几乎总是可以用对 yyless() 的调用和开始条件的组合来代替。)

  • 对于yymore()yylloc仍然是正确的,但是next动作一定不能覆盖token开始位置。要做到这一点可能需要维护一个标志来指示 yymore() 是否已被调用。

  • 对于input(),如果你希望读取的字符被认为是当前标记的一部分,你可以在调用[=之后在yylloc中提前结束位置20=](这需要区分 input() 返回换行符、文件结束指示符或常规字符)。或者,如果您希望使用 input() 读取的字符不被视为任何标记的一部分,则需要放弃使用前一个标记的结束位置作为当前标记的起始位置的想法,这将需要保留一个分隔位置值作为下一个token的起始位置。