请考虑以下 MWE,它是摘录自更大的项目。
场景是定义一个条件\IfComp{<number>}{<defined>}{<undefined>}
查询,最终控制序列(在本例中)是否\csname test-<number>\endcsname
通过中间控制序列定义(\csname macro-<number>\endcsname
)。
\documentclass{minimal}
\makeatletter
\parindent\z@
\expandafter\def\csname test-1\endcsname{foo}
\expandafter\def\csname macro-1\endcsname{\csname test-1\endcsname}
\expandafter\def\csname macro-2\endcsname{\csname test-2\endcsname}
\def\testA{%
\long\def\IfComp##1##2##3{%
\expandafter\expandafter\expandafter\let\expandafter\csname @comp@name\expandafter\endcsname\csname macro-##1\endcsname%
\expandafter\expandafter\expandafter\ifx\@comp@name\relax
##3
\else
##2
\fi%
}}
\def\testB{%
\long\def\IfComp##1##2##3{%
\expandafter\expandafter\expandafter\ifx\csname macro-##1\endcsname\relax
##3
\else
##2
\fi
}}
\def\testC{%
\long\def\IfComp##1##2##3{%
%%% Why seven?!?
\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter
\expandafter\ifx\csname macro-##1\endcsname\relax
##3
\else
##2
\fi
}}
\begin{document}
{\testA
\textbf{TEST A}\\
\IfComp{1}{defined}{undefined}\\
\IfComp{2}{defined}{undefined}\\
\textbf{TEST B}\\
\protected@edef\x{\IfComp{1}{defined}{undefined}}\expandafter\verb\expandafter|\meaning\x| \\
\protected@edef\x{\IfComp{2}{defined}{undefined}}\expandafter\verb\expandafter|\meaning\x| \\
}
{\testB
\textbf{TEST C}\\
\IfComp{1}{defined}{undefined}\\
\IfComp{2}{defined}{undefined}\\
\textbf{TEST D}\\
\protected@edef\x{\IfComp{1}{defined}{undefined}}\expandafter\verb\expandafter|\meaning\x| \\
\protected@edef\x{\IfComp{2}{defined}{undefined}}\expandafter\verb\expandafter|\meaning\x| \\
}
{\testC
\textbf{TEST E}\\
\IfComp{1}{defined}{undefined}\\
\IfComp{2}{defined}{undefined}\\
\textbf{TEST F}\\
\protected@edef\x{\IfComp{1}{defined}{undefined}}\expandafter\verb\expandafter|\meaning\x| \\
\protected@edef\x{\IfComp{2}{defined}{undefined}}\expandafter\verb\expandafter|\meaning\x| \\
}
\end{document}
输出结果如下:
\TestA
这是我的第一个尝试,它在大多数情况下都运行良好(参见测试 A)。但是,我后来遇到了一种情况,我需要将整个查询放在一个里面\protected@edef{…}
,代码崩溃了,因为它\let\@comp@name\test-1
在堆栈中留下了未展开的部分(参见测试 B)。
因此,我尝试了一下,并想出了\TestB
,希望\csname macro-##1\endcsname
在与 进行检查之前会扩展两次\relax
。但这并不奏效,我在两种情况下都得到了“定义”(参见测试 C 和 D)。
在连续添加 moar \expandafter
s 之后,我最终让它与 7 \expandafter
s 一起工作(参见测试 E 和 F)。
我的问题是:为什么七\expandafter
在两种情况下都有效?
答案1
\expandafter
在嵌套链中,您几乎总是需要 2^n -1 。
%%% Why seven?!?
%1 %2
\expandafter\expandafter\expandafter
%3
\expandafter\expandafter\expandafter
%4
\expandafter\ifx
%5
\csname macro-##1\endcsname\relax
如果#1
上面foo
标记了前五个扩展步骤,那么输入缓冲区将包含
%1
\expandafter
%2
\expandafter\expandafter
\ifx
%3
\macro-foo\relax
现在\macro-foo
扩展到\csname foo-wibble\endcsname
,并且令牌按照标记的顺序再次扩展,因此经过三个扩展步骤后,您将获得
%1
\expandafter
\ifx
%2
\csname foo-wibble\endcsname\relax
因此,经过两个扩展步骤后,输入缓冲区具有
\ifx
\foo-wibble\reax
\foo-wibble
如果先前没有定义,则结果为真。
正如您在这里看到的,您需要扩展令牌三次,但是在每一步应用另一个令牌时,\expandafter
您都需要链接所有现有的令牌,因此,如果您需要四次扩展,那么您需要\expandafter
在现有的 7 个令牌前面进行四次扩展,然后将一个\expandafter
令牌应用于令牌,因此 2*7+1=15,这又比 2 的幂少一。
如果你使用 expl3,你可能只会使用一个:e
函数,而根本:n
不需要任何函数\expandafter
,但这是链接的经典方式\expandafter
你可以更换
\expandafter\def\csname macro-2\endcsname{\csname test-2\endcsname}
经过
\expandafter\def\csname macro-2\expandafter\endcsname\expandafter{\csname test-2\endcsname}
那么你以后就不需要一个扩展了,所以只需要三个而不是七个链式 expandafter,或者你可以使用
\expandafter\let\csname macro-2\expandafter\endcsname\csname test-2\endcsname
这将删除另一层扩展,因此您\expandafter
以后只需要一层,而不是 7 层。