扫描器与解析器的交互

时间:2019-02-05 03:49:13

标签: parsing compiler-construction bison flex-lexer coroutine

我是flex / bison的新手。在阅读书籍时,似乎在几乎所有的编译器实现中,解析器都以“协程”的方式与扫描器进行交互,即只要解析器需要令牌,它就会调用扫描器获取一个令牌,并在繁忙时将扫描器放在一边移位/减少。一个自然的问题是,为什么不让扫描程序整体生成令牌流(从输入字节流),然后将整个令牌流传递给解析器,因此在两者之间没有明确的交互。他们俩?好吧,我可以想象这种方式存在一些缺点,并且我也可以看到这样做的一些好处。

我的问题是,在那方面是否存在某种“全面”的讨论,或者除“协程”方式以外,是否有任何编译器实现使用不同的扫描器/解析器交互方案?

1 个答案:

答案 0 :(得分:3)

在传统安排中,解析器在需要令牌时会调用扫描器。

这与在每次需要更多输入时调用I / O库的扫描器(或许多其他程序)中使用的逻辑相同。通常不将其描述为协程,而且我也不相信这也是对解析器/扫描器交互的准确描述。

在协程控制流程中,两个函数串联调用。这通常不是处理I / O的方式。 fread()接口的确为下一次调用(至少文件位置,可能还有一个缓冲区)保持状态,但这些调用是独立的。

从某种意义上说,调用yylex()获取下一个令牌和调用scanf()获取下一个数据值之间没有区别。

对于扫描仪来说,这并不总是最方便的体系结构。有时,扫描程序能够将令牌馈入解析器会很方便。一个典型的用例是扫描程序生成令牌时(例如通过宏扩展),但有时只是单个扫描程序模式的匹配包含多个令牌。

许多解析器生成器(包括Bison)都可以生成可调用的解析器,通常称为“推送解析器”。在此模型中,扫描程序使用每个成功令牌调用解析器。实际上,这仍然不是协程模型。这只是控制流反转。与普通I / O相似,这相当于调用一个名为fgets()的数据处理器来读取每条输入行,并将其重写为process_line()函数,该函数被赋予一行要处理的数据(因此不会与I / O库进行交互)。在Lemon解析器生成器中可以找到推送解析的早期实现。

类似协程的控制流对于创建解析器是很有用的,该解析器的最终输入流必须异步处理。但这实际上并不需要解析器和扫描器之间的关联。相反,它需要在扫描仪和输入流之间进行关联。同样,协程并不是真正必要的,并且可能是过大的:反转控制流就足够了。 Flex不提供“推送扫描器”界面,但其他扫描器生成器提供。我相信例如Re2c支持此功能。