> For the complete documentation index, see [llms.txt](https://overleaf-pro.ayaka.space/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://overleaf-pro.ayaka.space/latex/zh-tw/shen-ru-wen-zhang/22-how-does-expandafter-work-the-meaning-of-expansion.md).

# \expandafter 如何運作：展開的意義

[第 1 部分](/latex/zh-tw/shen-ru-wen-zhang/19-how-does-expandafter-work-an-introduction-to-tex-tokens.md) [第 2 部分](/latex/zh-tw/shen-ru-wen-zhang/22-how-does-expandafter-work-the-meaning-of-expansion.md) [第 3 部分](/latex/zh-tw/shen-ru-wen-zhang/21-how-does-expandafter-work-tex-uses-temporary-token-lists.md) [第 4 部分](/latex/zh-tw/shen-ru-wen-zhang/20-how-does-expandafter-work-from-basic-principles-to-exploring-tex-s-source-code.md) [第 5 部分](/latex/zh-tw/shen-ru-wen-zhang/17-how-does-expandafter-work-a-detailed-macro-case-study.md) [第 6 部分](/latex/zh-tw/shen-ru-wen-zhang/18-how-does-expandafter-work-a-detailed-study-of-consecutive-expandafter-commands.md)

## 介紹 TeX 的「展開」概念

在回顧了 [TeX 記號與記號清單](https://www.overleaf.com/learn/latex/Articles/\expandafter_TeX_tokens?preview=true)之後，接著要介紹的背景主題之一是 `\expandafter` 是 TeX 的 *展開*：TeX 的核心處理流程 `\expandafter` 所設計要配合的。

### 關於解釋展開的挑戰……

對大多數人來說，TeX 的展開概念，以及其背後的原因／機制，可能會令人困惑——它們並不容易 *完整地* 以簡單、精煉的方式解釋。TeX 的許多內部操作／流程彼此以相當複雜的方式相互關聯，這在試圖「剖析」TeX 操作的某些面向時，可能會帶來挑戰：也就是試著脫離整個程式單獨討論它們。展開當然屬於這一類概念，因為它是 TeX 行為中的一個 *基本* 元素，並且深深地嵌入於 TeX 的各項內部流程之中。然而，本文嘗試建立對展開的說明，提供讀者可以在其上繼續建構理解的基礎。

為了解釋 TeX 的展開機制，我們將循序漸進地進行，檢視一系列相關／相應主題，並將它們串連起來，以發展對這個 TeX 行為中關鍵元素的理解。本文後半部，對於喜歡細節、較為大膽的讀者，我們會一窺 TeX 內部，摘要說明驅動展開流程的關鍵原始碼。

### 關於 TeX 記號的重點

在接下來的討論中，請務必記得 TeX 可以從兩個來源取得下一個輸入項目：

1. **實體文字檔**：透過 *新 token* 讀取／掃描這些檔案中的字元與命令來產生，或
2. **token list**：從一份既有的、 *預先準備好的*、儲存在 TeX 記憶體中的記號清單讀取輸入。

另外請回想，TeX 使用記號作為一種方便的方式，將它已讀入的任何項目（字元或命令）的資訊整齊地「封裝」起來。

當 TeX 在產生 *新 token* 時， *在記號產生的當下*，它可以立即取得關於它正在計算其記號值的那個項目的資訊。例如，在產生新的命令記號時，TeX 會先計算一個稱為 `curcs` 的值，讓 TeX 能查詢即將產生其記號的命令資訊。

然而，如果 TeX 是從記號清單（由 *已儲存的記號*組成的序列）讀取，它就必須先將清單中的任何記號「解封裝」，才能判定每個記號代表什麼。例如，TeX 可能正在讀取作為巨集定義一部分而儲存的記號：那些記號原本是很久以前讀入的文字所產生的，之後被轉成記號並存放起來。記號類型（字元或命令）決定了 TeX 的「解封裝」究竟代表什麼：

* 的 *命令 token* （記號值 > 4095）：計算 `curcs` 經由 $$\text{curcs}=\text{token value}-4095$$ 並使用 `curcs` 來查詢命令資訊；
* 的 *字元記號* （記號值 < 4095）：將字元記號拆解為其組成的（字元代碼、類別代碼）配對。

### 一開始……

為了開始探索展開，我們先看看 TeX 引擎如何對它們能處理的命令集合進行分類或歸類。暫且假設「可展開」只是 TeX 命令的一種、尚未解釋的「屬性」。

### 「可展開」命令的概念

所有以 TeX 為基礎的排版程式（TeX「引擎」）都理解兩種主要的命令「類別」：

* 內建於可執行引擎本身的命令：所謂的 *原始命令*;
* 由使用者定義的命令：所謂的 *巨集*.

TeX 引擎也使用「是否可展開」這種命令「行為」，作為將所有命令分類為兩組的機制：

* 可展開命令；
* 不可展開命令。

因此，我們可以將所有命令（原始命令加上巨集）的集合分類如下圖所示：

![顯示所有 TeX 命令分類的圖示](/files/c8ab73a3acc2b04881b2dbde6e961fef1e8ad297)

雖然我們尚不知道展開／可展開實際上是什麼意思，但仍可看出：

* 所有巨集都被歸類為可展開命令；
* 有些原始（內建）命令也被歸類為可展開（但絕大多數不是）。

**關於活動字元的註記**：除了原始命令與巨集之外，還有第三類「可展開」項目：活動字元——任何被指定為類別代碼 13 的字元也被視為「可展開」，因為它們會被當作「迷你巨集」來處理。

#### TeX 如何知道某個命令是否「可展開」？

從我們對 [TeX 記號的討論](https://www.overleaf.com/learn/latex/Articles/\expandafter_TeX_tokens?preview=true) 中，我們知道記號不過是 TeX 計算並用來「封裝」輸入項目資訊的整數：也就是字元或命令。我們也注意到，對於任何記號（整數）值，TeX 都可以在需要時反向執行記號化過程，將記號「解封裝」，並揭示它所代表的命令或字元資料。

利用 TeX 產生新記號時所產生的資料，或從既有記號中擷取的資料——例如儲存在巨集或其他記號清單中的記號——TeX 可以查閱其內部資料儲存表，找出該記號所代表之命令（或字元）的詳細資訊。下圖總結了建立命令記號（「封裝」），以及當 TeX 需要某些資訊時，反轉此流程（「解封裝」記號）以取得特定記號值所代表命令之資料的過程。

![顯示 TeX 如何封裝／解封裝記號的圖示](/files/5e521592bb588157ac1352463ecd5b28afbd8512)

### 命令代碼

TeX 儲存關於每個命令（原始命令或巨集）的資訊中，有一部分稱為其 *命令代碼*：這是一個整數值，TeX 引擎用它（在內部）來分類命令——具有相似行為／功能的命令會共享相同的命令代碼。

**注意**：對於單一的 *非活性*字元， *類別碼* 其命令代碼就是它在被 TeX 讀入（掃描）時所被指派的

以下是一些命令代碼的例子：

* `\hbox`, `\vbox`, `\vtop`, `\vcenter`, `\box` （以及其他）都被歸類為「製作盒子」的命令，並共享命令代碼 20；
* `\def`, `\edef`, `\gdef`, `\xdef` 都屬於「巨集定義」命令，並共享命令代碼 97；
* 所有巨集都會被歸入命令代碼 111、112、113 或 114 其中之一：不同的命令代碼取決於在定義巨集時是否使用了 `\long` 或 `\outer` ：
* 111：非 long、非 outer 巨集的命令代碼（例如， `\def\foo{...}`)
* 112：long、非 outer 巨集的命令代碼（例如， `\long\def\foo{...}`)
* 113：非 long、outer 巨集的命令代碼（例如， `\outer\def\foo{...}`)
* 114：long、outer 巨集的命令代碼（例如， `\long\outer\def\foo{...}`)

