Jas*_*Jas 4 compiler-construction
我很好奇 C/C++ 词法分析器和解析器如何协同工作。我知道解析器通常需要至少一个标记的前瞻。我的问题是,在生产编译器(例如 gcc 或 clang)中:
1)词法分析器是否先运行,对整个文件进行词法分析,然后让解析器生成 AST。这意味着词法分析器会生成一个标记列表。
或者
2) 词法分析器是否只生成一小部分足以让解析器完成其工作的标记。这意味着词法分析器和解析器轮流运行。
我绝对认为使用选项 1,因为像 C++ 这样的语言有时需要任意前瞻,因为语法不是上下文无关的,但这会占用大量内存。
传统的答案接近你的情况2,但不完全是这样。请注意,词法分析器和解析器通常都实现为相对简单的状态机。
\n\n词法分析状态机可以通过以下任一方式驱动:
\n\n(这显然需要获取输入代码并将它们组装成令牌),或者:
\n\n(这最终会导致令牌从词法分析器中“掉出来”)。
\n\n解析器状态机可以从任一方向驱动:
\n\n(然后必须获取令牌直到找到完整的句子),或者:
\n\n(然后必须将标记组装成句子)。
\n\n如果我们使用的解析器算法是这样驱动的,我们将使用以下命令“编译”一个文件:
\n\nfor all input characters:\n feed character to tokenizer\nRun Code Online (Sandbox Code Playgroud)\n\n当标记从标记生成器“掉落”时,它们将驱动解析器。整个事情将是自下而上驱动的协程。
\n\n传统上,在 yacc、bison 等生成的解析器中,以及为它们提供服务的词法分析器中,我们运行更多“自上而下”的方式,即,有人调用 get me a Sentence函数(这可能会构建一个AST,或者直接发出代码,或者介于 \xe2\x80\x94 之间的东西,例如,为一个函数或声明构建一个 AST,然后将其转换为中间代码,然后为另一个函数或声明构建另一个 AST,等等)。这推动了一切朝着从词法分析器\xe2\x80\x94 拉入令牌的方向发展,但它仍然相当协程式,因为解析器一次只要求一个令牌。
\n\n这种方法也是手动编写递归下降解析器的明显方法:您的顶级函数是“给我一个句子”(或“给我所有句子”或其他什么),最终导致一些函数调用“给我一个句子”令牌”。因此,在这两种情况下,算法的实际表达式最终都会对词法分析器重复进行“给我一个令牌”调用。
\n\nGCC 有一个以这种方式工作的手工编码的解析器(和手工编码的词法分析器)。我没有看过 clang 的内部结构,但我怀疑它是一样的。
\n\n具体到 C++ 来说,它有一些非常令人讨厌的解析情况;请参阅https://en.wikipedia.org/wiki/Most_veshing_parse和Pavel Minaev\'s 的回答是否有一个可以解析 C++ 的好的 Python 库?。一些编译器使用临时方法来处理这个问题,例如,提供过度接受的语法并尝试对最终的 AST 进行反向修补,或者通过 hack 来“引导”语法。(我见过 C++ 编译器在这里崩溃:向它们提供语法上有效的标记,而这些标记在语义上是无意义的,而黑客可能会失败。)另一种可以说更好的方法是使用 GLR 解析器;请参阅此处 Ira Baxter 的回答。
\n\n(我已经很久没有做过任何解析器理论了,在写这个答案时遇到了sjoerd在 2011 年关于 GLL 解析器的评论,这非常有趣。)
\n