> 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/09-an-introduction-to-luatex-part-2-understanding-directlua.md).

# LuaTeX 簡介（第 2 部分）：了解 \directlua

## 本文的目標

在本文的第一部分， [LuaTeX 簡介（第 1 部分）：它是什麼——又為何如此與眾不同？](/latex/zh-tw/shen-ru-wen-zhang/07-an-introduction-to-luatex-part-1-what-is-it-and-what-makes-it-so-different.md)，我們簡要回顧了 LuaTeX 作為一個極其多才多藝的 TeX 引擎：一個精密、可程式化的排版系統，提供廣泛的工具來構建文件工程與製作解決方案。

在這最後一篇中，我們將仔細檢視 LuaTeX 工具箱中最重要的組件： `\directlua` 命令，它提供了透過 Lua 腳本語言對 LuaTeX 排版行為進行程式化控制的「入口」。

然而，要透過 `\directlua` 充分運用 LuaTeX 需要具備若干 TeX 主題的背景知識：TeX 的 token、token list 以及展開機制。本文的目標是探討並說明這些基本的 TeX 概念：把與 TeX 相關、位於 `\directlua` 背後的流程串連起來，以培養對其運作方式的理解，並為您使用 LuaTeX 建立自己的排版解決方案打下基礎。

本文包含許多簡短範例，用來展示並說明 `\directlua`行為的關鍵面向，刻意避開過於複雜的程式碼，而偏好簡短的程式片段。必要時，範例會使用基本（原始/plain）TeX——雖然大多數人使用並偏好 LaTeX（巨集），但基本 TeX 指令的優點是簡單。

## LuaTeX 中的 Lua 簡介

