> 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/53-what-is-a-tex-token.md).

# 什麼是「TeX token」？

## 關於 TeX 詞元與相關概念系列的動機

本文討論撰寫一系列關於 TeX 詞元與相關概念文章所採用的動機與方法。 [一系列關於 TeX 詞元與相關概念的新文章——但為什麼（以及如何）？](https://www.overleaf.com/blog/521-a-new-series-of-articles-tex-tokens-and-related-concepts-but-why-and-how) 如同那篇文章所指出的，在整個系列中，我們的討論與說明都以透過自訂編譯的 Knuth 原始 TeX 程式所獲得的洞見為基礎——利用它撰寫一系列文章，目標是對關鍵 TeX 概念提供簡明的描述與易於跟隨的說明。

## 導言：我們的目標是什麼？

在本文中，我們會透過追蹤從輸入檔中的字元到 TeX 詞元實際建立的處理旅程，來精確找出 TeX 詞元究竟是什麼。實務上，這非常複雜，因此我們把流程簡化到核心要點，努力讓它容易跟隨並理解，同時保留技術準確性。

我們先介紹一些重要的 TeX 內部概念： *原始指令*, *指令碼* 以及 *指令修飾碼*。接著，我們用一個非常簡單的巨集範例，看看 TeX 如何精確處理該指令 `\def` 以及 TeX 產生來代表該指令的詞元。

最後，我們會簡短看看 TeX 如何建立詞元來代表字元，以及字元的 `\catcode` 確實會永久附加在字元詞元上——這是關於 TeX 的書中常提到的事，但在這裡我們會看到它是如何達成的。

下圖顯示我們將要概述的旅程——從輸入文字到 TeX 詞元：

![從 TeX 輸入到 TeX 詞元的旅程。](/files/86caa1b39ad6811c876680c14dbdb8b393ba1931)

## 但首先：原始指令與指令碼

每個 TeX 引擎（Knuth 式 TeX、pdfTeX、XeTeX、LuaTeX）都能理解若干內建指令：所謂的 *原始指令*——這些是支撐 TeX 可程式化能力的基本積木指令。之所以稱為「原始指令」，是因為與使用者自定義巨集不同，它們不是由其他指令構成，也不能再進一步化簡為更簡單的指令。Knuth 的 TeX 大約有 320 個原始指令——不過要注意，其他 TeX 引擎如 pdfTeX、XeTeX 和 LuaTeX 都已在 Knuth 的原始程式中加入新指令，因此會包含 Knuth 的 TeX 軟體中沒有的原始指令。

在內部，TeX 會為所有指令指定一個數值 *命令碼* ，不論它們是使用者自定義巨集還是內建原始指令。這些指令碼對 TeX 使用者不可見，它們只是 TeX 處理過程內部機制的一部分，但為了後面討論 TeX 詞元，先了解它們是有幫助的。

具有相關功能的一組指令會共用相同的指令碼。例如， `\def`, `\gdef`, `\edef` 以及 `\xdef` 個原始指令都用來定義巨集，並且在（Knuth 的 TeX 中）共用指令碼 97。顯然，這 4 個巨集定義指令各自以稍微不同的方式建立巨集；因此在處理過程中，TeX 需要某種方法來區分它們。

單靠一個指令碼（例如 97）無法告訴你正在考慮哪一個巨集建立指令；因此，正如你所預期的，每個 TeX 指令都還會被指派一個額外資訊，稱為其 *命令修飾碼* （見下方範例）。

### 指令修飾碼：兩種型態

指令修飾碼可分為兩類，我們將稱之為「類型 1」與「類型 2」——TeX 並不使用這些術語，只是在這裡這樣稱呼比較方便：

* **類型 1**：TeX 在需要時可用來區分共用相同指令碼的指令的簡單整數值。
* **類型 2**：一個整數值，它是 TeX 記憶體中的某個數值位置，告訴 TeX 需要到哪裡查找該指令的資訊。例如，這適用於使用者自定義指令（巨集），此時指令修飾碼會告訴 TeX 巨集定義存放在記憶體中的哪裡。

#### 類型 1 指令修飾碼（範例）

如前所述，在 Knuth 的 TeX 中，用來定義巨集的四個原始指令： `\def`, `\gdef`, `\edef`, `\xdef` 都共用指令碼 97：它們透過下表列出的指令修飾碼來區分：

| 命令      | 指令碼 | 指令修飾碼 |
| ------- | --- | ----- |
| `\def`  | 97  | 0     |
| `\gdef` | 97  | 1     |
| `\edef` | 97  | 2     |
| `\xdef` | 97  | 3     |

再舉第二個例子，Knuth 決定把這些指令實作為 `\openout`, `\write`, `\closeout`, `\special`, `\immediate` 以及 `\setlanguage` 作為 TeX 的「延伸」，純粹是為了示範如何向 TeX 新增原始指令。在這種情況下，這些指令其實並沒有真正共享「相似功能」，只是 Knuth 為了說明如何擴充 TeX 而把它們分組。這 6 個指令被歸類為「延伸」，並以指令碼值 59 分組，但每一個都有適當的指令修飾碼來與其他指令區分：

| 命令             | 指令碼 | 指令修飾碼 |
| -------------- | --- | ----- |
| `\openout`     | 59  | 0     |
| `\write`       | 59  | 1     |
| `\closeout`    | 59  | 2     |
| `\special`     | 59  | 3     |
| `\immediate`   | 59  | 4     |
| `\setlanguage` | 59  | 5     |

#### 類型 2 指令修飾碼（簡短說明）

雖然所有指令修飾碼都是整數，但類型 2 修飾碼需要再多做一點說明。這些指令修飾碼在 TeX 中被稱為「指標」，因為它們指向記憶體中的某個位置，TeX 可以在那裡找到該指令的額外資訊。這聽起來可能有點模糊，但 TeX 使用這些指標查找資訊的方式非常多樣，若詳細說明會偏離本文的核心目標。有一個例子可以幫助理解：巨集。當一個巨集指令被定義時，TeX 需要把替換文字存放在記憶體中的某處。如下所示，使用者自定義巨集的指令碼介於 111 與 114 之間，而其指令修飾碼是一個指向記憶體的指標，告訴 TeX 其替換文字（巨集定義）存放在哪裡。

### 指令碼：可展開與不可展開

在 Knuth 的 TeX 原始碼中，指令碼的範圍是 0 到 120——請注意，其中有些代碼純粹供專門的內部用途，並不指派給使用者可存取的指令。值得一提的是，其他 TeX 引擎如 pdfTeX、XeTeX 與 LuaTeX 都已在 Knuth 的原始指令集合中新增指令，因此會包含更多原始指令，以及相應的指令碼；然而，這裡說明的原則是所有源自 Knuth 原始碼的 TeX 型引擎的核心。

這組指令碼可分為兩大類：

* *不可展開指令*：指令碼值小於或等於 100；
* *可展開指令*：指令碼值大於 100，最大到 120。101 到 120 的範圍包含使用者自定義巨集，以及像 `\csname`, `\expandafter` 以及 `\the`.

不可展開指令通常負責把某個值賦給內部參數，或直接產生可排版的內容。可展開指令通常會把一串詞元「注入」到 TeX 當前的處理活動中，或改變詞元處理的順序。

如上所述，所有巨集（使用者自定義指令）都被賦予介於 111 與 114 之間的指令碼：不同的值反映出該巨集是否被定義為 `\long`, `\outer`、兩者皆是，或兩者皆非。以下是範例：

| 巨集類型           | 範例                             | 註解                |
| -------------- | ------------------------------ | ----------------- |
| 非 long、非 outer | `\def\ohyeah{....}`            | `\ohyeah` 指令碼=111 |
| long、非 outer   | `\long\def\ohyeah{....}`       | `\ohyeah` 指令碼=112 |
| 非 long、outer   | `\outer\def\ohyeah{....}`      | `\ohyeah` 指令碼=113 |
| long、outer     | `\long\outer\def\ohyeah{....}` | `\ohyeah` 指令碼=114 |

提醒一下指令修飾碼：當一個巨集被定義時，TeX 會把該巨集的定義儲存在記憶體中的某個位置：那個位置（指標）就會成為該巨集指令的指令修飾碼，而該巨集會依其定義方式，以 111 到 114 之間的某個指令碼儲存。使用者自定義巨集所取的實際名稱其實並不重要：在輸入處理完成之後，它們都會被賦予 111–114 之間的指令碼，最終，TeX 從你的輸入中讀入的所有指令，不論是原始指令或使用者自定義巨集，最後都會轉換成一種稱為 *記號*.

## 從輸入文字到 TeX 詞元的旅程

在本節中，我們會用一個非常簡單的巨集範例，看看 TeX 如何精確處理指令 `\def` 來建立一個代表 `\def` 指令的詞元。TeX 的詳細處理過程可能極其複雜，因此我們不使用巨集參數或分隔符，因為那會增加複雜度並分散我們的旅程焦點。

假設你的 TeX 輸入檔包含下列這一行：

```latex
\def\ohyeah{Overleaf is cool!}
```

當 TeX 開始處理這一行輸入時，它會檢查每個字元的 `\catcode` 並看到第一個字元是 `\\` （ `\def`的第一個字元）。它偵測到（在內部表格中查找） `\\` 具有 `\catcode` 0，這表示它引入了一個 *控制序列*的開始。當然，你可以重新定義任何字元使其具有 `\catcode` 0，但我們在此假設使用的是 plain TeX 或 LaTeX 的慣例定義。

嚴格來說，術語 *控制序列* 有兩個子類別： *控制詞* 以及 *控制符號*:

* *控制詞*：由具有 `\catcode` 字母（11）值的字元所組成的序列；
* *控制符號*：一個單一字元，其 `\catcode` 是 *不是* 字母（11）值。

此時， `\\` 字元的工作已經完成，現在不再需要它了。偵測到跳脫字元後，TeX 的反應是開始讀取輸入中的所有後續字元，以便偵測控制字或控制符號。

在最初的 `\\`之後，TeX 立刻偵測到 `d`：一個字元，其 `\catcode` 值為 11，這告訴 TeX 它找到了 *控制詞*的第一個字母。它會繼續掃描後續字元，直到最後偵測到一個字元，其 *不會* 有 `\catcode` 不是字母（11）。在最初的 `\\`之後，所有 `\catcode` 11（字母）都被視為構成控制字名稱：也就是一個指令的名稱——可能是巨集或原始指令，但 TeX 目前還不知道它是哪一種指令。此時它只是一串字元。

因此，在我們的範例中，TeX 會愉快地一路掃描並檢查每個字元，直到它到達最初的 `\\` 的 `\ohyeah` ，而該字元也具有 `\catcode` 0。TeX 會認出自己掃描過頭了，並禮貌地把那個 `\\` 放回文字串流中，讓它成為後續再次掃描文字時將看到的下一個字元。此時，TeX 已辨識出一個字串（`def`）它知道這是由三個字元組成的控制字文字，而每個字元都有 `\catcode` 11 (`d`, `e` 以及 `f`）。TeX 現在需要做的是找出 `def` 是什麼意思：它要做什麼？如你所猜測，TeX 需要找到 `def` 的指令碼與指令識別碼，才能弄清楚要如何處理這個指令。

## 把它弄成一團雜湊

在偵測到一個控制字（`def`）之後，TeX 做的第一件事就是利用所謂的雜湊函數，把那串字元（`def` 在我們的例子中）「轉換」成一個整數。細節不必太在意，概略說明即可。本質上，TeX 會查看它剛偵測到的控制字中的每個字元，並使用每個字元的 ASCII 碼值（或 XeTeX/LuaTeX 的 Unicode 值）來計算一個稱為雜湊值的數字：它只是一個簡單的整數。

在這個雜湊計算過程中，TeX 也會檢查新偵測到的控制字中的字元字串是否已經為它所知。所有指令的可讀文字，不論是原始指令還是使用者自定義巨集，都會儲存在一個稱為 *字串池*的內部儲存區中。TeX 必須這麼做，因為它可能需要輸出某個指令的人類可讀名稱——例如當 TeX 需要回報錯誤並提供出錯指令名稱時。舉例來說，我們的巨集 `\def\ohyeah{Overleaf is cool!}` 正在定義一個名為 `\ohyeah` 的新指令，而 TeX（在稍後階段）不僅需要為 `ohyeah` (*而不* （最初的 `\\` 字元）計算雜湊值，還需要把文字字串（人類可讀）形式也儲存起來，以備日後需要用於錯誤報告（或其他工作）。

如果你想更詳細了解 TeX 的字串處理流程，我曾在我的 [個人部落格網站](http://www.readytext.co.uk/?p=3590).

上寫過這方面的文章。 `def` 最後結果是，代表指令 `d`, `e` 以及 `f` 的字元字串被轉換成數值 1218（這是 TeX 實際計算出的值）。此時，個別字元 *詞元*不再是主線故事的一部分——它們已從輸入中讀取完畢並完成工作：從現在開始，一切都與整數和 *有關——我們很快就會看到詞元究竟是什麼！在內部，TeX 把這些雜湊值數字稱為* 目前控制序列 `curcs`，但在原始碼中，這個術語縮寫為一個名為

但 TeX *實際上做了什麼* 來處理這個新鮮出爐的 1218 整數值？TeX 如何得知原始字串 `def`，現在以整數 1218 表示，實際上是指向一個定義巨集的指令？答案是，TeX 內部有一個類似「檔案櫃」的東西，用來儲存它目前所知道的每個指令的現行意義與值——不論該指令是使用者自定義巨集還是內建原始指令。TeX 之所以費心把 `def` 轉換成 1218 這個雜湊值（現在儲存在名為 `curcs`的變數中），是為了用它來查找 *意義* 的 `def`。當然，TeX 會對所有在輸入中偵測到的控制字重複這個雜湊計算過程——不過，當然，不同的控制字會從雜湊函數得到不同的整數值：這正是整個概念所在。

TeX 內部的這個「檔案櫃」稱為 *等價表* ，這將是下一節的主題。

### 查閱等價表

為了回顧一下，讓我們看看目前為止學到了什麼：

* `\\` 引入控制序列的開始（可以是 *控制符號* 或 *控制詞*).
* 如果第一個字元在 `\\` 具有 `\catcode` 之後是 11（字母），那麼它就是 *控制詞*.
* 對於 *控制字* TeX 會掃描以檢查所有後續具有 `\catcode` 11 的輸入字元，並且會在找到第一個不具有 *不是* 有 `\catcode` 11 值的字元時立刻停止掃描。
* （之後）的輸入字元字串，其 `\\`11 都被視為 `\catcode` ，也就是使用者輸入的指令：一個要求 TeX「做某事」的命令。 *控制詞* 要開始「做某事」的過程，TeX 會把控制字中的字元字串轉換成一個整數。它是透過所謂的雜湊函數來完成的，該函數會輸出一個整數。
* 這個整數（計算得到的雜湊值）稱為
* ，但 TeX 給它較短的名稱 *有關——我們很快就會看到詞元究竟是什麼！在內部，TeX 把這些雜湊值數字稱為*。 `curcs`.
* 在我們的例子中，控制字 `def` 被轉換成 1218——這個值會儲存在名為 `curcs`：即 `curcs=1218`.

TeX 現在需要找出新偵測到的 *有關——我們很快就會看到詞元究竟是什麼！在內部，TeX 把這些雜湊值數字稱為* 究竟意味著什麼——TeX 會如何處理它？

#### 關於分組的說明：儲存與還原資訊的需求

在這裡，我們稍微岔開一下，提醒自己 TeX 具有儲存與還原資訊的能力：也就是說，它具有某種內建的「記憶」。

任何曾寫過哪怕是最簡單巨集的人，都應該知道 TeX 的分組機制——例如使用 `\def` 在群組內建立巨集。除非你在群組內定義的 `\global` 前面加上 `\def`前綴，否則該巨集的值或意義只會在那個群組（以及其子群組）內持續有效：當群組結束時，其定義就會消失。舉例來說，如果你在群組內定義一個簡單巨集，像這樣：

```latex
{\def\foo{Hello}}
```

然後試著在群組外使用 `\foo` ，

```latex
{\def\foo{Hello}}% \foo 定義於群組內（注意：未使用 \global）
\foo %<--- 已不再定義，現在是未定義
```

那麼我們就會得到那個令人熟悉的錯誤： `未定義的控制序列`. `\foo` 只在它被定義的群組（及其子群組）內具有意義。此外，當你在群組內重新定義巨集時，新值可能會在群組結束時失去，而群組外先前的意義會被還原。

```latex
\def\foo{Goodbye}
\foo\par% 輸出 Goodbye
{\def\foo{Hello}% 在群組內重新定義：
{在第二層群組內：\foo\par}}% 在第二層群組內使用：\foo 輸出 Hello
群組外還原舊值：\foo\par% 輸出 Goodbye
```

這些簡單範例的目的，是指出 TeX 具有某種「儲存機制」或「記憶」，可以保存／還原指令的「意義」——當然，它確實如此。我們在上一節已暗示過這點：那個「儲存機制」或「檔案櫃」是一個大型內部表格，稱為 *等價表*。TeX 就在那裡儲存它目前所知道的所有指令——內建原始指令與使用者自定義巨集——的現行意義或值。

### 等價表：以類比來說

為了解釋等價表，我們將以類比方式來進行。我們會繼續使用一個擁有數千個小抽屜的檔案櫃概念，每個抽屜都標有唯一的整數。到了處理的這個階段，TeX 實際上是在說：

「好吧，我剛算出並儲存在名為 `curcs`的變數中的這個 1218 整數值。現在我需要知道它代表什麼：為此，我要去翻我的檔案櫃第 1218 號抽屜，看看裡面寫了什麼。」

TeX 使用 1218 來定位正確的抽屜，然後在那裡找到一張小便條，上面包含三項資訊，其名稱與 TeX 原始碼中使用的名稱相同：

* **`eq_level：`** 此項目被定義時所在的分組層級（第 1 層 = 全域定義）。我們前面已看過分組作用的效果：這裡就是儲存該分組層級資訊的地方；
* **`eq_type：`** 此項目的指令碼；
* **`equiv：`** 此項目的現行「值」——它可以是像前面提到的指令修飾碼那樣的簡單整數，或是指向記憶體某區域的指標；例如，代表巨集定義的一組詞元所存放的記憶體位置。

因此，我們的 1218 雜湊值（儲存在變數 `curcs`中）實際上已被用作 *鍵* ，用來存取一個抽屜，而該抽屜包含我們最初輸入為字母字串 `\def`.

在 TeX 程式的原始碼中，任何指令的 `eq_type` 是透過一個名為 `curcmd` 的變數來儲存，而 `equiv` 的值則儲存在名為 `curchr`.

### def 的等價表內容是什麼？

如前所述，為任何指令計算出的雜湊值會儲存在名為 `curcs`；因此對於 `def` 我們有 `curcs=1218`。查看等價表中的 1218 位置，TeX 會找到下列資訊：

* `curcmd`=97。這是 `\def`;
* `curchr`=0。這是 `\def`.

`\def` 是一個原始（內建）TeX 指令，除非它在別處被重新定義，否則第三也是最後一項資訊應該是 `eq_level=1` ，表示 `\def` 的意義是全域定義的，而不受限於某個較低的分組層級。在內部， `eq_level` 的值對於一個指令所附帶的作用在 TeX 的分組機制中扮演極其重要的角色，但我們不再進一步討論。

下圖總結了我們剛剛說明的內容：

![從 TeX 輸入到 TeX 詞元的旅程。](/files/86caa1b39ad6811c876680c14dbdb8b393ba1931)

## 指令的 TeX 詞元

經過上述說明之後，控制序列的 TeX 詞元實際計算結果其實非常簡單。TeX 使用雜湊函數得到的 `curcs` （1218）值，來建立一個它稱為 *記號*的簡單整數。由 `curcs` 值產生詞元的計算式是：

```c
curtok = 4095 + curcs
```

TeX 會把目前詞元（最近一次計算出的）值儲存在名為 `curtok`.

因此，總結來說，代表 `\def` 指令為 `4095 + 1218 = 5313`的 TeX 詞元。對於代表指令序列的 TeX 詞元，說到這裡就結束了：它們只不過是由雜湊表值加上 4095 計算出的整數。

## 字元的 TeX 詞元

當 TeX 需要建立一個代表字元的詞元時，它使用下列同樣簡單的計算：

```c
curtok = 256*catcode + (字元的 ASCII 值)
```

請注意，對於像 LuaTeX 這類具 Unicode 感知的引擎，會使用稍有不同的計算方式。

例如，代表一個空格字元的 TeX 詞元，其 `\catcode` 為 10，ASCII 值為 32，則為：

```c
256*10 + 32 = 2592
```

### 包含字元的詞元列表

當你建立一個簡單的詞元列表，例如

```latex
\toks100={Hello}
```

時，TeX 會建立以下詞元列表，並將其儲存在記憶體中以備稍後使用：

* H→ 256 × 11 + 72 = 2888
* e→ 256 × 11 + 101 = 2917
* l→ 256 × 11 +108 = 2924
* l→ 256 × 11 +108 = 2924
* o→256 × 11 + 111 = 2927

在 TeX 記憶體深處，詞元暫存器 100 會提供對「Hello」儲存位置的存取，該位置以 5 個詞元值儲存：2888、2917、2924、2924、2927。請注意，這些詞元結合了每個字元的 ASCII 碼與其 `\catcode`值，並在這時被轉換為詞元（已詞元化）。一旦字元被轉換成字元詞元，附加於它們的 `\catcode` 值就會永久保留在詞元中，以供稍後使用，例如當使用者輸入 `\the\toks100`.

如前所述，字元詞元是由 `256*catcode +（ASCII 值）` 計算而來，而控制序列詞元則由 `4095 + curcs` 其中 `curcs` 計算而來。curcs 是 TeX 透過輸入偵測到的控制字（使用者輸入的指令文字字串）的雜湊值。值得注意的是，字元詞元永遠小於 4095。因此 TeX 可以輕易判定某個詞元代表的是控制序列（指令）還是字元，然後再算出是哪一個控制序列或字元，以及 `\catcode` 其對應配對被編碼進了那個詞元中。


---

# 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/53-what-is-a-tex-token.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.
