在编译器构造中有两个语义分析阶段是否很常见?

Is it common to have two semantic analysis phases within compiler construction?

我一直在用 AST explorer 研究各种语言的语法和 AST 节点。 使用 python,我注意到在解析过程中进行了某种形式的语义分析。例如

x = 2
x = 2

产生以下由 VariableDeclaration 节点和 ExpressionStatement 节点组成的 AST。

所以当第一个x = 2行被解析时,它会检查一个符号table是否存在x然后注册它并产生一个VariableDeclaration节点。然后在解析第二行x = 2的时候,发现x已经定义了,产生了一个ExpressionStatement节点。

然而,当我尝试使用以下语义不正确的代码时:

2 + "string"

它接受代码,并生成一个 ExpressionStatement 节点 - 尽管它在语义上是不正确的,即 int + string,并且当我尝试使用 [=38] 执行它时正确地产生错误=] 解释器.

这表明语义分析发生了两次:一次是在解析过程中,另一次是在遍历完整的 AST 时。这个假设是否正确?如果是这样,为什么会这样?在解析过程中进行整个语义分析阶段而不是将其拆分会不会更简单?

语句 2 + "string" 中的语义错误在任何语义传递中均未检测到。这是一个运行时错误,当您尝试执行该语句时会报告它。如果语句从不执行,则不报错,执行脚本可以看到

    if False:
        2 + "string"
    print("All good!")

解决第一次使用全局变量作为声明比其他任何事情都更优化,编译器执行多次优化是很常见的。

总是有尝试组合这些多次传递的诱惑,但这是一种错误的经济:遍历 AST 的开销相对较低,而且当代码只尝试做一件事时,它会更清晰、更易于维护。将两个不相关的优化启发式交织在一起是糟糕的设计,就像将任何一组不相关的程序交织在一起一样。