为什么 \def 在 \edef 里面会失败?

为什么 \def 在 \edef 里面会失败?

回答我之前的一个问题提供了此代码来引用一系列行,其中行标签放置在行范围的开始和结束处。

\makeatletter
\newcommand{\reflines}[1]{%
    \begingroup
    \def\temp@a{\getrefnumber{start:#1}}%
    \def\temp@b{\getrefnumber{end:#1}}%
    \ifnum\temp@a = \temp@b
    line \temp@a%
    \else
    lines \temp@a--\temp@b%
    \fi
    \endgroup
}
\makeatother

在 MWE 中,如果我们将 的一个实例更改\reflines为 在 内发生\edef,则会出现编译错误:

\documentclass{memoir}


\usepackage{refcount}
\usepackage{lineno}

\newcommand{\labellines}[2]{%
    \linelabel{start:#1}%
    #2%
    \linelabel{end:#1}%
}

\makeatletter
\newcommand{\reflines}[1]{%
    \begingroup
    \edef\temp@a{\getrefnumber{start:#1}}%
    \edef\temp@b{\getrefnumber{end:#1}}%
    \ifnum\temp@a = \temp@b
    line \temp@a%
    \else
    lines \temp@a\ and \temp@b%
    \fi
    \endgroup
}
\makeatother

\begin{document}
    \runninglinenumbers*
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam lacinia posuere
    magna ut imperdiet. \labellines{nunc-pellentesque}{Nunc pellentesque} velit quis
    leo interdum ullamcorper. Ut fringilla sapien ut sem viverra, et rhoncus enim
    lobortis. Morbi diam eros, tristique nec feugiat eu, consequat ac dui. Duis
    molestie, sem id efficitur dignissim, velit metus auctor orci, sit amet mattis
    metus purus in nibh. Etiam mattis, magna ut imperdiet molestie, ex leo viverra
    velit, ac posuere augue est maximus risus. Etiam ac pellentesque nulla. Morbi
    quis pharetra purus. Proin porta, turpis et scelerisque hendrerit, elit eros
    malesuada mi, a imperdiet massa est id nibh. Class aptent taciti sociosqu ad
    litora torquent per conubia nostra, per inceptos himenaeos.
    \labellines{nunc-pharetra}{Nunc pharetra vehicula metus, eu tincidunt arcu
        aliquet et. Aenean ac odio nunc. Etiam ultricies purus non dolor tincidunt, et
        ultricies turpis consequat. Integer vitae ullamcorper eros. Nullam pulvinar
        dictum nunc. Morbi ac justo mi.}

    % \reflines{nunc-pellentesque} should print "line 2"
    ``Nunc pellentesque\ldots'' was on \reflines{nunc-pellentesque}.

    \edef\nuncref{\reflines{nunc-pharetra}}
    % \reflines{nunc-pharetra} should print "lines 10--13"
    ``Nunc pharetra\ldots'' was on \nuncref.
\end{document}

为什么是这样?

答案1

让我们看看当你有

\makeatletter
\newcommand{\reflines}[1]{%
    \begingroup
    \edef\temp@a{\getrefnumber{start:#1}}%
    \edef\temp@b{\getrefnumber{end:#1}}%
    \ifnum\temp@a = \temp@b
    line \temp@a%
    \else
    lines \temp@a\ and \temp@b%
    \fi
    \endgroup
}
\makeatother

并尝试去做

\edef\nuncref{\reflines{nunc-pharetra}}

首先\reflines是扩展,也就是找到它的参数,吸收它并用替换文本替换整个内容:

    \begingroup
    \edef\temp@a{\getrefnumber{start:nunc-pharetra}}%
    \edef\temp@b{\getrefnumber{end:nunc-pharetra}}%
    \ifnum\temp@a = \temp@b
    line \temp@a
    \else
    lines \temp@a\ and \temp@b
    \fi
    \endgroup

但是,\edef它会一直进行扩展,因此它会继续扫描标记列表以查找可扩展的标记;前两个是\begingroup\edef,它们是不可扩展的,因此它们会原封不动地通过。接下来是\temp@a,它是未定义的;实际上,错误消息是

! Undefined control sequence.
\reflines #1->\begingroup \edef \temp@a 
                                        {\getrefnumber {start:#1}}\edef \tem...
l.47     \edef\nuncref{\reflines{nunc-pharetra}
                                               }

这是一个常见的误解,它\edef也会执行命令,但实际上它不会。如果你尝试

\edef\foo{\edef\foobar{A}}

假设你\def\foobar{B}在这之前有某个地方,那么替换文本\foo将是

\edef B{A}

在使用时会引发错误\foo。它将不是执行时引发错误\edef\foo{...}。这与此级别的脆弱命令无关。

尝试\protected@edef\nuncref{\protect\reflines{nunc-pharetra}}是完全没用的,因为替换的文本\nuncref将是

\protect\reflines{nunc-pharetra}

如下\show\nuncref所示:

> \nuncref=macro:
->\protect \reflines {nunc-pharetra}.

目前还不清楚你为什么要使用\edef\nuncref,因为这样做并没有什么好处

\newcommand\nuncref{\reflines{nunc-pharetra}

答案2

您的问题是由于\reflines存在组和作业(\def)而无法扩展。这些是不需要的,所以我们可以删除它们

\newcommand{\reflines}[1]{%
    \ifnum\getrefnumber{start:#1}=\getrefnumber{end:#1} %
      line \getrefnumber{start:#1}%
    \else
      lines \getrefnumber{start:#1}\ and \getrefnumber{end:#1}%
    \fi
}

此时你 \edef \reflines。可以强制对两个查找各进行一次评估,但是这里似乎不需要付出努力。

答案3

它是脆弱命令的老问题

\documentclass{memoir}


\usepackage{refcount}
\usepackage{lineno}

\newcommand{\labellines}[2]{%
    \linelabel{start:#1}%
    #2%
    \linelabel{end:#1}%
}

\makeatletter
\newcommand{\reflines}[1]{%
    \begingroup
    \edef\temp@a{\getrefnumber{start:#1}}%
    \edef\temp@b{\getrefnumber{end:#1}}%
    \ifnum\temp@a = \temp@b
    line \temp@a%
    \else
    lines \temp@a\ and \temp@b%
    \fi
    \endgroup
}
\makeatother

\begin{document}
    \runninglinenumbers*
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam lacinia posuere
    magna ut imperdiet. \labellines{nunc-pellentesque}{Nunc pellentesque} velit quis
    leo interdum ullamcorper. Ut fringilla sapien ut sem viverra, et rhoncus enim
    lobortis. Morbi diam eros, tristique nec feugiat eu, consequat ac dui. Duis
    molestie, sem id efficitur dignissim, velit metus auctor orci, sit amet mattis
    metus purus in nibh. Etiam mattis, magna ut imperdiet molestie, ex leo viverra
    velit, ac posuere augue est maximus risus. Etiam ac pellentesque nulla. Morbi
    quis pharetra purus. Proin porta, turpis et scelerisque hendrerit, elit eros
    malesuada mi, a imperdiet massa est id nibh. Class aptent taciti sociosqu ad
    litora torquent per conubia nostra, per inceptos himenaeos.
    \labellines{nunc-pharetra}{Nunc pharetra vehicula metus, eu tincidunt arcu
        aliquet et. Aenean ac odio nunc. Etiam ultricies purus non dolor tincidunt, et
        ultricies turpis consequat. Integer vitae ullamcorper eros. Nullam pulvinar
        dictum nunc. Morbi ac justo mi.}

    % \reflines{nunc-pellentesque} should print "line 2"
    ``Nunc pellentesque\ldots'' was on \reflines{nunc-pellentesque}.

    \makeatletter
    \protected@edef\nuncref{\protect\reflines{nunc-pharetra}}
    \makeatother
    % \reflines{nunc-pharetra} should print "lines 10--13"
    ``Nunc pharetra\ldots'' was on \nuncref.
\end{document}

相关内容