循环的新命令导致处理挂起

循环的新命令导致处理挂起

由于快速搜索/替换“mathcal”为“mc”,我最终得到了一个循环或递归语句,例如

\newcommand{\mc}{\mc}

这似乎导致我的文档编译挂起。这是一个已知错误吗?(为什么还没有修复?)

答案1

可以根据自身定义宏。正如评论中提到的,你需要小心。问题\newcommand\mc{\mc}不是在定义时发生的,而是在你第一次尝试使用它时发生的,此时,如果保持原样,它将递归调用自身。有时这是你想要的行为。

但有时您想要别的东西,在这种情况下,您可以使用其中一种强大的功能\edef\xdef创建您想要的东西。

这是一个非常简单的例子,它说明了如何从元素列表构建表格环境。表格环境的内容是根据先前定义的内容构建的:换句话说,它\ae@tabular@content是根据其自身定义的,但要采取预防措施,不要扩展太多(因此使用了 中的工具)etoolbox

\documentclass{article}
\usepackage{etoolbox}
\usepackage{pgffor}

\makeatletter
\def\ae@tabular@content{}
\def\ae@empty@content{}
\def\aebuildtabular#1{%%
  \def\ae@tabular@content{}%%
  \foreach \myx/\myy in {#1}  
  {\ifx\ae@tabular@content\ae@empty@content
    \xdef\ae@tabular@content{\expandonce\myx & \myy }%%
   \else
    \xdef\ae@tabular@content{\expandonce\ae@tabular@content \noexpand\\ \expandonce\myx & \myy }%%
  \fi}
  \begin{tabular}{lr}
    \ae@tabular@content
  \end{tabular}}
\makeatother

\begin{document}

\aebuildtabular{A/Apple,B/Banana,C/Carrot,D/Daikon,E/Endive,F/Fig}

\end{document}

生产

在此处输入图片描述

有时你可能想要递归调用修剪(或执行其他操作)。下一个示例接收一个字符串并丢弃标记,直到字符串中第一次出现E。如果没有E,则它仅恢复原始字符串。

\documentclass{article}
\makeatletter
\newcommand\aestriptoFirstE[1]{%%
  \def\ae@old{#1}%%
  #1 $\rightarrow$ \ae@striptoE#1\@nil
}

\def\ae@old{}
\def\ae@E@test{E}
\def\ae@empty@test{}
\def\ae@striptoE#1#2\@nil{%%
  \def\ae@continue{}%%
  \def\ae@first{#1}%%
  \def\ae@second{#2}%%
  \ifx\ae@E@test\ae@first
    #2
  \else
    \ifx\ae@empty@test\ae@second
      (unchanged) \ae@old
    \else
      \def\ae@continue{\ae@striptoE#2\@nil}%%
    \fi
  \fi
  \ae@continue
}

\makeatother
\begin{document}

\aestriptoFirstE{ABCDEFGE}

\aestriptoFirstE{WXYZ}

\end{document}

在此处输入图片描述

答案2

\newcommand{\mc}{\mc}就 TeX 基元而言,声明最终变为

\long\def\mc{\mc}

\long与讨论的情况无关。

你必须考虑到 TeX 不会以任何方式解释宏的替换文本当它存储定义时这是必要的功能,因为当我们扩张宏,我们希望替换文本中的宏使用它们的当前的意思是,它们不是在定义时有效的,甚至不需要被定义!

举一个愚蠢的例子,考虑宏定义

\newcommand{\mybreak}{BREAK\\}

在 LaTeX 中,宏\\在多种场合会改变其含义;它在外层表示某种含义,在\centering或等声明\raggedright有效时表示其他含义,而在 或 内部时则具有非常不同arraytabular含义tabbing

这是想要的!用户只需知道这\\会导致换行。然后可以在任何可以给出的\mybreak情况下使用,因为替换文本中的标记将在调用时被解释(扩展和执行)。\\\\

以下是 TeXbook 中的一个例子:

\def\hex{{\count0=\n \divide\n by16
  \ifnum\n>0 \hex\fi \count2=\n \multiply\count2 by-16
  \advance\count0 by\count2 \hexdigit}}
\def\hexdigit{\ifnum\count0<10 \number\count0
  \else\advance\count0 by-10 \advance\count0 by`A \char\count0 \fi}

标记\n应该是一个计数寄存器。如您所见,宏\hex有一个替换文本调用\hex自身;但是每次调用时的值\n都会减少,因此最终在条件分支的真分支中发生的调用\hex将不会发生,宏的工作将有一个圆满的结局。

因此,无法实现简单的检查,即宏的替换文本不包含宏本身,因为这将使递归变得更加困难,甚至不可能。

这当然是有代价的:您的定义会导致 TeX 在发现\mc要替换\mc为时\mc,将其替换为\mc……Bingo!无限循环。程序不会挂起:它只是尽职尽责地用自身替换标记,而不会耗尽计算机中的资源;不会引发任何错误,因为从机器的角度来看,这并没有什么问题,它只是遵循规则。

另一种情况是

\newcommand{\mc}{(\mc)}

现在,在第一次调用时,\mcTeX 的某些部分内存会被填满,在本例中是“输入堆栈”,TeX Live 2013(同时输入源)为其分配了 5000 的大小:的扩展在扩展的同时\mc将第一个推(入堆栈\mc(在扩展的同时将第一个推入堆栈\mc......另一个无限循环,但这个循环会填满内存。

假设\mc最初定义为无参数宏,则在其替换文本的任一端添加括号可以通过以下方式实现

\expandafter\def\expandafter\mc\expandafter{\expandafter(\mc)}

因为\expandafter会把手指放在替换文本里面定义已执行。但是,在这种情况下,新的替换文本将不包含\mc(假设原始定义中不包含)。

等效结构如下

\edef\mc{(\unexpanded\expandafter{\mc})}

但这会让我们走得太远(它需要 e-TeX,因此它不能与 Knuth 的 TeX 一起使用)。

相关内容