我看到很多宏都是条件测试,后跟一{true}{false}
对定义,因此它们的结果如下:
\expandafter\@firstoftwo
或者
\expandafter\@secondoftwo
为什么这些\expandafter
s 在那里?我本以为它们只会捕获下一{true}{false}
对中的第一个括号?
答案1
s\expandafter
用于处理以下\else
或\fi
。正如 Ryan 链接到的问题中所述,完整代码如下:
\def\ifeq#1#2{%
\ifx#1#2\relax
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
让我们来追踪一下发生了什么。我们将其放入\ifeq\stuff\nonsense{true}{false}
我们的文档中。\ifeq
吸收了\stuff
,因此\nonsense
在第一次扩展之后我们得到了:
\ifx\stuff\nonsense\relax
\expandafter\@firstofone
\else
\expandafter\@secondoftwo
\fi
{true}{false}
假设是\stuff
(\nonsense
即条件为真)。然后\ifx
开始扩展其“真”路径中的所有内容,该路径被定义为直到下一个\else
或\fi
(模数嵌套)的所有内容。关键点是它开始扩展第一的并且不会提前查找\else
或\fi
。TeX 认为当它到达时就会知道。所以我们有:
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
{true}{false}
TeX 现在扩展了。这具有从 延伸到并扩展\expandafter
的效果。“扩展”意味着从流中移除它以及匹配之前的所有内容。所以我们剩下:\@firstoftwo
\else
\else
\fi
\@firstoftwo{true}{false}
然后,它就会扩展为简单的true
。
没有\expandafter
了 s,我们得到:
\@firstoftwo
\else
\@secondoftwo
\fi
{true}{false}
TeX 仍在扩展真正的分支,因此会扩展\@firstoftwo
。这会从流中吸收两个标记/括号组。它们恰好是\else
和\@secondoftwo
。然后它将第一个留在流中,因此我们得到
\else
\fi
{true}{false}
\else
与条件匹配,因此 TeX 会吸收此内容以及流中留下的所有内容。\fi
但这{true}{false}
并不是我们想要的。
总之,\expandafters
在结果条件被扩展,从而确保条件的结果看到流中的下一个位,而不是未完成条件留下的位。
答案2
我会从不同的角度来解决这个问题。TeX 中的原始条件测试一个条件,该条件可以从输入流中吸收标记,或者直到可以确定条件的真假为止。因此,让我们用
<IF>
原始条件和必须吸收的标记(可能为空)列表表示。例如,\ifhmode
不需要标记,\ifx
需要两个。在某些情况下(\if
,\ifcat
,\ifnum
,\ifdim
),TeX 会执行扩展以找到测试所需的标记类型;在其他情况下(\ifx
,\ifmmode
,\ifhmode
,\ifvmode
\ifinner
,\iftrue
,\iffalse
)不执行扩展。因此<IF>
将表示条件和所需的标记后扩展已经发生并且可以测试条件。
你所指的典型结构是
<IF>
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
我们已经有了
\long\def\@firstoftwo#1#2{#1}
\long\def\@secondoftwo#1#2{#2}
更一般地,我们有
<IF><true>\else<false>\fi
或者
\IF<true>\fi
其中和<true>
都可<false>
以为空。
条件为真
TeX 将简单地<IF>
从输入流中删除,留下
<true>\else<false>\fi
或者
<true>\fi
条件为假
TeX 会查找下一个\else
标记,同时考虑可能出现的嵌套条件,而<true>
无需扩展任何内容。因此,\else
属于嵌套\if...\else\fi
内部的<true>
标记将被跳过。在这种情况下,扩展也是空的,并且直到的每个标记\else
都将消失。如果\else
找不到匹配项,TeX 将停止查找应该在某处的匹配项\fi
。因此,在这两种情况下,我们都会得到
<false>\fi
或者如果没有\else
分支则什么也没有。
以下 TeX 输入证明了这一点:
\def\showx{\show\x}
\def\showif{\afterassignment\showx
\expandafter\def\expandafter\x\expandafter}
\showif{\ifvmode<true>\else<false>\fi}
\showif{\ifvmode<true>\fi}
\showif{\ifhmode<true>\else<false>\fi}
\showif{\ifhmode<true>\fi}
\bye
在其上运行 TeX 将产生以下记录:
This is TeX, Version 3.1415926 (TeX Live 2012)
(./plkfi.tex
> \x=macro:
-><true>\else <false>\fi .
\showx ->\show \x
l.5 \showif{\ifvmode<true>\else<false>\fi}
?
> \x=macro:
-><true>\fi .
\showx ->\show \x
l.6 \showif{\ifvmode<true>\fi}
?
> \x=macro:
-><false>\fi .
\showx ->\show \x
l.7 \showif{\ifhmode<true>\else<false>\fi}
?
> \x=macro:
->.
\showx ->\show \x
l.8 \showif{\ifhmode<true>\fi}
?
接下来发生什么
的扩展
\else
包括删除匹配之前的所有内容\fi
,并且在输入流中不留下任何内容。嵌套条件将像以前一样被考虑。的展开
\fi
为空。
的作用\expandafter
现在,我们已经有了可以投放的基地\expandafter
。
让我们看一个典型的用法:
\def\@ifundefined#1{%
\expandafter\ifx\csname#1\endcsname\relax
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi}
我们想要的是能够说
\@ifundefined{foo}{T}{F}
因此,将使用参数作为名称构建的宏进行比较\relax
(这实际上是无趣的部分),然后遵循真或假分支。
移除后<IF>
我们仍然
\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi{T}{F}
现在 TeX 会适时扩展第一个标记。这会触发扩展\else
,而这正是好玩的地方。
的扩展\expandafter
包括扩展(如果可能的话)下一个标记之后的标记并消失。因此\else
根据上述规则进行扩展,我们剩下
\@firstoftwo{T}{F}
这导致留T
在输入流中。
现在假设条件为假。然后将<IF>
连同直到的所有内容一起删除\else
,剩下
\expandafter\@secondoftwo\fi{T}{F}
现在\expandafter
它的工作就是扩大\fi
和消失。因此我们得到
\@secondoftwo{T}{F}
终于离开了F
。
重要的提示
在\@ifundefined{foo}{T}{F}
我们能够得到T
或F
没有执行命令:只使用了宏扩展。这使得\@ifundefined
类似定义的宏可以在内部使用\edef
:
\edef\test{\@ifundefined{foo}{T}{F}}
相当于
\def\test{T}
如果\foo
定义了(而不是等同于\relax
LaTeX 中的通常情况),或者
\def\test{F}
如果\foo
未定义(或等同于\relax
)。
如果没有会发生什么\expandafter
?如果条件成立,TeX 将面临
\@firstoftwo\else\@secondoftwo\fi{T}{F}
并且 的两个参数将\@firstoftwo
是\else
和\@secondoftwo
,这不会做任何有用的事情,不是吗?
类似地,对于错误条件,我们会得到
\@secondoftwo\fi{T}{F}
事情又会出错。
答案3
此类代码的完整上下文位于条件宏中,例如
\def\IfZero#1{%
\ifnum0=#1\relax
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
\expandafter
如果你以不太格式化的方式查看代码,则 s 是必需的:
\def\IfZero#1{\ifnum0=#1\relax\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi}
(抱歉,代码很长)其中没有给出 TeX 条件块的结构线索。如您所见,标记“after”\@firstoftwo
是\else
,而 after\@secondoftwo
是\fi
,它们分别被 扩展\expandafter
。这种愚蠢的做法的目的是 TeX 在扩展 时不会读取整个条件\ifnum
;它只是向前扫描到正确的 true 或 false 块并从那里继续。\else
or\fi
留在输入流中!一旦它们被扩展,TeX 就会扫描到条件的末尾,输入流中的下一个内容是 后面的内容\IfZero
。
如果没有,和\expandafter
所消耗的“两个”将分别是和以及下面的宏参数,而不是实际需要的,即作为 的“第二”和“第三个”参数的以下两个宏参数。\@firstoftwo
\@secondoftwo
\else\@secondoftwo
\fi
\IfZero