为什么花括号会破坏这个循环?

为什么花括号会破坏这个循环?

我试图解决 TeX Book 中的“练习 20.20”,它要求创建一个\punishment能够使用 Plain TeX 格式重复给定句子 n 次的宏。我的第一次尝试如下:

% not working
\def\punishment#1#2{
    \newcount\ii\ii=0
    \loop {
        \advance\ii by 1 
        \message{\the\ii}
        {#1}
    } \ifnum\ii<#2 \repeat
}

但这会出现错误! TeX capacity exceeded, sorry [main memory size=5000000].此外,终端上打印的消息1 1 1 1 1 ...显示计数器保持固定为 1 并且循环永远持续下去。

你能解释一下为什么我必须删除花括号以获得以下工作解决方案?

% working
\def\punishment#1#2{
    \newcount\ii\ii=0
    \loop
        \advance\ii by 1 
        \message{\the\ii}
        {#1}
    \ifnum\ii<#2 \repeat
}

更具体地说,为什么第一次尝试会表现成那样?

答案1

{...} 形成一个组,并且\advance\ii by 1是一个本地分配,因此在组结束时恢复。

所以\ii始终为 0\ifnum\ii<#2并且永远循环。

你的定义可能根本不适合用纯文本,如果你使用纯文本,你会得到错误

Runaway definition?
#1#2-> 
! Forbidden control sequence found while scanning definition of \punishment.
<inserted text> 
                }
<to be read again> 
                   \newcount 
l.2     \newcount
                 \ii\ii=0
? 

\newcount因为\outer定义中不允许这样。

在乳胶中这不是一个错误,但你仍然应该移出\newcount定义,这样每次使用宏时你只分配一个计数器而不是一个新的计数器。

答案2

括号在 TeX 中有双重作用:

  1. 它们可以用来组成一个组,或者
  2. 它们可以包含一个未限定宏的参数。

在后一种情况下,没有团体参与:如果你这样做

\def\foo#1{(#1)}
\foo{\it italics} and still italics

您将看到\it没有作用域,但继续。实际上,传递给的参数\mytext只是italics宏替换产生

(\it italics) and still italics

相反,如果你这样做

\def\foo{(}
\foo{\it italics} and upright text

你会得到

({\it italics} and upright text

因为没有参数抓取,所以孤立的左括号打开了一个组。

现在我们可以看看的定义\loop

527 \def\loop#1\repeat{\def\body{#1}\iterate}
528 \def\iterate{\body \let\next\iterate \else\let\next\relax\fi \next}
529 \let\repeat=\fi % this makes \loop...\if...\repeat skippable

(行号参见plain.tex)。嗯,\loop好像需要争论,不是吗?

是的,但论点是分隔并由直到第一次出现\repeat在同一括号级别的所有内容组成。在这种情况下,只有当参数的形式为

{<tokens>}

并且剥去外面的牙套不会导致牙套不平衡。

就你的情况而言

{•\advance\ii by•1•\message{\the\ii}•{#1}•}•\ifnum\ii<#2•

其中表示空格标记,因此不会剥离括号,而独立的左括号会打开一个组。在组的末尾, 的值\ii会恢复到以前的值,这解释了为什么您总是得到1,因为在组\ii返回后会返回 的值0

如果你这样做

\newcount\ii\ii=0
\def\punishment#1#2{
    \loop {
        \advance\ii by 1 
        \message{\the\ii}
        {#1}
    \ifnum\ii<#2 }\repeat
}

你会发现它起作用了,因为外面的支架被剥掉了,没有留下不平衡的支架。

当然,这不是编写循环的最佳方法,原因如下:

  1. 那些外括号毫无用处,并且影响了代码的可读性;
  2. 存在大量虚假空间;
  3. 没有换行。

请注意,\newcount在纯 TeX 中是外部的(您不应该使用 LaTeX 测试纯 TeX 代码),所以它不能出现在宏的替换文本中,所以我将它移到外面。

初始化\ii=0应该放在宏内部。

\newcount\ii
\def\punishment#1#2{%
  \ii=0
  \loop
    \advance\ii by 1 
    \message{\the\ii}%
    #1\endgraf % \par wouldn't work in plain
  \ifnum\ii<#2\repeat
}

我不会在 周围使用括号#1。为什么\repeat就在 之后#2

好的,我们来做个测试。

\newcount\ii
\def\punishment#1#2{%
  \ii=0
  \loop
    \advance\ii by 1 
    \message{\the\ii}%
    #1
  \ifnum\ii<#2
\repeat
}

\newcount\test
\test=4

\punishment{Test}{4}

\punishment{Test}{\test}

\bye

输出应该是一样的,不是吗?

在此处输入图片描述

移动\repeat或添加\relax紧接着#2获得

在此处输入图片描述

练习:找出原因。

相关内容