TeX 也使用命令代碼來識別被歸類為「可展開」的命令子集。為此，TeX 會將可展開命令與超過 100 這個閾值的命令代碼值相連結（這是 Knuth 原始 TeX、e-TeX 和 pdfTeX 使用的值）。因此：

* *不可展開的* 命令（絕大多數）具有 <= 100 的命令代碼
* *可展開* 命令具有 > 100 的命令代碼

因此，如果 TeX 查詢某個特定命令的詳細資訊並偵測到其命令代碼 > 100，TeX 立刻就知道它是可展開的。所有巨集，以及少數原始（內建）命令，都會被指派一個表示它們可展開的命令代碼：但 TeX 為什麼要費心做這種「可展開」分類呢？如你所料，這單純表示那些命令有些「特別」；也就是說，它們可以接受 TeX 的 *展開*.

**註記（字元）**：除非某個字元被定義為活動字元（類別代碼 13），否則所有字元都不可展開。活動字元可以被視為單字母的「迷你巨集」。

## 是，是，但展開到底是什麼？？？

我們已注意到，大於 4095 的記號值用來表示命令；因此，在需要時，TeX 可以將該記號「解封裝」，以存取該記號所代表的 TeX 命令資訊。TeX 可以查詢該命令的命令代碼值，以判定它是否屬於可展開這個特殊類別。但 TeX 會如何使用這些資訊，而「可展開」，也就是展開， *到底真正表示什麼*?

