# 編譯原理

::: tip 前言
**當你按下「執行」按鈕，程式碼是怎麼變成螢幕上的結果的？** 你寫的每一行程式碼，電腦其實都「看不懂」——它只認識 0 和 1。編譯器就是那個把人類語言翻譯成機器語言的系統。理解編譯原理，你就能理解報錯資訊從哪來、為什麼有些語言快有些慢、以及程式碼最佳化的底層邏輯。
:::

**這篇文章會帶你學什麼？**

學完這章後，你將獲得：

- **全域視野**：掌握從原始碼到可執行程式的完整編譯流水線
- **詞法分析**：理解編譯器如何把程式碼拆成一個個 Token
- **語法分析**：理解 AST（抽象語法樹）的建構過程
- **AST 視覺化**：直觀看到程式碼的樹形結構
- **語義分析與最佳化**：理解型別檢查和程式碼最佳化的原理
- **最佳化技術實戰**：掌握常數摺疊、死程式碼消除等核心最佳化手段
- **執行模型**：區分編譯型、直譯型和 JIT 三種執行方式

| 章节 | 內容 | 核心概念 |
|-----|------|---------|
| **第 1 章** | 編譯器是什麼 | 編譯流水線概述 |
| **第 2 章** | 詞法分析 | Token、詞法規則 |
| **第 3 章** | 語法分析 | AST、語法樹、優先級 |
| **第 4 章** | AST 視覺化 | 互動式語法樹、節點型別 |
| **第 5 章** | 語義分析與最佳化 | 型別檢查、常數摺疊、死程式碼消除 |
| **第 6 章** | 最佳化技術實戰 | 函式內聯、迴圈外提、常數傳播 |
| **第 7 章** | 編譯型 vs 直譯型 vs JIT | 三種執行模型對比 |

---

## 0. 全景圖：編譯流程概述

想象你是一個翻譯官，要把一本中文小說翻譯成英文。你不會一個字一個字地直譯，而是：

1. **辨識詞語** — 把句子拆成一個個詞（詞法分析）
2. **理解句法** — 判斷句子結構是否正確（語法分析）
3. **理解語義** — 確保意思通順、沒有矛盾（語義分析）
4. **潤色最佳化** — 讓譯文更地道流暢（程式碼最佳化）
5. **輸出譯文** — 寫出最終的英文版本（程式碼產生）

編譯器做的事情完全一樣，只不過它翻譯的是程式語言。

<CompilerAnalogyDemo />

---

## 1. 編譯器的六步流水線

編譯器的工作可以分為六個階段，像工廠流水線一樣，每個階段處理完交給下一個階段。

<CompilerDemo />

::: tip 編譯流水線
1. **詞法分析（Lexical Analysis）**：把原始碼拆成一個個 Token（單詞）
2. **語法分析（Syntax Analysis）**：把 Token 組織成語法樹（AST）
3. **語義分析（Semantic Analysis）**：檢查型別是否正確、變數是否宣告
4. **中間程式碼產生（IR Generation）**：產生與平台無關的中間表示
5. **程式碼最佳化（Optimization）**：讓中間程式碼更高效
6. **程式碼產生（Code Generation）**：產生目標平台的機器碼
:::

| 階段 | 輸入 | 輸出 | 類比 |
|------|------|------|------|
| 詞法分析 | 原始碼字元流 | Token 流 | 把句子拆成單詞 |
| 語法分析 | Token 流 | AST（語法樹） | 分析句子結構 |
| 語義分析 | AST | 帶型別的 AST | 檢查意思是否通順 |
| 中間程式碼 | 帶型別的 AST | IR | 寫出初稿 |
| 程式碼最佳化 | IR | 最佳化後的 IR | 潤色刪減 |
| 程式碼產生 | 最佳化後的 IR | 機器碼 | 輸出終稿 |

---

## 2. 詞法分析：把程式碼拆成「單詞」

詞法分析是編譯的第一步。編譯器從左到右掃描原始碼的每個字元，把它們組合成有意義的 **Token（詞法單元）**。

<LexerTokenDemo />

就像讀英文句子時，你的大腦會自動把字母組合成單詞一樣，詞法分析器把字元組合成 Token：

```
原始碼: let x = 10 + 5;

Token 流:
[let]   → 關鍵字（語言保留字）
[x]     → 識別符（變數名）
[=]     → 運算子（賦值）
[10]    → 數字字面量
[+]     → 運算子（加法）
[5]     → 數字字面量
[;]     → 分隔符（敘述結束）
```

