我试图解决 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 中有双重作用:
- 它们可以用来组成一个组,或者
- 它们可以包含一个未限定宏的参数。
在后一种情况下,没有团体参与:如果你这样做
\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
}
你会发现它起作用了,因为外面的支架被剥掉了,没有留下不平衡的支架。
当然,这不是编写循环的最佳方法,原因如下:
- 那些外括号毫无用处,并且影响了代码的可读性;
- 存在大量虚假空间;
- 没有换行。
请注意,\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
获得
练习:找出原因。