嵌套 \noexpand 和 \edef 的问题

嵌套 \noexpand 和 \edef 的问题

我有一个 LaTeX 问题,想用以下代码片段来解释它:

\documentclass{article}
\begin{document}

  \def\myline{A}
  \edef\myline{\myline \noexpand\bf B}
  \edef\myline{\myline C}

  \myline
\end{document}

我必须逐步建立一行。这发生在 for 循环内。问题是我想用粗体突出显示一些文本,但每当我扩展我的行时,我都会收到以下错误:

! Missing control sequence inserted.
<inserted text> 
                \inaccessible 
l.9 \myline

? X

我认为,这是因为第三个\edef,它试图扩展\bf第二个\edef,但是我必须扩展它以避免无限递归。

答案1

让我们看看发生了什么

\def\myline{A}
\edef\myline{\myline \noexpand\bf B}

给定第一行,第二行相当于

\def\myline{A\bf B}

现在,当你说

\edef\myline{\myline C}

TeX 完全扩展了内部\myline,它变成了这样(\show\myline为避免错误的换行而进行编辑的结果):

> \myline=macro:
->A\protect \protect \protect \edef OT1{OT1}\let \enc@update \relax
 \protect \edef cmr{cmr}\protect \edef m{m}\protect \edef n{n}
 \protect \xdef \OT1/cmr/m/n/10 {\OT1/cmr/m/n/10 }\OT1/cmr/m/n/10
 \size@update \enc@update \ignorespaces \relax \protect \relax 
 \protect \edef m{bx}\protect \xdef \OT1/cmr/m/n/10  
 {\OT1/cmr/m/n/10}\OT1/cmr/m/n/10 \size@update \enc@update BC.

这肯定不是 TeX 在执行新定义的时所希望看到的\myline

问题是,\noexpand抑制扩张一次:第二个\edef,令牌\bf 将要被扩展。这是“正确”的做法:

\documentclass{article}
\begin{document}

\makeatletter
\def\myline{A}
\protected@edef\myline{\myline \bfseries B}
\protected@edef\myline{\myline C}

\myline
\end{document}

注意,\protect前面的\bfseries是不需要的,因为\bfseries已经是一个“LaTeX 强命令”。

另一种方法是使用etoolbox

\documentclass{article}
\usepackage{etoolbox}
\robustify\bfseries

\begin{document}

\def\myline{A}
\edef\myline{\myline \bfseries B}
\edef\myline{\myline C}

\myline
\end{document}

这将使\bfseries内部“永远受到保护” \edef

马丁在他的回答中建议使用\appto,这是一个通过累积构建宏的好方法:

\usepackage{etoolbox}

\def\myline{}
\appto\myline{A \bfseries B}
\appto\myline{C}

在这种情况下,我们得到的结果与

\def\myline{A \bfseries BC}

如果吸积必须是全局的,则可以使用\gappto(相当于内核宏\g@addto@macro)。

另一个策略是使用令牌寄存器:

\newtoks\mylinetoks

\mylinetoks={}
\mylinetoks={A \bfseries B}
\mylinetoks=\expandafter{\mylinetoks C}

当然,寄存器可以使用任意多次,只需分配一次即可。内核提供\addto@hook

\newtoks\mylinetoks

\makeatletter
\mylinetoks={}
\addto@hook\mylinetoks{A \bfseries B}
\addto@hook\mylinetoks{\mylinetoks C}

要使用寄存器的内容,你可以说

\the\mylinetoks

答案2

正如您自己所说,\bf第二个 将被扩展。您还需要为此\edef添加一个,其前缀必须为:\noexpand\edef\noexpand

\documentclass{article}
\begin{document}

  \def\myline{A}
  \edef\myline{\myline \noexpand\noexpand\noexpand\bf B}
  \edef\myline{\myline C}

  \myline
\end{document}

当然,如果您有更多,情况就会很快失控\edef,特别是当您不知道确切的数字时。

更好的方法是使用\protect和,\protected@edef以确保将\protect与扩展数无关。在正常文本中它不会执行任何操作,因此不会造成任何问题。

\documentclass{article}
\begin{document}

  \def\myline{A}
  \makeatletter
  \protected@edef\myline{\myline \protect\bf B}
  \protected@edef\myline{\myline C}
  \makeatother

  \myline
\end{document}

如果您想要简单地附加材料\myline而不扩展任何内容,则可以使用\expandafter

\expandafter\def\expandafter\myline\expandafter{\myline <new stuff>}

因此,在再次定义之前,旧的定义会被扩展。

还有一个 LaTeX 宏,\g@addto@macro\yourmacro{<stuff>}它可以将内容添加到给定的宏中,但是是全局的。它使用一个令牌寄存器 (托克斯) 为了这。

etoolbox包中有一些很好的宏,例如\appto\yourmacro{<stuff>}仅在本地起作用的宏。

答案3

正如 egreg 指出的那样,\bf\textbf都是LaTeX 保护。没有必要在\protect他们后面\protected@edef

也许这就是约瑟夫赖特所指的:

\def\myline{A}
% \bfseries needs localization:
\@temptokena{{\bfseries B}}
\edef\myline{\myline \the\@temptokena}

问题是,当你做出定义时,它就\protect变成了这样。你本可以这样做\relax

% In document preamble:
\makeatletter
\@ifdefinable\protedef{\let\protedef\protected@edef}
\makeatother
% Subsequently:
\def\myline{A}
\protedef\myline{\myline {\bfseries B}}

您无需加载包来增强功能\bf\bfseries只需执行

\newcommand*\mybf{} % \mybf is definable.
\let\mybf\bfseries
\protected\def\bfseries{\mybf}
\def\myline{A}
\edef\myline{\myline {\bfseries B}}

但这会发生\bfseries全局变化,其(与 \robustify 类似)只能在本地或文档主体内完成。

如果你想知道这如何不导致循环定义,以下是它在文档主体中的工作方式:

{\bfseries XX}

\bfseries->\mybf
\mybf->\protect\bfseries
{\relax}
\bfseries->\not@math@alphabet\bfseries\mathbf\fontseries\bfdefault\selectfont 

相关内容