### 展開：對一組操作的通稱

在 TeX 的處理過程中某些時點，它需要知道某個記號是否代表可展開命令：

* **否**：如果該命令記號 *不會* 不屬於可展開類別，TeX  պարզապես（直接）將它傳到下一個處理階段。
* **是**：如果該命令記號 *確實* 若屬於可展開類別，你可以把 TeX 視為必須採取某種動作，藉由執行 TeX 稱為「展開該記號」的過程，或執行展開，來將該記號「過濾掉」。

本質上，展開包含 TeX *將* 將該記號從輸入中移除，並 *以* 以來自與該命令或共享相同命令代碼之命令群組的「展開過程」所產生的記號取代它。

要知道 TeX 何時會、何時不會進行記號展開，需要一些練習與經驗——The TeXbook 第 215 頁列出了可展開記號不會被展開的情況。事實上，TeX 進行展開的過程相當複雜且細膩。

#### 哪些命令是可展開的？

如果你查看 TeX 引擎歸類為可展開的一份原始（內建）命令清單（例如 The TeXbook 第 212–215 頁），就會明白，實際上展開是一個相當一般性的術語，TeX 用它來描述一組相當雜多命令的行為。某個特定（可展開）命令經過展開後的結果確實各不相同，我們可以透過列出一組按功能分組的可展開命令來看出這一點。個別命令的精確語意在此並不重要，最值得關注的是其動作的一般後果——它們的展開實際上 *做了什麼或達成了什麼*.