[Lua](https://www.lua.org/about.html) 是一種腳本語言，其 [原始碼](https://www.lua.org/download.html) 具有高度可攜性且易於嵌入軟體應用程式，使開發者能將腳本功能整合進其程式。Lua 已被嵌入到 [許多應用程式](https://en.wikipedia.org/wiki/List_of_applications_using_Lua) 中，也是遊戲軟體產業中的熱門選擇——也許最著名的例子是 [《魔獸世界》](https://wowwiki.fandom.com/wiki/Lua_functions).

LuaTeX，顧名思義，是一個嵌入 Lua 腳本語言的 TeX 引擎，讓使用者能夠透過把 Lua 程式（腳本）包含進文件中來控制 LuaTeX 的排版行為。除了直接控制 LuaTeX 之外，使用者也可以純粹把 Lua 當作一種非常強大的程式語言，用來執行以 TeX 語言來說可能極其困難的任務——而 TeX 語言，無論怎麼公平地看，都是一門難以學習並精通的語言。透過加入並整合 Lua，LuaTeX 變成一個極其多才多藝且強大的 TeX 引擎，直接支援兩種程式語言。

### 在文件中使用 Lua 與 TeX：輸入 \directlua

Lua 與 TeX 是兩種 *非常不同的* 程式語言：Lua 更接近多數人對「程式語言」的想像，但 TeX 以其類別碼、token、巨集與展開機制，與大多數人對於用來撰寫程式的語言之經驗／期待相去甚遠。然而，歷史已證明 TeX 語言之所以能延續，是因為它在其設計目的上表現出色：控制排版，即使其運作方式略顯晦澀。

為了應對在單一 TeX 文件中混用 Lua 與 TeX 語言的挑戰，LuaTeX 的開發者引入了一個名為 `\directlua` 的全新命令，它是使用 Lua 的途徑——既可作為一門獨立的程式語言本身，也可用來控制 LuaTeX 的排版行為。

該 `\directlua` 命令允許使用者在其 TeX 文件中嵌入 Lua 程式碼；該程式碼之後會被傳送到 LuaTeX 內建的 Lua 語言直譯器。然而， `\directlua` 也讓您能夠 *結合* Lua 與（La）TeX 程式碼，放在同一個 `\directlua` 命令中——雖然這會因為 Lua 與基於 TeX 的程式語言之間的根本差異而帶來額外的複雜性。使用（La）TeX 與 Lua 程式碼組合時，關鍵挑戰在於確保這兩種語言能和平共存，而不會「互相干擾」。

`\directlua` 最適合用於較短、直接寫在文件中的 Lua 程式片段，但如果您願意，也可以將它用於較大規模的 Lua 程式。一般而言，較大型的 Lua 程式與 Lua 程式庫會儲存在外部檔案中，並可透過 Lua 的 `dofile()` 函式在 `\directlua` 命令中載入。從 TeX 處理的角度來看，使用外部 Lua 程式碼檔案的一大優勢，是可避免因 TeX 的類別碼機制而產生的複雜問題——這個主題將在本文中完整探討。

### 更正式的 \directlua 說明

該 [LuaTeX 參考手冊](http://www.pragma-ade.com/general/manuals/luatex.pdf) 對其說明如下（略作修改）： `\directlua` 為了將 Lua 程式碼與 TeX 輸入合併，需要一些新的原語。原語

> 用於立即執行 Lua 程式碼。基本語法是 `\directlua` \directlua{⟨程式碼⟩} `\directlua{⟨程式碼⟩}`。 `⟨程式碼⟩` 會被完全展開，然後送入 Lua 直譯器。對 `⟨程式碼⟩`，進行讀取與展開之後，所得的 token list 會被轉換成字串，彷彿它是透過 `\the\toks`.

當然，這在技術上是正確的，但若沒有一些較低層的 TeX 流程知識——例如 token 與展開——也許不那麼容易理解。

## 理解 \directlua：我們將涵蓋哪些主題？

在本文中，我們將更深入探討一些關鍵背景主題，並提供若干範例，旨在說明如何 `\directlua` 運作，以及在文件中將 TeX 與 Lua 結合時，何時（或為什麼）需要小心 `⟨程式碼⟩`.

我們會在足以作為理解 `\directlua` 以及它對您在其中使用的程式碼所做「前處理」的基礎上，詳細探討下列主題：

* 類別碼與 TeX token：將文字轉為 token，並將 token 轉回文字；
* TeX 的展開過程（以及防止展開）；
* Lua 用於字元與字串的跳脫序列／機制；
* 使用 Lua 風格註解；
* LuaTeX 的 Lua API 簡介。

如果您理解 TeX 引擎如何建立與使用 token，並對 TeX 的展開機制有概念，那麼您就具備了解鎖 LuaTeX 驚人多樣性的必要基礎 `\directlua` 指令。

## 基礎：從文字到 token，再從 token 到文字

Overleaf 已發表多篇深入探討 TeX token 與相關概念的文章，因此這裡不重複所有內容；相反地，我們將概述那些有助於更深入理解 `\directlua`.

以下是一些先前發表、可能值得一讀的文章：

* [什麼是 TeX token？](/latex/zh-tw/shen-ru-wen-zhang/53-what-is-a-tex-token.md)
* [什麼是 TeX token list？](/latex/zh-tw/shen-ru-wen-zhang/54-what-is-a-tex-token-list.md)
* [\expandafter 如何運作：TeX token 簡介](/latex/zh-tw/shen-ru-wen-zhang/19-how-does-expandafter-work-an-introduction-to-tex-tokens.md)
* [六篇系列：TeX 巨集到底是如何運作的？](/latex/zh-tw/geng-duo-zhu-ti/01-a-six-part-series-how-do-tex-macros-actually-work.md)

### 理解字元 token

TeX 引擎能從文字檔讀取的任何字元，都會以兩個數值表示：

* 其 *字元碼* （ASCII 值，或如今的 Unicode code point）；
* 第二個、以 TeX 為中心的數值，稱為其 *類別碼*.

想更了解類別碼的讀者，可能會有興趣閱讀 Overleaf 發表的這篇入門文章： [那麼從哪裡開始？從類別碼開始](/latex/zh-tw/geng-duo-zhu-ti/19-how-tex-macros-actually-work-part-1.md#so2c-where-do-we-start3f-with-category-codes).

例如，如果 TeX 引擎讀入字元 `一個` 它就會取得兩項資訊： `一個`的字元碼（65），以及其類別碼（通常為 11）。一旦 TeX 輸入了那個字元 `一個`，其類別碼就不會改變，但使用者巨集可以變更類別碼，進而影響任何 *後續的* 字元 `一個` 尚未被讀入的 *TeX。因而，TeX 需要記錄這個* by TeX. Consequently, TeX needs to record that *這個* 字元 `一個`, *剛讀入的*，其類別碼為 11。為了做到這點，TeX 使用整數對 (65,11) 計算出另一個整數值，稱為 *字元 token*。藉由計算出那個 token 值，並將其傳遞給 TeX 的內部處理，該特定的 `一個` 以及其類別碼就 *綁在一起*；實際上，這個字元 token *封裝了* TeX 需要知道的該字元資料，以便在 TeX 引擎更深層的任何後續排版活動中使用。

#### 字元 token 如何計算？

首先，我們需要記住 TeX 引擎使用類別碼 13 來建立所謂的 *活性字元*：任何類別碼為 13 的字元都表現得像一個迷你巨集；因此，如下所示，活性字元的 token 計算方式與其他類別碼為 10、11 或 12 的一般字元不同。

對於 *非活性* 字元：

* 較舊的 8 位元引擎（Knuth 的 TeX、e-TeX、pdfTeX）會為 *非活性* 字元使用

$$\text{(non-active) character token} = (256 \times \text{category code}) + (\text{ASCII character code})$$

* 而 LuaTeX 必須處理 Unicode 字元值，因此 *非活性* 字元的計算方式類似，但會產生大得多的整數值：

$$\text{(non-active) character token} = (2^{21} \times \text{category code}) + (\text{Unicode value})$$

回到前面以類別碼 11 的字母 A 為例，LuaTeX 會計算出一個字元 token 值 $$2^{21} \times 11 + 65 = 23068737$$。一旦計算完成，該字元 token 值就 *把* 那個特定字元 A 與類別碼值 11 綁定。使用者巨集可能會改變後續任何字元 A 的類別碼，但這個字元的類別碼已透過轉成 token 而被固定下來，供其在 LuaTeX 的內部運作中通過時使用。LuaTeX 已保留，或封裝，該字元在讀入當時所決定的預期意義。

TeX 引擎總共使用 [16 種不同的類別碼](/latex/zh-tw/geng-duo-zhu-ti/43-table-of-tex-category-codes.md) 和 *任何* 這些類別碼都可以透過 `\catcode` 命令，被指派給 TeX 引擎能讀取的 *任何* 字元。類別碼的變更用來改變 TeX 引擎處理輸入中特定字元的方式，使 TeX 使用者能撰寫可產生特殊排版結果或行為的巨集。

**活性字元**

如前所述，TeX 引擎使用類別碼 13 為字元附加「特殊意義」，使其成為所謂的 *活性字元* ，其行為像一個迷你巨集：不需要前導 `\\` 即可，單獨的這個字元由於其類別碼就足以觸發其類巨集般的行為。

因為活性字元的行為像迷你巨集，所以它不會被轉換成 *字元 token* ，而是轉換為第二種（整數） token 類型，稱為 *命令 token*。它們的計算方式如下：

* 對於較舊的 8 位元引擎（Knuth 的 TeX、e-TeX、pdfTeX），活性字元的 token 透過下式計算：

1. 計算一個稱為 $$\text{curcs}$$ (**cur**rent **c**ontrol **s**equence) where $$\text{curcs} = \text{character code} + 1$$3. 計算 token 值，其中 $$\text{active character token} = \text{curcs} + \text{4095}$$

* 對 LuaTeX 而言，計算稍微複雜一些，因為它必須處理完整範圍的 Unicode 字元，而其中任何一個都可能被設為活性：

1. 透過套用所謂的 $$\text{curcs}$$ hash function *到以 UTF-8 表示的活性字元 Unicode code point 值：* 3. 計算整數 token 值： $$\text{curcs}=\texttt{hashfunction}\text{(UTF-8 text for Unicode value of active character)}$$8 位元引擎：活性字元 $$\text{active character token} = \text{curcs} + 2^{29} - 1$$

**範例**

* （字元碼 126）的 token 計算結果為 `~` ，得到的 token 值為 $$\text{curcs} = 126 + 1 = 127$$，得到的 token 值為 $$4095 + 127 = 4222$$.
* LuaTeX：活性字元 `~` 的 token 計算結果為 $$\text{curcs}=3186$$ ，得到的 token 值為 $$3186 + 2^{29} - 1 = 536874097$$。LuaTeX 的 token 使用大得多的整數值！

### 理解命令 token

除了處理 *單一* 字元之外，TeX 引擎當然也可以處理 *序列* ，稱為 *命令* （更正確地說， *控制序列*）。按照傳統， `\\` 字元被用來標示命令的開頭，但那只是一種慣例——事實上，任何類別碼為 0（跳脫字元）的字元都可以取而代之。

TeX 引擎辨識兩種命令，稱為 *控制字* 和 *控制符號*:

* **控制字**：由一個或多個類別碼為 11 的字元組成的命令；
* **控制符號**：單字元命令，其中該字元的類別碼 *沒有* 11：例如 `\$`, `\#` 或 `\\`.

**注意**：TeX 原語 `\chardef`, `\mathchardef`, `\countdef`, `\dimendef`, `\skipdef`, `\muskipdef` 和 `\toksdef` 也用來定義控制序列，但不同於一般的巨集定義，所得到的控制序列（控制字或控制符號） *不可展開*——我們將在下文更詳細地探討這些。

#### 命令 token 如何計算？

與活性字元一樣，TeX 引擎使用第二種整數 token 值來表示命令： *命令 token*——請記得，活性字元也會產生命令 token，因為它們的行為像迷你巨集。

8 位元引擎用來建立命令 token 整數的計算方式可見於這篇 [Overleaf 文章](/latex/zh-tw/shen-ru-wen-zhang/19-how-does-expandafter-work-an-introduction-to-tex-tokens.md#how-tex-calculates-token-values)。在這裡，我們將摘要 LuaTeX 的命令 token 計算關鍵步驟——它們略有不同，因為 LuaTeX 必須處理 Unicode 字元碼值，而這些值可能遠大於 8 位元值；不過，LuaTeX 的計算遵循與較舊 8 位元引擎相同的一般原則。

在偵測到傳入的命令後，包括 LuaTeX 在內的 TeX 引擎會忽略前導 `\\` 字元：它不會用於命令 token 值的計算，而只是作為一個「開關」，通知 TeX 引擎它需要處理一個命令。命令 token 值是利用命令名稱中所含的（1 個或多個）字元序列來計算的——LuaTeX 使用相同的演算法來計算控制符號與控制字的命令 token：

1. 透過套用所謂的 $$\text{curcs}$$ hash function [到以 UTF-8 表示的活性字元 Unicode code point 值：](https://en.wikipedia.org/wiki/Hash_function) 到命令名稱中所包含字元的 Unicode UTF-8 字串： $$\text{curcs}=\texttt{hashfunction}\text{(Unicode UTF-8 string of characters in command name)}$$3. 計算命令 token 值，其中 $$\text{command token} = \text{curcs} + 2^{29} - 1$$

**範例**

* 適用於 `\\` 命令（控制符號），LuaTeX 計算 $$\text{curcs}=94$$，得到 `\\` 的 token 值 $$94 + 2^{29} - 1 = 536871005$$.
* 適用於 `\vskip` 原語命令（控制字）LuaTeX 計算 $$\text{curcs}=3560$$，得到 `\vskip` 的 token 值 $$3560 + 2^{29} -1 = 536874471$$.
* 使用者定義巨集 `\mynewmacro` （控制字）LuaTeX 計算 $$\text{curcs} = 2971$$，得到 `\mynewmacro` 的 token 值 $$2971 + 2^{29} -1 = 536873882$$.

一旦建立，token 便可透過所謂的 *token list* 儲存以供日後使用，或者它們可以立即傳遞到 TeX 引擎內部進行進一步處理。使用整數值來表示 token 不僅適用於所有運算平台／作業系統類型，而且也是 TeX 儲存／處理資料的一種非常有效率的方式。

### TeX 引擎如何辨識 token 的類型（命令或字元）

給定某個特定的整數 token 值， $$T$$，TeX 引擎可以很容易地判定 $$T$$ 代表命令或字元，只要測試 $$T$$ 是否超過某個 $$\text{threshold value}$$——這 $$\text{threshold value}$$ 取決於 TeX 引擎。如果 $$T \geq \text{threshold value}$$ ，那麼 $$T$$ 是命令 token，否則 $$T$$ 是字元 token。 $$\text{threshold value}$$ 是 $$4095$$ 對於 8 位元引擎而言， $$2^{29}-1$$ （536,870,911）則是 LuaTeX 的門檻。Knuth 設計了 token 計算公式所使用的方法，使他的 TeX 引擎，以及所有之後基於其程式碼／架構的引擎，都能快速且輕鬆地測試 token 值。

## token 可以被拆開（並轉回文字）

token（整數）是 TeX 引擎用來「封裝」它需要記錄的某個輸入項目（字元或命令）所有資訊的機制。然而，有時 TeX 引擎需要反向執行 token 化過程——找出最初讀入的內容，以產生該 token 值——也就是單一字元，或組成命令名稱的一個或多個字元序列：

* **針對字元 token**：任何字元 token 都可以分解為兩個組成部分：該字元的字元碼與對應的類別碼 *在其最初被讀入的時點*。如同所有 TeX 引擎一樣，LuaTeX 不會改變那個原始類別碼分配，而是會在後續的內部處理活動中加以使用。
* **針對命令 token：** 這些稍微更詳細一些，但如果您看 LuaTeX 對命令 token 的計算，包括活性字元的 token，您會發現它們遵循一個模式： $$\text{command token} = \text{curcs} + 2^{29} -1$$

其中 $$\text{curcs}$$ 會根據所產生的命令 token 類型來計算：活性字元、控制符號或控制字。 $$\text{curcs}$$ 變數是 *極其* TeX 引擎內部運作的重要組成部分：給定任何命令 token（整數）值，LuaTeX 都可以很容易地從該命令 token 中使用 $$\text{curcs}$$ 提取 $$\text{curcs} = \text{token value} - (2^{29} -1)$$.

### 為什麼 $$\text{curcs}$$ 如此重要？

TeX 內部變數 $$\text{curcs}$$ (**cur**rent **c**ontrol **s**equence) 是 TeX 引擎內部「幕後」運作中極其重要的組成部分。雖然您不會、也不能，在程式碼中直接使用或存取它， $$\text{curcs}$$ 發揮關鍵作用，因為 TeX 引擎會使用目前的 $$\text{curcs}$$ 作為索引，查詢內部表格；那些表格儲存了引擎目前已知的每一個命令的資料。那些表格會儲存一個命令的目前意義：它做什麼、或代表什麼；此外，還會記錄最初用來計算那個 $$\text{curcs}$$ 值的字元序列。藉由擷取 $$\text{curcs}$$ 從命令 token 中，TeX 引擎就能判定任何（命令）token 對應的名稱，也就是人類可讀文字，進而執行 token 轉文字的轉換，而這正是 `\directlua`的運作中的關鍵部分。

### 將整數 token 轉回字元或字元序列（命令名稱）

我們已經看到 TeX 引擎會把輸入字元，或字元序列，轉成整數 token 值，但有時 TeX 引擎需要 *反向* 該過程——把原本用來建立那些整數 token 值的人類可讀文字輸出出來；例如：

* 把錯誤或警告訊息寫到螢幕上，或 `.log` 檔案；
* 透過 `\write` 命令；
* 將 token 序列轉成文字時，位於 `\directlua` 中（我們很快就會看到！）

#### 將字元 token 轉回文字

如前所述，非活性字元的 token 是使用輸入字元的類別碼與字元碼（Unicode 值）計算而成。LuaTeX 使用以下公式：

$$\text{character token} = (2^{21} \times \text{category code}) + (\text{Unicode value})$$

將整數 $$\text{character token}$$ 值拆開以取得其組成的字元碼（$$\text{Unicode value}$$）與 $$\text{category code}$$.

#### 將命令 token 轉回文字

所有 TeX 引擎都會儲存它們「所知道」的每個命令的名稱（字元序列）：無論該命令是使用者定義的巨集還是內建的原語——原語命令名稱的儲存是在 TeX 引擎啟動時進行的，遠早於它開始處理您的程式碼之前。對於使用者定義的命令（巨集），該巨集的名稱（去掉前導 `\\`）會作為 TeX 引擎內部巨集定義過程的一部分被儲存起來。

當 TeX 引擎需要存取或輸出最初用來計算某個整數命令 token 的人類可讀文字時，它會先判定該 token 的 $$\text{curcs}$$ 值；在 LuaTeX 中， $$\text{curcs} = \text{token} - (2^{29} -1$$）。利用從命令 token 擷取出的 $$\text{curcs}$$ ，TeX 引擎可以存取一個稱為 *字串池* 的內部資料結構，以判定最初用來計算該 $$\text{curcs}$$ 值的、由人類可讀字元所組成的序列，進而找出對應的命令 token。

如我們將會看到，這些 token 處理活動——將字元序列轉換為整數 token 值，以及將整數 token 值轉回字元序列（「去 token 化」）——就是 *基本機制* 在 `\directlua`.

## Token list

當 TeX 引擎正在讀取輸入、產生字元與命令 token（並進行處理）時，它可能會遇到某些命令，這些命令會指示引擎（暫時）停止將 token 繼續傳遞以供後續處理，而是將它們儲存起來以便日後使用。最常見的例子是使用其中一個巨集定義命令來定義巨集 `\def`, `\edef`, `\gdef` 或 `\xdef`——例如 LaTeX 命令 `\newcommand` 就是建立在低階原語之上的巨集，用來提供額外功能，而這些原語最終會執行真正的巨集定義過程。巨集可以被視為賦予某個已儲存 token 列表的一個名稱：token list。

TeX 引擎會 *大量* 使用 token list，尤其是 [暫時性的、僅供內部使用的列表](/latex/zh-tw/shen-ru-wen-zhang/21-how-does-expandafter-work-tex-uses-temporary-token-lists.md) ，用於內部處理目的。每個 TeX 引擎也提供使用者層級的命令，以建立 token list，供使用者或 TeX 引擎本身需要時取用。可建立 token list 的命令（內建原語）數量依 TeX 引擎而異，但它們都共享每個引擎所支援的一組核心最小集合，例如 `\toks` 原語。

實際上，token list 只是一串已儲存的整數值：

* 先讀入輸入以產生（計算出）單個 token，代表一個字元或命令；
* 接著將每個 token 儲存起來，保留它們從輸入中生成的順序。

TeX 引擎使用一種稱為 [linked list](https://en.wikipedia.org/wiki/Linked_list) 的資料結構來儲存 token list（單向鏈結的那一種）。想進一步了解 token list 的讀者，歡迎閱讀 Overleaf 文章 [什麼是 TeX token list？](/latex/zh-tw/shen-ru-wen-zhang/54-what-is-a-tex-token-list.md) ，其中使用類比來建構 token list 背後的概念／想法。若要深入探討 TeX 的 token list，以及它們在巨集處理中的用法，可參考 Overleaf 的文章系列 [TeX 巨集究竟是如何運作的？](/latex/zh-tw/geng-duo-zhu-ti/01-a-six-part-series-how-do-tex-macros-actually-work.md)

#### 圖示化的 token list

以下圖示顯示一個由 LuaTeX 產生的 token list，以及由下列輸入所產生的對應 token 值

`Hi, \TeX! \hskip 5bp`

例如，如果我們定義 `\mymacro` 寫成 `\def\mymacro{Hi, \TeX! \hskip 5bp}` 的定義 `\mymacro` 會像這樣使用 token list 儲存在記憶體中：

![](/files/7fa9fab1bbea62c90a5a0518e09806dc585047d9)

token list 是一串相連項目，稱為 *節點*，這是 LuaTeX 分配給用來容納列表中每個項目的小型記憶體區塊名稱（就像鏈條中的每一個連結）。每個節點都包含一個整數 token 值，以及 *下一個* 節點在鏈中的記憶體位址，形成一種稱為 [linked list](https://en.wikipedia.org/wiki/Linked_list)的資料結構。最後一個節點會使用一個特殊的「空值」來表示下一個節點不存在，因為確實沒有下一個節點。

**附註：**

* 為方便起見，我們加入了每個個別節點的位址，但實際上，這些資料不會儲存在 token list 節點內；建構 TeX 引擎 token list 只需要 *下一個節點* 的位址。
* 圖中標題為「每個 token 的意義」的第二欄顯示了一系列灰色方框，包含各節點所含 token 的資訊：這些純粹是為了資訊說明，並不 *不會* 構成 token list 中實際儲存的資料。

以下是上方所示 token list 中包含的 token 值表：

|          |          |                                                            |             |
| -------- | -------- | ---------------------------------------------------------- | ----------- |
| **輸入項目** | **輸入類型** | <p><strong>類別碼</strong><br><br><strong>（若為字元）</strong></p> | **Token 值** |
| H        | 字元       | 11                                                         | 23068744    |
| i        | 字元       | 11                                                         | 23068777    |
| ,        | 字元       | 12                                                         | 25165868    |
|          | 字元       | 10                                                         | 20971552    |
| \TeX     | 命令（巨集）   |                                                            | 536871539   |
| !        | 字元       | 12                                                         | 25165857    |
|          | 字元       | 10                                                         | 20971552    |
| \hskip   | 命令（原語）   |                                                            | 536874247   |
| 5        | 字元       | 12                                                         | 25165877    |
| b        | 字元       | 11                                                         | 23068770    |
| p        | 字元       | 11                                                         | 23068784    |

**注意：** 我們原本的輸入文字在 `\hskip` 命令之後有一個，但 token list 中沒有代表該字元的 token。那個字元在 LuaTeX 的輸入掃描（讀取）過程中被吸收了，因為它被用來終止 LuaTeX 對構成 `\hskip` 指令。

## 其字元序列的搜尋。

既然我們已經探討了記號、記號清單，以及將記號轉為文字，下一個挑戰是理解 TeX 引擎中「記號」的概念 *展開*.

如前所述， `\directlua{⟨程式碼⟩}` 可以被要求處理 `⟨程式碼⟩` 其中同時包含 Lua 和 TeX/LaTeX 程式碼，但 LuaTeX 內建的 Lua 語言直譯器並不理解 TeX 或 LaTeX：那麼這怎麼可能運作？要如何讓 `⟨程式碼⟩` 能夠包含 TeX/LaTeX 指令，而不會讓 Lua 直譯器被它無法理解的命令徹底搞混？例如，下列 `\directlua` 命令只使用 TeX 巨集，但它可以運作：

```
\def\aa{tex}
\def\bb{.}
\def\cc{print}
\def\dd{("Hello")}
\directlua{
   \aa\bb\cc\dd
}
```

這個 `\directlua` 命令會使 LuaTeX 排版出 `Hello` 但為什麼、又是如何運作的，因為 Lua 語言不理解 TeX 巨集呢？

答案包含在我們先前借用自 [LuaTeX 參考手冊](http://www.pragma-ade.com/general/manuals/luatex.pdf) 的描述之中，但我們可以將其視為 `\directlua{⟨程式碼⟩}` 先對 `⟨程式碼⟩` 進行「前處理」，之後才把任何內容交給 Lua 直譯器。這種「前處理」的本質——也就是它真正的含義，以及它對你的 `⟨程式碼⟩`所造成的影響——是我們接下來要討論的主題，幫助有興趣的讀者掌握 `\directlua`.

### LuaTeX 如何處理 \directlua：初步一瞥

為了建立我們對 `\directlua`的「前處理」活動的理解，我們可以從下列簡化圖開始，它概述了會發生什麼事。 `⟨程式碼⟩` 提供給 `\directlua{⟨程式碼⟩}` 的內容會先使用上面討論的流程與計算轉換為記號；那串記號會儲存在一個記號清單中。當該記號清單建立完成後，清單中的每個記號都會再轉回其文字表示：每個記號——無論是字元記號或命令記號——所產生的文字都會被合併（串接）成單一的程式碼字串，接著交給 Lua 直譯器執行。

![](/files/d9b07481b078589c242144e8884a16371ff9c827)

但是等等，從文字轉成記號，再把那些記號直接轉回文字，這樣有什麼意義？你或許不會感到意外，確實還有一個我們尚未放進這張圖裡的重要額外流程： *記號展開*。從你的 `⟨程式碼⟩` 中產生的每個記號，都會經過一種類型的「檢查」，LuaTeX 會對該記號做測試，看它是否代表屬於 *可展開命令*這個子集合的命令。如果是，LuaTeX 會透過 *將* 把該命令從你的 `⟨程式碼⟩` 和 *中過濾掉，* 以 TeX 引擎稱為 *記號展開*.

### LuaTeX 如何處理 \directlua：第二次檢視（展開）

TeX 的展開機制是所有基於 TeX 的排版引擎的核心元件，因為最終它們都源自（或基於）Knuth 最初的 TeX 原始碼與設計。然而，要用簡潔而又易懂的語言來解釋「展開」這個概念是很困難的，因為在實務上，展開是一個「總括性」術語，用來描述單一流程——但這個流程會產生一系列不同的輸出。那些多樣化的結果，是由於可套用展開的一組命令相當不一致，因此你可以把每個可展開命令都視為具有某種「展開行為」。

作為 *初步近似* 來理解展開，我們可以說，一個記號（命令）的展開表示 *將* 將該命令（記號）從 TeX 目前的輸入中 *以* ，並 *行為*成一串由執行該特定可展開命令所產生的記號——以展開的結果／後果取代原始記號

。不過，這個關於展開的初始「定義」——以產生 TeX 要讀取的新記號來描述——對所有可展開命令而言並不完全準確，但作為起點已經足夠。 `\jobname` TeX 基本命令 *展開* 是可展開命令，而且它的 `\jobname` 是一串字元記號，代表主 TeX 輸入檔的名稱。如果 TeX 決定展開一個 *命令（記號），它會被* 從 TeX 目前的輸入來源中 *，並由它產生的字元記號序列* 取代，接著 TeX 會繼續讀取／處理這些字元記號。

在 `\directlua`中，一旦某個可展開記號被處理（移除）並以新記號取代，LuaTeX 會繼續讀取它剛剛放入的位置上的那些新記號——但這些新記號中有些也可能是可展開的。因為 `\directlua` 執行所謂的 *完全展開*，LuaTeX 會讀取那些新記號，並再次進入展開流程，展開（移除）任何新的（可展開）記號——這個展開流程會持續進行，直到沒有任何可展開記號為止。不過，這個「持續展開」規則有兩個重要例外，我們會在下方討論：

* 使用結構 `\the\toks`;
* 刻意阻止（抑制）一個或多個選定記號的展開。

如前所述，我們用來理解展開的工作定義（初步近似）並未涵蓋可展開命令子集合所展現的全部展開行為。例如，有些可展開命令不會像 `\jobname` 那樣產生記號，但它們可能會：

* 從輸入中「過濾」記號：TeX 引擎的條件命令（`\if`, `\ifcat`, `\ifnum`, `\ifdim`, `\ifodd`, `\ifvmode`，…）都是可展開的。它們的展開行為是一種「記號過濾」——條件式可用於 `\directlua`.
* 在輸入中「調動」記號： [`\expandafter` 指令](/latex/zh-tw/shen-ru-wen-zhang/03-a-six-part-article-series-on-expandafter-tex-tokens-and-expansion.md) 是可展開的，並改變兩個記號被展開的順序。
* 阻止展開：可展開命令 `\noexpand` 和 `\unexpanded` 會抑制輸入中命令記號的展開。
* 將輸入中的字元序列轉換為命令記號： `\csname … \endcsname。`
* 將內部數值轉換為一串字元記號： `\number` 和 `\the` 是可展開命令，會產生一串代表某個內部數值的字元記號。
* 將命令記號轉換為字元記號： `\string` 和 `\detokenize` 是可展開命令，會將它們的引數轉換為一串類別碼為 12 的字元記號。請注意 `\detokenize` 與 `\string`: `\detokenize` 不同， *控制字*可以處理多個記號，並在處理由 `\detokenize` 建立的命令記號之後，插入一個類別碼為 10 的空白字元。實際上，

#### 精煉我們對「展開」的定義

現在我們可以把定義概括為：一個命令（記號）的展開涉及 *移除* 該命令（記號）從 TeX 目前的輸入來源中，並以 *中過濾掉，* 取代 *所執行的* 記號操作

。本質上，展開流程會使一個可展開命令對 TeX 目前輸入中的記號執行某種「操作」，這會影響 TeX 隨後會讀取的記號數量或行為——該「操作」的精確性質取決於正在展開的是哪個命令。所有巨集與活動字元都是可展開的，但 TeX 引擎內建命令（基本命令）中只有少數被歸類為可展開——可展開命令的清單取決於你正在使用的 TeX 引擎。

另一個在說明／理解展開時的困難，也許才是真正的挑戰，是精確知道 *何時* TeX 引擎實際上會或不會執行展開流程。這是一個龐大而複雜的主題，因為展開深深嵌入 TeX 引擎的內部運作中：我們沒有空間在此詳述，除了在 `\directlua`.

### LuaTeX 如何處理 \directlua：最後一瞥

下列圖示總結了 `\directlua` 在 LuaTeX 引擎內部發生的前處理活動。在這張圖中我們也顯示了兩個底層（內部）的 LuaTeX 函式，它們實際負責工作： `scan_toks()` 和 `tokenlist_to_cstring()`。這些函式以 C 語言撰寫，位於可執行的 LuaTeX 軟體深處：它們是 LuaTeX 內部機制的一部分，並 *直接* 無法從你的 TeX/LaTeX 程式碼直接存取。

![](/files/bc99ac2ea7edadbfe1af2f6e7849ac6e0b80d04a)

以下對 `\directlua ⟨code⟩`的前處理活動之說明，總結了上面的圖示。

1. 你在 ⟨code⟩ 中的字元序列會由 `scan_toks()`處理。其目的，是逐字元讀取你的 ⟨code⟩，以產生字元記號和命令記號。由於它是在建立記號，因此讀入 ⟨code⟩ 時每個字元所被指派的類別碼非常重要。
2. 在 `scan_toks()`的記號處理（生成）過程中，任何可展開命令（記號）都會被展開 *除非* ，除非透過例如 `\protected` （巨集定義）之類的命令加以防止， `\noexpand`, `\unexpanded` 等等。活動字元（類別碼 13）也會被展開（除非被阻止）。
3. 由 `scan_toks()` 建立的記號流會被組成一條很長的記號清單——該清單中的記號包括對你 `⟨程式碼⟩`中的可展開命令（例如巨集）進行展開後所產生的記號。另請注意 `scan_toks()` *不會* 不會觸發或導致任何代表不可展開命令的記號執行：這類不可展開記號只是被直接傳遞，並被納入正在建構的記號清單中。
4. 一旦記號清單完成且所有展開活動也完成，該記號清單會由另一個名為 `tokenlist_to_cstring()` 的函式處理，將最終記號清單中的每個記號轉回其文字表示。這會產生一串文字，也就是要傳給 Lua 直譯器的 Lua 程式碼。要成功執行，這串文字需要包含語法正確的 Lua 程式碼。
5. Lua 對該程式碼的處理分兩步進行：
6. LuaTeX 內建的 Lua 直譯器會解析並「編譯」前述步驟所產生的 Lua 程式碼。如果解析／編譯失敗，Lua 直譯器會產生錯誤（例如語法錯誤）——除非你選擇在命令列中使用 `--interaction=nonstopmode` ，否則這些錯誤可能導致 LuaTeX 執行失敗。
7. 如果解析／編譯成功，Lua 直譯器就會執行在步驟（5a）中編譯的程式碼。

本質上， `scan_toks()` 函式是 LuaTeX 前處理活動的核心：它的主要任務是展開你 `⟨程式碼⟩` 文字中包含的所有可展開 TeX/LaTeX 命令，並把它處理過的一切組成一個記號清單。再次強調， `scan_toks()` *不會執行不可展開命令* （記號）：它只是將這些記號 *儲存* 在它正在建構的記號清單中。完成後，該記號清單會隨後被轉換 *回文字表示* ，由 `tokenlist_to_cstring()`——記號清單是純 TeX 才有的概念，對 Lua 直譯器來說完全陌生，因此需要把它轉成文字，成為可傳給 Lua 直譯器的 Lua 程式碼。

## 作為程式語言的「介面」之展開

你可以把 `\directlua`的展開流程想成一種機制，或一個介面，用來把資料／資訊從「TeX 世界」傳送到「Lua 世界」：提供 TeX 語言向 Lua 語言傳遞資料的方法。例如，像 `\number\count75` 這樣的 TeX 程式碼，可以用來把儲存在第 75 號計數器暫存器中的「TeX 世界」值傳送到「Lua 世界」中的整數變數 x：

```
\count75=1564 % 存在於「TeX 世界」中的資料
\directlua{
   local x=\number\count75 \space % 將 TeX 資料傳到「Lua 世界」
   tex.print("x= "..x)
   local y = (2*x-65)/5
   tex.print(" and y = "..y)
}
```

這會產生 Lua 程式碼

```
 local x=1564 tex.print("x= "..x) local y = (2*x-65)/5 tex.print(" and y = "..y)
```

**注意**：我們加入了 `<space>\space` 在 `\number\count75` 之後，以確保 `1564` 和 `tex.print`之間保留一個空白字元——嚴格來說這裡並非必要，因為即使省略它，Lua 仍會正確解析程式碼。緊接在 `\count75` 之後的空白字元，會在 TeX 引擎用來尋找數值的過程中被吸收——在這裡，所提供的是 `\count`的值。 `75` 之後的空白字元用來終止 LuaTeX 對數字序列 `75` 的搜尋，並從輸入中被吸收。 `\space` 巨集會展開以提供分隔文字所需的空白字元 `1564` 和 `tex.print`.

使用上面的程式碼，LuaTeX 會排版出

`x= 1564 and y = 612.6`

在這裡，「資料傳遞」機制是透過 `\number`實現的：這是一個可展開命令，在這個例子中，它指示 TeX 取出儲存在 `\count` 暫存器 `75` 中的值，並由該值（`1546`）產生一系列字元記號，每個數字各對應一個字元記號，最後形成數字 `1`, `5`, `6` 和 `4`的記號序列。這 4 個字元記號被納入由 `\directlua` 正在建構的主記號清單中，之後在記號清單轉為文字時，再被轉回文字表示。從 LuaTeX 內部儲存的 `\count75` 暫存器值，到最終要交給 Lua 程式碼使用的數字，這條路徑無疑非常迂迴，但最終它確實可行。

**提示：** 如果你想檢視 LuaTeX 展開活動的結果，可以寫出像這樣的程式碼：

```
\directlua{
   local foo=[[local x=\number\count75
   tex.print("x= "..x)
   local y = (2*x-65)/5
   tex.print(" and y = "..y)]]
   print(foo)
}
```

在這個例子中，我們使用長括號方法來建立一個字串變數 `foo` ，其目的是保存由展開 `[[` 和 `]]`與 `print(foo)`.

之間所有內容所產生的 Lua 程式碼字串。該字串會透過 Lua 函式呼叫輸出到主控台。 `foo` 到 `.log` 檔案的內容寫入時，你也可以在 Overleaf 上看到類似的結果，方法是使用 LuaTeX 的 Lua 函式 `texio.write()`:

```
\directlua{
   local foo=[[local x=\number\count75
   tex.print("x= "..x)
   local y = (2*x-65)/5
   tex.print(" and y = "..y)]]
   texio.write(foo)
}
```

## \directlua 記號清單中的記號：不可展開記號與未展開記號

我們已經指出 `\directlua{⟨程式碼⟩}` 執行 *完全展開* 你的 `⟨程式碼⟩`的前處理：它會移除並展開所有可展開命令，直到只剩下不可展開記號。由 `\directlua`處理所產生的記號序列（在 `scan_toks()` 函式中）會被串接成一個記號清單，而其中各個記號之後會被轉回文字，以便傳送給 Lua。

然而，我們尚未處理這個故事的最後一部分，因為我們需要考慮兩種可以進入由 `\directlua`內部正在建構的記號清單的命令記號「類別」：我們稱它們為 *簡寫命令記號* 和 *未展開* 記號：

* **簡寫命令記號**：這種類型的命令記號，源自於使用下列某個 TeX 基本命令所定義的控制序列： `\chardef`, `\mathchardef`, `\countdef`, `\dimendef`, `\skipdef`, `\muskipdef` 和 `\toksdef`這些基本命令用來定義代表數值的控制序列——結果得到的控制序列是 *不* 可展開的。
* **未展開記號**：這種記號類型源自於通常本來會被展開，但 `\directlua` 已經被以下方式之一明確指示
* 其展開 *不* ；例如，透過以下命令抑制展開： `\noexpand` 或 `\unexpanded`——我們稍後會說明這是如何完成的；
* 透過處理序列而插入的記號 `\the\toks` （下文會進一步說明）。

### \directlua 記號清單中的兩「組」記號

根據我們的討論，我們可以說，在 `\directlua`前處理的第一階段（在 `scan_toks()` 函式中）建構的記號清單裡，記號可分成兩組：

1. *本質上不可展開的* 記號

* 任何代表非活動 *字元*;
* 任何代表不可展開 *基本命令的* *指令*;
* 任何代表一個 *簡寫命令* 的記號（這些不可展開，見下文）。

3. *未展開* 記號：

* 任何代表一個可展開命令，而其展開已在 *被抑制* （或避免）於 `\directlua`的前處理期間。

#### 簡寫命令記號：建立不可展開命令

如前所述，TeX 引擎提供一組基本命令（內建命令），可用來建構 *不可展開的* 控制序列（此處以 `⟨command⟩`表示）。這些基本命令的形式如下：

* `\chardef ⟨command⟩ = ⟨numeric value⟩`
* `\mathchardef ⟨command⟩ = ⟨numeric value⟩`
* `\countdef ⟨command⟩ = ⟨numeric value⟩`
* `\dimendef ⟨command⟩ = ⟨numeric value⟩`
* `\skipdef ⟨command⟩ = ⟨numeric value⟩`
* `\muskipdef ⟨command⟩ = ⟨numeric value⟩`
* `\toksdef ⟨command⟩ = ⟨numeric value⟩`

其中 `⟨numeric value⟩` 是適用於各個命令的某個整數值。

在這裡，我們將簡要回顧 `\chardef` 的用法，以示範這些基本命令的關鍵特徵——產生一個 `⟨command⟩` ，而且它是不可展開的。你可以使用 ``\chardef\mydollar=`\$`` 來建立控制序列 `\mydollar` ，並用它來排版一個 `$`:

`I paid \mydollar30.`

這會排版成 `I paid $30.` 控制序列 `\mydollar` 是由 `\chardef` 建立的，並不可展開，這可從以下例子看出：

```
\chardef\mydollar=`\$
\directlua{
   local x =[[I paid \mydollar30.]]
   texio.write(x)
}
```

這會在 `.log` 檔案

`輸出下列文字：`

這顯示 `\mydollar` 是 *不* 在 `\directlua`的前處理期間被展開。 `\mydollar` 在命令記號被轉換成文字表示時，會在其後加入的空白字元。

當你使用 `\chardef` 建立控制序列時，TeX 對該控制序列（命令）的內部分類會使它變成 *不可展開的* ，這與以巨集定義命令 \def、\edef、\gdef 或 \xdef 之一所定義的控制序列有非常不同的行為。如上所述，在建構其記號清單的過程中， `\directlua` 會檢查每個進來的命令記號以確認其可展開性。如果某個命令記號不可展開，它就會直接通過進入記號清單，而其文字表示稍後會在記號清單轉回文字時，重新出現在產生的 Lua 程式碼字串中。

**關於 plain TeX 與 LaTeX 的簡短說明**

從歷史上看，Knuth 最初的 plain TeX 定義了常用的控制符號 `\%`, `\&`, `\#` 和 `\$` ，使用 `\chardef`——不是使用標準的巨集定義命令之一 `\def`, `\edef`, `\gdef` 或 `\xdef`時將其指定為「語言名稱」。例如：

```
   \chardef\#=`\#
   \chardef\$=`\$
   \chardef\%=`\%
   \chardef\&=`\&
```

奇怪的 `` `\ `` 語法是 TeX 用來取得數字字元碼值的方法。在舊的 plain TeX 體系中，這些控制符號不可展開（因為 `\chardef`），但 LaTeX（或套件）可能會將其中一些重新定義為 *巨集* 以提供更強大的功能——那就會使它們可展開，因此你可能需要留意這一點。

**這會如何影響 \directlua？**

讓我們比較下列程式碼在 plain TeX 與 LaTeX 下執行的結果。為求簡單，我們會把結果寫入 `.log` 檔案，使用 LuaTeX 的 Lua API 函式 `texio.write()`.

```
\directlua{
   local x=[[\$150 for the "\#1" product---20\%! more than its competitor, Widget \& Co.]]
   texio.write(x)
}
```

使用 **plain TeX** 執行這段程式碼，會在 `.log` 檔案中產生以下輸出，顯示任何展開的結果：

```
\$150 for the "\#1" product---20\%! more than its competitor, Widget \& Co.
```

很明顯，在 plain TeX 下，這些控制符號都沒有被展開——因為它們都是使用`\$`, `\#`, `\%` 或 `\&` 建立的 `\chardef`.

使用 **LaTeX** 文件執行這段程式碼：

```
\documentclass{article}
\begin{document}
   \directlua{local x=[[\$150 for the "\#1" product---20\%! more than its competitor, Widget \& Co.]] texio.write(x)}
\end{document}
```

執行這段程式碼，會在 `.log` 檔案

```
\protect \TU\textdollar 150 for the "\#1" product---20\%! more than its competitor, Widget \& Co.
```

很明顯，在 LaTeX 下執行會產生與 plain TeX 不同的結果，因為在 LaTeX 中命令 `\$` 已被展開，表示它是一個巨集。

**注意：** 在 plain TeX 和 LaTeX 中 `\directlua` 都沒有完全處理任何控制符號 `\%`, `\&`, `\#` 和 `\$` 來產生對應的字元。於 `\directlua` 所進行的展開過程中，代表這些控制符號的記號——或者對 LaTeX 來說，它們的展開結果——會直接通過進入正在建構的主記號清單。

**注意：** 控制符號是由單一個非 11 類別碼字元所構成，例如 `\#`。當代表控制符號的記號被轉回其文字表示時，TeX 引擎不會在那段文字後面插入空白字元。這種特殊處理是 TeX 引擎運作方式中的內建規則。

### 未展開記號：抑制展開

`\directlua`’ 的前處理是一個範例，說明 TeX 引擎正在執行展開，但你可能想要 *防止* 避免將展開套用到一個或多個原本會被展開的 token。再舉另一個例子，LuaTeX（以及所有 TeX 引擎）會執行一個展開程序，類似於 `\directlua`，當它們處理 `\write` 命令：

`\write file-number {⟨material⟩}`

\write 指示 TeX 引擎輸出 `⟨material⟩`——通常包含 TeX/LaTeX 指令——到一個文字檔（`file-number`）；其中任何可展開指令在 `⟨material⟩` 中，除非被阻止，否則會在之前被展開 `⟨material⟩` 實際寫入該檔案之前。

如你所料，TeX 引擎提供用來抑制或控制展開的指令：

* `\noexpand⟨token⟩`：防止單一 `⟨token⟩`;
* `\unexpanded{⟨material⟩}`：防止其中所有可展開指令（token）的展開 `⟨material⟩`。實際上，它是以下命令的多 token 版本： `\noexpand`;
* `\protected`：加在巨集定義前的前綴，可在某些情況下（例如在 `\directlua`, `\write` 或 `\edef`).

儘管名稱看起來另有含意，兩者 `\noexpand` 和 `\unexpanded` 已 *可展開命令* 和 都很好地示範了，將 TeX 引擎的展開程序視為執行「token 操作」：這裡的操作是防止一個或多個後續 token（指令）被展開。因為 `\noexpand` 和 `\unexpanded` 兩者都是可展開指令，因此在以下階段中會被移除並處理（執行）： `\directlua`的前處理過程中，當它從你的 `⟨程式碼⟩`.

#### \noexpand ⟨token⟩

`\noexpand ⟨token⟩` 防止單一 `⟨token⟩`. `\noexpand` 在 `\directlua` 會被展開（從輸入中移除），並以其「展開行為」的結果取代。展開 `\noexpand` 的結果是建立一個特殊（隱藏的） `⟨marker token⟩` ，它會被放在原本的 `⟨token⟩` 之前，而原本的 token 的展開將被抑制：這個 `⟨marker token⟩` 充當一個旗標，表示「不要展開下一個 token」。因為 `\directlua` 正在執行完整展開，因此它會重新處理任何由可展開指令的「展開行為」所產生的 token。因此，當 `\noexpand ⟨token⟩` 的展開完成後，LuaTeX 會回頭讀取結果，並看到由兩個 token 組成的序列 `⟨marker token⟩⟨token⟩` 這會使原本的 `⟨token⟩` 以未展開的狀態通過，進入正在由 `\directlua`.

**範例**

如果我們寫

```
\directlua{
   local x= "\TeX"
}
```

該 `\TeX` 巨集會展開成其組成的 token，而在 plain TeX 中，這會導致以下文字被傳遞給 Lua（注意：Lua 無法處理這段程式碼；這只是用來示範流程的範例）：

`local x = "T\kern -.1667em\lower .5ex\hbox {E}\kern -.125emX"`

如果我們 *抑制* 對以下項目的展開 `\TeX` 巨集，使用 `\noexpand`

`\directlua{local x= "\noexpand\TeX"}`

就會產生以下 Lua 程式碼（同樣地，Lua 無法執行這段程式碼；它只是用來示範 `\noexpand`):

`local x= "\TeX "`

由於 `\noexpand`, `\directlua` 不會展開 `\TeX` 而只是讓代表 `\TeX` 指令的 token 值原封不動地通過，進入在第一階段中正在建立的 token 清單。 `\directlua`的前處理期間。

**注意：** 出現在之後的空白字元 `\TeX` 是由 LuaTeX 隨後將 `\TeX` 整數 token 值轉回其文字表示時所引入的（在 `tokenlist_to_cstring()` 函式中）。

#### \unexpanded{⟨material⟩}

`\unexpanded` 是一個可展開指令，可抑制由 `⟨material⟩`所形成之所有 token 的展開。如我們先前所指出，當 TeX 引擎執行展開時，任何可展開指令都會 *命令（記號），它會被* 從輸入中移除，並 *，並由它產生的字元記號序列* 以其「展開行為」的結果取代它；那麼這對 `\unexpanded`而言實際上代表什麼呢？通常，在 *完全展開*中，一旦某個指令的展開程序完成，TeX 引擎就會繼續讀取／處理由該指令「展開行為」所產生的任何 token——它需要進一步展開任何產生出來的 token。然而， `\unexpanded` *會繞過* 任何進一步的展開：它是這樣做到的。

在 TeX 引擎內， `\unexpanded` 指令首先會將其中的字元和指令轉換為 `⟨material⟩` 一個暫時的 token 清單，由 *未展開* 個 token 組成。當所有 token 都建立並儲存在該暫時 token 清單後， `\unexpanded` 指令會使 `\directlua` 改為 *跳過* 回頭讀取並處理它們——即使 \directlua 正在執行完整展開。相反地，這些 *未展開* token 會直接通過，並被納入正在由 `\directlua` （在 `scan_toks()` 函式中）。 `⟨material⟩` 會被轉換為 token，而該組 token 的展開程序會被跳過。 `\unexpanded{⟨material⟩}` 與使用 `\the\toks`，我們在下文會討論。

**範例**

`\unexpanded` 產生結果的方式類似於 `\noexpand` 只是它可以防止多個 token 的展開；以下是範例：

```
\directlua{
   local x = "\unexpanded{\foo\bar\foobar}. But Lua can't process this code!"
}
```

這會產生以下文字，作為 Lua 的程式碼：

`local x = "\foo \bar \foobar . But Lua can't process this code!"`

**注意**：每個指令名稱後都有空白字元。這同樣是 LuaTeX 隨後將未展開的 token `\foo`, `\bar` 和 `\foobar` 轉回文字，發生在 `tokenlist_to_cstring()` 函式中。

#### \protected 巨集定義

該 `\protected` 指令是加在巨集定義前的前綴，用來防止該巨集在 TeX 建立展開後的 token 清單時被展開，例如由 `\directlua`的前處理期間。

**範例**

假設你分別有使用和未使用以下 `\protected` 前綴來定義以下巨集：

```
\def\macroA{"This unprotected macro contains a string"}
\protected\def\macroB{"This protected macro also contains a string"}
```

如果你使用 Lua 的字串串接運算子（`..`）來寫出

```
\directlua{
   local x=\macroA..\macroB
}
```

`\directlua`的前處理會產生以下要傳遞給 Lua 的程式碼：

`local x="This unprotected macro contains a string"..\macroB`

`\macroA` 並未使用 `\protected` 來定義，因此它會被展開，產生要串接的字串第一部分，但 `\macroB` 是使用 `\protected` 來定義，所以它並未被展開。

在前處理期間，LuaTeX 的 `scan_toks()` 函式會為 `\macroA`建立一個 token，並辨識出它是一個一般的可展開指令，於是將其展開：該展開會產生一串字元 token，每個字元對應一個字元 token，分別對應於 `"This unprotected macro contains a string"`。每個字元 token 都會被傳遞並加入正在建立的 token 清單。

當 `scan_toks()` 會為以下項目建立 token： `\macroB` 它注意到該指令是以 `\protected` 來定義，因此不會展開它：代表 `\macroB` 的 token 會原封不動（未展開）地通過，進入正在建立的 token 清單。當該 token 清單建立完成後，前處理的下一階段，在 `tokenlist_to_cstring()` 函式中，會將 token 清單中的所有 token 轉回其文字表示。代表 `\macroB` 的未展開 token 會被偵測並轉換成其文字表示，因而產生文字 `\macroB` 出現在要傳給 Lua 的程式碼中。請注意，Lua 實際上無法串接 `"This unprotected macro contains a string"..\macroB` 來產生最終字串，因為 `\macroB` 在 Lua 語法中毫無意義，因此會導致錯誤 `在 '\' 附近出現非預期的符號`.

**小知識**： `\protected` 指令是由 $$\varepsilon\text{-}\mathrm{\TeX}$$引入的，它是 Knuth 原始 TeX 軟體的第一個重大擴充，並且所有程式碼系譜包含 $$\varepsilon\text{-}\mathrm{\TeX}$$.

### 未展開的 token：在 \directlua 中使用 \the\toks

程式設計生活若沒有那些需要處理的「特例」以及 `\the` 與……搭配使用 `\toks` 在一個 `\directlua` 指令就是這樣的一個特例。

#### \toks 的簡要背景

TeX 原始命令 `\toks` 指示 TeX 引擎將一些 token 儲存起來以供稍後使用：這些 token 不會被繼續處理，而是被放到一旁，並儲存在一個透過 *token 暫存器*指定的記憶體位置中。例如，我們可以告訴 TeX 引擎建立一些 token 並將它們存入 token 暫存器位置 `100` ，使用

`\toks100={Hi, \TeX! \hskip 5bp}`

在這裡，TeX 使用 token 暫存器 `100` 來存取其記憶體中的一個已知位置：一個用來儲存 token 清單的儲存區。

代表介於以下兩者之間所有內容的 token `{` 和 `}` 會被建立， *但不會展開*，並串接成一個 token 清單——類似於本文稍早探討過的 token 清單。若要重複使用那些 token，我們會寫 `\the\toks100` 其中 `\the` （一個可展開指令）會指示 TeX 取回已儲存的 token，並將它們插入到你寫下 `\the\toks100`的位置。另一種理解方式是 `\the\toks` 會讓 TeX 在該位置插入一些 token。

該 `\toks` 指令 *不會展開* 它被要求建立並儲存的任何 token：它只是將介於之間的字元和指令轉換為 `{` 和 `}` token 並將其儲存。

#### 回到 \directlua

在討論展開時，我們注意到 `\directlua{⟨程式碼⟩}` 執行 *完全展開* 的 token 值 `⟨程式碼⟩`：移除所有可展開指令，並以其展開行為的結果取代它們——持續 *進一步展開* 任何由可展開指令的初始展開所產生的 token。

`\the` 是一個可展開指令，因此 `\directlua` 會將其展開；然而，當 `\the` 與……搭配使用時 `\toks` 在 `\directlua`，例如 `\the\toks⟨token register⟩`，插入的 token 會 *不再進一步展開*。展開 `\the\toks⟨token register⟩` 會注入由 *未展開* 儲存在 `⟨token register⟩`中的 token 序列，直接注入到正在由 `\directlua`：這種行為繞過了通常的完整展開程序。實際上，那些 token 會通過， *未展開*，並被納入正在由 `\directlua`——這種未展開 token 的通過流程，其運作方式類似於 `\unexpanded`，如前所述。

**範例**

假設我們定義巨集 `\mymacro` 寫成 `\def\mymacro{\TeX}`。它只包含一個對應於 `\TeX` 指令的 token（它是一個巨集）：因此我們有一個可展開指令 `\mymacro` ，其中又包含另一個巨集 `\TeX`，而它同樣可展開。

以下程式碼會讓 Lua 嘗試建立一個字串變數 `x`:

```
\def\mymacro{\TeX}
\directlua{
   local x="\mymacro"
}
```

在 \\`directlua`中，代表 `\mymacro` 的 token 會被展開，但那會導致另一個可展開 token， `\TeX`接著還會再被進一步展開。在 plain TeX 中，這些展開會產生以下文字，並傳遞給 Lua：

`local x = "T\kern -.1667em\lower .5ex\hbox {E}\kern -.125emX"`

這段程式碼試圖定義一個字串，其中包含代表以下項目展開後版本的文字： `\TeX` 巨集。如果你嘗試執行這個範例，Lua 會嘗試建構該字串，但會失敗，並產生錯誤：

`在 ' "T\k' 附近有無效的跳脫序列。`

本文後面我們會探討「無效的跳脫序列」的意義。

現在讓我們對照 `\mymacro` 的使用與將 `\TeX` token 放入由 `\toks` 命令：

```
\toks100={\TeX}
\directlua{
   local x="\the\toks100"
}
```

LuaTeX 的 `\directlua` 處理會為 Lua 產生以下字串：

`local x = "\TeX "`

在之後的空白字元 `\TeX` 是由 LuaTeX 的指令 token 轉字串轉換程序所產生。

**但請注意**： `\TeX` 巨集已 *不* 被展開成其組成 token。 `\the\toks100` 使儲存在暫存器 100 中的 token 被插入，但僅止於此：它們 *不* 不會再進一步展開，並被納入正在由 `\directlua` （在函式 `scan_toks()`中）。將 token 放入由 `\toks` 所建立的 token 清單，是防止 token 被展開的另一種方式。

如果我們執行這個範例，它也會產生錯誤：

`在 ' "\T' 附近有無效的跳脫序列。`

我們會在本文後面探討 Lua 的跳脫序列。

## 展開中使用的其他指令／技術

在本節中，我們看看一些額外的 TeX 指令／方法，它們在套用展開的情況下會很有用（例如在 `\directlua`).

### \string ⟨token⟩

`\string` 是一個可展開指令，會將 ⟨token⟩ 轉換為一串字元 token，每個 token 的類別碼都是 12。

例如， `\string\TeX` 會產生一串 4 個字元 token `\\`, `T`, `e` 和 `X` ，其中每個字元都被指定為類別碼 12（包括前導的 `\\` 字元）。

如果我們寫

```
\directlua{
   local x="I will use \string\newcommand"
   print(x)
}
```

該 `\string` 指令會被展開，產生一串類別碼為 12 的字元 token。在 `\string` 被展開後，所產生的字元 token（代表中的每個字元）會 `\newcommand`）會被納入正在由 `\directlua`。一旦 `\directlua` 完成主 token 清單的建構後，其組成的 token 會被轉回文字表示，從而產生以下要傳遞給 Lua 解譯器的程式碼：

`local x="I will use \newcommand" print(x)`

當這段程式碼傳給 Lua 時， `print(x)` 會將字串輸出為 `x` 到螢幕（主控台）。然而，我們稍微動了點手腳，刻意使用了一個以 `\n`開頭的範例指令。如果你能在本機 TeX 安裝上執行這個範例，你會注意到 Lua 會將以下文字輸出到螢幕：

```
   我將使用
   ewcommand
```

若要在 Overleaf 上執行這段程式碼，你可以指示 LuaTeX 直接寫入 `.log` 檔案，使用 LuaTeX 的 Lua API 函式 `texio.write(*string*)`:

```
\directlua{
   local x="I will use \string\newcommand"
   texio.write(x)
}
```

如果你檢查產生的 `.log` 檔案，你會看到它也包含

```
   我將使用
   ewcommand
```

這個意外輸出是因為 Lua 將 `\n` 在開頭的 `**\n**ewcommand` 解讀為換行字元（字元代碼 10）的跳脫序列：它會假設你想要開始一行新的文字，而這一行以 `ewcommand`開頭。本文後面我們會討論 Lua 的跳脫序列。

### \detokenize{⟨material⟩}

`\detokenize` 在效果上，是以下命令的多 token 版本： `\string` 而它同樣是一個可展開指令，會將其中的一切轉換為 `⟨material⟩` 一串類別碼為 12 的字元 token——*但* 空白字元（ASCII/Unicode 值 32）則會取得類別碼 10。 `\detokenize` 還會在命令名稱後面插入一個尾隨空白字元，當命令名稱是 *控制字* （例如， `\foo`）時適用），但在 *控制符號* （例如， `\#`, `\%` 等等）。

### 範例

即使這些巨集 `\foohoo`, `\foo`, `\bar` 和 `\foobar` 尚未定義，如果你這樣寫：

```
\directlua{
   local x = "\string\foohoo\detokenize{\foo\bar\foobar}"
}
```

它會產生以下文字，作為要傳給 Lua 解譯器的程式碼：

`local x = "\foohoo\foo \bar \foobar "`

如果你不使用 `\string` 和 `\detokenize` 並寫下：

`\directlua{local x = "\foohoo\foo\bar\foobar"}`

`\directlua` 會處理 `\foohoo`，辨識出它是一個指令並嘗試展開它；但由於 `\foohoo` 未定義，這會導致錯誤：

```
   ! 未定義的控制序列。
   l.1 \directlua{local x = "\foohoo
                      \foo\bar\foobar"}
         ?
```

因為 `\string` 和 `\detokenize` 將它們的引數轉換為一串字元 token， `\directlua`的展開程序確實有機會偵測可展開指令的 token `\foohoo`, `\foo`, `\bar`、或 `\foobar`：它們在有機會觸發展開之前，就已經被轉換成一串字元 token。

如前所述，指令的展開涉及將其從輸入中移除，並以其「展開行為」的結果取代它。展開的結果（通常是 token）接著會被 TeX 引擎讀取。在這裡， `\string` 和 `\detokenize` 的「展開行為」是從輸入中吸收字元和指令 token，並將它們轉換成一串字元 token，起初會儲存在一個暫時的 token 清單中，而 `\directlua` 隨後會讀取它。那些字元 token 會被納入正在由 `\directlua`.

下圖說明了 `\string` 如何將 `\foohoo` 指令轉換為一串字元 token，產生一個暫時的 token 清單，隨後由 `\directlua` 讀取，並將那些字元 token 納入正在建構的主 token 清單。

![](/files/50b8dba8e15d54898c1a05d9f7734ee1be3bff8f)

如果 `\string` 或 `\detokenize` 在其引數中遇到字元，例如： `\string a` 或 `\detokenize{abc}` 那些字元（此處的類別碼為 11）會產生字元 token，但其類別碼會是 12。

附註：

如果我們回到上面的範例：

`\directlua{local x = "\string\foohoo\detokenize{\foo\bar\foobar}"}`

這會產生以下文字，作為要傳給 Lua 解譯器的程式碼：

`local x = "\foohoo\foo \bar \foobar "`

我們可以觀察到以下幾點：

* `\detokenize` 在每個巨集名稱後都插入了一個空白字元，但 `\string` 沒有。
* `\string` 只作用於單一 token。
* 在字串 `"\foohoo\foo \bar \foobar "` 用來定義 `x` 我們將再次遇到 Lua 的跳脫字元機制（如下文所述）：

  * `\bar` 以 `\b` 開頭，這是 Lua 用來表示 [退格字元](https://en.wikipedia.org/wiki/Backspace) （字元代碼 8）；
  * 命令 `\foohoo`, `\foo` 和 `\foobar` 全部都以 `\f`開頭，這是 Lua 用來表示 [換頁字元](https://en.wikipedia.org/wiki/Page_break#Form_feed) （字元代碼 12）。

  因為字元序列 `\b` 和 `\f` 會在使用雙引號建立的字串中使用 `"..."` 它們會產生不想要的結果，除非採取措施，使用 Lua 所謂的 *長括號* 字串方法來加以避免：這個主題我們現在可以連同 Lua 的跳脫序列一起討論。

## 什麼是「Lua 跳脫序列」？

程式語言會保留某些字元作為語言語法中的「特殊用途」：實際上，這些字元被定義為具有某種特殊意義。然而，有時你需要暫時「關閉」某個字元的特殊意義，例如當你希望該字元被嵌入到較長的字串中，而它的標準行為會導致語法錯誤時。從本質上說，該字元需要被處理 *沒有* 而不觸發其標準解讀——悄悄地通過，不被察覺。為了做到這一點，程式設計師會使用一種稱為 *跳脫* 的方法，其中一個「特殊字元」會以其所謂的 *跳脫序列*.

來表示。一個標準範例（Lua 也支援）是在字串中使用雙引號，並用跳脫序列 `\"`:

`對字串內部的雙引號進行跳脫。`

Lua 語言提供了多種處理跳脫序列的機制：

* 標準序列，包括 `\n` （換行）， `\r` （歸位）， `\\` （反斜線）， `\"` （雙引號）， `\t` （水平定位字元）， `\v` （垂直定位字元）以及 `\'` （單引號）；
* `\xXX`，其中 `XX` 是恰好兩個十六進位數字的序列；
* `\ddd`，其中 `ddd` 是最多三個十進位數字的序列；
* 在本文撰寫時（2019 年 8 月），最新版本的 LuaTeX 雖然尚未在 Overleaf 上提供，但它使用的是 Lua 5.3 版，該版本引入了對 UTF-8 跳脫序列的支援： `\u{XXX}`。這種跳脫機制適用於 UTF-8 編碼的 Unicode 字元，其中 `XXX` 是由一個或多個十六進位數字組成的序列，代表該字元的碼位。請注意，外層括號 `{ }` 是必要的。

### 控制跳脫序列

傳統上，字串是用雙引號來定義的，如 `"this is a string"`；在這樣的字串中你可以使用跳脫序列： `"this is a string.\nI'll now start on a new line."`。然而，Lua 還有第二種而且 *非常* 方便的字串定義機制：其所謂的 *長括號* 機制，透過將文字包在 `[[` 和 `]]`:

`[[I am a long brackets string]]`

在使用長括號方法建立的字串中，Lua 的字元跳脫機制是 *關閉的*：跳脫序列會被當作一般字元。舉例來說，在字串

`[[I am a long brackets\n string]]`

該 `\n` 中，跳脫序列不會被視為單一的歸位字元（ASCII 碼 13），而是兩個一般字元： `\\` 後面接著 `n`.

### 為什麼長括號字串如此有用？

如我們稍後將探討的，LuaTeX 提供了一套專門的內建 Lua 函式，你可以用 `\directlua` 來控制 LuaTeX 的排版行為。在這些眾多函式中，有一個叫做 `tex.print(*string*)` 的函式，允許你把 `*string*` 內容從 Lua 程式碼傳回 LuaTeX 進行排版。一個非常簡單的例子是：

`\directlua{tex.print("Hello, World!") }`

這會讓 LuaTeX 排版 `Hello, World!`

該 `*string*` 用於 `tex.print(*string*)` 也可以包含代表 TeX 與 LaTeX 指令的文字，供 LuaTeX 處理。然而，TeX/LaTeX 指令以一個 `\\` 字元開頭，而這對使用雙引號建立的字串來說是有問題的，因為 Lua 會嘗試解析該字串，偵測到開頭的 `\\` 字元，並將其解釋為跳脫序列的開頭。當 Lua 嘗試處理這個跳脫序列時，通常會失敗，因為開頭的 `\\` 與許多 TeX/LaTeX 指令名稱中的第一個字元組合起來，不會形成 Lua 所認識的有效跳脫序列。例如，當處理像這樣的字串時 `"I like \LaTeX"` Lua 會看到 `\L` 並以「無效的跳脫序列」錯誤失敗，而這就是上述錯誤的原因。

#### 長括號字串來救援！

建立（定義）字串的長括號方法非常有用，因為即使 TeX/LaTeX 指令以一個 `\\` 字元開頭，長括號字串方法也會停用（關閉）Lua 的跳脫序列機制。以下是一個簡短範例，請記住，我們需要避免巨集被展開，例如透過 `\protected` 或 `\noexpand`.

假設我們定義一個 `\newtest` 巨集如下

`**\protected**\def\newtest#1{The argument: #1}`

並在 `\directlua` 中使用它，搭配 LuaTeX 的 Lua API 函式 `tex.print()`:

```
\directlua{
   tex.print("\newtest{Hello}")
}
```

由於使用了 `\protected`，巨集 `\newtest` 不會被展開，結果傳給 Lua 的文字如下：

`tex.print("\newtest {Hello}")`

加在 `\newtest` 之後、左大括號（`{`）之前的空白字元，是 `\directlua`將命令記號轉回文字表示形式時所造成的副作用。

這段程式碼會傳給 Lua，接著由 Lua 執行 LuaTeX 函式 `tex.print()` 但會出現一個問題，其表現方式取決於你使用的字型。在 Overleaf 上的 LaTeX 中，你會看到類似這樣的輸出：

![](/files/10c9122c13080bafb639e15503924313f722df80)

同時在日誌檔中還會看到警告：

```
   Missing character: There is no
   （U+000A）in font [lmroman10-regular]:+tlig;!
```

在純 TeX 中，你可能會看到看起來像這樣的輸出：

![](/files/87133adcd1c9ffa73a2845b89d726a884824fb3e)

在這兩種情況下， `\newtest` 巨集都沒有被呼叫，而且輸出並非我們所預期。錯誤是由 Lua 的跳脫字元機制引起的：在文字 `\newtest {Hello}` 中，巨集名稱以 `\n` 開頭，而 Lua 將其識別為換行字元的跳脫序列，因此會把 `\n` 替換為 ASCII 字元 10，或十六進位的 0A。在 LaTeX 的錯誤訊息中， `U+000A` 是以 4 個十六進位數字表示 Unicode 值的一種方式。

由於該 `\n` 被轉換成換行字元後，LuaTeX 看不到巨集呼叫，而是認為它被要求排版某些以 ASCII 字元代碼 10 開頭的文字：

`⟨ASCII 10⟩ewtest {Hello}`

視所使用的字型而定，LuaTeX 可能可以，也可能無法排版 `⟨ASCII 10⟩` 字元，但其餘文字會原樣輸出，而 `{` 和 `}` 會被視為一個群組而不列印。

純 TeX 會得到不同的結果，因為預設字型是 Computer Modern Roman，它使用一種奇特的編碼，導致在看到字元代碼 10 時會排版出一個大寫 Omega。

為了避免這些問題，我們需要使用長括號字串，以避免 Lua 的跳脫機制被套用。使用

`\directlua{tex.print([[\newtest{Hello}]])}`

可以得到下方截圖所示的結果：

![](/files/54e38c53d2d68b0ef7530ef248ed3827671ce924)

### 展開與不可展開命令的不執行

在討論展開時，我們注意到這是一個 TeX 引擎會 *移除* 目前輸入中的可展開命令（記號），並 *將其替換為* 該可展開命令所產生結果的過程。由於 \directlua 正在進行 *僅展開* 活動（以產生一個記號清單），因此它 *不會* 不會讓 LuaTeX 的處理再往前進一步。一旦可展開命令被讀入並完全展開，該展開的結果——其中經常包含不可展開命令（記號）——就會被納入正在建立的記號清單，準備再轉回文字傳給 Lua。

這裡有一個重要原則：在 *僅展開* 旨在產生記號清單的活動中，TeX 引擎，包括 LuaTeX， *不會執行* 任何不可展開的基本內建 TeX 命令。

在 `\directlua{⟨程式碼⟩}`的情況下，如果你的 `⟨程式碼⟩` 完全展開後產生，或包含不可展開的 TeX/LaTeX 命令，它們 *會被傳送到 Lua* （以文字表示）。

#### 範例

以下是一個範例，說明在僅展開處理期間（例如在 `\directlua`中）不可展開的基本命令不會被執行。假設我們定義一個巨集 `\setcountreg` 如下：

`\def\setcountreg#1#2{\count#1=#2\relax}`

**注意**：我們使用 `\relax` 參數 `#2` 之後，用來避免 LuaTeX 在搜尋要與參數 `#2`.

比對的數值（引數）時過度前進。 `\directlua`如果在

```
   之外，我們稍後這樣執行巨集
   The value in count register 100 is \the\count100.
```

它會輸出

`The value in count register 100 is 50.`

在這種情況下，任何 TeX 引擎都會處理這個巨集 `\setcountreg`——展開巨集、決定引數，並繼續讀取 *以及作用* （執行）巨集替換文字（定義）中包含的命令。此處的結果是將 `50` 作為儲存在暫存器 `\count100`.

然而，當 TeX 引擎正在進行 *僅展開* 活動時，如同在 `\directlua`中那樣，它 *不會執行* 巨集定義中包含的不可展開命令。

如果我們寫

```
\def\setcountreg#1#2{\count#1=#2\relax}
\directlua{
   local x = [[\setcountreg{100}{50}]]
}
```

它會產生以下文字作為 Lua 的程式碼：

`local x = [[\count 100=50\relax ]]`

上面的 Lua 程式碼顯示，在 `\directlua` 該 `\setcountreg` 中，已經完成展開，其引數已被辨識並替換到適當的參數（`#1` 和 `#2`）中，但就到此為止：不可展開的基本 TeX 命令 `\count` 是 *未被執行* 於 `\directlua`的展開處理期間。

然而，如果我們把產生的字串 `x` *傳回 LuaTeX* 經由 `tex.print(x)` 像這樣

```
\count100=50 % 將 \count100 設為初始值 50
\def\setcountreg#1#2{\count#1=#2\relax}
\directlua{
   local x = [[\setcountreg{100}{250}]]
   tex.print(x)
}
The value stored in count register 100 is \the\count100.
```

之後， `\directlua` 完成後，輸出會是

`The value stored in count register 100 is 250.`

顯示計數暫存器 `100` 現在確實包含值 `250`.

以上範例產生的 Lua 程式碼是

`local x = [[\count 100=250\relax ]] tex.print(x)`

這段程式碼定義 `x` 為一個使用長括號方法建立的字串，用來避免錯誤跳脫序列所造成的錯誤。如果我們改用雙引號 `"..."` 來定義 x，字元組合 `\c` 在開頭的 `\count` 就會觸發錯誤： `invalid escape sequence near ' "\c'`.

LuaTeX 的 Lua API 呼叫 `tex.print(x)` 會讓 LuaTeX 執行 TeX 程式碼序列 `\count 100=250\relax` 和 `\count100` 被賦值為 `250` 如排版輸出所示：

`The value stored in count register 100 is 250.`

#### 注意：巨集與 LuaTeX 的 Lua API

在上述範例中我們看到，在 `\directlua`的前處理（展開）期間，LuaTeX 並沒有執行程式碼 `\count 100=250`，其中包含 `不可展開的` 基本命令 `\count`：若要執行（作用）那段程式碼，我們必須 *把它傳回 LuaTeX* 經由 `tex.print()`.

`\directlua` 只是 LuaTeX 正在執行僅展開處理以建立記號清單的一個例子。還有其他命令也會進行類似的展開處理與記號清單產生活動，例如 `\write` 和 `\edef`：那些命令在其展開處理期間也不會執行不可展開的基本命令。一般原則是：TeX 引擎在僅展開處理活動中建立記號清單時，不會執行不可展開的基本命令。

**將我們的巨集重寫為使用 LuaTeX 的 Lua API**

我們可以重寫 `\setcountreg` 巨集，使用一個名為 `tex.setcount()`的 LuaTeX Lua API 函式，從而避免使用 TeX 命令來變更儲存在計數暫存器 `100`:

```
   \def\setcount#1#2{\directlua{tex.setcount(#1,#2)}}
   \count100=50
   計數暫存器 100 包含 \the\count100\par
   \setcount{100}{250}
   計數暫存器 100 現在包含 \the\count100\par
```

這段程式碼將排版出：

```
計數暫存器 100 包含 50
計數暫存器 100 現在包含 250
```

這裡我們使用 `tex.setcount()`，LuaTeX 許多 Lua API 函式之一，來 *直接存取* LuaTeX 的內部資料儲存區，以將值 `250` 放入代表計數暫存器 `100`的記憶體位置。我們實際上是 *繞過了* LuaTeX 標準 TeX 引擎的輸入處理方法：讀入輸入、建立記號以及執行 TeX 基本命令。不過，這裡有一個警示故事：使用 LuaTeX 的 Lua API 函式時，僅展開處理活動 *可能會導致副作用*：對 TeX 引擎內部所儲存值的變更，而這些變更若只使用純 TeX/LaTeX 命令則不會發生。

**範例：意外的副作用**

以下是一個示範 *意外的* 副作用，這些副作用可能會在使用 `\directlua`的巨集中出現。假設我們寫下以下程式碼：

```
\def\dochange{\directlua{tex.setcount(999,12345)}}
\edef\careful{\dochange}
\the\count999
```

執行這段程式碼會排版出 `12345`!

這怎麼可能？我們並沒有 *明確地* 呼叫任何程式碼或巨集來把那個值放入計數暫存器 `999`。還是有嗎？

我們定義了 `\dochange` 取代，改成一個 `\directlua` 命令，它使用 `tex.setcount()` 來儲存值 `12345` 在計數暫存器 `999`中：在 TeX 程式碼裡，它等同於 `\count999=12345`。接著我們使用標準 TeX 基本命令 `\edef` 來定義巨集 `\careful`——觸發這個意外副作用的，是 `\edef` 的使用。

`\edef` 會完全展開其引數：在這裡，它偵測到一個可展開的巨集 `\dochange` 並將其展開。 `\dochange` 巨集使用可展開命令 `\directlua` ，其中包含一個 Lua API 呼叫；因此 `\dochange` 的展開會導致 `\directlua` 的展開，而這會使 `tex.setcount()` 被呼叫，進而改變計數暫存器中的值 `999`.

如果我們重新定義 `\dochange` ，改用 TeX 命令：

```
   Before: count register 999 contains \the\count999.\par
   \def\dochange{\count999=12345\relax}
   \edef\careful{\dochange}
   After: count register 999 contains \the\count999.\par
```

執行這段程式碼會排版出

```
Before: count register 999 contains 0.
After: count register 999 contains 0.
```

很明顯地，對 `\count999`沒有造成任何影響。當 `\edef` 定義 `\careful` 時，它會展開 `\dochange` ，但那個展開只會產生不可展開的 TeX 基本命令：它們只是被 *未被執行* 而不是被執行， *儲存在* 構成 `\careful`.

定義的記號清單中。順帶一提，相同的原則也解釋了為什麼這會產生排版輸出：

```
\def\dochange{\directlua{tex.print("Hello")}}
\edef\careful{\dochange}
```

## LuaTeX 的 Lua API 簡介

如我們所見， `\directlua` 不僅讓你能撰寫傳統的 Lua 程式碼，或 Lua 與 TeX/LaTeX 程式碼的混合體，還提供了一套額外的 Lua 函式（LuaTeX 特有），你可以使用（呼叫）它們來與 LuaTeX 排版軟體的內部運作溝通，或直接控制其內部運作。我們已在本文中使用了多個 Lua 函式， `tex.print()`, `texio.write()`, `tex.setcount()` 而這些函式連同 *許多* 其他函式，都記載於 [LuaTeX 參考手冊](http://www.pragma-ade.com/general/manuals/luatex.pdf) 其中相關函式群組被稱為 *函式庫*.

你可以把這些 Lua 函式視為 LuaTeX 的 Lua API（**一個**pplication **P**rogramming **I**nterface），它們提供了工具，讓你能透過使用 Lua 作為驅動來控制 LuaTeX 的排版行為，進而建構先進的排版與文件工程解決方案。

如前所述，LuaTeX 將其 API 組織成一組組它稱為函式庫的函式：也就是依其目的或動作相關聯的一群函式。每一組函式都旨在讓人能存取 LuaTeX 內部流程、資料結構、資料儲存與排版演算法的特定面向。從內部來看，LuaTeX 是由多個元件組成：軟體函式庫／工具（多數以 C 撰寫），它們不僅構成 TeX 引擎本身，也包含其他子系統，例如 Lua、MetaPost、Kpathsea、FontForge、libpng 與 zlib。這些函式庫被整合起來構成 LuaTeX 可執行軟體的功能，而使用者正是透過 Lua API，取得由這些多個軟體元件整合與協調所衍生出的 LuaTeX 功能。

## 一些範例與陷阱

在本節中，我們提供一些進一步的範例，運用本文中提供的主題、概念與解釋。

### 使用波浪號字元（\~）

Lua 語言使用 `~` 字元（稱為波浪號）作為其語法的一部分，包括用於執行「不等於」測試的語法；例如，要測試某個變數 `x` 不等於 `4` ，我們可以寫：

```
   local x=3
   if x ~= 4 then
   print("x 不等於 4")
   end
```

如果我們嘗試透過 `\directlua`:

```
\directlua{
   local x=3
   if x ~= 4 then
   print("x 不等於 4")
   end
}
```

就會得到一個錯誤：

`[\directlua]:1: 'then' expected near '\'.`

這很奇怪，因為我們的程式碼是正確的：我們已經使用了 `'then'` 而且我們的程式碼中也沒有 `\\` 字元，所以到底哪裡出錯了？要理解這一點，我們必須記得，對 TeX/LaTeX 而言， `~` 通常被定義為一個「特殊字元」，其類別碼為 13：所謂的 active characters，也就是迷你巨集，因此會受到展開。當 `\directlua` 偵測到 `~` 字元時，它會被 *移除* 從輸入中移除，並 *中過濾掉，* 其展開結果所取代。使用純 TeX 時，LuaTeX 所產生並傳給 Lua 解譯器的結果文字（程式碼）實際上並不包含 `~` 字元，而且是：

`local x=3 if x \penalty \@M \ = 4 then print("x 不等於 4") end`

該 `~` 字元已經 *命令（記號），它會被* 和 *展開* 成其構成指令——上面的 Lua 程式碼是由純 TeX 對這個主動字元的定義所產生的 `~`。現在我們可以看出為什麼 Lua 會回應這個錯誤： `'then' expected near '\'`——它開始解析這段程式碼，但遇到了單字 `\penalty` ，這對 Lua 毫無意義，於是產生語法錯誤。

要修正這個問題， `~` 字元在 `\directlua` 正在處理你的程式碼時，需要具有安全的類別碼；例如，我們可以暫時將 `~` 的類別碼改成 11（字母），方法是把程式碼包在一個群組中：

```
\begingroup
\catcode`\~=11
\directlua{
   local x=3
   if x ~= 4 then
   print("x 不等於 4")
   end
}
\endgroup
```

這段程式碼如預期般運作， `x 不等於 4` 會被印到主控台。還有其他選項：我們可以使用可展開的指令 `\noexpand` 或 `\string`.

#### 使用 \string⟨token⟩

我們可以將 `\string` 套用到單一字元的 `⟨token⟩` `~` ，它的類別碼為 13（主動字元）； `\string` 如何將 `~` 字元，以產生一個類別碼為 12 的字元 token。如果我們寫

```
\directlua{
   local x=3
   if x \string~= 4 then
   print("x 不等於 4")
   end
}
```

它會產生我們需要的 Lua 程式碼：

`local x=3 if x ~= 4 then tex.print("x 不等於 4") end`

#### 使用 \noexpand⟨token⟩

我們可以使用 `\noexpand~` 來抑制主動字元的展開 `~`

```
\directlua{
   local x=3
   if x \noexpand~= 4 then
   print("x 不等於 4")
   end
}
```

未展開的 `~` token 會被傳遞到正在 `\directlua` 建立的 token list，並會被轉回文字，產生可正常運作的 Lua 程式碼。

### 使用 # 字元

在 Lua 語言中， `#` 字元可用來取得表格的長度。然而，如果我們嘗試以下程式碼

```
\directlua{
   local tbl = {}
   tbl[1] = "Hello"
   tbl[2] = "World"
   tex.print("表格長度為 "..#tbl)
}
```

我們可能會以為 LuaTeX 會排版出

`表格長度為 2`

，但它卻產生一個錯誤：

`\directlua]:1: attempt to get length of a number value`

這個錯誤是因為 `#` 字元通常具有類別碼 6（巨集參數）—— `#` 字元在 TeX/LaTeX 中有兩種用途：用來表示巨集參數（`#1`, `#2`… `#9`）以及對齊樣板中的替換文字（例如 `\halign` 和 `\valign`).

當 `\directlua` 正在產生 token 以建立它的 token list 時，會看到 `#` 字元的類別碼為 6，並建立一個適當的字元 token 來表示它。當需要把最終的 token list 轉回文字形式時，# 的字元 token（類別碼為 6）會受到特殊處理：它會輸出為 *兩個連續字元*: `##`，因此會得到以下傳給 Lua 的程式碼：

`local tbl = {} tbl[1] = "Hello" tbl[2] = "World" print(##tbl)`

在轉換成 Lua 程式碼時，原本的 `#` 已經被加倍，於是產生一個錯誤：

`\directlua]:1: attempt to get length of a number value`

這個問題源自於 TeX 的語法會使用雙井號 `##` 來表示或產生一個單一的 `#` token；這種語法用在那些定義其他、帶參數的巨集，或者用來為 `\halign` 或 `\valign` 表格建構指令建立樣板的巨集中。這相當令人困惑，所以讓我們看一個例子。

#### 範例

假設我們定義一個巨集 `\mymacro` ，它接受一個參數 `#1`，但它也定義了第二個巨集 `\foo` ，而這個第二個巨集本身也接受一個參數。為了區分參數 `#1` 在與 `\mymacro` 搭配使用時 `\foo` 以及定義 `#1` 要使用它自己的參數 `##1` ，TeX 語法要求你必須在 `\mymacro` 內使用 `\foo`:

`\def\mymacro#1{\def\foo##1{#1 哈囉##1}}`

如果你寫 `\mymacro{Hey!}` 它會定義巨集 `\foo` 為

`\def\foo#1{Hey! 哈囉#1}`

請注意， `\mymacro`的參數 `#1` (`Hey!`）已被納入 `\foo` 的定義中 `##1` 已被轉換成 `#1` 在 `\foo`的定義中。因此我們可以使用 `\foo` 如下：

`\foo{, World!}`

會排版為 `Hey! 哈囉，世界！`

我們可以透過在 LuaTeX 處理程式碼之前暫時變更 `\directlua`對 `#` 字元的處理來解決這個問題。例如：

```
\begingroup
   \catcode`\#=11
   \directlua{
   local tbl = {}
   tbl[1] = "Hello"
   tbl[2] = "World"
   tex.print("表格長度為 "..#tbl)
}
\endgroup
```

這會產生 Lua 程式碼

```
local tbl = {} tbl[1] = "Hello" tbl[2] = "World" tex.print("表格長度為 "..#tbl)
```

這會排版出我們預期的結果：

`表格長度為 2`

### 使用 % 字元

在 TeX/LaTeX 中， `%` 字元通常用來在你的程式碼中加入單行註解：告訴 TeX 引擎，應該忽略從那個位置開始直到該 `%` 所在行結尾的所有內容。然而，在 Lua 語言中， `%` 字元會用在某些非常有用的字串處理函式中，例如 `string.format(...)`, `string.gmatch(...)`、以及 `string.gsub(...)` 其中 `%` 字元在這些函式的語法中扮演重要角色。

當與 TeX/LaTeX 搭配使用時， `%` 會扮演註解字元，因為它被指定了類別碼 14。要讓它表現得像一般字元，並關閉它在 TeX/LaTeX 中的慣常行為，我們需要把它的類別碼改成某個安全值，例如 12。 `\directlua` 下面的範例使用了本文前面討論過的多種技巧，另外還加上一個我們尚未提過的： ``\catcode`\^^M=12``，這讓我們可以在程式碼中使用 Lua 註解；這點會在下文說明。

#### 範例

以下範例借自 [lua-users.org](http://lua-users.org/wiki/StringLibraryTutorial)，並經過適度修改以供 `\directlua`.

```
\documentclass{article}
\begin{document}
\begingroup
\ttfamily
\let\\\relax
\catcode`\^^M=12 %<---我們在下方會進一步探討這一點！
\catcode`\%=12
\directlua{
   local str -- 宣告一個本地變數來保存結果

   tex.print("使用 string.format():".."\\par")

   str=string.format("%s %q", "Hello", "Lua user!") -- 字串與帶引號的字串
   tex.print(str.."\\par")
   str = string.format("%c%c%c", 76, 117, 97) -- 字元
   tex.print(str.."\\par")
   str=string.format("%e, %E", math.pi, math.pi) -- 指數
   tex.print(str.."\\par")
   str=string.format("%f", math.pi) -- 浮點數
   tex.print(str.."\\par")
   str=string.format("%g, %g", math.pi, 10^9) -- 浮點數或指數
   tex.print(str.."\\par")
   str = string.format("%o, %x, %X", 99, 125, 125)  -- 八進位、十六進位、十六進位
   tex.print(str.."\\par")

   tex.print("\\vskip3mm".."使用 string.gmatch():".."\\par")

   for word in string.gmatch("Hello TeX user", "%a+") do
      tex.print(word.."\\par")
   end

   tex.print("\\vskip3mm".."使用 string.gsub():".."\\par")
   str=string.gsub("banana", "(an)", "%1-") -- 擷取所有出現的 "an" 並替換
   tex.print(str.."\\par")
}
\endgroup
\end{document}
```

以下截圖顯示了上面程式碼排版後的結果：

![在 \directlua 中使用 Lua 字串函式](/files/fe4cf2c09c265a06fe4f38f499d8cd66c50382a6)

## 為什麼 Lua 程式碼會以單行顯示？

正如你可能已注意到的，本文範例中所有（產生的）Lua 程式碼片段都以單行文字呈現：原本在 `\directlua` 程式碼片段中出現的換行並沒有被保留。為什麼會這樣？因為 Lua 程式碼中的換行已經被 *移除* 在 LuaTeX 於 `\directlua`內進行前處理時被移除，導致 Lua 程式碼變成一整長行文字。這個行為可追溯到 TeX 引擎如何處理行尾字元——在程式設計文獻中通常以 `\r` （回車）與 `\n` （換行）表示。至於為什麼我們需要注意這些細節，等我們討論如何使用 Lua 的機制來註解掉程式碼區段時就會明白。

當軟體寫入（儲存）文字檔時，每一行文字都會以所謂的「換行」字元結束——實際的換行字元取決於用來寫出該檔案的應用程式和作業系統。Wikipedia 有一篇 [有趣的文章](https://en.wikipedia.org/wiki/Newline) ，探討今日所用換行字元的歷史／演變。

對於任何文字檔而言，其各行文字的結尾可能由各種字元組合構成，通常稱為 carriage return（ASCII/Unicode 字元 13）和／或 line feed（ASCII/Unicode 字元 10），分別以 `\r` 和 `\n` 表示。由於 TeX 引擎設計為與平台無關，它們需要一種方法來避開文字檔中本質上與平台相關的行尾形式。自然地，TeX 引擎內建了一套（但可設定的）處理行尾字元的方法。

### TeX 引擎如何處理行尾

當 LuaTeX 正在處理 `\directlua{⟨程式碼⟩}` 時，它會讀取你的 `⟨程式碼⟩` 中的文字內容，並套用標準 TeX 引擎方法來處理你 `⟨程式碼⟩`中所包含的任何行尾。預設情況下，這些標準 TeX 方法會把所有行尾字元（回車與換行）移除，並以空白字元取代。我們說「預設」是因為 TeX 引擎對行尾字元的處理可以透過一個可由使用者設定的參數 `\endlinechar`來加以修改。這裡我們會提供一個簡短的兩步驟總覽，但更完整的細節可參考 Overleaf 文章 [\endlinechar 入門：TeX 如何從文字檔讀取各行](/latex/zh-tw/shen-ru-wen-zhang/05-an-introduction-to-endlinechar-how-tex-reads-lines-from-text-files.md).

#### 步驟 1：TeX 插入它自己的行尾字元

在從你的輸入檔讀取一行文字之後，TeX 引擎會立刻移除該行結尾的任何 `\r` 或 `\n` 字元。接著，TeX 引擎會 *插入* （加回）它自己的行尾字元到該行的結尾。該字元由一個可由使用者設定的 TeX 參數 `\endlinechar` 的值決定，而 TeX 引擎正是透過這個機制，以與平台無關的方式處理行尾字元：它們會選定並設定行尾字元，而不管輸入文字檔原本包含的是什麼。

通常，TeX 引擎會使用設定

`\endlinechar=13`

，這就是回車字元（`\r`）。不過，使用者也可以一律將其他字元代碼指定為 `\endlinechar`——我們稍後會在本文中看到。

因此，你的 `⟨程式碼⟩` 中所包含的任何行尾字元都會被 `\directlua{⟨程式碼⟩}` 處理時移除，並由 TeX 引擎自己決定的一個字元取代。請注意，TeX 引擎會在從檔案讀取一行新文字之後，立刻進行這個行尾處理，而且在 *，在* 處理該行中的任何字元（以產生 token）之前就完成了。不過，故事還沒結束：TeX 引擎 *確實* 對這些行尾字元所做的事情（它已經插入了什麼）解釋了為什麼 Lua 程式碼會變成單行。

#### 步驟 2：TeX 將其行尾字元轉換成空白

除了插入它們自己的行尾字元（其值由 `\endlinechar`決定）之外，TeX 引擎也會將類別碼 5 用於那些應該被 *視為* 行尾字元的字元。這導致 TeX 引擎通常會使用：

1. 一個由 `\endlinechar`;
2. 定義的行尾字元 *通常* 被指派了類別碼 5。

TeX 對那個行尾字元所做的事，就解釋了我們對單行 Lua 程式碼的疑問。當 TeX 引擎處理一行輸入時，它最終會偵測到該行最後一個字元：也就是由 `\endlinechar`所定義的字元。通常，該字元的類別碼為 5，這會使 TeX *將它取代* 為空白字元：也就是說，在行尾處，TeX 實際上會把它的行尾字元移除，並以空白取代。順帶一提，TeX 引擎也會使用類別碼 5 的字元來偵測空白行並開始新的段落，但這裡我們不會討論這一點。

當然，既然是 TeX，你可以透過重新設定 `\endlinechar` 為其他字元，和／或將指派給 `\endlinechar` 的字元賦予你選擇的類別碼值，來施展各種特殊的巨集程式設計技巧。

如果你想避免 Lua 程式碼變成單行文字，你可以（暫時）變更 `\endlinechar` 的值，或者變更標準行尾終止符 `\r`.

### TeX 古怪的 ^^ 記號

在以下各節中，我們將會遇到 TeX 不尋常的 `^^` 記號法，這稱為「延伸字元機制」。它是 Knuth 設計來幫助輸入「控制字元」的方式，例如行尾終止符、定位字元等等。例如：

* `^^J` 代表字元代碼 10（`\n`，換行）；
* `^^M` 代表字元代碼 13（`\r`，回車）。

像 `^^M` 這樣的字元序列，會在 TeX 進行輸入掃描程序的早期就被轉換成對應的字元代碼，當 TeX 正在讀取輸入字元以產生相對應的字元 token 時便是如此。

### 變更指派給 \endlinechar 的字元

請記得我們仍然需要防止 `~` 字元被展開，因此我們可以寫

```
\begingroup
\endlinechar=10 % 將行尾字元改為 \n
\directlua{
   local x=3
   if x \noexpand~= 4 then
   print("x 不等於 4")
   end
}% 不希望 \n 出現在這裡
\endgroup% 或這裡也不要有 \n
```

上面對 `\endlinechar` 的設定會使 LuaTeX 在它讀入的每一行結尾附加字元代碼 10（`\n`，換行）。我們這麼做是因為 `\n` （換行）通常的類別碼為 12，你可以透過撰寫 ``\the\catcode`\^^J``來測試。因為 `\n` 沒有類別碼 5，LuaTeX 不會把它轉換成空白字元，因此它會留在 LuaTeX 讀入的每一行尾端。這會使字元代碼 10 留在每一行的尾端，因而進入 `\directlua` 正在建立的 token list，並在 token list 轉回文字後，再次出現在 Lua 程式碼中。經過上述變更後，Lua 程式碼會以下列字元序列送往 Lua 解譯器：

**\n**local x=3\*\*\n**if x \~= 4 then**\n**print("x 不等於 4")**\n**end**\n\*\*

其中 **\n** 記號法是要表示字元代碼 10 *不* 某個未知的巨集 `\n`。現在，Lua 解譯器將會看到程式碼中的換行，正如它原本在 `\directlua` 命令：

```
   local x=3
   if x ~= 4 then
   print("x 不等於 4")
   end
```

中所寫的一樣。 `\n` 順帶一提，請注意 Lua 程式碼字串中的第一個字元是 `本機` （在 `\n` 關鍵字之前）。這也是由

`\directlua{`

這一行所造成的，因為在開頭的 `{` 之後立刻就有一個換行，而且這一點也會被保留。若要避免它，你可以寫

`\directlua{%`

### 變更 \r 的類別碼

為了在 Lua 程式碼中保留換行，我們也可以把 `\r` 的類別碼改成非 5 的其他值，如此一來 `\r` 就不再被辨識（視為）行尾字元。使用這個技巧時，LuaTeX 仍然會使用 `\endlinechar=13` ，並會繼續在每一行尾端加上一個 `\r` ；然而，由於 `\r` 不再具有類別碼 5，LuaTeX 就不會把 `\r` 字元視為行尾：它不會將它轉換成空白，並會原封不動地傳遞過去，使其出現在 Lua 程式碼中。

請記得我們仍然需要防止 `~` 字元被展開，因此我們可以寫

```
\begingroup
\catcode`\^^M=12 % 將 \r 的類別碼改為 12
\directlua{
   local x=3
   if x \noexpand~= 4 then
   print("x 不等於 4")
   end
}
\endgroup
```

在這個例子中，Lua 程式碼會以下列方式送到 Lua 解譯器：

**\r**local x=3\*\*\r**if x \~= 4 then**\r**print("x 不等於 4")**\r**end**\r\*\*

其中 `\r` 記號法是要表示字元代碼 13，而不是某個未知的巨集 `\r`。和 `\endlinechar` 範例一樣，Lua 解譯器現在會看到程式碼中的換行，正如它原本在 `\directlua` 命令：

```
   local x=3
   if x ~= 4 then
   print("x 不等於 4")
   end
```

順帶一提，再次注意 Lua 程式碼字串中的第一個字元是 `\r` （在 local 關鍵字之前）：這同樣源自於

`\directlua{`

#### 為什麼 \r 使用類別碼 12 而不是類別碼 11？

答案在於，若不小心把由 `\r` （類別碼 11）觸發的錯誤加到從輸入檔讀取的 TeX/LaTeX 指令尾端，可能會有風險。看這個例子：

```
\begingroup
\catcode`\^^M=11 % 將 \r 的類別碼改為 11
\directlua{
   local x=3
   if x \noexpand~= 4 then
   print("x 不等於 4")
   end
}
\endgroup
```

這會產生一個錯誤：

```
   ! 未定義的控制序列。
   l.9 \endgroup
```

這怎麼可能，因為 `\endgroup` 是標準的 TeX 原始指令？錯誤的原因相當微妙：當 LuaTeX 讀取最後一行文字——也就是包含 `\endgroup`——時，它也把 `\endlinechar` 字元 `\r` 加到了那一行的結尾。現在，在它的記憶體裡，LuaTeX 看到的字元序列是

`\endgroup\r`

其中我們使用 `\r` 來表示字元代碼 13——不是某個未知 TeX 巨集的名稱 `\r`.

在 LuaTeX 從我們的文字檔讀取這一行的那一刻，原本的 `\begingroup` 仍在運作中：我們仍處於一個群組內，而這個群組尚未透過執行相對應的 `\endgroup` 指令來關閉——那會使 `\r` 回復為先前的類別碼值 5。

當 LuaTeX 開始從文字行中處理（建立 token）時， `\endgroup\r` 它辨識出第一個字元 `\\` 為跳脫字元，這會觸發 LuaTeX 開始尋找指令名稱。為了辨識指令名稱，LuaTeX 會尋找一串類別碼為 11 的字元，但因為 `\r` 也具有類別碼 11，所以 LuaTeX 以為 `\r` 字元（仍然具有類別碼 11）構成 *指令的一部分* ，其名稱是 `\endgroup\r` ，當然這並不存在，所以 LuaTeX 回報 `未定義的控制序列` 錯誤。這就是為什麼我們使用類別碼 12 而不是 11。

因為 LuaTeX 的錯誤訊息是寫到主控台上，所以我們不容易看見／注意到 `\r` 字元，因此並不明顯到底是什麼導致了這個錯誤。

### 為什麼我們要在意行尾？

原因是為了讓你能在程式碼中使用 Lua 的註解方法！你可以使用 LuaTeX 標準機制，加入 `%` 字元來將程式碼中的單行註解掉；然而，Lua 語言也有自己非常實用的 *多行* 註解機制，這是你可能會想利用的。

讓我們先看看，如果不處理換行問題就嘗試使用 Lua 語言的單行註解，會發生什麼事。TeX 使用 `%` 字元來將單行程式碼註解掉，而 Lua 則使用雙連字號： `--`.

如果我們試著執行這段程式：

```
\directlua{
   local x=3
   if x \noexpand~= 4 then
   -- 我要輸出這個複雜測試的結果
   print("x 不等於 4")
   end
}
```

我們會得到一個錯誤：

`[\directlua]:1: 'end' expected near <eof>`

這個錯誤是由於傳給解譯器的 Lua 程式碼中缺少換行所致，解譯器只看到一個連續的字串，而註解是從這個字串的中段開始：

```

local x=3 if x ~= 4 then -- 我要輸出這個複雜測試的結果 print("x 不等於 4") end
```

在 `**local x=3 if x ~= 4 then**` 之後的所有內容都被視為註解掉，因此解譯器看到的是不完整的 Lua 程式碼片段，於是產生錯誤

`'end' expected near <eof>`.

其中 `<eof>` 表示檔案結尾。

正如你大概已猜到的，我們必須透過確保換行會傳遞到最後生成的 Lua 程式碼中來補救這個問題；例如，我們可以透過變更 `\r` 的類別碼為 12 來做到：

```
\begingroup
\catcode`\^^M=12 % 將 \r 的類別碼改為 12
\directlua{
   local x=3
   if x \noexpand~= 4 then
   -- 我將輸出這個複雜測試的結果
   print("x 不等於 4")
   end
}
\endgroup
```

現在，Lua 直譯器看到一個字串，但它包含 `\r` 如同原文所寫的換行 `\directlua` 片段：

**\r**local x=3\*\*\r**if x \~= 4 then**\r\*\*-- 我將輸出這個複雜測試的結果\*\*\r**tex.print("x 不等於 4")**\r**end**\r\*\*

這在效果上等同於寫下

```
   local x=3
   if x \noexpand~= 4 then
   -- 我將輸出這個複雜測試的結果
   print("x 不等於 4")
   end
```

這表示 Lua 能夠正確處理這段程式碼，並忽略我們註解掉的那一行。

**區塊註解**

Lua 語言也支援一種它稱為 [「區塊註解」](https://www.lua.org/pil/1.3.html) （或 *長註解*）：這些以 `--[[` 並且會持續生效直到相對應的 `]]`。我們可以用這種方便的語法來撰寫多行註解，或將我們想暫時移除的程式碼區段註解掉：

```
\begingroup
\catcode`\^^M=12 % 將 \r 的類別碼改為 12
\directlua{
   local x=3
   if x \noexpand~= 4 then
   --[[ 我將輸出這個複雜測試的結果
   只是因為它確實是一個
   如此驚人的結論]]
   print("x 不等於 4")
   end
}
\endgroup
```

## 總結

首先，恭喜你成功讀完這篇內容相當豐富的文章！我們嘗試撰寫一份相當完整的 TeX 相關概念與主題指南，提供透過 `\directlua` 指令來充分運用 LuaTeX 所需的背景知識。我們希望能寫出一篇具有啟發性、並對 Overleaf 使用者社群乃至更廣泛讀者有所助益與價值的文章。一如往常，我們很樂意收到回饋，所以請隨時 [與我們聯絡](https://www.overleaf.com/contact) 對本文提出意見，或提供你希望我們之後撰寫的其他主題建議。

祝你 $$\text{Lua}\mathrm{\TeX}\text{-ing!}$$ 來自 Graham Douglas 與 Overleaf 團隊。

### 最後……只要使用 luacode 套件即可

雖然 TeX 和 Lua 的運作方式根本不同，但這些語言共享一些在各自語境中具有「特殊意義」的字元——例如 \\, %, \~, #, ^, &——當然，Lua 與 TeX 對這些特殊意義的賦予是為了 *非常* 不同的目的。我們對有問題字元的探討說明了困難為何會出現，以及你可以如何解決；然而，手動修正許多零碎的小型 Lua 程式碼片段可能相當繁瑣，因此大多數使用者偏好使用能消除這些挑戰的 LaTeX 套件。其中一個套件是 [`luacode`](https://ctan.org/pkg/luacode?lang=en) ，它提供了一套旨在簡化使用 `\directlua`，不過至少你現在可能對這些問題有更好的理解 `luacode` 為你所解決。


---

# 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/09-an-introduction-to-luatex-part-2-understanding-directlua.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.
