重置 flex and/or bison 的状态

Resetting the state of flex and/or bison

作为玩具项目的一部分,我一直在尝试根据 flex/bison 对其他人的解析器进行小幅修改。我真的没有经验。你可以找到原始解析器 here.

我一直在尝试将一个接受字符串的简单函数和 returns 一个解析树放在一起,这样我就可以通过 FFI 公开它以供在另一种编程语言中使用。我的大部分是基于原程序中的main()函数,我的屠宰版如下:

TreeNode* parse_string(char *s)
{
    FILE *in = fmemopen(s, strlen(s), "r");
    lex2_initialise();
    parse_file(in);
    fclose(in);
    preprocess_tokens();
    yyparse();
    return top;
}

这实际上工作得很好,至少在我第一次调用它时是这样。第二次它抱怨错误的标记,并且使用的错误报告函数似乎是在调用 yyparse() 期间从生成的解析器中的 goto 语句迷宫中的某个地方调用的,此时我不明白是什么继续了。

原来的程序本身似乎只被设计为预先接受所有输入然后退出,所以它并没有给我留下太多关于我遗漏了什么的线索。抛开这个并非完全古怪的想法,在程序的其余部分的其他地方保留了一些旧状态,我的主要问题是:

Do either Flex or Bison maintain global state between calls to yyparse()

Flex 维护有关当前输入流的信息。如果解析没有消耗整个输入流(这对于因错误异常终止的解析器来说很常见),那么对 yyparse 的下一次调用将从上一次停止的地方继续读取。提供一个新的输入缓冲区将(大部分)重置词法分析器的状态,但可能有一些方面没有被重置,特别是当前的开始条件,以及如果该选项已启用的条件堆栈。

bison 生成的解析器不依赖于全局状态。它旨在在从 yyparse 返回之前清除其内部状态。但是,如果解析器操作直接执行 return 语句( 推荐),则清除将被绕过,这很可能会造成内存泄漏。过早终止解析的操作应该使用宏 YYACCEPTYYABORT 而不是 return 语句。

Is there some simple function call I could put at the end of the function above to wipe it all and reset everything back to the initial state?

默认的 flex 生成的解析器(设计为每次需要令牌时调用)严重依赖于全局变量。大多数(但不是全部)flex 状态在当前 YY_BUFFER_STATE 中维护(保存在全局变量中),并且该对象可以通过 yyreset 函数或任何函数重置它提供一个字符缓冲区作为词法分析器输入。但是,这些函数不会重置开始条件,也不会刷新条件堆栈(如果启用)或缓冲区堆栈。如果你想完全重置状态,你需要手动刷新堆栈,并用 BEGIN(INITIAL).

重置开始条件

制作更容易重启的扫描器的一种方法是构建一个 reentrant scanner。可重入扫描器将其所有状态(包括启动条件和缓冲区堆栈)保存在扫描器结构中,这意味着您可以通过创建一个新的扫描器结构(当然,还可以销毁旧结构以避免内存泄漏。)

使用可重入扫描仪有很多充分的理由 [注 1]。一方面,它允许您同时激活多个解析器,并且消除了对全局状态的依赖。但不幸的是,它并不像设置 flex 选项那么简单。

可重入扫描器有一个不同的 API(包括指向扫描器状态结构的指针)。这个状态结构需要传给yyparseyyparse需要传给yylex;所有这些都需要对 bison 选项进行一些修改。此外,可重入扫描器无法使用全局 yylval 将令牌的语义值传达给解析器 [注 2]。

如果您使用 %bison-bridge 选项并告诉 bison 生成可重入解析器,那么 yylex 将期望使用另一个附加参数(或两个,如果您使用位置)调用,并且可重入的 bison 解析器将提供额外的参数。一切正常,但它具有将 yylval(和 yylloc,如果使用)更改为指针的效果,这意味着您需要通过所有扫描仪操作更改 yylval.somethingyylval->something.

备注

  1. 您还可以使用一些额外的 bison 选项创建可重入解析器。通常,bison 生成的解析器使用的唯一可变全局变量是 yylvalyylloc(如果您使用位置报告)。 (和 yynerrs,但很少在解析器操作之外引用该变量。)指定可重入解析器会将这些全局变量转换为词法分析器参数,但不会创建外部可见的解析器状态结构。但它也为您提供了使用“推送解析器”的选项,它确实具有持久的解析器状态结构。在某些情况下,推送解析器的灵活性可以显着简化扫描器。

  2. 严格来说,没有什么能阻止你创建一个仍然使用全局变量与解析器通信的可重入扫描器,除了它不再是真正可重入的。出于显而易见的原因,我不会推荐此选项,但您可能希望将其作为过渡策略来执行,因为它需要对解析器和扫描器操作进行较少的修改。

即使您使用的是不可重入解析器,您也可以在词法分析之后使用 yylex_destroy(不带参数)强制初始化,下次调用词法分析器时:

extern int yylex_destroy(void);
...
// do parsing here
...
yylex_destroy()

对于可重入解析器,请参阅 here