| **指令中可用的括號**                                                                                                                                                                                                                                                                                                                                                                                                                                                               | **說明**                                                                  | **展開行為**                                                                                                                                                                                                          |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <ul><li>所有巨集</li></ul>                                                                                                                                                                                                                                                                                                                                                                                                                                                     | 儲存的一串命令／字元記號                                                            | <p>實際上，巨集展開就是插入一份記號清單供 TeX 讀取。這包含：</p><ul><li>為任何巨集參數建立記號清單——該過程包括識別／吸收分隔記號；</li><li>取回一份已儲存的記號清單（巨集定義），並準備將第一步驟所建立的參數記號插入（至巨集定義中）。</li></ul><p>實際上，巨集展開 <em>會移除巨集記號</em> 從輸入中移除，並 <em>並以</em> 定義中所包含的記號取而代之。</p> |
| <ul><li><code>\expandafter</code></li></ul>                                                                                                                                                                                                                                                                                                                                                                                                                                | 改變展開的順序                                                                 | 記號過濾／洗牌                                                                                                                                                                                                           |
| <ul><li><code>\noexpand</code></li></ul>                                                                                                                                                                                                                                                                                                                                                                                                                                   | 防止展開（是的，令人驚訝的是它被歸類為可展開命令）                                               | 延後展開（某種程度上的記號過濾）                                                                                                                                                                                                  |
| <ul><li><code>\input</code>, <code>\endinput</code>, <code>\scantokens</code></li></ul>                                                                                                                                                                                                                                                                                                                                                                                    | 與透過原始檔輸入新的記號來源有關——是的， `\scantokens` 因為它使用了所謂的「偽檔案」而被歸入此類                | 作為新記號的來源                                                                                                                                                                                                          |
| <ul><li><code>\if</code>, <code>\ifcat</code>, <code>\ifnum</code>, <code>\ifdim</code>, <code>\ifodd</code>, <code>\ifvmode</code>, <code>\ifhmode</code>, <code>\ifmmode</code>, <code>\ifinner</code>, <code>\ifvoid</code>, <code>\ifhbox</code>, <code>\ifvbox</code>, <code>\ifx</code>, <code>\ifeof</code>, <code>\iftrue</code>, <code>\iffalse</code>, <code>\ifcase</code>, <code>\ifdefined</code>, <code>\ifcsname</code>, <code>\iffontchar</code></li></ul> | 開始條件式表達式                                                                | 開始記號「過濾」：決定接下來要處理哪些記號。                                                                                                                                                                                            |
| <ul><li><code>\fi</code>, <code>\or</code>, <code>\else</code></li></ul>                                                                                                                                                                                                                                                                                                                                                                                                   | 結束條件式表達式                                                                | 停止記號「過濾」：決定要處理哪些記號                                                                                                                                                                                                |
| <ul><li><code>\csname</code>… <code>\endcsname</code></li></ul>                                                                                                                                                                                                                                                                                                                                                                                                            | 建構／產生命令記號                                                               | 將一串記號轉換為單一命令記號                                                                                                                                                                                                    |
| <ul><li><code>\number</code>, <code>\romannumeral</code>, <code>\string</code>, <code>\meaning</code>, <code>\fontname</code>, <code>\jobname</code>, <code>\eTeXrevision</code></li></ul>                                                                                                                                                                                                                                                                                 | 將某些內容轉換為一串記號                                                            | 產生一些記號（代表內部的數量或值）                                                                                                                                                                                                 |
| <ul><li><code>\the</code>, <code>\unexpanded</code>, <code>\detokenize</code></li></ul>                                                                                                                                                                                                                                                                                                                                                                                    | `\the` 有多種用途，包括插入記號； `\detokenize` 將記號清單轉換為字元記號清單； `\unexpanded` 防止記號展開 | 插入／產生一些記號或控制展開                                                                                                                                                                                                    |
| <ul><li><code>\topmark</code>, <code>\firstmark</code>, <code>\botmark</code>, <code>\splitfirstmark</code>, <code>\splitbotmark</code>, <code>\topmarks</code>, <code>\firstmarks</code>, <code>\botmarks</code>, <code>\splitfirstmarks</code>, <code>\splitbotmarks</code></li></ul>                                                                                                                                                                                    | 用於存取具有特殊用途的記號清單的命令                                                      | 存取一個已儲存記號的來源（記號清單）                                                                                                                                                                                                |

根據上面的清單，有沒有可能從這組命令中萃取出任何「一般行為」——以進一步精煉我們對展開的理解？大致來說，展開一個命令的結果可歸納為幾個關鍵活動：

* 記號產生
* 記號插入
* 記號過濾
* 改變／控制下一個記號的展開

### 最後：展開的意義？

