> 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/geng-duo-zhu-ti/22-how-tex-macros-actually-work-part-4.md).

# TeX 巨集實際上如何運作：第 4 部分

[第 1 部分](/latex/zh-tw/geng-duo-zhu-ti/19-how-tex-macros-actually-work-part-1.md) [第 2 部分](/latex/zh-tw/geng-duo-zhu-ti/20-how-tex-macros-actually-work-part-2.md) [第 3 部分](/latex/zh-tw/geng-duo-zhu-ti/21-how-tex-macros-actually-work-part-3.md) [第 4 部分](/latex/zh-tw/geng-duo-zhu-ti/22-how-tex-macros-actually-work-part-4.md) [第 5 部分](/latex/zh-tw/geng-duo-zhu-ti/23-how-tex-macros-actually-work-part-5.md) [第 6 部分](/latex/zh-tw/geng-duo-zhu-ti/24-how-tex-macros-actually-work-part-6.md)

## 介紹與概覽

在第 1–3 部分中，我們深入探討了一些底層背景細節，為理解 TeX 巨集如何運作做準備。本文中，我們要先「喘口氣」，回顧一些 TeX 巨集的基本實務原則，以便為第 5 與第 6 部分的另一輪深入探討做好準備。整篇文章中，我們將展示以 TeX 原始指令定義的範例巨集 `\def`：我們不會使用那個也許更熟悉的 LaTeX 指令 `\newcommand`。這麼做有很好的理由：我們的目標是理解 TeX 巨集行為背後的基本原理，而要做到這一點，我們需要使用核心指令 *內建於* TeX 軟體中。像 `\newcommand`這類 LaTeX 指令本身就是巨集：具有特定程式化行為的指令，而這些行為最終是由多層較低階的 TeX 原始指令構成。要更好地理解 TeX 的基本行為，我們必須使用 TeX 原始指令，而不是 LaTeX 巨集。

### 我們要往哪裡去？

本質上，我們正朝著一個對巨集的解釋前進：把它視為一種特殊形式的詞元清單。當你指示 TeX 定義一個巨集時，它會建立一串詞元（詞元清單），並將它們儲存在記憶體中，且連結到你所定義的名稱。在第 5 與第 6 部分中，我們會較詳細地看巨集詞元清單；但如果你想先跳離本文去閱讀一些背景資訊，可以在以下內容中找到 [什麼是 TeX 詞元清單？](/latex/zh-tw/shen-ru-wen-zhang/54-what-is-a-tex-token-list.md)

用於巨集及其參數的詞元清單，還包含幾個額外的變化，我們會詳細說明——並使用大量範例。要更好地理解 TeX 的巨集處理行為，真正需要記住的一個關鍵因素是：TeX 在輸入處理的最早階段只會思考字元；從那之後一路都是詞元！在這篇以及本系列剩餘的文章中，我們將探討詞元在 TeX 巨集裡所扮演的角色。

## 巨集的四個部分

### 提醒：巨集參數與巨集引數

在開始之前，值得先提醒自己巨集的 *參數* 與巨集的 *引數*之間的差異，因為本文會一直使用這兩個詞。假設你定義了一個巨集 \foo

```
\def\foo#1#2{這是 #1，這是 #2}
```

結構（詞元） `#1`, `#2` （直到 `#9`）稱為巨集的 *參數*：可以把它們想成在你呼叫巨集時要使用的實際資料的「佔位符」：

```
\foo{alpha}{beta}
```

在這裡， `alpha` 以及 `beta` 是 *引數* 在這次 \foo 呼叫中所使用的引數：巨集的引數是實際填入參數佔位符（`#1`, `#2`... `#9`）中的真實值，而這些佔位符是在你定義巨集時使用的。

### 巨集定義：有 4 個元素

我們先從一個或許看起來有點正式的巨集定義開始，但它確實為後續討論提供了有用的框架。

任何巨集定義都由 4 個部分組成：

```
<TeX macro primitive><macro name><parameter text>{<replacement text>}
```

其中：

* `<TeX macro primitive>`：下列之一 `\def`, `\edef`, `\gdef` 或 `\xdef`;
* `<macro name>`：你給巨集指令取的名稱，例如 `\foo`;
* `<parameter text>`：這可以不存在，但如果有，它就是一串 *詞元* ，出現在巨集的 `<replacement text>` 之前。 `<parameter text>` 可以包含巨集參數（`#1`, `#2`... `#9`）以及其他詞元類型。本質上，正如我們將詳細看到的， `<parameter text>` 提供了一種 *詞元範本* ，TeX 會用它來判定使用者想把哪些詞元當作巨集的引數：當你呼叫一個巨集時，TeX 會把 *引數* 詞元送入你巨集的 `<replacement text>` （它本身也是一個詞元清單）；
* `{<replacement text>}`：這就是你巨集的實際主體：它是一串詞元，而 *引數* 會在巨集被處理（展開）時被「注入」其中。引數會被送入原始定義中由巨集參數所指示的位置。

