用 C# 开发一个解释器语言——基于《Crafting Interpreters》的实战系列(三)表达式的抽象语法树设计(Expr)

在前两篇博客中,我们完成了源码的词法分析,将代码拆解为 Token 流,接下来就进入理解如何用代码结构来表示程序中的表达式,也就是抽象语法树(AST)的构建。

本篇重点围绕书中核心数据结构 Expr 类展开,结合 C# 实现,详细讲解它的设计理念、组成部分及其作用。理解了它,就为后续语法分析器和解释器实现打下坚实基础。


为什么需要抽象语法树(AST)?

编程语言的源代码本质上是字符串,但解释器需要有“结构化”语义才能理解执行。抽象语法树正是将线性文本转换成结构化、树状的模型。

举例:

(1 + 2) * 3

文本难以直接表达操作顺序和层次,但 AST 会明确表达“先计算括号内的加法,再乘以3”:

        *
       / \
      +   3
     / \
    1   2

Expr 类的设计理念

Expr 是一个抽象基类,代表所有表达式的通用类型。不同的具体表达式继承自它:

  • Literal:字面量表达式,比如数字、字符串等
  • Binary:二元运算表达式,如加法、乘法
  • Grouping:用括号分组的表达式
  • Unary:一元运算表达式,如取负
  • VariableAssign 等(后续)

访问者模式在 Expr 中的应用

为了对表达式做各种操作(打印、求值、编译等),书中采用了访问者模式(Visitor Pattern),它能避免在表达式类里写死所有逻辑,而是分离操作和数据结构。

Visitor接口定义

public interface Visitor<T>
{
    T VisitBinaryExpr(Binary expr);
    T VisitGroupingExpr(Grouping expr);
    T VisitLiteralExpr(Literal expr);
    T VisitUnaryExpr(Unary expr);
}
  • 这里的泛型 <T> 表示访问方法的返回类型,可以是字符串、求值结果等。

Expr 抽象基类的 Accept 方法

public abstract T Accept<T>(Visitor<T> visitor);
  • 每个具体表达式类都实现此方法,调用对应的访问者方法。

具体表达式类详解

1. Binary(二元表达式)

public class Binary : Expr
{
    public Expr Left { get; }
    public Token Operator { get; }
    public Expr Right { get; }

    public Binary(Expr left, Token op, Expr right)
    {
        Left = left;
        Operator = op;
        Right = right;
    }

    public override T Accept<T>(Visitor<T> visitor)
    {
        return visitor.VisitBinaryExpr(this);
    }
}
  • LeftRight 是操作数,均为 Expr 类型,支持嵌套表达式。
  • OperatorToken,记录运算符本身,比如 +

2. Literal(字面量表达式)

public class Literal : Expr
{
    public object Value { get; }

    public Literal(object value)
    {
        Value = value;
    }

    public override T Accept<T>(Visitor<T> visitor)
    {
        return visitor.VisitLiteralExpr(this);
    }
}
  • 保存字面量值,可以是数字、字符串、布尔等。
  • 访问时直接返回该值。

3. Grouping(分组表达式)

public class Grouping : Expr
{
    public Expr Expression { get; }

    public Grouping(Expr expression)
    {
        Expression = expression;
    }

    public override T Accept<T>(Visitor<T> visitor)
    {
        return visitor.VisitGroupingExpr(this);
    }
}
  • 表示括号内的表达式,用于明确优先级。

4. Unary(一元表达式)

public class Unary : Expr
{
    public Token Operator { get; }
    public Expr Right { get; }

    public Unary(Token op, Expr right)
    {
        Operator = op;
        Right = right;
    }

    public override T Accept<T>(Visitor<T> visitor)
    {
        return visitor.VisitUnaryExpr(this);
    }
}
  • 一元操作符,如负号 -

Expr 的作用和价值

  • 表达式树是解释器的核心数据模型,它将文本代码转换为层次分明的结构。
  • 使用访问者模式后,我们可以灵活地扩展各种表达式操作,比如打印、求值、静态分析。
  • Expr 的设计让代码清晰且易维护,符合面向对象设计原则。
posted @ 2025-08-04 17:04  daviyoung  阅读(30)  评论(0)    收藏  举报