直譯器 (Interpreter)

★★★★★Studied

FromDesign Patterns (GoF)大話設計模式

📚 From the Books

直譯器 (Interpreter)ConceptBehavioral

給定一種語言,為它的文法定義一個表示法,再定義一個直譯器用這個表示法來解釋語言中的句子。做法是每條文法規則對應一個類別,句子由這些類別的實例組成一棵抽象語法樹(AST),靠遞迴呼叫 interpret 解釋整棵樹。

🧠 When to Use

TIP

訊號:某類問題反覆出現,且每個實例都能寫成「一種簡單語言的句子」(正規表達式、瀏覽器解 HTML、機器人指令「前進 10 步、左轉 90 度」)——與其為每個實例寫一個演算法,不如寫一次直譯器來解釋它們。

⚖️ Structure & Variants

經典結構 — 每條規則一個類別,組成 AST
// AbstractExpression:所有節點共有的 interpret
fun interface Expr { fun interpret(ctx: Map<String, Int>): Int }

// TerminalExpression:遞迴的基本情況(終結符)
class Num(val value: Int) : Expr {
    override fun interpret(ctx: Map<String, Int>) = value
}
class Var(val name: String) : Expr {
    override fun interpret(ctx: Map<String, Int>) = ctx.getValue(name)
}

// NonterminalExpression:規則 R ::= R1 op R2,持有子表達式、遞迴解釋
class Add(val l: Expr, val r: Expr) : Expr {
    override fun interpret(ctx: Map<String, Int>) = l.interpret(ctx) + r.interpret(ctx)
}
class Mul(val l: Expr, val r: Expr) : Expr {
    override fun interpret(ctx: Map<String, Int>) = l.interpret(ctx) * r.interpret(ctx)
}

// Client 建 AST(此處直接手組)→ 傳入 Context → 從根呼叫 interpret
val ast = Add(Var("x"), Mul(Num(2), Var("y")))   // x + 2*y
ast.interpret(mapOf("x" to 5, "y" to 3))          // 11
  • Map<String,Int> 就是 Context(存直譯器的全域狀態,如變數表);Num/Var 是終結符,Add/Mul 是非終結符,interpret 沿樹遞迴。
Interpreter 不管 parsing
  • 模式只負責「解釋 AST」,不負責「把字串解析成 AST」。建樹可由手寫的 recursive descent parser、table-driven parser,或客戶端直接組。
  • 實務最常見的組合:用 parser generator 處理 parsing,用 Interpreter 模式處理 AST 的解釋——不必全部手寫。
和三個近親的關係
  • Composite:AST 本身就是一棵 Composite 樹;Interpreter 是「把類別階層當語言」的特化用法。
  • Visitor:若要頻繁新增「解釋方式」(求值、pretty print、type check),把操作搬到 visitor,就不必每次改所有文法類別。
  • Flyweight:句子中重複出現的終結符(如變數名)可共享同一實例——內在狀態共享、位置等外在狀態由父節點解釋時傳入。

⚠️ Misuse & Anti-patterns

🔑 Takeaways

✍️ My Notes

No notes yet — jot your takeaways or Q&A here.

📖 Further Reading