**注意**：在整個討論中，我們假設 `<macro name>` 後面會接著一個類別碼 10 的空格字元，作為終止 `<macro name>`的分隔符。 *我們並沒有* 在文字／討論中明確顯示那個空格字元，但我們假設它存在。嚴格來說，我們應該把它表示成這樣：

```
<TeX macro primitive><macro name><space><parameter text>{<replacement text>}
```

然而，我們會省略明確寫出 `<space>` 字元，並隱含假設它的存在。

#### 關於 {} 的註解

我們已展示使用兩個大括號： `{` 以及 `}` 它們包住（界定）你的巨集實際主體 `<replacement text>`。然而，使用 `{` 以及 `}` 只是一種採用的慣例，因為 TeX 真正期待的是你的巨集文字 `<replacement text>` 以類別碼 1（「開始一個群組」）的字元開頭，並以類別碼 2（「結束一個群組」）的字元結束。按照慣例，這些分別就是 `{` 以及 `}` 大括號字元。你當然也可以自行指定任意一對字元來做這件事。例如

```
\catcode`\(=1
\catcode`\)=2
```

現在，你可以像這樣定義並使用巨集：

```
\def\foo #1(Hello, #1)
\foo(World!)
```

而且你仍然可以使用 `{` 以及 `}` ，因為我們沒有改變它們的類別碼——多個字元可以，而且確實會，有相同的類別碼，因此你仍然可以照常定義巨集：

```
\def\foo #1{Hello, #1}
\foo{World!}
```

### 以及 {}

在巨集定義中與我們討論最相關的組成部分是 `<parameter text>` 以及 `{<replacement text>}`。當你定義一個巨集時， `<parameter text>` 實際上就是一個嚴格的詞元範本，它定義了巨集應該如何被使用。如前所述， `<parameter text>` 可以是空的，例如 `\def\foo{Some text}` 其中在我們的指令名稱（`\foo`）與開括號之間沒有任何內容 `{` ，而開括號在這裡標示巨集的開始 `<replacement text>`.

**注意**：有些讀者可能知道 TeX 的「hashquote」機制（`#{`），但我們在此不會涉及。

### 以及巨集分隔符

這篇文章並不是要對撰寫巨集做一個非常全面的回顧，但值得簡短複習一下，並透過一些範例說明，讓你看到 `<parameter text>` 可能會變得很複雜，因為 TeX 允許 `<parameter text>` 包含：

* **巨集參數**: (`#1`, `#2`,... `#9`）它們充當使用者在執行巨集時所提供值的「佔位符」——巨集的 *引數*;
* **分隔符詞元**：任意詞元，夾在參數詞元之間或周圍，用來指定巨集參數之間的邊界。你可以把這些分隔符想成形成某種「標點」，使得 `<parameter text>` 成為你在使用巨集時必須遵循的「詞元範本」。分隔符詞元不會被排版。

那麼這實際上是什麼意思——讓我們看幾個範例。 `<parameter text>` 位於巨集名稱與左大括號之間 `{` 之間。

#### 分隔符：範例 1

假設我們定義一個基本巨集 `\foo` 如下：

```
\def\foo ABC{hello, there}
```

* `<parameter text>` = `ABC`
* 沒有參數詞元（`#1`, `#2`,... `#9`)
* 分隔符是 *三個字元詞元* `ABC` 但在這個例子中，它們有些多餘，僅作為範例使用。

這三個字元詞元 `ABC` 被視為分隔符：不是要被排版的內容，而是呼叫巨集時預期會出現的「標點」——當你使用巨集 `\foo` 時，你必須提供與其定義時相同的分隔符。

如果你輸入 `\foo ABC` 在你的文字中，這會排版出 `hello, there`——字元詞元 `ABC` 不會被排版，但 TeX 確實非常仔細地檢查它們是否存在於你對 `\foo`的呼叫中。TeX 會尋找那些分隔符（「標點」）並將它們移除（吸收），如下方 Overleaf 畫面所示： `ABC` 不會被排版：

![Overleaf 執行 TeX 巨集](/files/5538ce60565c5ffbd117c486b1a7be3d2dd6fc1d)

如果你試著使用 `\foo` 而沒有 `ABC` 分隔符，TeX 會抱怨 `\foo 的使用與其定義不符。`:

![在 Overleaf 上顯示 TeX 錯誤](/files/83b69cf60d89d8f9157468ac5e29510a3c5c063f)

在上面的例子中，偵測到 `\foo` 之後，TeX 預期會看到那三個字元詞元 `ABC` ，但它沒有：它看到的是 `w` 的字元詞元，並立即偵測到出問題了。

#### 分隔符：範例 2

有個有趣、也許還令人驚訝的事實是：分隔符可以是任意指令詞元——甚至包括尚未 *根本還沒被定義的*指令。例如，我們可以將 `\foo` 定義為：

```
\def\foo A\bob B\anne{Hello \TeX{}}
```

* `<parameter text>` = `A\bob B\anne`
* 沒有參數詞元（`#1`, `#2`,... `#9`)
* 分隔符是詞元 `A\bob B\anne` 但同樣地，在這個例子裡它們是多餘的，只是為了討論而存在。請注意 `\bob` 以及 `\anne` 是虛構的指令，它們 *未被定義*——而且也不需要被定義。

