第122集:属性文法

核心知识点讲解

什么是属性文法?

属性文法(Attribute Grammar)是上下文无关文法的扩展,它为文法的每个符号(终结符和非终结符)添加了属性,并为每个产生式添加了语义规则来计算这些属性的值。属性文法是编译原理中用于描述语义分析的重要工具。

属性文法的主要组成部分:

  1. 上下文无关文法:提供语法结构
  2. 属性:与文法符号相关的值
  3. 语义规则:计算属性值的规则
  4. 条件:(可选)限制属性值的条件

属性文法的核心思想是:在语法分析的过程中,通过语义规则计算属性的值,从而完成语义分析和中间代码生成。

综合属性

综合属性(Synthesized Attribute)是从子节点传递到父节点的属性,即父节点的属性值由子节点的属性值计算得到。

综合属性的特点

  • 自底向上计算:从语法树的叶节点开始,向上计算到根节点
  • 值的传递方向:子节点 → 父节点
  • 计算时机:在自底向上的语法分析过程中计算
  • 应用场景:计算表达式的值、构建抽象语法树等

示例

考虑以下文法:

E → E1 + T  | T
T → T1 * F  | F
F → ( E )   | id

为每个非终结符添加一个综合属性 val,表示该表达式的值:

产生式 语义规则
E → E1 + T E.val = E1.val + T.val
E → T E.val = T.val
T → T1 * F T.val = T1.val * F.val
T → F T.val = F.val
F → ( E ) F.val = E.val
F → id F.val = id.lexval

其中 id.lexval 是终结符 id 的属性,表示标识符的值。

继承属性

继承属性(Inherited Attribute)是从父节点传递到子节点的属性,即子节点的属性值由父节点的属性值计算得到。

继承属性的特点

  • 自顶向下计算:从语法树的根节点开始,向下计算到叶节点
  • 值的传递方向:父节点 → 子节点
  • 计算时机:在自顶向下的语法分析过程中计算
  • 应用场景:传递上下文信息、类型环境等

示例

考虑以下文法,用于处理变量声明:

D → T L
T → int | float
L → L1, id | id

为文法添加属性:

  • T.type:综合属性,表示类型
  • L.in:继承属性,表示当前的类型环境
  • L.out:综合属性,表示更新后的类型环境

语义规则:

产生式 语义规则
D → T L L.in = T.type; D.out = L.out
T → int T.type = "int"
T → float T.type = "float"
L → L1, id L1.in = L.in; L.out = L1.out ∪ { (id.name, L.in) }
L → id L.out = { (id.name, L.in) }

依赖图

依赖图(Dependency Graph)是用于表示属性之间依赖关系的有向图,它是属性文法求值的基础。

依赖图的构建

  1. 节点:每个文法符号的每个属性对应一个节点
  2. :如果属性 A 的值依赖于属性 B 的值,则添加一条从 B 到 A 的边
  3. 依赖关系:由语义规则确定

依赖图的应用

  1. 属性求值顺序:拓扑排序依赖图,得到属性的计算顺序
  2. 循环依赖检测:如果依赖图中存在环,则属性文法有循环依赖,无法求值
  3. 求值策略选择:根据依赖图的结构选择合适的求值策略

示例

对于产生式 E → E1 + T,语义规则为 E.val = E1.val + T.val,则依赖图包含:

  • 节点:E.val, E1.val, T.val
  • 边:E1.val → E.val, T.val → E.val

实用案例分析

属性文法的分类

根据属性的计算顺序,属性文法可以分为:

  1. S-属性文法

    • 只包含综合属性
    • 可以在自底向上的语法分析过程中计算
    • 实现简单,效率高
  2. L-属性文法

    • 包含综合属性和继承属性
    • 继承属性的值只依赖于父节点的属性和/或左侧兄弟节点的属性
    • 可以在自顶向下的语法分析过程中计算
  3. 一般属性文法

    • 包含任意的依赖关系
    • 可能需要多遍扫描才能计算所有属性
    • 实现复杂,效率较低

属性文法的求值策略

自底向上求值

适用于 S-属性文法,在 LR 分析过程中计算属性值:

  1. 分析栈:保存文法符号和对应的属性值
  2. 归约时计算:当使用产生式归约时,根据语义规则计算综合属性的值
  3. 效率:与语法分析同时进行,效率高