::: tip Token 的五大型別
- **關鍵字**：語言保留的特殊單詞，如 `let`、`if`、`return`、`function`
- **識別符**：程式設計師定義的名稱，如變數名、函式名
- **字面量**：直接寫在程式碼裡的值，如數字 `42`、字串 `"hello"`
- **運算子**：執行運算的符號，如 `+`、`-`、`=`、`===`
- **分隔符**：分隔程式碼結構的符號，如 `;`、`,`、`(`、`)`
:::

---

## 3. 語法分析：建構語法樹（AST）

詞法分析把程式碼拆成了 Token，但 Token 只是一個個孤立的「單詞」。語法分析的任務是把這些 Token 按照語法規則組織成一棵 **抽象語法樹（Abstract Syntax Tree, AST）**——它反映了程式碼的結構和運算優先級。

```
運算式: 1 + 2 * 3

語法樹:        為什麼這樣？
       +       因為 * 的優先級
      / \      高於 +，所以
     1   *     2 * 3 先結合
        / \    成為一個子樹
       2   3
```

::: tip AST 的重要性
AST 是編譯器的「核心資料結構」，後續的語義分析、最佳化、程式碼產生都基於它進行。現代開發工具也大量使用 AST：
- **ESLint**：解析程式碼為 AST，檢查是否違反規則
- **Prettier**：解析為 AST 後重新格式化輸出
- **Babel**：解析 AST → 轉換 → 產生相容程式碼
- **IDE 重構**：基於 AST 進行安全的變數重新命名、函式提取
:::

| 語法結構 | Token 序列 | AST 節點 |
|---------|-----------|---------|
| 變數宣告 | `let` `x` `=` `10` | VariableDeclaration → Identifier + Literal |
| 函式呼叫 | `add` `(` `1` `,` `2` `)` | CallExpression → Identifier + Arguments |
| 條件敘述 | `if` `(` `a` `>` `b` `)` | IfStatement → BinaryExpression + Block |

---

## 4. AST 視覺化：看見程式碼結構

上面我們用文字描述了 AST 的結構，但「看到」比「讀到」更直觀。下面的互動元件讓你選擇不同的運算式，即時觀察它們的語法樹長什麼樣。

<ASTVisualizerDemo />

透過視覺化你會發現，AST 的核心規律其實很簡單：

| 程式碼結構 | AST 根節點 | 子節點 |
|---------|-----------|-------|
| `1 + 2 * 3` | BinaryExpression (+) | 左: NumericLiteral(1)，右: BinaryExpression(*) |
| `let x = 10` | VariableDeclaration | VariableDeclarator → Identifier(x) + NumericLiteral(10) |
| `add(a, b)` | CallExpression | Identifier(add) + Arguments(a, b) |

::: tip AST 在日常開發中的應用
你可能沒直接寫過編譯器，但你每天都在用基於 AST 的工具：
- **ESLint / Prettier**：解析程式碼為 AST，檢查規則或重新格式化
- **Babel / SWC**：解析 AST → 轉換語法 → 產生相容程式碼
- **IDE 重構**：基於 AST 做安全的重新命名、提取函式
- **Tree-shaking**：分析 AST 中的 import/export，刪除未使用的程式碼
:::

---

## 5. 語義分析與程式碼最佳化

語法分析確保程式碼「結構正確」，但結構正確不代表「意思正確」。語義分析負責檢查程式碼的含義是否合法，程式碼最佳化則讓程式跑得更快。

<CompilationPracticeDemo />

### 5.1 語義分析原理

| 檢查內容 | 範例 | 結果 |
|---------|------|------|
| 型別檢查 | `int x = "hello"` | ❌ 型別不匹配 |
| 作用域檢查 | 使用未宣告的變數 `y` | ❌ 變數不存在 |
| 型別推斷 | `1 + 2.0` | ✅ 推斷結果為 float |
| 參數檢查 | `add(1, 2, 3)` 但函式只接受 2 個參數 | ❌ 參數數量不匹配 |

::: tip 你見過的報錯，大多來自語義分析
- `TypeError: Cannot read properties of undefined` — 型別檢查
- `ReferenceError: x is not defined` — 作用域檢查
- `Expected 2 arguments, but got 3` — 參數檢查
:::

### 5.2 程式碼最佳化技術

編譯器在產生最終程式碼前，會對中間程式碼做各種最佳化。這些最佳化對程式設計師透明，但能顯著提升效能。

| 最佳化技術 | 最佳化前 | 最佳化後 | 原理 |
|---------|-------|-------|------|
| 常數摺疊 | `x = 10 + 5` | `x = 15` | 編譯時直接算出結果 |
| 死程式碼消除 | `if (false) { ... }` | 直接刪除 | 永遠不會執行的程式碼 |
| 常數傳播 | `x = 15; y = x * 2` | `y = 30` | 已知值直接替換 |
| 迴圈不變量外提 | 迴圈內重複計算 `len = arr.length` | 提到迴圈外 | 避免重複計算 |

