进阶 #compiler#ast

抽象语法树(AST)

抽象语法树(Abstract Syntax Tree, AST)是语法分析的核心输出——它去掉了括号、分号等语法细节,只保留程序的"骨架结构"

🌳 一棵”树”——去掉标点,只留骨架

语法分析(Parser)产生的具体语法树包含了所有细节——括号、分号、逗号——这些对编译器后续阶段是冗余的。

AST(Abstract Syntax Tree,抽象语法树) 去掉这些冗余,只保留程序的逻辑骨架。

🏪 类比:房屋结构图 vs 装修效果图

具体语法树 = 建筑蓝图——标注了每根钢筋、每个螺丝的位置。非常精确,但信息太多。

AST = 装修效果图——只显示房间布局、门窗位置。没人需要在效果图里看到螺丝钉的位置。

编译器后续阶段(语义分析、代码生成)只关心”房间怎么布局”——不需要知道”螺丝钉钉在哪”。

具体语法树(含分号、括号):          AST(只留骨架):
       语句                              =
      / / \ \ \                         / \
  int  a  = 表达式 ;                   a   +
          / | \                           / \
       表达式 + 表达式                  42   1
         |        |
        42        1

🧱 AST 的节点类型

# AST 的节点类型定义——每种节点对应一种语法构造

class ASTNode:
    """所有 AST 节点的基类"""
    pass

class Program(ASTNode):
    def __init__(self, statements):
        self.statements = statements  # 语句列表

class BinaryOp(ASTNode):
    """二元运算:a + b, a * b 等"""
    def __init__(self, op, left, right):
        self.op = op          # '+', '-', '*', '/', ...
        self.left = left      # ASTNode(左操作数)
        self.right = right    # ASTNode(右操作数)

class Number(ASTNode):
    """整数字面量:42"""
    def __init__(self, value):
        self.value = value

class Variable(ASTNode):
    """变量引用:a, b, sum"""
    def __init__(self, name):
        self.name = name

class Assignment(ASTNode):
    """赋值语句:a = expr"""
    def __init__(self, target, value):
        self.target = target  # Variable
        self.value = value    # ASTNode

class Print(ASTNode):
    """输出语句:print(x)"""
    def __init__(self, expr):
        self.expr = expr

🔍 遍历 AST——编译器”读”程序的方式

编译器不是”看”源文件的——它遍历 AST。AST 是递归结构,所以遍历也是递归的:

# 递归遍历 AST——计算表达式的值(解释器模式)
def evaluate(node):
    if isinstance(node, Number):
        return node.value
    
    elif isinstance(node, BinaryOp):
        left = evaluate(node.left)
        right = evaluate(node.right)
        if node.op == '+': return left + right
        if node.op == '-': return left - right
        if node.op == '*': return left * right
        if node.op == '/': return left // right
    
    elif isinstance(node, Assignment):
        value = evaluate(node.value)
        # 存入符号表
        symbol_table[node.target.name] = value
        return value
    
    elif isinstance(node, Print):
        value = evaluate(node.expr)
        print(value)
        return value
    
    elif isinstance(node, Program):
        for stmt in node.statements:
            evaluate(stmt)

# 用 AST 表示 a = 42 + 1 并求值
ast = Assignment(
    Variable("a"),
    BinaryOp('+', Number(42), Number(1))
)
print(evaluate(ast))  # 43

这段代码本质上就是一个微型解释器——递归遍历 AST,执行每个节点对应的操作。实际的 Python 解释器也是这样工作的。


🔧 AST 在编译器各阶段的作用

AST 是编译器的"中央数据结构"——前端各阶段都围绕它工作:

语法分析(Parser)→ 生成 AST

语义分析 → 在 AST 上添加类型信息、检查语义正确性

IR 生成 → 递归遍历 AST,生成三地址码

优化 → 在某些编译器中直接在 AST 上做

📝 小结

概念一句话
AST(抽象语法树)去掉标点符号等冗余的程序骨架结构——树状
具体语法树 vs AST具体树包含所有细节,AST 只保留逻辑结构
节点类型每种语法构造对应一种节点(BinaryOp、Number、Assignment 等)
递归遍历编译器”读”程序的方式——递归访问 AST 节点
AST 的角色编译器的”中央数据结构”——前后端围绕它工作

为什么先学这个? AST 是语义分析的基础——下一步在 AST 上做语义分析与符号表