编译器设计 - 语义分析
我们已经了解了解析器如何在语法分析阶段构建解析树。在该阶段构建的普通解析树通常对编译器没有用处,因为它不包含任何有关如何评估树的信息。上下文无关语法的产生式(构成语言规则)并不适用于如何解释这些规则。
例如
E → E + T
上述 CFG 产生式没有与之相关的语义规则,并且无法帮助理解产生式。
语义
语言的语义为其构造(如标记和语法结构)提供意义。语义有助于解释符号、符号类型及其相互关系。语义分析判断源程序中构建的语法结构是否具有意义。
CFG + 语义规则 = 语法导向定义
例如:
int a = “value”;
不应在词汇和语法分析阶段发出错误,因为它在词汇和结构上是正确的,但它应该产生语义错误,因为赋值的类型不同。这些规则由语言的语法设置,并在语义分析中进行评估。语义分析中应执行以下任务:
- 范围解析
- 类型检查
- 数组绑定检查
语义错误
我们已经提到了语义分析器应该识别的一些语义错误:
- 类型不匹配
- 未声明的变量
- 保留标识符滥用。
- 在范围内多次声明变量。
- 访问范围之外的变量。
- 实际和形式参数不匹配。
属性语法
属性语法是一种特殊形式的上下文无关语法,其中某些附加信息(属性)附加到其一个或多个非终结符以提供上下文相关信息。每个属性都有明确定义的值域,例如整数、浮点数、字符、字符串和表达式。
属性语法是一种为上下文无关语法提供语义的媒介,它可以帮助指定编程语言的语法和语义。属性语法(当被视为解析树时)可以在树的节点之间传递值或信息。
示例:
E → E + T { E.value = E.value + T.value }
CFG 的右侧部分包含指定如何解释语法的语义规则。这里,非终结符 E 和 T 的值相加,结果复制到非终结符 E。
语义属性可以在解析时从其域中分配给它们的值,并在分配或条件时进行评估。根据属性获取其值的方式,它们可以大致分为两类:合成属性和继承属性。
合成属性
这些属性从其子节点的属性值中获取值。为了说明,假设以下产生式:
S → ABC
如果 S 从其子节点 (A、B、C) 获取值,则称其为合成属性,因为 ABC 的值被合成到 S。
如我们前面的示例 (E → E + T) 所示,父节点 E 从其子节点获取值。合成属性永远不会从其父节点或任何兄弟节点获取值。
继承属性
与合成属性相比,继承属性可以从父节点和/或兄弟节点获取值。如以下产生式所示,
S → ABC
A 可以从 S、B 和 C 中获取值。B 可以从 S、A 和 C 中获取值。同样,C 可以从 S、A 和 B 中获取值。
扩展:根据语法规则将非终结符扩展为终结符

缩减:根据语法规则将终结符缩减为其对应的非终结符。语法树的解析顺序为自上而下、从左到右。每当发生归约时,我们都会应用其相应的语义规则(动作)。
语义分析使用语法制导翻译来执行上述任务。
语义分析器从其前一阶段(语法分析)接收 AST(抽象语法树)。
语义分析器将属性信息附加到 AST,这被称为属性 AST。
属性是两个元组值,<属性名称,属性值>
例如:
int value = 5; <type, “integer”> <presentvalue, “5”>
对于每个产生式,我们附加一个语义规则。
S 属性 SDT
如果 SDT 仅使用合成属性,则称为 S 属性 SDT。这些属性使用 S 属性 SDT 进行评估,其语义操作写在产生式之后(右侧)。

如上所示,S 属性 SDT 中的属性在自下而上的解析中进行评估,因为父节点的值取决于子节点的值。
L 属性 SDT
这种形式的 SDT 使用合成和继承属性,但限制不从右兄弟节点获取值。
在 L 属性 SDT 中,非终端可以从其父节点、子节点和兄弟节点获取值。如以下产生式所示
S → ABC
S 可以从 A、B 和 C(合成)获取值。 A 只能从 S 中获取值。B 可以从 S 和 A 中获取值。C 可以从 S、A 和 B 中获取值。任何非终结符都不能从其右侧的兄弟节点中获取值。
L 属性 SDT 中的属性通过深度优先和从左到右的解析方式进行评估。

我们可以得出结论,如果定义是 S 属性的,那么它也是 L 属性的,因为 L 属性定义包含 S 属性定义。