> 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/17-how-does-expandafter-work-a-detailed-macro-case-study.md).

# \expandafter 如何運作：詳細的巨集案例研究

&#x20;[第 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)&#x20;

## 案例研究：\expandafter 來自 The 的範例 $$\varepsilon\mathrm{\text{-}{\TeX}}$$ 手冊

該 $$\varepsilon\mathrm{\text{-}{\TeX}}$$ 排版引擎源自 Knuth 的 TeX 軟體，最初是作為通往 [新排版系統](https://en.wikipedia.org/wiki/New_Typesetting_System) （NTS），以 Java 程式語言撰寫的發展過程中的一個「過渡」步驟。 $$\varepsilon\mathrm{\text{-}{\TeX}}$$ 最早於 1990 年代末期開發，目的是加入一套新的原始命令，提供 Knuth 原始程式中沒有的額外功能。雖然 $$\varepsilon\mathrm{\text{-}{\TeX}}$$ 自初次發布以來曾定期更新，但如今它並未廣泛作為獨立排版引擎使用，儘管其創新已被後來的 TeX 世代所吸收：pdfTeX、XeTeX 和 LuaTeX。

該 [$$\varepsilon\mathrm{\text{-}{\TeX}}$$ manual](http://mirror.ox.ac.uk/sites/ctan.org/systems/doc/etex/etex_man.pdf) 包含一個啟發性的範例，展示一個巧妙運用 `\expandafter`:

```
    \def\foo#1#2{\number#1
    \ifnum#1<#2,
    \expandafter\foo
    \expandafter{\number\numexpr#1+1\expandafter}%
    \expandafter{\number#2\expandafter}%
    \fi}
```

`\foo` 的循環機制，像是 `\foo{7}{13}` 會產生 `7, 8, 9, 10, 11, 12, 13`；然而， `\foo` 並未使用任何 *對變數的賦值* 來控制循環過程——這使它成為一個值得深入探討的有趣巨集。

### 一些背景：運算式與賦值

的一個重要元素是 `\foo`的程式碼中使用了命令 `\numexpr`，這是一組四個相關原始命令之一，最早由 $$\varepsilon\mathrm{\text{-}{\TeX}}$$: `\numexpr`, `\dimexpr`, `\glueexpr` 以及 `\muexpr`引入。它們的用途是建立所謂的 *運算式* ，可用來計算／操作 TeX 的 number、dimen、glue 或 muglue 型別值（分別對應）。如《The $$\varepsilon\mathrm{\text{-}{\TeX}}$$ 手冊》第 8–9 頁所討論的， *運算式* 的一個重要特性是，其求值（計算）不需要 TeX 執行任何 *賦值*.

在程式設計術語中，賦值是將一個變數設定（指定）為特定值的過程；例如，將 `\count` 暫存器 `99` 設定為包含值 `12345` 透過 `\count99=12345`。在 TeX 處理過程中還會發生許多其他類型的賦值——例如將 token 暫存器設定為包含一串 token、將 box 暫存器設定為包含方框內容，等等。

要執行一個賦值，例如 `\count99=12345`，TeX 需要啟動（執行）實作 `\count` 行為的內部程式碼，或任何其他會執行某種賦值的原始命令。然而，有時 TeX 正在進行純粹的 *展開* ，而在那些時候，這類賦值並不會被執行——*在 TeX 的處理過程中的那個階段*。這種情況的例子包括以下命令：

* `\edef\command {*token list*}` 「展開定義」巨集定義命令，它會展開 *token list* 中的 token，並將結果儲存為 `\command`.
* `\write *number* {*token list*}` 展開 `*token list*` 中的 token，並將它們寫入由 `*number*`.
* `\directlua {*token list*}` 這個 LuaTeX 原始命令用來將 Lua 程式碼傳送給內建的 Lua 直譯器。 `*token list*` 中的所有 token 都會在傳送給 Lua 直譯器執行之前完全展開。

#### \edef 的快速範例

如果我們寫下以下基本巨集：

```
     \def\mycount{\count99=12345}
     \edef\mymacro{\mycount}
```

`\edef` 會展開 `\mycount` 為其構成 token，但不會再往下進行： `\mymacro` 定義中所包含的任何命令都不會被執行：也就是說， `12345` 到 `\count99` *的賦值此時並不會發生*；只有在我們呼叫 `\mymacro` 時，TeX 執行處理 `\count` 原始命令的程式碼時，這個賦值才會發生。當 TeX 正在進行 *僅展開的操作* 時，任何賦值都會延後到 TeX 處理的後續階段才執行，而不是在展開過程本身中發生。

#### 為什麼這裡會關注賦值？

在任何程式語言中撰寫程式來執行迴圈時，通常會指定一個變數作為「迴圈計數器」：用來控制迴圈執行的次數。迴圈通常是透過測試該指定的迴圈計數器變數是否已達到某個特定值來控制——每次迭代時，該變數都會遞增（或遞減）。然而，修改迴圈計數器變數就意味著要為它賦予新值，而對 TeX 來說，這通常需要原始命令 `\advance` 來遞增（或遞減）儲存在 `\count` 暫存器中的值。正如我們所見，在 TeX 的純展開過程中，這類賦值（包括遞增變數）無法發生：巨集 `\foo` 巧妙地繞過了這個限制。

### 回到解釋 \foo

這個巨集 `\foo` 能夠控制迴圈過程 *而不* 需要對任何變數賦值：它是利用來自展開的資料——儲存在暫時 token list 中的資料值——來控制迴圈發生的次數。利用我們對 TeX 使用（建立）暫時 token list 的認識，我們可以更仔細地看看 `\foo` 究竟如何達成其結果。

**記住**：我們正在逐步追蹤一個巨集的執行；其定義的原始文字——位於實體 `.tex` 檔案中——已被掃描（由 TeX 讀入）並轉換為代表巨集定義的 token list。基本上，我們是在追蹤 TeX 對那些儲存的 *詞元* 的處理，同時它正在讀取並處理存放在 TeX 記憶體某處的巨集定義中的 token。巨集定義的 TeX 程式碼中原本存在的任何空白字元（ `.tex` 檔案中的文字）都會在 TeX 掃描該文字尋找命令時被吸收（空白作為終止符），或者它們會被轉換為 token，例如逗號後面的空白字元（`,`）在 `\ifnum#1<#2,` 中，其來源是行尾字元（`\r`）被轉換成空白。

因為 `\foo` 中的 TeX 程式碼使用了多個 `\expandafter` 命令，我們會在每個 `\expandafter`上加上下標，以指出我們正在指的是哪一個。此外，我們會擴充 `\expandafter` 到 $$\mathrm{T^i\_1}$$ 以及 $$\mathrm{T^i\_2}$$所處理 token 的記號，表示 token $$\mathrm{T\_1}$$ 以及 $$\mathrm{T\_2}$$ 用於 `\expandafter<sub>i</sub>`: `\expandafter<sub>i</sub>` $$\mathrm{T^i\_1T^i\_2}$$

以下是加註說明的巨集程式碼：

```
    \def\foo#1#2{\number#1
    \ifnum#1<#2,
    \expandafter1\foo
    \expandafter2{\number\numexpr#1+1\expandafter3}%
    \expandafter4{\number#2\expandafter5}%
    \fi}
```

`\foo` 以 `\number#1` 開頭，它使用可展開命令 `\number` 將第一個引數值轉換為其排版表示。 `\number` 命令的運作方式是產生一個暫時 token list，其中包含代表該數值中各個數字的字元 token，而 `\number` 正作用於其上。那個 token list 會成為 TeX 的下一個輸入來源。在這裡，該 token list 會被讀入，然後輸出這些 token 來排版 `#1`.

接著，巨集進行測試 `\ifnum#1<#2` 以檢查 `#1` 的引數是否小於傳入給 `#2`的引數。若是如此，逗號（`,`）token 會被輸出（排版），後面接著一些空白，這些空白來自逗號後換行字元（`,`）所產生的 token。那個空白字元最初是在 TeX 讀取這一行時從 `.tex` 檔案中。

產生的。巨集接著處理下一段程式碼，這是其運作的核心：

```
    \expandafter1\foo
    \expandafter2{\number\numexpr#1+1\expandafter3}%
    \expandafter4{\number#2\expandafter5}%
    \fi}
```

從本質上說，這段程式碼會產生一系列暫時 token list，進而導致多次呼叫 `\foo` 巨集，並在 if-test `\ifnum#1<#2` 不再為真時終止。但是 *如何* 的迴圈控制是如何實現的，因為沒有發生賦值：那麼「迴圈計數器」在哪裡？

讓我們先看看程式碼 `\expandafter<sub>1</sub>\foo\expandafter<sub>2</sub>`。請注意，我們會使用下標記號 `<sub>token</sub>` （或 `<sub>(token)</sub>`）來提醒自己，在這裡 TeX 正在讀取／處理數值（整數）token 值。

在這裡，我們有以下 tokens 作為 `\\expandafter<sub>1</sub>`:

* $$\mathrm{T^1\_1} =$$`\foo<sub>token</sub>` 的輸入，它會被讀入並儲存以供稍後 *重新插入* 回到輸入中
* $$\mathrm{T^1\_2} =$$`\\expandafter<sub>2 (token)</sub>` 它被展開

對於 `\\expandafter<sub>2</sub>` 我們得到：

* $$\mathrm{T^2\_1} =$$`{<sub>token</sub>` 它會被保存以供稍後 *重新插入* 回到輸入中
* $$\mathrm{T^2\_2} =$$ `\number<sub>token</sub>` 它被展開

**注意：**`\number` 是一個可展開命令，其用途是「轉換為 token」：也就是將數值轉換為一串代表該數值的字元 token。當 `\number` 被展開時，TeX 做的第一件事是掃描輸入以尋找整數：這個過程會觸發進一步的展開。

**故事的關鍵在於：** 在這裡， `\number` 正在作用於 *運算式* `\numexpr#1+1` ，它計算 `#1+1`的值。該計算的結果會由 `\number` 處理，將其轉換為一個包含代表 `#1 + 1`之值的字元 token 的暫時 token list。那個由 `\number`產生的暫時 token list，最終會被讀入作為另一個 `\foo`呼叫的第一個引數。與其透過 `\advance` 和賦值來遞增迴圈計數器，不如使用 `\numexpr` 建立一個新值，但不需要賦值。透過這個機制，控制迴圈的變數（`\foo`的參數 `#1`）會遞增，並且迴圈的反覆執行被控制並最終終止：相當巧妙！

接著， `\\expandafter<sub>3</sub>` 被處理，得到：

* $$\mathrm{T^3\_1} =$$`}<sub>token</sub>` 它會被保存以供稍後 *重新插入* 回到輸入中
* $$\mathrm{T^3\_2} =$$`\expandafter<sub>4 (token)</sub>`，其被展開：

對於 `\expandafter<sub>4</sub>` 我們得到：

* $$\mathrm{T^4\_1} =$$`{<sub>token</sub>` 它會被保存以供稍後 *重新插入* 回到輸入中
* $$\mathrm{T^4\_2} =$$`\number<sub>token</sub>` 它會被展開並將 `#2` 轉換成另一個暫時 token list。

最後，`\expandafter<sub>5</sub>` 被展開：

* $$\mathrm{T^5\_1} =$$`}<sub>token</sub>` 它會被保存以供稍後 *重新插入* 回到輸入中
* $$\mathrm{T^5\_2} =$$`\fi<sub>token</sub>`，這是一個可展開命令。

  對 `\fi` 的展開實際上終止了 `敘述來檢查我們的計數器值（` ，並且實際上結束了這個巨集的這一輪迭代。TeX 現在完成重新插入由多個 `\expandafter` 命令暫時保存的所有 token：這會產生一系列單一 token 的 token list，來源是每個 `\expandafter`所保存的 token。另外，TeX 也已透過 `\number`.

### 組合 token list

從本質上說， `\foo` 巨集會產生一連串 token list：你可以把 `\foo` 想成一個 token-list 的「製造工廠」。這些 token list 會被 TeX 讀取，成為下一批輸入來源。巧妙之處在於 `\foo`:

```
    \expandafter1\foo\expandafter2
```

中較早的動作之一， `\foo` 透過它安排再次呼叫自身，但使用由 `\number`建構的 token list 中所儲存的不同引數。為了讓這些 token list 一起表現得像一次巨集呼叫，花括號 `{` 以及 `}` 都已被儲存，並由 `\expandafter` 命令的動作重新插入到輸入中（作為單一 token 的 list）。

![由 \foo 巨集產生的 token list](/files/63d9ddc89976098d08eb1362871a71c02bb19dde)

&#x20;[第 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/17-how-does-expandafter-work-a-detailed-macro-case-study.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.
