何谓“扩张”?

何谓“扩张”?

我对 (La)TeX 还比较陌生,但我对计算编程语言有相当多的经验。在我开始学习 (La)TeX 之前,我从未听说过“扩展”...

那么,何谓“扩张”?需要明确的是,我并不是问如何使用类似这样的命令\expandafter

也许我只是不熟悉这个术语的名称。“扩展”在计算语言中有类似的东西吗?从我读过的内容(主要是命令的解释)来看,它似乎类似于“求值”,直到达到最终值,但我不确定。

答案1

扩展是指:你拿一个可扩展的东西,然后...嗯...你扩展它。

例如,如果您定义\newcommand\Aa{\Bb\relax},那么:

  1. \Aa是(一次)可扩展,其展开式为\Bb\relax

  2. \Aa不是完全可扩展,因为它的第一个扩展包含\relax,而这是不可扩展的。

简而言之,扩展是用宏内容代替宏调用的过程。TeX-core(以及 LaTeX)的作用是:

  • 扩展它看到的任何可扩展的东西;
  • 处理所看到的任何不可扩展的内容。

因此,例如,TeX 定义的宏\def是不可扩展的。因此,TeX 以以下方式处理它(忽略任何特殊性):取第一个之后的内容\def-- 这是被定义的宏的名称。取后面的内容直到{-- 即参数,然后取下一个块{...},这就是新宏要扩展的内容。在此过程中,除非必要,否则 TeX 当然不会扩展它处理的内容,但 并非如此\def

另一方面,当 TeX-core 看到 LaTeX 中的 时\newcommand\greeting[1]{Hello #1!},它就开始扩展\newcommand(在一定程度上是可扩展的),经过许多疯狂的事情(我说得不太准确),它将序列扩展\newcommand\greeting[1]{Hello #1!}\def\greeting#1{Hello #1!}。我们之前已经知道\def是不可扩展的,当处理它时,它将定义一个名为 的宏,\greeting它接受一个参数并扩展到Hello #1!任何使用的地方。

现在我们有了\greeting宏,让我们使用它吧!所以我们写\greeting{glassbjs}。TeX-core 看到它,知道 是\greeting可扩展的并且接受一个参数,它将序列扩展\greeting{glassbjs}Hello glassbjs!。由于可打印字符(字母、空格、标点符号等)默认情况下是不可扩展的,因此 TeX-core 会尝试处理它们,这意味着它将它们转换为当前字体中的字形(我再次不准确)并将它们添加到称为“当前水平列表”的东西中。此时,TeX-core 对此感到高兴并继续前进。

如果你在其他语言中寻找相似之处,扩展最接近于 C 之类的预处理器替换#define f(x) something-here。因此,上述内容与(在 C++ 中)非常相似:

#define greeting(x) cout << "Hello " << x << "!"

但是,扩展有一些奇怪的规则,不适用于 C 中的预处理。主要有两个区别:

  1. 扩展不是预处理,而是伴随处理进行的。这意味着:

  2. 您可以重新定义事物,并且所使用的定义是在当前范围内有效的定义;从这个意义上讲,所有宏与变量的相似度都高于与函数的相似度;它们对变化、分组等都很敏感。

答案2

扩展只是 TeX 工作方式的一半,因此这不是一个简单的答案。

首先,TeX 的操作代币(看什么是代币?)。token 可以是可扩展的,也可以是不可扩展的;如果它是不可扩展的,它显然永远不会扩展,否则它扩展。可扩展标记扩展为什么取决于它如何成为语言的一部分。

一些原语(即,其含义在 TeX 从头开始​​启动时已预先声明)标记是可扩展的,而有些则不是。在可扩展原语中,可以找到\expandafter\if以及所有其他条件;这是可以预见的,因为这些原语用于控制扩展过程本身;可扩展原语的扩展可以为空,但它可以触发其他操作。所有用 定义的宏\def(及其变体\gdef\edef\xdef)都是可扩展的,它们的扩展正是替换文本。

因此,假设我们有

\def\test{some text}

或者,在 LaTeX 中,\newcommand\test{some text}情况也非常类似。在正常操作下,如果 TeX 发现

Here is \test

它将吸收Here<space>is<space>(八个不可扩展的标记)并停在\test,检查它并决定它是可扩展的(TeX 使用当前的的意思\test),所以它用它代替some text并继续从“s”开始由于这个标记不可扩展,因此它被移交给另一个处理阶段,TeX 继续处理“o”等等。

现在,假设我们有

\def\test{\foo{o}me text}
\def\foo#1{s#1}

输入如下

Here is \test

将使 TeX 交出找到的标记\test,并将其替换为

\foo{o}me text

在吸收了其论点之后,现在\foo将扩展为。so

我说“正常运作”,因为在某些情况下扩张会受到抑制:

  1. 当 TeX 正在寻找宏参数时;

  2. \defTeX 使用或进行定义时\gdef,关于要定义的宏、参数文本和替换文本;

  3. \edef当 TeX 使用或进行定义时\xdef,但这仅涉及要定义的宏和参数文本,而不涉及替换文本;

  4. \let当 TeX 检查在、\chardef\mathchardef所有其他\...def命令之后定义的标记时;

  5. 当 TeX 检查 的“右侧”时\let

  6. \write当 TeX 吸收、\uppercase\lowercase、的标记\detokenize\unexpanded将其分配给标记寄存器时。

有时我们会谈论“全面扩张”。那是什么?当我们这样做时

\edef\baz{<tokens>}

可以<tokens>扩展,生成的标记列表可以扩展,依此类推,直到只剩下不可扩展的标记,始终从下一个标记开始,如前所述。此后,使用由此获得的标记列表作为替换文本执行定义。因此,使用上述定义,

\edef\baz{\test}

会扩展\test为,\foo{o}me text并将其扩展为some text,结果与我们说 的结果相同\def\baz{some text}。我们为什么要这样做?因为 TeX 使用当前的标记的含义;如果我们说

\def\baz{\test}

\foo然后改变(或者当然 )的定义\test,宏\baz将扩展为其他东西;\edef我们确信它会扩展为some text

但是,\edef不执行分配。因此,如果我们这样做

\count255=1
\edef\baz{\advance\count255 by 1 \number\count255 }

使用\baz将打印 1,而不是 2。这是因为\advance\count不可扩展;字符标记也不可扩展;\number是可扩展的,并且它的扩展是其后的十进制表示<number>。所以这\edef相当于

\def\baz{\advance\count255 by 1 1}

当 TeX 最终扩展 中的标记列表时,也会发生同样的情况\write:扩展“一直进行”,就像 中一样\edef。可以通过将我们不想扩展的标记放在前面\noexpand或将它们作为 的参数括起来来抑制标记的扩展\unexpanded。请注意,这只是一个“一次性”活动:如果一个人做了

\def\a{A}\def\b{B}\def\c{C}
\edef\foo{\a\b\noexpand\c}

的替换文本\foo将是AB\c;使用时\foo\c 将要按其当前含义进行扩展。

\csname匹配之后和之前出现的标记也会\endcsname进行完全扩展;在这种情况下,只有字符标记必须保留,因此 a在这里是非法的,而在或\relax中则不会出现。\edef\write

复杂吗?是的。

答案3

举例来说,从TeX 书(第 200 页,答案在第 328 页):

练习20.2\puzzle:给定以下定义,的展开是什么?

\def\a{\b}
\def\b{A\def\a{B\def\a{C\def\a{\b}}}}
\def\puzzle{\a\a\a\a\a}

回答: ABCAB。(第一个\a扩展为A\def\a{B...};这会重新定义 \a,因此第二个\a扩展为B...,等等。)至少,\puzzle当 TeX 构建列表时,如果遇到 ,就会发生这种情况。但如果 在或或类似的东西\puzzle中扩展,我们稍后会看到,在扩展过程中不会执行 内部命令,并且后面的控制序列会被扩展;因此结果是一个无限字符串\edef\message\def\def

A\def A\def A\def A\def A\def A\def A\def A\def A...

这会导致 TeX 中止,因为程序的输入堆栈是有限的。此示例指出,当控制序列(例如\b)出现在定义的替换文本中时,无需定义。该示例还表明,TeX 只有在需要时才会扩展宏。

我们可以看到使用时的扩展\tracingmacros=1(参见LaTeX\tracing命令列表?) 并检查.log以下 MWE:

\documentclass{article}
\def\a{\b}
\def\b{A\def\a{B\def\a{C\def\a{\b}}}}
\def\puzzle{\a\a\a\a\a}
\begin{document}
{\tracingmacros=1

\puzzle}
\end{document}

一些评论:

\puzzle ->\a \a \a \a \a % Expansion of \puzzle contains five \a's

\a ->\b % Expansion of first \a

\b ->A\def \a {B\def \a {C\def \a {\b }}} % Expansion of \b (adds A to output)

\a ->B\def \a {C\def \a {\b }} % Expansion of second \a (adds B to output)

\a ->C\def \a {\b } % Expansion of third \a (adds C to output)

\a ->\b % Expansions of fourth \a

\b ->A\def \a {B\def \a {C\def \a {\b }}} % Expansion of \b (adds A to output)

\a ->B\def \a {C\def \a {\b }} % Expansion of fifth \a (adds B to output)

\a(至)的最终重新定义C\def\a{\b}从未被“使用”。

答案4

@glassbjs,其他人,

我本想在这个时候发表评论,因为这个问题已经被“回答”了,而不是再发一条评论——我讨厌在每个 StackExchange 网站上,你都必须在积累了足够的声誉后才能发表评论。那个特定的网站- 但由于这确实是我唯一能回答的方式,我会尝试更直接地回答你的问题(即从“计算语言”的角度)。

那么,何谓“扩张”?

它是通过替换转换静态变量赋值的过程。

“扩展”在计算语言中是否有类似物?...它似乎类似于“评估”,直到达到终值,但我不确定。

是的,确实如此。它本身甚至不是模拟,而是相同的概念和术语。
而且,它不像eval,不完全像。

确切地说,它是这样的:在 shell 脚本中,带有“双引号”的字符串会被扩展。而“单引号”中的字符串则不会。这就是为什么如果你echo "some $(echo thing)",输出是some thing:首先执行 内的命令$()。之后,它被称为“完全扩展”的字符串,然后对其执行外部命令echo。另一方面,echo 'some $(echo thing)'输出some $(echo thing),因为它的不是$@展开。这也是和之间的区别$*

相关内容