自顶向下求值

适用于 L-属性文法,在 LL 分析过程中计算属性值:

  1. 递归下降:每个非终结符对应一个函数,计算继承属性并传递给子节点
  2. 参数传递:继承属性作为函数参数传递
  3. 返回值:综合属性作为函数返回值

依赖图拓扑排序

适用于一般属性文法:

  1. 构建依赖图:根据语义规则构建属性之间的依赖关系
  2. 拓扑排序:对依赖图进行拓扑排序,得到属性的计算顺序
  3. 按序计算:按照拓扑排序的顺序计算属性值
  4. 循环检测:如果依赖图中存在环,则报告错误

属性文法的实现

基于抽象语法树的实现

  1. 构建抽象语法树:首先进行语法分析,构建抽象语法树
  2. 遍历树计算属性
    • 对于 S-属性文法:后序遍历
    • 对于 L-属性文法:先序遍历
    • 对于一般属性文法:拓扑排序遍历

代码实现

class ASTNode:
    """抽象语法树节点"""
    def __init__(self, node_type):
        self.node_type = node_type
        self.children = []
        self.attributes = {}  # 存储属性值

class AttributeGrammar:
    """属性文法"""
    def __init__(self, grammar):
        self.grammar = grammar  # 上下文无关文法
        self.semantic_rules = {}  # 语义规则
    
    def add_semantic_rule(self, production, rule):
        """添加语义规则"""
        self.semantic_rules[production] = rule
    
    def compute_synthesized_attributes(self, ast):
        """自底向上计算综合属性"""
        # 递归计算子节点的属性
        for child in ast.children:
            self.compute_synthesized_attributes(child)
        
        # 查找适用的语义规则
        for production, rule in self.semantic_rules.items():
            if self.matches_production(ast, production):
                # 应用语义规则
                rule(ast)
                break
    
    def compute_inherited_attributes(self, ast, inherited_attrs):
        """自顶向下计算继承属性"""
        # 设置继承属性
        for attr, value in inherited_attrs.items():
            ast.attributes[attr] = value
        
        # 查找适用的语义规则
        for production, rule in self.semantic_rules.items():
            if self.matches_production(ast, production):
                # 应用语义规则计算子节点的继承属性
                child_attrs = rule(ast)
                
                # 递归计算子节点的属性
                for i, child in enumerate(ast.children):
                    if i < len(child_attrs):
                        self.compute_inherited_attributes(child, child_attrs[i])
                break
    
    def matches_production(self, ast, production):
        """检查节点是否匹配产生式"""
        # 简化实现,实际需要更复杂的匹配逻辑
        return True

依赖图的构建与拓扑排序

class DependencyGraph:
    """属性依赖图"""
    def __init__(self):
        self.nodes = set()  # 属性节点
        self.edges = {}  # 边:{from_node: [to_nodes]}
    
    def add_node(self, node):
        """添加节点"""
        self.nodes.add(node)
        if node not in self.edges:
            self.edges[node] = []
    
    def add_edge(self, from_node, to_node):
        """添加边"""
        self.add_node(from_node)
        self.add_node(to_node)
        if to_node not in self.edges[from_node]:
            self.edges[from_node].append(to_node)
    
    def has_cycle(self):
        """检测是否有环"""
        visited = set()
        rec_stack = set()
        
        def dfs(node):
            visited.add(node)
            rec_stack.add(node)
            
            for neighbor in self.edges.get(node, []):
                if neighbor not in visited:
                    if dfs(neighbor):
                        return True
                elif neighbor in rec_stack:
                    return True
            
            rec_stack.remove(node)
            return False
        
        for node in self.nodes:
            if node not in visited:
                if dfs(node):
                    return True
        
        return False
    
    def topological_sort(self):
        """拓扑排序"""
        if self.has_cycle():
            raise ValueError("Dependency graph has cycle")
        
        visited = set()
        result = []
        
        def dfs(node):
            visited.add(node)
            for neighbor in self.edges.get(node, []):
                if neighbor not in visited:
                    dfs(neighbor)
            result.append(node)
        
        for node in self.nodes:
            if node not in visited:
                dfs(node)
        
        return result[::-1]  # 反转结果,得到正确的拓扑顺序

实用案例分析

属性文法的应用

表达式求值

使用 S-属性文法计算表达式的值:

文法

