用 C# 開發(fā)一個(gè)解釋器語言——基于《Crafting Interpreters》的實(shí)戰(zhàn)系列(三)表達(dá)式的抽象語法樹設(shè)計(jì)(Expr)
在前兩篇博客中,我們完成了源碼的詞法分析,將代碼拆解為 Token 流,接下來就進(jìn)入理解如何用代碼結(jié)構(gòu)來表示程序中的表達(dá)式,也就是抽象語法樹(AST)的構(gòu)建。
本篇重點(diǎn)圍繞書中核心數(shù)據(jù)結(jié)構(gòu) Expr 類展開,結(jié)合 C# 實(shí)現(xiàn),詳細(xì)講解它的設(shè)計(jì)理念、組成部分及其作用。理解了它,就為后續(xù)語法分析器和解釋器實(shí)現(xiàn)打下堅(jiān)實(shí)基礎(chǔ)。
為什么需要抽象語法樹(AST)?
編程語言的源代碼本質(zhì)上是字符串,但解釋器需要有“結(jié)構(gòu)化”語義才能理解執(zhí)行。抽象語法樹正是將線性文本轉(zhuǎn)換成結(jié)構(gòu)化、樹狀的模型。
舉例:
(1 + 2) * 3
文本難以直接表達(dá)操作順序和層次,但 AST 會(huì)明確表達(dá)“先計(jì)算括號(hào)內(nèi)的加法,再乘以3”:
*
/ \
+ 3
/ \
1 2
Expr 類的設(shè)計(jì)理念
Expr 是一個(gè)抽象基類,代表所有表達(dá)式的通用類型。不同的具體表達(dá)式繼承自它:
- Literal:字面量表達(dá)式,比如數(shù)字、字符串等
- Binary:二元運(yùn)算表達(dá)式,如加法、乘法
- Grouping:用括號(hào)分組的表達(dá)式
- Unary:一元運(yùn)算表達(dá)式,如取負(fù)
- Variable、Assign 等(后續(xù))
訪問者模式在 Expr 中的應(yīng)用
為了對(duì)表達(dá)式做各種操作(打印、求值、編譯等),書中采用了訪問者模式(Visitor Pattern),它能避免在表達(dá)式類里寫死所有邏輯,而是分離操作和數(shù)據(jù)結(jié)構(gòu)。
Visitor接口定義
public interface Visitor<T>
{
T VisitBinaryExpr(Binary expr);
T VisitGroupingExpr(Grouping expr);
T VisitLiteralExpr(Literal expr);
T VisitUnaryExpr(Unary expr);
}
- 這里的泛型
<T>表示訪問方法的返回類型,可以是字符串、求值結(jié)果等。
Expr 抽象基類的 Accept 方法
public abstract T Accept<T>(Visitor<T> visitor);
- 每個(gè)具體表達(dá)式類都實(shí)現(xiàn)此方法,調(diào)用對(duì)應(yīng)的訪問者方法。
具體表達(dá)式類詳解
1. Binary(二元表達(dá)式)
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);
}
}
Left和Right是操作數(shù),均為Expr類型,支持嵌套表達(dá)式。Operator是Token,記錄運(yùn)算符本身,比如+。
2. Literal(字面量表達(dá)式)
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);
}
}
- 保存字面量值,可以是數(shù)字、字符串、布爾等。
- 訪問時(shí)直接返回該值。
3. Grouping(分組表達(dá)式)
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);
}
}
- 表示括號(hào)內(nèi)的表達(dá)式,用于明確優(yōu)先級(jí)。
4. Unary(一元表達(dá)式)
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);
}
}
- 一元操作符,如負(fù)號(hào)
-。
Expr 的作用和價(jià)值
- 表達(dá)式樹是解釋器的核心數(shù)據(jù)模型,它將文本代碼轉(zhuǎn)換為層次分明的結(jié)構(gòu)。
- 使用訪問者模式后,我們可以靈活地?cái)U(kuò)展各種表達(dá)式操作,比如打印、求值、靜態(tài)分析。
Expr的設(shè)計(jì)讓代碼清晰且易維護(hù),符合面向?qū)ο笤O(shè)計(jì)原則。

浙公網(wǎng)安備 33010602011771號(hào)