测试 \expandafter (纯 Tex) 的“连续性” - 奇怪的结果

测试 \expandafter (纯 Tex) 的“连续性” - 奇怪的结果

为了更好地理解/控制 \expandafter,我编写了这段代码。

\count100 = 0
\count101 = 1
\def\c{%
   \the \count100
   \advance \count100 by \count101\relax}%
%
\everypar{\count100 = 0\relax}


% the ending dot-brace sequence remembers me the commands executed on each line

\expandafter\c\c\c \hfil $...$

\expandafter{\c\c}\c \hfil $\{..\}.$ % I think it's
%                                      useless for
%                                      this situation


\expandafter\c{\c}\c \hfil $.\{.\}.$

\expandafter\c{\c\c} \hfil $.\{..\}$

输出数字序列:

012

313

411

212

经过长时间思考这个结果并在互联网上查看后,我得出结论:这对我来说毫无意义。

有人能帮助我理解这样的输出吗?

答案1

基本上,您已将\everypar计数寄存器 100 重置为值 0,并\c传送命令\the\count100,即计数寄存器 100 的当前值并增加该计数寄存器。

我认为棘手的事情不是\expandafter各种情况的混合,

  • 扩展不是排版,因此不会改变排版模式。
  • 范围界定不是排版,因此不会改变排版模式。
  • \everypar在某些时刻被调用,用于将标记“注入”/插入到标记流中。这些时刻是当 TeX 遇到触发切换到(不受限制的)水平模式的标记时 — (不受限制的)水平模式是排版模式,TeX 还会为您跨行拆分文本 — 并开始收集材料,收集完成后,将制作一个段落,其文本将尽可能整齐地跨行拆分。在那些时刻,在注入这些标记之前,将垂直\parskip-glue 添加到垂直列表中,其中将附加形成段落行的框,并在包含\parindent段落第一行的框的左端/开头插入水平 -indentation。(这是对事件的简化描述。)
  • TeX 切换到水平模式的时刻可能是之前打开本地范围和/或通过\the-expansion 或任何扩展技巧获取(尚未排版)标记和/或任何其他不会改变排版模式的事情发生的时刻。

\expandafter\c\c\c \hfil $...$

\count100初始化为0。TeX 处于垂直模式。我们有三个序列来传递 的值\count100并然后增加\count100:当处理\the\count100来自第一个时\c,您会得到标记0。处理该标记会导致 TeX 对其进行排版并由此切换到水平模式并从而执行\everypar,这反过来会重置\count100为 0。然后执行待处理的\advance\count100 by\count101\relax,同样来自第一个\c,因此\count100具有值 1。第二个和第三个\c分别产生排版和增加 的值\count100,以便您得到12并且最终\count100具有值 3。
(只是产生在扩展第一个之前传递第二\expandafter个的顶层扩展但这并不重要:第一个不使用属于第二个的顶层扩展的标记作为参数。)\c\c\c\c

所以最后你得到了012并且\count100有值 3。当处理“0”时,TeX 切换到水平模式来排版一个段落,并在此将值为 0 的寄存器重置为值 0。当遇到空行时,段落就完成了,TeX 切换回垂直模式。

\expandafter{\c\c}\c \hfil $\{..\}.$ %

此处得出的结果是,在 TeX 看到花括号(从而打开局部作用域)之前,\expandafter的顶层扩展被传递。在这种情况下,它不会对最终结果产生影响。无论如何,TeX 仍处于垂直模式并打开局部作用域。在该局部作用域中,来自第一个的标记传递/打印为 的当前值。当 TeX 处理该标记时,TeX 通过切换到水平模式来开始一个新段落。因此执行并将计数寄存器 100 重置为 0。后续的,也来自第一个,将计数寄存器 100 增加 1,因此现在它的值是 1。后续的/第二个打印 1 并将计数寄存器 100 增加 1,因此现在它的值是 2。结束括号关闭本地范围:现在计数寄存器 100 的值回到 3。第三个打印值“3”并增加寄存器,因此现在它的值是 4。后续的空行结束段落并且 TeX 返回垂直模式。\c\the\count100\c3\count100\everypar\advance \count100 by \count101\relax\c\c\c