E → E + T | T
T → T * F | F
F → ( E ) | num

属性

  • E.val, T.val, F.val:综合属性,表示表达式的值
  • num.val:综合属性,表示数字的字面值

语义规则

产生式 语义规则
E → E1 + T E.val = E1.val + T.val
E → T E.val = T.val
T → T1 * F T.val = T1.val * F.val
T → F T.val = F.val
F → ( E ) F.val = E.val
F → num F.val = num.val

示例

对于表达式 3 + 4 * 5,语法树如下:

      E
     / \
    E   T
   /   / \
  T   T   F
 /   /   |
F   F   num
|   |   5
num num
3   4

计算过程:

  1. 叶节点:num.val=3, num.val=4, num.val=5
  2. F.val=3, F.val=4, F.val=5
  3. T.val=3, T.val=4*5=20
  4. E.val=3, E.val=3+20=23

最终结果:E.val=23

类型检查

使用 L-属性文法进行类型检查:

文法

S → D ; E
D → int id
E → E + E | id

属性

  • D.type:综合属性,表示声明的类型
  • E.type:综合属性,表示表达式的类型
  • E.env:继承属性,表示符号表
  • id.type:综合属性,从符号表中查找

语义规则

产生式 语义规则
S → D ; E E.env = { (D.id, D.type) }; S.type = E.type
D → int id D.type = "int"
E → E1 + E2 E1.env = E.env; E2.env = E.env; E.type = (E1.type == E2.type) ? E1.type : "error"
E → id E.type = lookup(E.env, id.name)

示例

对于程序 int x; x + x,语法树如下:

  S
 / | \
D  ;  E
|     / \
int  E   E
|    |   |
id   id  id
x    x   x

计算过程:

  1. 自顶向下传递继承属性:S.env → E.env = { (x, int) }
  2. E1.env = { (x, int) }, E2.env = { (x, int) }
  3. 自底向上计算综合属性:id.type=x.type=int
  4. E1.type=int, E2.type=int
  5. E.type=int
  6. S.type=int

类型检查结果:类型正确

依赖图示例

构建依赖图

对于产生式 E → E1 + T,语义规则为 E.val = E1.val + T.val,依赖图包含:

  • 节点:E.val, E1.val, T.val
  • 边:E1.val → E.val, T.val → E.val

对于产生式 D → T L,语义规则为 L.in = T.type,依赖图包含:

  • 节点:D.out, T.type, L.in, L.out
  • 边:T.type → L.in, L.in → L.out, L.out → D.out

拓扑排序

对于表达式 E → E1 + T 的依赖图,拓扑排序结果为:

  1. E1.val
  2. T.val
  3. E.val

对于声明 D → T L 的依赖图,拓扑排序结果为:

  1. T.type
  2. L.in
  3. L.out
  4. D.out

属性文法的挑战

循环依赖

如果属性之间存在循环依赖,属性文法无法求值:

A → B
B → A

A.a = B.b
B.b = A.a

此时依赖图中存在环:A.a → B.b → A.a

计算效率

对于复杂的属性文法,计算属性值可能需要多遍扫描,影响编译效率:

  1. 多遍扫描:第一遍计算继承属性,第二遍计算综合属性
  2. 惰性计算:只在需要时计算属性值
  3. 缓存:缓存已计算的属性值,避免重复计算

实现复杂度

属性文法的实现复杂度较高,特别是对于一般属性文法:

  1. 依赖图构建:需要分析语义规则,构建属性之间的依赖关系
  2. 拓扑排序:需要处理大型依赖图的拓扑排序
  3. 错误处理:需要处理循环依赖等错误情况

小结

属性文法是编译原理中用于描述语义分析的强大工具:

  • 综合属性:从子节点传递到父节点,用于自底向上的计算
  • 继承属性:从父节点传递到子节点,用于自顶向下的计算
  • 依赖图:表示属性之间的依赖关系,用于确定计算顺序
  • 求值策略:根据属性类型选择合适的计算方法
  • 应用场景:表达式求值、类型检查、中间代码生成等

属性文法为编译器的语义分析提供了形式化的方法,使得语义规则的描述更加清晰和系统。在实际编译器开发中,属性文法通常与语法分析器结合使用,在语法分析的过程中完成语义分析和中间代码生成。

« 上一篇 数据流分析 下一篇 » S-属性文法