将参数传递给 yylex
Passing an argument to yylex
我正在尝试更改我的 .l 和 .y 文件以使我的扫描器和解析器可重入。按照 GNU 文档等,我将其放入我的 .y 文件中:
%define api.pure
%lex-param { YYSTYPE *yylval }
%parse-param { astNodePtr *programTree }
此外,我已将 reentrant
和 bison-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中是
#include
d,并且
- 在 bison 生成的解析器中声明
yylex
的原型。
yylex
的原型一般是
int yylex(void);
您可以让 flex 生成具有不同原型甚至不同名称的 yylex
,但是如果您这样做,您需要告诉 bison 如何调用 yylex
(因为它没有知道您已经更改了扫描仪中的原型的方法)。
如果您在 parser.tab.c
文件中找到该行,几乎可以肯定是因为您将它插入了 parser.y
文件的代码块中,因为编译时必须这样做non-reentrant 解析器,当您修改文件以生成可重入解析器时忘记删除它。
默认情况下,解析器和词法分析器使用全局变量 yylval
进行交互,以保存词法标记的语义值。 (并且全局变量 yylloc
如果位置也被传达。)Bison 在生成的解析器中定义这些全局变量并在生成的 header 中声明它们,因此只要生成的 header #include
d 进入生成的扫描仪,两个程序可以互操作。
但这确实不是共享数据的好方法,现代编码风格不赞成全局变量的这种使用。通过生成可重入解析器,您可以启用替代机制,其中解析器将扫描器指针传递到它自己的本地 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
我正在尝试更改我的 .l 和 .y 文件以使我的扫描器和解析器可重入。按照 GNU 文档等,我将其放入我的 .y 文件中:
%define api.pure
%lex-param { YYSTYPE *yylval }
%parse-param { astNodePtr *programTree }
此外,我已将 reentrant
和 bison-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中是
#include
d,并且 - 在 bison 生成的解析器中声明
yylex
的原型。
yylex
的原型一般是
int yylex(void);
您可以让 flex 生成具有不同原型甚至不同名称的 yylex
,但是如果您这样做,您需要告诉 bison 如何调用 yylex
(因为它没有知道您已经更改了扫描仪中的原型的方法)。
如果您在 parser.tab.c
文件中找到该行,几乎可以肯定是因为您将它插入了 parser.y
文件的代码块中,因为编译时必须这样做non-reentrant 解析器,当您修改文件以生成可重入解析器时忘记删除它。
默认情况下,解析器和词法分析器使用全局变量 yylval
进行交互,以保存词法标记的语义值。 (并且全局变量 yylloc
如果位置也被传达。)Bison 在生成的解析器中定义这些全局变量并在生成的 header 中声明它们,因此只要生成的 header #include
d 进入生成的扫描仪,两个程序可以互操作。
但这确实不是共享数据的好方法,现代编码风格不赞成全局变量的这种使用。通过生成可重入解析器,您可以启用替代机制,其中解析器将扫描器指针传递到它自己的本地 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