\expandafter\c{\c}\c \hfil $.\{.\}.$

这里\expandafter导致 TeX{在查看第一个之前先查看 (不可扩展)\c,因此\expandafter没有效果。

第一个\c传递: \the \count100 \advance \count100 by \count101\relax
由于计数寄存器 100 的值为 4 \the\count100,因此第一个\c传递4。为了排版,此标记 TeX 切换到水平模式,从而\everypar执行 ,以便\count100将 重置为 0。后续的\advance\count100 by \count101\relax,也来自第一个\c,递增\count100,以便现在的值为 1。
现在打开局部作用域。在局部作用域内,由于第二个,传递 的\c当前值\count100,即 ,1并且\count100递增,现在的值为 2。然后关闭局部作用域,并将 的值\count100重置为打开局部作用域之前的值,即1。第三个\c传递该值的排版1并递增,\count100以便\count100现在的值为 2。
后续的空行导致 TeX 完成该段落并切换回垂直模式。

\expandafter\c{\c\c} \hfil $..\{.\}$

再次\expandafter导致 TeX{在查看第一个之前先查看 (不可扩展)\c,因此\expandafter没有效果。

第一个命令\c导致 TeX 传递\the \count100 \advance \count100 by \count101\relax。由于计数寄存器 100 的值为 2,\the\count100因此产生令牌2。处理此令牌时,TeX 切换到水平模式并执行\everypar。因此计数寄存器被重置为值0。然后执行后续命令\advance \count100 by \count101\relax,产生计数寄存器 100 的值为 1。然后通过 打开本地作用域{。然后,由于第二个命令\c,传递计数寄存器 100 的值,即令牌,1并递增计数寄存器 100,使其值为“2”。然后,由于第三个命令\c,传递计数寄存器 100 的值,即令牌,2并递增计数寄存器 100,使其值为“3”。然后通过 关闭本地作用域},计数寄存器 100 的值回到 1。

答案2

首先要意识到的是,与 相关的重置\everypar只有在您离开垂直模式时才会生效。因此,为了使事情变得简单,让我们修改\c定义以最初离开垂直模式。其他一切保持不变。

要意识到的第二件事是,每个命令\expandafter都没有完成任何事情(稍后会解释),因此为了理解输出,可以忽略它们。

\documentclass{article}
\begin{document}
\count100 = 0
\count101 = 1
\def\c{\leavevmode%
   \the \count100
   \advance \count100 by \count101\relax}%
%
\everypar{\count100 = 0\relax}


% the ending dot-brace sequence remembers me the commands executed on each line

\expandafter\c\c\c \hfil $...$

\expandafter{\c\c}\c \hfil $\{..\}.$ % I think it's
%                                      useless for
%                                      this situation

\expandafter\c{\c}\c \hfil $.\{.\}.$

\expandafter\c{\c\c} \hfil $..\{.\}$


\end{document}

在此处输入图片描述

使用此设置,每个段落中第一次调用的\c都将以 等于 开始\count1000在第一种情况下,\c\c\c,其行为符合预期,初始重置为0,每次调用时增量为 1。

第二种情况{\c\c}\c是将 重置为0组内,从而占01输出中的首字母。但是,当组结束时,重置被限制在组内,因此第三种\c情况从上一行剩下的内容开始递增,即递增23

第三行\c{\c}\c具有类似的行为。计数被重置为0,它1在进入组之前递增为 。但是,在退出组时,它只调用 的组前值1,因此最后\c再次打印1,然后再递增。

在最后一个示例中,调用产生了。但是,在退出组时,即第三次调用之后,012的值仍然是,这是计数器在进入组之前递增的值。\count1001

现在让我们来理解为什么 什么\expandafter都没做。作用于 时\c,它只是用 的定义替换了\c标记\c。但是,\the\count100仍然没有执行(甚至 也没有执行\leavevmode)。因此,它对 内部任何内容的预扩展没有影响\c。在某些情况下,您甚至没有扩展\c,而是试图扩展{,这什么也没做。

最后,让我们回到 OP 的原始案例,其中省略了\leavevmode\expandafter仍然无用)。现在,与 关联的计数重置\everypar直到离开垂直模式才会执行,而这直到\the\count100在第一次调用 时遇到初始值时才会发生\c

在此处输入图片描述

因此,第一行按预期运行,只是因为\count100最初等于0

对于第二行,上一段末尾的 left 值\count100有效,\the\count100并且然后重置为 0。因此,31第二行的 反映了这一点。但是,与之前一样,当组结束时,最后的\c调用只会记住3前一段末尾的 。

对于第三行,4反映了前一段末尾的值,在排版0后重置为 。因此,第二行产生了。然而,最后一行也产生了,因为组内计数的增量在范围内丢失了。4\c1\c1

类似地,对于最后一段,首字母2是从前一段的末尾留下的,并且然后计数被重置为0。因此,第 2 次和第 3 次调用产生12


如果我明白了您想要通过\expandafters 实现什么目标,我也许能够提供帮助,但我不清楚您希望实现什么目标。

答案3

首先,也是最重要的:你\expandafter什么都没做。

如果你希望它在重新检查\count100第一条指令之前导致被执行,那么你的期望是错误的。事实上,在扩展之后,TeX 将看到标记列表(换行只是为了方便阅读)\c\expandafter

\c
\the\count100 \advance\count100 by \count101\relax
\c

它会\c根据其定义继续扩展。所以你会得到

\the\count100 \advance\count100 by \count101\relax
\the\count100 \advance\count100 by \count101\relax
\c

所以你得到

  • 打印0;
  • \count100步进至 1;
  • 1 已打印;
  • \count100步进至 2;
  • 2 已打印;
  • \count100已升至 3。

最后,\hfil$..$被处理。现在来\par结束段落。现在你有

\expandafter{\c\c}\c \hfil text

(后面的内容\hfil基本上无关紧要)。同样,在这种情况下,\expandafterTeX 最终看到

{\the\count100 \advance\count100 by \count101\relax\c}\c \hfil text

OK,打开一个组,TeX 展开\the并意识到这会导致处理一个字符标记。这个字符标记是,也就是此刻3的值。因为这将\count100开始一个段落,在提供缩进框后,传递的内容\everypar;计数器重置为 0,但\the\count100已经展开,因此结果是

  • \count100重置为0;
  • 3 已打印;
  • \count100步进至 1;
  • 1 已打印;
  • \count100步进至 2;
  • 该组结束并\count100取回其在组开始之前的值
  • 3 已打印;
  • \count100已升至 4。

现在你可以继续解释下一个输出,我已经证明了 012 和 313有道理。可能不是你所期望的,但是……

  1. \everypar用来某件事引发了一个段落的开始;
  2. \expandafter导致只是扩展步骤;
  3. 无论如何,在宏扩展期间不会执行分配。

第 1 点非常重要。你可以尝试解释一下你得到了什么

\count100 = 0
\count101 = 1
\def\c{%
   \the \count100
   \advance \count100 by \count101\relax}%
%
\everypar{\count100 = 0\relax}


% the ending dot-brace sequence remembers me the commands executed on each line

\noindent\c\c\c \quad $...$

\noindent{\c\c}\c \quad $\{..\}.$

\noindent\c{\c}\c \quad $.\{.\}.$

\noindent\c{\c\c} \quad $..\{.\}$

我删除了\expandafter令牌,因为它们没有任何意义;\noindent会导致\everypar令牌被传递并\quad产生更清晰的输出。

在此处输入图片描述

相关内容