當你呼叫巨集時，TeX 會檢查（掃描） `<parameter text>` 那個已存在的 *在你的巨集呼叫中* ，並將它逐一詞元地與儲存在 *記憶體中的「詞元範本」版本進行比較*——也就是在巨集被定義時建立的那一個。TeX 會掃描 *在你的巨集呼叫中使用的* ，並且只是把其中找到的任何指令轉換成其數值詞元值：它並不是在試圖執行那些指令，因此 `\bob` 以及 `\anne` 從未被定義 `<parameter text>` 也就無所謂。TeX 只是使用儲存在記憶體中的詞元範本作為指南，讓它能判斷巨集呼叫的 `<replacement text>`.

從下列 Overleaf 畫面片段可以看出，兩個字元詞元（`A` 以及 `B`）中 `A\bob B\anne` 沒有被排版，而我們未定義的指令 `\bob` 以及 `\anne` 也沒有造成任何問題。所有這些詞元都被 *吸收* 進 TeX 中，因為它在比對你對 `\foo` 與……的定義（詞元範本） `\foo` （一個詞元清單）時，該定義儲存在記憶體中。

![Overleaf 執行 TeX 巨集](/files/e841c42c7e889ffb6dec7d4f75e619e845d754cb)

#### 分隔符：範例 3

你可以把各種分隔符（字元詞元、指令詞元）穿插在巨集參數之間，如本例所示：

```
\def\foo A\bob#1B\anne#2\jane#3bye!{Hello from \TeX{} to \#1=#1, \#2=#2 and \#3=#3}
```

* `<parameter text>` = `A\bob#1B\anne#2\jane#3bye!`
* 共有 3 個巨集參數詞元： `#1`, `#2`, `#3`
* 這次，參數（`#1`, `#2`, `#3`）是由下列詞元組合所界定：

  `A\bob#1B\anne#2\jane#3bye!`

在這裡，你等於是提供了一個範本給 TeX；當你呼叫 `\foo`時，TeX 會非常仔細地嘗試比對，並預期能比對到： *逐一詞元地*，並預期比對到（找到）：

* 那兩個詞元 `A` 以及 `\bob` 之前 `#1`
* 那兩個詞元 `B` 以及 `\anne` 之前 `#2`
* 詞元 `\jane` 在參數 `#3` 和 *四個字元詞元* `b`, `y`,`e` 以及 `!` 之後 `#3`.

**記住**：TeX 以詞元思考，所以 `bye!` 是 *四個字元詞元*.

我們有兩種方式來使用這個巨集——我們可以把任何多詞元引數放在大括號中，以提供一個群組：

```
\foo A\bob{This}B\anne{That}\jane{Other}bye!
```

然而， *並非必要* ，因為我們的巨集定義有分隔符，提供了一個詞元範本。TeX 可以利用該範本，從純粹作為分隔符的詞元中挑出各個引數的詞元。我們可以像這樣使用巨集：

```
\foo A\bob ThisB\anne That\jane Otherbye!
```

，而 TeX 可以挑出那些引數，產生與使用群組相同的結果 `{...}`:

![Overleaf 執行使用分隔符的 TeX 巨集](/files/501317d92ba6cab48c7538f83d7228c733711ecf)

注意 TeX 如何能精確地偵測出哪些詞元對應到參數 `#1` 以及 `#3`:

![Overleaf 執行使用分隔符的 TeX 巨集](/files/f8859b232e1d1aafef8071abf21dcc9e9205dcb8)

#### 分隔符：範例 4（重點在詞元，不在字元！）

這裡有一個簡短的例子，說明在處理巨集時，務必要記住，我們其實是在 *詞元* 以及 **不是** *字元*……

我們將定義下列簡短巨集，其中 A 和 B 是充當分隔符的字元，而且兩者的類別碼都是 11。這可以正常運作：