---

## 6. 最佳化技術實戰：編譯器程式碼優化機制

上面我們提到了幾種最佳化技術名稱，現在來深入看看編譯器具體是怎麼做的。下面的互動元件展示了 5 種最常見的編譯器最佳化，你可以直觀對比最佳化前後的程式碼差異。

<CodeOptimizationDemo />

現代編譯器和 JIT 引擎（如 V8、GCC、LLVM）會自動應用數十種最佳化。作為開發者，你不需要手動做這些最佳化，但理解它們能幫你：

- **寫出更容易被最佳化的程式碼**：比如用 `const` 而不是 `let`，編譯器更容易做常數摺疊
- **理解效能差異**：為什麼小函式比大函式快？因為編譯器能內聯它們
- **避免「反最佳化」**：某些寫法會阻止編譯器最佳化，比如 `eval()` 和 `with`

| 最佳化技術 | 觸發條件 | 效能影響 | 開發者能做什麼 |
|---------|---------|---------|-------------|
| 常數摺疊 | 運算式中全是常數 | 消除執行時計算 | 多用 const 宣告 |
| 死程式碼消除 | 程式碼不可達或結果未使用 | 減小程式碼體積 | 及時清理無用程式碼 |
| 迴圈不變量外提 | 迴圈內有不變的計算 | 減少重複計算 | 手動提取也是好習慣 |
| 函式內聯 | 小函式被頻繁呼叫 | 消除呼叫開銷 | 保持函式小而專注 |
| 常數傳播 | 變數值在編譯時可確定 | 整條計算鏈被消除 | 用常數代替魔法數字 |

---

## 7. 編譯型 vs 直譯型 vs JIT

程式碼寫完後，有三種「翻譯方式」讓它執行起來。這三種方式各有優劣，直接決定了語言的效能特徵和使用場景。

<CompileVsInterpretDemo />

| 維度 | 編譯型 | 直譯型 | JIT 即時編譯 |
|------|-------|-------|------------|
| 過程 | 先全量編譯成機器碼，再執行 | 邊讀邊執行，逐行翻譯 | 先直譯執行，熱點程式碼再編譯 |
| 執行速度 | 最快 | 最慢 | 中等（熱點接近編譯型） |
| 啟動速度 | 慢（需要編譯） | 快（直接執行） | 中等（需要預熱） |
| 跨平台 | 需要重新編譯 | 天然跨平台 | 跨平台 |
| 代表語言 | C, Rust, Go | Python, Ruby | JavaScript (V8), Java |

::: tip 為什麼 JavaScript 這麼快？
V8 引擎的 JIT 編譯器會監測哪些程式碼被頻繁執行（熱點程式碼），然後把它們編譯成高度最佳化的機器碼。所以雖然 JavaScript 是「直譯型語言」，但在 V8 中它的效能可以接近編譯型語言。這也是 Node.js 能做伺服器端的底氣。
:::

---

## 總結

編譯原理不是只有編譯器開發者才需要了解的知識。理解編譯流程，能幫你更好地理解報錯資訊、選擇合適的語言、寫出更高效的程式碼。

回顧本章的關鍵要點：

1. **編譯器是翻譯系統**：把人類可讀的程式碼翻譯成機器可執行的指令
2. **六步流水線**：詞法分析 → 語法分析 → 語義分析 → 中間程式碼 → 最佳化 → 程式碼產生
3. **詞法分析拆 Token**：把字元流拆成關鍵字、識別符、運算子等有意義的單元
4. **語法分析建 AST**：按語法規則把 Token 組織成樹形結構，反映運算優先級
5. **語義分析保正確**：型別檢查、作用域檢查，你見過的大多數報錯都來自這裡
6. **編譯器自動最佳化**：常數摺疊、死程式碼消除、函式內聯等技術讓程式碼自動變快
7. **三種執行模型**：編譯型最快、直譯型最靈活、JIT 兼顧兩者

## 延伸閱讀

- [AST Explorer](https://astexplorer.net/) - 線上檢視程式碼的 AST 結構
- [Crafting Interpreters](https://craftinginterpreters.com/) - 從零實作一門程式語言（免費線上書）
- [The Super Tiny Compiler](https://github.com/jamiebuilds/the-super-tiny-compiler) - 用 JavaScript 實作的超小編譯器
- [V8 Blog](https://v8.dev/blog) - V8 引擎的 JIT 編譯技術部落格
- [LLVM 官網](https://llvm.org/) - 最流行的編譯器基礎設施