我們可以將命令記號（包括活動字元）的展開視為 TeX **將** 將該命令記號從輸入中移除，並 **以** 以來自其展開行為的其他記號取代該記號：產生／插入記號、過濾記號，或改變／控制下一個記號的展開。當一個記號完成展開後，TeX 會繼續讀取／處理由展開過程所產生的任何記號。TeX 究竟何時或何處會進行展開，是另一個截然不同的問題：由於 TeX 處理的細微差異與複雜性，我們無法指望在本文中完整討論，但可以概述 TeX 主要的輸入處理行為。

## 展開與 TeX 的結構

可以說，要看出 TeX 在何處／如何區分可展開與不可展開命令，最好的方法就是檢視 TeX 的原始碼。

在內部，TeX 是一個極其複雜的軟體；因此，要完整描述 TeX 運作中固有的眾多細膩行為並不實際。如前所述，TeX 在讀取記號時究竟何時或何處真正執行展開，「頗為複雜」；因此，下列討論假設 TeX 處於需要進行展開的狀態。TeX 會讀取／建立記號但不執行展開的情況，包括為 `\def` 巨集定義命令建立／儲存記號，或將記號儲存在 `\toks` 暫存器中。

下圖提供了一個簡化概觀，說明 TeX 中處理輸入與記號的核心部分：目標是凸顯在 TeX 輸入處理期間，可展開與不可展開記號是如何被「過濾」的。這裡使用 C 程式語言來描述各個關鍵函式，但希望即使你不熟悉 C 語言，基本結構也能清楚理解。

![](/files/5e646b60f86ed1fe0a3933528d3994d8529d101d)

### TeX 結構圖解說明

概略來說，TeX 的處理由一個名為 `maincontrol()` 的函式所驅動，在 TeX 的原始碼中，Knuth 將其稱為「首席執行者」，並如下描述：

> 「我們現在來到 `maincontrol` 常式，它包含那個主開關，讓 TeX 的各個部分按照正確的順序各自做它們的事……這是程式的最高潮……我們現在位於網的樞紐，位於那個觸及大多數其他部分並將它們連結起來的中樞神經系統。」

以下的描述雖然有些簡化，但可讓人感受到以 TeX 為基礎的軟體之底層結構：可展開命令如何在 TeX 的輸入處理過程中被識別（「攔截」）並過濾掉。

本質上，在 TeX 最早期的輸入處理階段，它會使用目前命令（記號）的命令代碼值來偵測可展開命令（巨集、可展開原始命令與活動字元）；若識別出其中之一，這些命令就會被「攔截」並「預處理」——也就是被展開。透過這個過濾（展開）過程，TeX 會將可展開命令從輸入中移除，並以來自其各自展開行為所產生的記號取代。當展開過程完成後，TeX 會讀取由該展開所產生的任何記號。只有不可展開的項目會被傳到 TeX 處理的下一階段：那個「大開關」，TeX 會在那裡執行所有不可展開的原始命令並排版字元。

### 理解 maincontrol()

該 `maincontrol()` 函式包含一個大型的所謂 [switch 敘述](https://en.wikipedia.org/wiki/Switch_statement)——Knuth 稱之為「大開關」——TeX 便用它來處理所有 *不可展開的* 原始命令：那些命令代碼 <=100 的命令。

為了執行某個特定命令（或字元），這個「大開關」敘述會結合 TeX 的目前模式：

* 內部／外部垂直模式，或
* 受限／段落水平模式，或
* 行間／顯示數學模式

*加上* 以及每個原始命令或字元的命令代碼值—— *非活性* 字元的命令代碼就是其 *類別碼* 值。順帶一提，TeX 的 6 種模式都會被分配一個整數值，TeX 可利用它在切換排版各種結構——數學、段落、盒子等等——時儲存並還原目前模式。以下是 TeX 各種模式所對應的整數：

|          |                    |         |
| -------- | ------------------ | ------- |
| **模式**   | **意義**             | **模式值** |
| 外部垂直     | 段落之間（建頁時）          | 1       |
| 內部垂直     | 緊接在一個 `\vbox{...}` | -1      |
| 水平       | 在排版／建立段落時          | 102     |
| 受限水平     | 緊接在一個 `\hbox{...}` | -102    |
| 顯示數學     | 排版 *顯示* 方程式／公式     | 203     |
| 行內（公式）數學 | 排版 *行內* 方程式／公式     | -203    |

請注意，像內部與外部垂直模式這類各模式的補集會使用負值。當處理位於 `maincontrol()`中的命令時，TeX 會使用目前模式的絕對值——也就是忽略負號。因此，例如，如果 TeX 處於兩種水平模式之一，且需要處理一個類別代碼 11 的字元，它會在其「大開關」中使用其總和 $$102 + 11 = 113$$ 來分支到處理這些字元的程式碼，無論是在任一種水平模式中（建立段落或在一個 `\hbox`).

