扫描器和解析器交互

Scanner and parser interaction

我是 flex/bison 的新手。阅读书籍,似乎在几乎所有编译器实现中,解析器都以 "coroutine" 方式与扫描器交互,每当解析器需要令牌时,它都会调用扫描器来获取令牌,并在需要时将扫描器放在一边shift/reduce 忙。一个自然的问题是,为什么不让扫描器作为一个整体生成令牌流(从输入字节流),然后将整个令牌流传递给解析器,因此两者之间没有显式交互。他们俩?好吧,我可以想象这种方式有一些缺点,我也可以看到这样做的一些好处。

我的问题是,是否有关于这方面的某种 "comprehensive" 讨论,或者是否有任何编译器实现使用不同的 scanner/parser 交互方案而不是 "coroutine" 方式?

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

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

在协程控制流程中,两个函数串联调用。 I/O 通常不是这样处理的。 fread() 接口确实为下一次调用维护状态(至少是文件位置,可能还有一个缓冲区),但调用是自包含的。

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

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

许多解析器生成器,包括 Bison,都可以生成可调用的解析器,通常称为 "push parsers"。在此模型中,扫描器使用每个连续的标记调用解析器。这仍然不是协程模型,真的;这只是控制流反转。类比普通的I/O,相当于用一个叫fgets()的数据处理器读取每一行输入,改写成一个process_line()函数,给定一行数据给进程(因此不与 I/O 库交互)。可以在 Lemon 解析器生成器中找到推送解析的早期实现。

类似协程的控制流可用于创建其最终输入流必须异步处理的解析器。但这并不真的需要解析器和扫描器之间的协程;相反,它需要扫描仪和输入流之间的协程。同样,协程并不是真正必要的,而且可能有点矫枉过正:反转控制流就足够了。 Flex 不提供 "push scanner" 接口,但其他扫描仪生成器提供。例如,我相信 Re2c 支持此功能。