我对 (La)TeX 还比较陌生,但我对计算编程语言有相当多的经验。在我开始学习 (La)TeX 之前,我从未听说过“扩展”...
那么,何谓“扩张”?需要明确的是,我并不是问如何使用类似这样的命令\expandafter
。
也许我只是不熟悉这个术语的名称。“扩展”在计算语言中有类似的东西吗?从我读过的内容(主要是命令的解释)来看,它似乎类似于“求值”,直到达到最终值,但我不确定。
答案1
扩展是指:你拿一个可扩展的东西,然后...嗯...你扩展它。
例如,如果您定义\newcommand\Aa{\Bb\relax}
,那么:
\Aa
是(一次)可扩展,其展开式为\Bb\relax
。\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 中的预处理。主要有两个区别:
扩展不是预处理,而是伴随处理进行的。这意味着:
您可以重新定义事物,并且所使用的定义是在当前范围内有效的定义;从这个意义上讲,所有宏与变量的相似度都高于与函数的相似度;它们对变化、分组等都很敏感。
答案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
我说“正常运作”,因为在某些情况下扩张会受到抑制:
当 TeX 正在寻找宏参数时;
\def
TeX 使用或进行定义时\gdef
,关于要定义的宏、参数文本和替换文本;\edef
当 TeX 使用或进行定义时\xdef
,但这仅涉及要定义的宏和参数文本,而不涉及替换文本;\let
当 TeX 检查在、\chardef
和\mathchardef
所有其他\...def
命令之后定义的标记时;当 TeX 检查 的“右侧”时
\let
;\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)
,因为它的不是$@
展开。这也是和之间的区别$*
。