```
\documentclass{article}
\begin{document}
\def\foo A#1B{Hello, #1}
\foo AGrahamB
\end{document}
```

並排版出 `Hello, Graham`.

然而，如果你把 A 或 B 的類別碼改成不是 11 的其他值，巨集呼叫就會失敗。假設我們使用 ``\catcode`B=12`` 將 B 的類別碼改掉，然後像先前一樣再呼叫這個巨集：

```
\documentclass{article}
\begin{document}
\def\foo A#1B{Hello, #1}
\foo AGrahamB % This works
\catcode`\B=12\relax
\foo AGrahamB
\end{document}
```

它會失敗，並出現一個有點令人困惑的錯誤：

```
Runaway argument?
GrahamB \end {document}
! File ended while scanning use of \foo.
<插入的文字>
                \par
<*> main.tex

我懷疑你忘了 `}'，使我
讀過了我本來應該停下來的位置。
我會試著恢復；但如果錯誤很嚴重，
你最好現在就輸入 `E' 或 `X'，並修正你的檔案。

! 緊急停止。
<*> main.tex

***（工作已中止，找不到合法的 \end）
```

![Overleaf 顯示 TeX 巨集錯誤](/files/7bc13f1e8ba5b98fe20f125e38aaaaaaf3b51a02)

不幸的是，TeX 預設建議的 ``我懷疑你忘了 `}'`` 是 **並不正確**，這並不是由缺少大括號所造成的（`}`).

#### 發生了什麼事？

TeX 正在嘗試比對 `\foo` 巨集 *呼叫* 與它儲存在記憶體中的 `\foo` 巨集 *定義* 。當你呼叫一個需要一個或多個引數的巨集時，TeX 必須檢查（掃描）你對該巨集的使用方式，以找出你提供給那個巨集的 *引數* 引數。這裡，TeX 預期引數會被夾在一個 A（類別碼 11）與一個 B（類別碼 11）之間。記住：TeX 思考的是 *詞元*, **不是** *字元*.

A 和 B 的詞元值 $$\mathrm{T\_A}$$ 以及 $$\mathrm{T\_B}$$ ，分別是：

$$\mathrm{T\_A = 256 \times 11 + 65 = 2881}$$ $$\mathrm{T\_B = 256 \times 11 + 66 =2882}$$

然而，當 TeX 繼續搜尋時，它看到了 B *但是* 現在它的類別碼是 12，因此對應到不同的詞元值：

$$\mathrm{T'\_B= 256 \times 12 + 66 =3138}$$

在 `\foo` 在巨集定義中，用作分隔符的 B 的詞元值是 2882，但 TeX 現在看到的詞元值是 3138：它認為這只是另一個詞元，將會被用於 *引數* 供給 `\foo`。就 TeX 而言，引數的最後一個詞元尚未找到，因此它會繼續取得另一個詞元，尋找一個類別碼 11 的 B。也就是在那時、也正因如此，巨集失敗了：在試圖找出引數時，TeX「越界讀取」，開始讀取那些你原本無意讓它們成為 `\foo` 巨集呼叫引數一部分的詞元。接下來會發生什麼，取決於 TeX 在 B 之後發現了哪些詞元——它們會觸發各種 `Runaway argument?` 錯誤。

在我們的例子中，TeX 越界讀取並消耗了詞元串 `\end {document}` ，並很快就碰到檔案結尾，因此出現錯誤訊息：

```
! 在掃描使用 \foo 時檔案結束
```

## 第 5 部分

如上所見，巨集定義中的 `<parameter text>` 部分可以從極其簡單一路延伸到一個複雜組合：其中夾雜著作為分隔符的字元詞元與指令詞元。TeX 能夠處理 `<parameter text>` 中出現的各種詞元組合，以擷取傳給巨集的引數——並偵測我們何時誤用巨集。它如何做到這一點，是本系列下一篇文章的主題。

[第 1 部分](/latex/zh-tw/geng-duo-zhu-ti/19-how-tex-macros-actually-work-part-1.md) [第 2 部分](/latex/zh-tw/geng-duo-zhu-ti/20-how-tex-macros-actually-work-part-2.md) [第 3 部分](/latex/zh-tw/geng-duo-zhu-ti/21-how-tex-macros-actually-work-part-3.md) [第 4 部分](/latex/zh-tw/geng-duo-zhu-ti/22-how-tex-macros-actually-work-part-4.md) [第 5 部分](/latex/zh-tw/geng-duo-zhu-ti/23-how-tex-macros-actually-work-part-5.md) [第 6 部分](/latex/zh-tw/geng-duo-zhu-ti/24-how-tex-macros-actually-work-part-6.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/geng-duo-zhu-ti/22-how-tex-macros-actually-work-part-4.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.
