> For the complete documentation index, see [llms.txt](https://ayakaleaf-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://ayakaleaf-pro.ayaka.space/latex/zh-cn/shen-du-wen-zhang/17-how-does-expandafter-work-a-detailed-macro-case-study.md).

# \expandafter 是如何工作的：详细的宏案例研究

&#x20;[第 1 部分](/latex/zh-cn/shen-du-wen-zhang/19-how-does-expandafter-work-an-introduction-to-tex-tokens.md)   [第 2 部分](/latex/zh-cn/shen-du-wen-zhang/22-how-does-expandafter-work-the-meaning-of-expansion.md)   [第 3 部分](/latex/zh-cn/shen-du-wen-zhang/21-how-does-expandafter-work-tex-uses-temporary-token-lists.md)   [第 4 部分](/latex/zh-cn/shen-du-wen-zhang/20-how-does-expandafter-work-from-basic-principles-to-exploring-tex-s-source-code.md)   [第 5 部分](/latex/zh-cn/shen-du-wen-zhang/17-how-does-expandafter-work-a-detailed-macro-case-study.md)   [第 6 部分](/latex/zh-cn/shen-du-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}}$$ 最初于 20 世纪 90 年代末开发，旨在增加一套新的原始命令，以提供 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》的第 8–9 页所讨论的 $$\varepsilon\mathrm{\text{-}{\TeX}}$$ 手册，……的一个重要特征是 *表达式* 是它们的求值（计算）不要求 TeX 执行任何 *赋值*.

在编程术语中，赋值是将一个变量设定（分配）为具有特定值的过程；例如，赋值 `\count` 寄存器 `99` 设为包含值 `12345` 通过 `\count99=12345`。在 TeX 处理过程中还会发生许多其他类型的赋值——例如将 token 寄存器赋值为包含一系列 token，将 box 寄存器赋值为包含盒子内容，等等。

要执行一个赋值，例如 `\count99=12345`，TeX 需要调用（执行）实现 `\count` 或任何其他执行某种赋值的原语的内部代码。然而，有时 TeX 正在执行纯 *展开* 而在那些时候，这些赋值不会被执行——*在 TeX 处理的那个阶段*。这种情况的例子包括以下命令：

* `\edef\command {*token list*}` “展开定义”宏定义命令，它会展开中的 token *token 列表* 并将结果存储为 `\command`.
* `\write *number* {*token list*}` 展开中的 token `*token list*` 并将它们写入由 `*number*`.
* `\directlua {*token list*}` 这个 LuaTeX 原语命令用于将 Lua 代码传递给内置 Lua 解释器。中的所有 token `*token list*` 在传递给 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 列表中。利用我们对 TeX 使用（创建）临时 token 列表的了解，我们可以仔细看看它究竟是如何 `\foo` 实现其结果的。

**请记住**：我们正在跟踪一个宏的执行过程，此时其定义的原始文本——包含在一个实体 `.tex` 文件中——已经被扫描（由 TeX 读入）并转换为表示该宏定义的 token 列表。本质上，我们是在跟踪 TeX 对这些已存储的 *记号* 在 TeX 内存某处所包含的宏定义中的 token 时的读取和处理过程。宏定义的 TeX 代码中原本存在的任何空格字符（位于 `.tex` file) will have been absorbed whilst TeX was scanning that text for commands (spaces as terminators), or they will have been converted to tokens, such as the space character after the comma (`,`) in `\ifnum#1<#2,` 它源自行尾字符（`\r`）被转换为空格。

因为 `\foo` 使用了多个 `\expandafter` 命令，我们将通过给每个 `\expandafter`添加下标来帮助说明， `\expandafter` 更改为 $$\mathrm{T^i\_1}$$ 和 $$\mathrm{T^i\_2}$$表示由 TeX 处理的 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 列表，其中包含表示该数值中各个数字的字符 token，而 `\number` 正在处理的对象。该 token 列表会成为 TeX 的下一个输入源。在这里，该 token 列表被读取，token 被输出以排版 `#1`.

接下来，宏执行测试 `\ifnum#1<#2` 来检查 `#1` 的参数是否小于传入给 `#2`的参数`,`。如果是，则输出一个逗号（`,`）后换行字符生成的 token（ `.tex` 文件中使用的

宏继续处理下面这段代码，这是其运作的核心：

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

本质上，这段代码生成了一系列临时 token 列表，从而导致对 `\foo` 宏的多次调用，并在 if 测试 `\ifnum#1<#2` 不再成立时终止。但 *如何* 是受控循环，因为没有发生赋值：那么“循环计数器”在哪里？

让我们先看看这段代码 `\expandafter<sub>1</sub>\foo\expandafter<sub>2</sub>`。请注意，我们将使用下标记号 `<sub>token</sub>` （或者 `<sub>(token)</sub>`）来提醒我们，在这里，TeX 正在读取/处理数值（整数）token 值。

在这里，以下 token 作为 `\\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` 将其转换为一个临时 token 列表，其中包含表示 `#1 + 1`值的字符 token。该临时 token 列表由 `\number`生成，最终会被读入，作为对另一次调用 `\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 列表。

最后，`\expandafter<sub>5</sub>` 被展开：

* $$\mathrm{T^5\_1} =$$`}<sub>token</sub>` 它被保存以供稍后 *重新插入* 回输入中
* $$\mathrm{T^5\_2} =$$`\fi<sub>token</sub>`，这是一个可展开命令。

  对 `\fi` 实际上终止了 `\ifnum` ，并实际上关闭了宏的这一轮迭代。TeX 现在完成重新插入由多个 `\expandafter` 命令临时保存的所有 token：这会生成一系列单 token 的 token 列表，这些列表来自每个 `\expandafter`所保存的 token。此外，TeX 还通过 `\number`.

### 组装 token 列表

本质上， `\foo` 宏生成了一串 token 列表：你可以把 `\foo` 看作一个 token 列表“制造工厂”。这些 token 列表被 TeX 读取，成为下一批输入源。巧妙之处包含在 `\foo`:

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

通过它 `\foo` 安排再次调用自身，但使用存储在由 `\number`构造的 token 列表中的不同参数。为了让这些 token 列表整体上表现得像一次宏调用，大括号 `{` 和 `}` 都已通过 `\expandafter` 命令的作用被保存并重新插入输入（作为单 token 列表）

![由 \foo 宏生成的 token 列表](/files/fb7ce4f9c1f1e20685415c623e3ee663955209c1)

&#x20;[第 1 部分](/latex/zh-cn/shen-du-wen-zhang/19-how-does-expandafter-work-an-introduction-to-tex-tokens.md)   [第 2 部分](/latex/zh-cn/shen-du-wen-zhang/22-how-does-expandafter-work-the-meaning-of-expansion.md)   [第 3 部分](/latex/zh-cn/shen-du-wen-zhang/21-how-does-expandafter-work-tex-uses-temporary-token-lists.md)   [第 4 部分](/latex/zh-cn/shen-du-wen-zhang/20-how-does-expandafter-work-from-basic-principles-to-exploring-tex-s-source-code.md)   [第 5 部分](/latex/zh-cn/shen-du-wen-zhang/17-how-does-expandafter-work-a-detailed-macro-case-study.md)   [第 6 部分](/latex/zh-cn/shen-du-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://ayakaleaf-pro.ayaka.space/latex/zh-cn/shen-du-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.
