为什么 tex 有“仅扩展”模式?

为什么 tex 有“仅扩展”模式?

该示例来自另一个线程经过一些修改:

\documentclass{article}
\newcommand\abc{\\xyz}
\newcommand\foo[1]{\def\abc{#1}\ifx\abc\empty T\else F\fi}
\begin{document}
\tableofcontents
\section{\foo{}}
\end{document}

经过2轮编译后,toc文件包含:

\contentsline {section}{\numberline {1}\def \\xyz{}F}{1}% 

根据该答案,扩展\foo{}可能是:

  • \def是逐字复制的。

  • \abc{#1}变成\\xyz{}因为\abc\\xyz#1是空。

  • \ifx\abc\empty T\else F\fiF因为\abc非空,所以计算结果为。

总体而言,在“仅扩展”模式下评估时,它们与目录内容相匹配。

但这种模式不是完全无稽之谈吗? 的定义\foo明确指出宏\abc是在此处定义的,但 tex 引擎正在用现有定义替换此宏。 这不是代码的本意。 代码并不意味着定义\\xyz

我认为罪魁祸首是这种“扩展但不执行”模式。我不明白为什么这种部分扩展会变得有意义。我相信正确处理宏列表的唯一方法是按顺序扩展并执行所有宏。所以,我的问题很简单:我们为什么需要这种“仅扩展”模式?

答案1

\edef欢迎来到的奇妙世界\write

如果要制作目录,则必须收集与标题相关的材料并将它们写在辅助文件中,以便在下次运行时输入。

主要问题\section{<text>}有:

  1. 你需要的\thesection立即地计算;
  2. \thepage相反,只有包含该部分标题的页面发出时才应该计算的值。

实际情况是,TeX 在找到时不知道它在哪一页\section。事实上,它可能已经处理了几乎填满一页的材料,只有在处理完\section下一段之后,TeX 才会意识到这个标题必须移到下一页。

那么,LaTeX 在这种情况下会做什么呢?两个动作:首先它应该暂时设置\thepage为平均值\relax并执行类似以下操作

\edef\x{\write\tocfile{\contentsline{section}{\numberline{\thesection}<text>}{\thepage}}
\x

(这是一种简化;实际上该.toc文件写在文档末尾,因为不知道目录应该排版在哪里)。

然而,一个问题立即出现:许多宏必须不是在的时候会扩展\edef。好的,我们可以做

\edef\x{\write\tocfile{\noexpand\contentsline{section}{\noexpand\numberline{\thesection}<text>}{\noexpand\thepage}

(无需在和\noexpand前面添加,因为它们无法通过构造扩展)。嗯,是也不是:里面有什么?\write\tocfile<text>

标题可能包含用户想要需要扩展(例如,讲座编号,带有一个独立于section计数器的计数器)或用户不想展开:这就是你的情况,不是吗?

实际情况远比上面提到的要复杂得多:诸如\textbfdo 之类的宏会使事情变得非常复杂,并且在写入文件的过程中永远不应扩展.toc。因此,LaTeX 使用\protected@edef和 ,\protected@write它们是 和 的包装器\edef\write它们负责必要的保护机制,以避免过早扩展这些命令。

在写入文件时,是否应该扩展某些内容由用户决定.toc。怎么做?在\protect不应扩展的标记前面加上标记,或者利用一般保护机制:如果你这样做

\DeclareRobustCommand{\foo}[1]{\def\abc{#1}\ifx\abc\empty T\else F\fi}

那么你的.toc文件将包含

\contentsline {section}{\numberline {1}\foo {}}{1}%

答案2

只有经典 TeX 才具有仅扩展模式。但是如今所有 TeX 引擎都包含 eTeX 扩展(1998 年的第 2 版),其中原语\detokenize\unexpanded允许抑制扩展。

\section可以定义,而不会出现问题。处理宏时,我们需要将三个不同的信息保存到参考文件(toc例如) 。\section

  • 该部分标题:未展开。
  • 章节编号(例如 2.3):立即扩展。
  • 页码:\shipout正在处理中,稍后展开。

假设我们已将标题保存在 中\sectitle,将号码保存在 中\secnumber,并且我们必须创建一个宏来执行宏\write的部分\section。我们可以这样做:

\edef\tmp{\string\Dosection{\secnumber}{\detokenize\expandafter{\sectitle}}}
\expandafter\write\expandafter\tocfile\expandafter{\tmp{\the\pageno}}

现在,如果你写入\section{Text \foo{xy}, hello},则会进入toc文件:

\Dosection{1.1}{Text \foo{xy}, hello}{13}

正是您想要的。您不需要使用\protect\protected\def使用过\foo宏。

但是如果你使用 LaTeX 的\section宏,那么就会有更多问题。原因是:LaTeX 在其部分宏中不使用\detokenize(1998 年的原始宏)\write。因为它比这个原始宏更老。

答案3

首先让我们澄清一些事情:

  • 扩张是对输入流的操作。这可以通过扩展宏(即用其定义替换它,可能带有参数)或扩展可扩展基元(如\ifx)来实现。
  • 执行正在操纵 TeX 引擎的属性。这可以通过不可扩展的原语(如\def)或赋值来实现。这些属性会影响 TeX 引擎将来的行为。
  • 在通常的比喻中,扩展和执行都发生在TeX 引擎。然而,这并不是 TeX 所做的一切(否则我们永远不会得到一个文档)。当引擎a在输入流的开头找到字母 时,它将也就是说,它(通常)会将当前选定字体的字形“a”放入文档中(这里跳过了很多细节)。

现在,我们经常希望存储一些输入流以供以后使用,最明显的是在宏中(用于同一个 TeX 运行)和文件(通常用于稍后的 TeX 运行)1。让我们集中讨论前者。

除了将一些输入字符串存储在编译前我们在源代码中输入的宏中之外,存储某些内容的扩展(例如当前页码)也很有用,我们可以用 来实现这一点\edef。请注意,我们“存储输入流的片段”,而不是其他内容。我们无法将整个 TeX 引擎的状态存储在宏中(扩展它意味着什么?)。说“让我们存储它的执行”是没有意义的。因此,\edef完全扩展了它的参数,但不执行任何操作(就像 一样\write)。

正如 David Carlisle 在他的评论,这确实是宏扩展语言的“正常”模式。所有其他执行和吞咽TeX 引擎所做的完全是另一回事。因此,只进行扩展应该是很自然的。

1当然,这并不完全相同,因为宏的定义包括代币但文件的内容包括人物,但我们不要纠结于此。


如果你仔细想想,这确实是唯一的解决方法。在你的问题中,你也建议在这些情况下执行,但这到底意味着什么?如果我这样做

\edef\foo{\def\baz{asdf}}

的扩展是什么\foo\baz现在应该定义什么?应该

\write\@auxout{\color{blue}Hello}

写入aux文件?后面的文本应该\write是蓝色的吗?

我确信人们可以找到更多荒谬的例子,但最终归结为:您正在存储一段输入流,因此在此操作期间只应执行输入流操作(即扩展)。如果您要进行的扩展需要某些定义,请在此操作之外执行,例如

{\def\foo{baz}
  \immediate\write\@auxout{I just wanted to say \foo.}
}

最后要说的是,当然还有其他地方也会出现仅扩展的上下文。例如,当 TeX 期望一个数字时,它会扩展所有内容,直到找到无法扩展的、不再是数字一部分的内容。在那里执行也没有任何意义。

相关内容