#### 取得下一個記號

該 `maincontrol()` 函式會呼叫 `getxtoken()` 來取得下一個輸入記號；接著， `getxtoken()` 會呼叫最低層級的輸入函式 `getnext()`。在 `getnext()` 完成工作並設定關鍵 *全域變數*, `getxtoken()` 之後，它會測試剛讀入的項目是否代表一個巨集或可展開原始命令。

關於 `getnext()` 和 `getxtoken()`:

* **`getnext()`**&#x7684;一些註記：這是 TeX 的核心輸入函式。它會讀取並處理從實體檔案輸入的字元，或從記號清單讀取記號。請注意， `getnext()` 實際上並不會計算記號值，但它確實會設定若干關鍵的 *全域* 變數（例如命令代碼和 `curcs`），這些變數之後會在處理中使用。
* **`getxtoken()`**：「get expanded token」使用 `getnext()` 來讀取下一個輸入項目。接著它會測試剛讀入項目的命令代碼，以檢查該項目是否代表可展開命令：若是，便將其展開或呼叫該巨集。對於不可展開的項目， `getxtoken()` 會計算出一個記號值，而這些細節會被傳到下一個處理階段：那個「大開關」，在那裡不可展開的原始命令會被執行，字元則會被排版。

本質上，正是透過 `getxtoken()` ，所有可展開項目在進入 `maincontrol()`之前就被「過濾掉」：這使得 TeX 的「大開關」只會處理不可展開的原始命令。

我們再次強調，上面的圖只是簡化版，因為 TeX 的其他處理區域也會導致記號被展開；例如：

* 當 TeX 正在尋找（掃描）數值時，它會在搜尋整數的過程中觸發展開（藉由呼叫 `getxtoken()`) ;
* 原始命令 `\romannumeral` 會造成展開——請參見文章 [使用 `\romannumeral`](https://www.texdev.net/2011/07/05/expansion-using-romannumeral/) 中的細緻用法；
* 該 `\edef` （「展開定義」）巨集定義命令會強制完全展開；
* 當 TeX 處理一個 `\halign` 或 `\valign` 的導言時，會在某些時點展開記號（這兩個命令都與 TeX 的表格排版演算法有關）。

[第 1 部分](/latex/zh-tw/shen-ru-wen-zhang/19-how-does-expandafter-work-an-introduction-to-tex-tokens.md) [第 2 部分](/latex/zh-tw/shen-ru-wen-zhang/22-how-does-expandafter-work-the-meaning-of-expansion.md) [第 3 部分](/latex/zh-tw/shen-ru-wen-zhang/21-how-does-expandafter-work-tex-uses-temporary-token-lists.md) [第 4 部分](/latex/zh-tw/shen-ru-wen-zhang/20-how-does-expandafter-work-from-basic-principles-to-exploring-tex-s-source-code.md) [第 5 部分](/latex/zh-tw/shen-ru-wen-zhang/17-how-does-expandafter-work-a-detailed-macro-case-study.md) [第 6 部分](/latex/zh-tw/shen-ru-wen-zhang/18-how-does-expandafter-work-a-detailed-study-of-consecutive-expandafter-commands.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://overleaf-pro.ayaka.space/latex/zh-tw/shen-ru-wen-zhang/22-how-does-expandafter-work-the-meaning-of-expansion.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
