可重入 Bison/Flex,如何获取 yyscan_t 的每个实例的错误消息

Reentrant Bison/Flex, how to get error message for each instance of yyscan_t

我正在尝试创建一个使用多线程和 flex/bison 来解析大量数据的程序。我对如何以可重入方式获得 yyerror 有点迷茫。

在之前使用 bison/flex 的不可重入测试中,我使用 extern 得到 yyerror

extern void yyerror(const char*);

void yyerror(const char* msg) {
    std::cout << " Error: " + std::string(msg) << std::endl;
    ...
    calling appropriate code to handle error etc
    ...
}

现在我正在尝试使用可重入的 bison 和 flex 来实现它。

使用来自用户@rici 的示例代码,我试图了解如何在调用 yyparse 后获取错误消息。我该如何实现以下内容?


class container {

public:

bool errorOccured;
std::string errorMessage;

void parse() {
    yyscan_t scanner;
    yylex_init(&scanner);
    yy_scan_string("123 + + 123 \n", scanner);
    yyparse(scanner);
    yylex_destroy(scanner);
    //errorOccured = ?;
    //errorMessage = ?;
}

bool checkIfErrorOccured() {
    std::cout << errorMessage << std::endl;
    return errorOccured;
}

}

此处供参考的是我正在使用的 lex 代码,由用户@rici

编写
%option noinput nounput noyywrap 8bit nodefault                                 
%option yylineno
%option reentrant bison-bridge bison-locations                                  

%{
  #include <stdlib.h>                                                           
  #include <string.h>
  #include "parser.tab.h"                                                   

  #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'); \
    }
%}                                                                              
%%
[ \t]+            ;                                                  
#.*               ;                                                  

[[:digit:]]+      *yylval = strtol(yytext, NULL, 0); return NUMBER;  

.|\n              return *yytext;

野牛

%define api.pure full
%define parse.error verbose
%locations
%param { yyscan_t scanner }

%code top {
  #include <stdio.h>
  #include <string.h>
} 
%code requires {
  typedef void* yyscan_t;
}
%code {
  int yylex(YYSTYPE* yylvalp, YYLTYPE* yyllocp, yyscan_t scanner);
  void yyerror(YYLTYPE* yyllocp, yyscan_t unused, const char* msg);
}

%token NUMBER UNOP
%left '+' '-'
%left '*' '/' '%'
%precedence UNOP
%%
input: %empty
     | input expr '\n'      { printf("[%d]: %d\n", @2.first_line, ); }
     | input '\n'
     | input error '\n'     { yyerrok; }
expr : NUMBER
     | '(' expr ')'         { $$ = ; }
     | '-' expr %prec UNOP  { $$ = -; }
     | expr '+' expr        { $$ =  + ; }
     | expr '-' expr        { $$ =  - ; }
     | expr '*' expr        { $$ =  * ; }
     | expr '/' expr        { $$ =  / ; }
     | expr '%' expr        { $$ =  % ; }

%%

void yyerror(YYLTYPE* yyllocp, yyscan_t unused, const char* msg) {
  fprintf(stderr, "[%d:%d]: %s\n",
                  yyllocp->first_line, yyllocp->first_column, msg);
}


如果解析失败,yyparse return 是一个非零值。这对于可重入和非可重入解析器都是一样的。所以你应该 总是yyparse:

收集 return 值
status = yy_parse(scanner);

如果您正在进行错误恢复(也就是说,您有一个或多个 error 作品),那么您将不得不自己记录错误数。 yyparse 错误 return 仅在错误恢复失败(或存在内存分配错误)时发生。

yyerror 在检测到错误时调用(在尝试错误恢复之前)。在玩具示例中,它通常只是将其参数打印到 stderr。 (在默认配置中,参数是 "syntax error",但您可以使用 %define parse.error verbose 获得更好的错误消息。)在具有错误恢复的生产解析器中,yyerror 可能什么都不做,将其留给错误恢复过程尝试产生更有意义的错误消息。或者它可能将 bison 的错误消息存储在某处以供将来参考。

打印到 stderr 没有大问题,因为 yyerror 调用在与解析器相同的线程中同步执行(野牛完全不感知线程)。但是一些应用程序更喜欢将消息放入某种数据结构中以供以后处理。 (您肯定会希望在多线程应用程序中考虑这一点。)为了促进这一点,正如您在我的代码中看到的那样,调用 yyerror 时使用了与 yyparse 相同的附加参数。

在示例代码中,没有使用此功能(这就是为什么 scanner_t 参数被称为 unused 的原因)。但是由于 flex 允许您使用 extra data 扩展扫描器上下文对象,那将是放置错误收集器的明智位置,因此证明 yyerror 可以访问它是有用的。 (当然,它也可用于任何解析器操作,因为它是 yyparse 的参数。)

我将 yyerror 定义放在扫描器文件而不是解析器文件中,这可能令人困惑。因为它是一个外部函数,所以它进入哪个翻译单元并不重要。将它放在解析器中可能是您​​在示例中最常看到的,但是在翻译单元中定义它也很有意义调用解析器。

把它放在扫描仪中充其量是古怪的。我这样做只是为了避免我在链接答案中详细描述的循环依赖问题的麻烦,所以我不会在这里重复。

除了 bison 生成的代码外,循环依赖在任何翻译单元中都不会成为问题。如果你想使用上面提到的额外数据技术,你需要让 flex 生成一个头文件,并确保你在定义 yyerror 的文件中 #include 该头文件。