将参数传递给 yylex

Passing an argument to yylex

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

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

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

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

int yylex(void);

后来又涉及到诸如

之类的调用
yychar = yylex (&yylval, yylval);

此外,尝试编译 flex 创建的 .c 文件会生成以某个名为 yyg.

的变量为中心的各种错误。

是否有其他 flags/whatever 我需要添加到我的 .l 或 .y 文件中?

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

对于默认(不可重入)生成的扫描器和解析器,您可以通过

安排互操作性
  • 确保bison生成的header在flex生成的scanner中是#included,并且
  • 在 bison 生成的解析器中声明 yylex 的原型。

yylex的原型一般是

int yylex(void);

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

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

默认情况下,解析器和词法分析器使用全局变量 yylval 进行交互,以保存词法标记的语义值。 (并且全局变量 yylloc 如果位置也被传达。)Bison 在生成的解析器中定义这些全局变量并在生成的 header 中声明它们,因此只要生成的 header #included 进入生成的扫描仪,两个程序可以互操作。

但这确实不是共享数据的好方法,现代编码风格不赞成全局变量的这种使用。通过生成可重入解析器,您可以启用替代机制,其中解析器将扫描器指针传递到它自己的本地 yylval(和 yylloc,如果使用的话)。这解决了重入问题,但现在您需要传达对 flex 的渴望,以便生成需要这些参数的 yylex。你这样做的方法是将 %bison-bridge(可能还有 %bison-locations)插入到你的 flex 输入文件中。

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

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

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

很明显,上面提到的 %parse-param%lex-param 声明几乎总是相同的。参数通过 yyparse 传递到 yylex 的唯一方法是如果添加 %parse-param 的参数与添加 %lex-param 的参数同名。既然如此,bison 非常明智地允许您将 %parse-param%lex-param 组合成一个 %param 声明。

现在你只有一个小问题。您需要通过 yyparse 传递到 yylex 的参数具有不透明类型 yyscan_t。由于该参数是 yyparse 的参数,因此类型 yyscan_t 需要在生成的 bison header 中可见。但是,yylex 与其他类型为 YYSTYPE*YYLTYPE* 的参数一起调用(因此必须声明),并且这些类型在 bison-generated header. Flex 也可以生成 header,但这对您没有帮助,因为生成的文件之间存在循环 header 依赖关系。 (这是所谓的独立扫描器和解析器之间类型耦合的自然结果。但我不会进一步发展这种批评,因为它基本上是iven.)[=​​61=]

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

另一种是通过手动声明yyscan_t:

来解决循环依赖
typedef void* yyscan_t;

有关完整的(和记录的)示例,请参阅 this answer