0

我正在尝试更改我的 .l 和 .y 文件以使我的扫描仪和解析器可重入。按照 GNU 文档等,我把它放在我的 .y 文件中:

%define api.pure
%lex-param { YYSTYPE *yylval }
%parse-param { astNodePtr *programTree }

此外,我已将reentrantandbison-bridge选项添加到我的 .l 文件中。

但是,在我用 构建 parser.tab.h 和 parser.tab.c 之后bison -dv parser.y,我注意到 parser.tab.c 包含声明

int yylex(void);

后来又涉及到诸如

yychar = yylex (&yylval, yylval);

此外,尝试编译 flex 创建的 .c 文件会产生各种错误,这些错误以某个名为yyg.

是否有其他标志/我需要添加到我的 .l 或 .y 文件中的任何内容?

4

1 回答 1

2

请记住,flex 和 bison 是两个完全独立的处理器,它们接受两个完全独立的输入文件并生成两个完全独立的程序,然后分别编译。Bison 和 flex 不会读取对方的输入文件(或输出文件),也不会读取程序员的想法。因此,生成的程序之间的任何互操作性都是存在的,因为您作为程序员已经安排好了。

对于默认(不可重入)生成的扫描器和解析器,您可以通过以下方式安排互操作性

  • 确保 bison 生成的 header#include在 flex 生成的扫描仪中是 d ,并且
  • yylex在野牛生成的解析器中声明原型。

的原型yylex通常是

int yylex(void);

您可以使用 flex 生成yylex不同的原型,甚至是不同的名称,但如果这样做,您需要告诉 bison 如何调用yylex(因为它无法知道您已更改扫描仪中的原型)。

如果您在parser.tab.c文件中找到该行,几乎可以肯定是因为您将它插入到parser.y文件的代码块中,因为这是编译不可重入解析器所必需的,并且在修改时忘记删除它文件以生成可重入解析器。

默认情况下,解析器和词法分析器使用全局变量进行通信,yylval以保存词法标记的语义值。(以及全局变量yylloc如果位置也正在通信。)Bison 在生成的解析器中定义这些全局变量并在生成的头文件中声明它们,因此只要将生成的头文件#include放入生成的扫描仪中,两个程序就可以互操作。

但这确实不是共享数据的好方法,现代编码风格不赞成这种全局变量的使用。通过生成可重入解析器,您可以启用另一种机制,其中解析器将扫描仪指针传递给它自己的本地yylvalyylloc如果使用,则为 )。这解决了可重入问题,但现在您需要传达灵活的愿望,以便生成yylex期望这些参数的 a which。你这样做的方式是插入%bison-bridge(并且可能%bison-locations)到你的 flex 输入文件中。

这样做会调整 的调用约定yylex,但实际上不会产生可重入扫描器。它只是生成一个不依赖全局变量与解析器通信的扫描器。扫描器依赖许多其他全局变量来维护自己的状态。如果您想要一个可重入扫描器,您还需要将%reentrant声明插入到 flex 文件中,这将导致它生成一个扫描器,该扫描器将其“全局”状态保持在 opaque 类型的上下文对象中yyscan_t。该上下文对象必须yylex作为参数传递(位于参数列表的末尾yylex)。flex声明%reentrant产生一个期望yylex那个论点,但现在野牛已经不在循环中了;再一次,您有责任将这一事实传达给野牛。

yyscan_t分配一个可以传递给词法分析器的对象也成为您的责任。但是您不会直接调用扫描仪。您调用解析器 ( yyparse),它会在必要时调用扫描仪。

Flex 允许您将任意代码添加到扫描器中(通过将其放置在第一条规则之前,缩进)。但是,不幸的是,野牛没有这样的设施。将新变量注入解析器的唯一方法是将其添加到yyparse参数列表(使用%parse-param声明)。所以你需要自己创建yyscan_t对象,并将其传递给yyparse. 然后,您需要告诉野牛在调用时使用该对象yylex,您可以使用%lex-param声明执行此操作。

应该相当清楚的是,上面提到的%parse-param%lex-param声明几乎总是相同的。传递参数的唯一方法yyparseyylex添加的参数与添加的参数%parse-param具有相同的名称%lex-param。既然是这样,bison 非常明智地允许您将其合并%parse-param%lex-param一个%param声明中。

现在你只有一个小问题。您需要传入的参数yyparse具有yylexopaque 类型yyscan_t。由于该参数是 的参数yyparse,因此该类型yyscan_t需要在生成的野牛标头中可见。但是,yylex使用其他参数调用(因此必须声明)类型为YYSTYPE*and YYLTYPE*,并且这些类型在野牛生成的标头中声明。Flex 也可以生成标题,但这对您没有帮助,因为生成的文件之间存在循环标题依赖关系。(这是所谓的独立扫描器和解析器之间类型耦合的自然结果。但我不会进一步展开这种批评,因为它基本上是给定的。)

有两种解决方法。最好的一个,恕我直言,是通过使用推送解析器而不是拉解析器来避免循环头依赖。在推送解析器模型中,解析器由扫描仪调用(或由传递扫描仪生成的令牌的驱动程序调用)而不是调用扫描仪。这是我一直推荐的模型。

另一种是通过手动声明来解决循环依赖yyscan_t

typedef void* yyscan_t;

有关完整制定(并记录)的示例,请参阅此答案

于 2020-07-09T21:20:09.293 回答