flex 和 bison 需要从对方那里得到什么?

What do flex and bison need from each other?

同时使用 flex 和 bison 时,

bison-generated header 中最重要的是用于标识标记类型的枚举值(它们是通过词法操作返回给解析器的值)。

header 还声明了 YYSTYPE 语义类型和变量 yylval (具有该类型),用于将每个标记的语义值传递给解析器。 (至少,对于具有语义值的标记。)类似地,如果解析器使用位置信息,则 header 定义 YYLTYPE 位置类型,以及该类型的变量 yylloc

由于header 依赖不能循环,解析器对扫描器没有任何header 依赖。正是出于这个原因,您的野牛输入文件必须包含 yylex.

的声明

这对于使用全局变量进行通信的解析器和扫描器之间的经典接口来说很好。它也或多或少地与 re-entrant(“纯”)解析器一起工作,其中语义值(和位置,如果使用)通过参数传递给 yylex,尽管声明yylex 不是自动的更烦人。

它开始崩溃的地方是扫描仪也 re-entrant。在这种情况下,解析器必须使用 yyscan_t 类型的不透明扫描器上下文 object 调用扫描器。 yyscan_t“属于”扫描仪,所以只能在header中定义为扫描仪。但如上所述,这将导致循环依赖链。这揭示了传统模型的弱点:解析器是扫描器的客户端,因此扫描器依赖于解析器来定义必要的数据结构这一事实是依赖倒置。

这是一个非常现实的问题,因为 re-entrant 扫描器的 public 接口包含其原型需要 parser-specific 数据类型(YYSTYPEYYLTYPE 的函数),而解析器原型几乎肯定需要接受扫描器上下文 object 作为参数,因此不能在没有 scanner-specific 数据类型 yyscan_t.

的情况下声明它

这个问题的通常解决方案是通过注意 yyscan_t 只是一个 void* 来打破封装,因此它可以在解析器中声明为这样,从而避免解析器的需要#include 扫描仪 header(只要它不需要访问 header 中声明的任何其他 public 方法)。

在我看来,一个不太丑陋的解决方案是完全避免这种特殊配置。 Bison 允许您请求可重入的“推送解析器”,并且可以与可重入扫描器一起使用,而不会出现上述任何复杂情况。

在推送解析器模型中,扫描器是解析文件时调用的顶级函数,扫描器在每次识别令牌时调用解析器。 header 依赖关系不再是循环的,因为解析器不需要知道有关扫描器上下文 object 的任何信息,或者 yylex() 的原型。扫描器现在是解析器的客户端,因此对解析器具有自然的 header 依赖性,因此解析器定义令牌枚举以及语义和位置数据类型的事实不再是例外。

除了简化两个组件之间的 header 依赖关系外,推送解析器通常还简化了扫描器本身内部的控制流。在许多用例中,单个扫描仪模式将导致识别多个令牌。在传统模型中,扫描器必须保留 queue 个令牌,并在解析器调用时一次释放一个。但在推送模型中,扫描器操作可以简单地多次调用解析器,对每个已识别的标记调用一次。该模型由 Lemon 解析器生成器(sqlite3 的一部分)推广,随后由包括 bison 在内的其他解析器生成器实现。