检查嵌套的 `\csname…\endcsname` 时理解 `\expandafter`

检查嵌套的 `\csname…\endcsname` 时理解 `\expandafter`

请考虑以下 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 \expandafters 之后,我最终让它与 7 \expandafters 一起工作(参见测试 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 层。

相关内容