CTAN 上的代码comment-pln
(关联) 定义纯 TeX 宏\begincomment
和\endcomment
,忽略其间输入的所有内容。完整代码库如下:
% Comment.tex
% Macro to allow block comments in TeX
%
% Usage: Text between \begincomment and \endcomment is ignored; i.e.
%
% text
% \begincomment
% this text is ignored
% \endcomment
% more text
%
% Notes: Do NOT nest.
% The \endcomment MUST appear at the end of a line.
% TeX processes each line of ignored text, so the macro
% is not particularly fast. Use sparingly. The main use
% is to cause TeX to pass over small blocks of text.
%
% J.C. Alexander, May, 1986
%
\edef\Saveatcatcode{\the\catcode`\@}
\catcode`\@=11
\def\newcodes@{\edef\S@veslashcatcode{\the\catcode`\\}
\edef\S@velbraccatcode{\the\catcode`\{}
\edef\S@verbraccatcode{\the\catcode`\}}
\edef\S@venumsgcatcode{\the\catcode`\#}
\edef\S@veperctcatcode{\the\catcode`\%}
\catcode`\\=12 \catcode`\{=12 \catcode`\}=12 \catcode`\#=12
\catcode`\%=12\relax}
\def\oldcodes@{\catcode`\\=\S@veslashcatcode
\catcode`\{=\S@velbraccatcode
\catcode`\}=\S@verbraccatcode
\catcode`\#=\S@venumsgcatcode
\catcode`\%=\S@veperctcatcode
\relax}
\def\begincomment{\newcodes@\endlinechar=10 \comment@}
{\lccode`\!=`\\
\lowercase{\gdef\comment@#1^^J{\comment@@#1!endcomment\comment@@@}%
\gdef\comment@@#1!endcomment{\futurelet\next\comment@@@}%
\gdef\comment@@@#1\comment@@@{\ifx\next\comment@@@\let
\next=\comment@\else\def\next{\oldcodes@\endlinechar=`\^^M\relax}%
\fi\next}}}
\catcode`\@=\Saveatcatcode
这是什么黑魔法?
\
我得到的前几行:它提供了一种方法来重新定义、、、和{
的catcode 为“}
其他”并恢复更改。据推测,当注释被视为注释时,这将导致它们不会像往常一样做巫术。#
%
(然而:分别是0、1、2、6、14个字符;为什么$
、_
、 和^
可以省略?)
我也认为我得到了它的定义\begincomment
:它改变了 catcodes,设置了结束符,并“调用宏” \comment@
(我认为由于结束符的改变,它实际上在处理过程中占用了注释部分)。
因此,我的主要问题是:这究竟是如何
\comment@
工作的,以及它与紧随其后的组中的代码有何关系?我完全不知道这三个\gdef
是做什么的。
答案1
在尝试理解该定义之前,让我们给它一个适当的缩进和行号,以便它(可以说)更具可读性:
01:{%
02: \lccode`\!=`\\
03: \lowercase{%
04: \gdef\comment@#1^^J{\comment@@#1!endcomment\comment@@@}%
05: \gdef\comment@@#1!endcomment{\futurelet\next\comment@@@}%
06: \gdef\comment@@@#1\comment@@@{%
07: \ifx\next\comment@@@
08: \let\next=\comment@
09: \else
10: \def\next{\oldcodes@\endlinechar=`\^^M\relax}%
11: \fi
12: \next}%
13: }%
14:}
它首先打开一个组(第 1 行),使以下\lccode
设置成为本地设置。然后它将 的 更改为\lccode
的!
,\
然后发出\lowercase
剩余的代码块。这就是所谓的\lowercase
诡计用于在定义中注入具有不寻常 catcode 的字符。该装置:
{\lccode`\!=`\\
\lowercase{<code>}%
}
可以替换为
\begingroup
\lccode`\!=`\\
\lowercase{%
\endgroup
<code>}
那么\gdef
就可以用 代替\def
。但回到定义。 After\lowercase
完成了它的工作,所有具有非零的标记\lccode
都被替换为小写字母,这包括将字符替换!
为人物 \
(即 catcode 12 反斜杠)。我将像\\
上面的代码块一样显示它们,以区别于 catcode 0 反斜杠,但它们实际上是单个\
12 个标记:
04: \gdef\comment@#1^^J{\comment@@#1\\endcomment\comment@@@}%
05: \gdef\comment@@#1\\endcomment{\futurelet\next\comment@@@}%
06: \gdef\comment@@@#1\comment@@@{%
07: \ifx\next\comment@@@
08: \let\next=\comment@
09: \else
10: \def\next{\oldcodes@\endlinechar=`\^^M\relax}%
11: \fi
12: \next}%
现在,这三个命令的作用如下:\comment@
抓取所有内容,直到下一个^^J
(由于\endlinechar
设置为 10,因此是行尾)并执行\comment@@#1\\endcomment\comment@@@
(请记住,这\\
是一个反斜杠)。这里有两种可能性:第一种是,如果#1
是要注释的“正常”代码行,第二种是,如果#1
是字符串\\endcomment
。
现在让我们分别看看每种可能性:如果该行不包含\\endcomment
,\comment@@
则将抓取 左侧的所有内容\\endcomment
,\comment@
丢弃它,然后执行\futurelet\next\comment@@@
。由于 之前的所有内容都\\endcomment
被丢弃了,因此下一个标记是其他 \comment@@@
。现在\futurelet
开始工作,(第一个)\comment@@@
被展开,并检查\next
标记是否也是\comment@@@
。如果是,则进行处理,\let\next=\comment@
并处理另一行。
\\endcomment
当找到包含的行时,\comment@@
将使用它作为分隔符,然后该\futurelet\next\comment@@@
事物将分配其他东西至 \next
,而非\comment@@@
。在这种情况下,\ifx
中的测试\comment@@@
将返回 false ,并将\next
被重新定义为使用 使 catcodes 返回正常\oldcodes@
,处理完成。
答案2
的定义\newcodes@
将当前的类别代码保存\ { } # %
到宏中并将其更改为12。
将反斜杠变成“其他字符”是必要的,不是为了阻止宏的解释,因为宏在尝试扩展之前会被丢弃,而是为了避免 TeX 看到条件语句并保持它们在跳过的分支中的嵌套。此外,\comment@@@
注释块中的出现会毁掉一切。
括号被设为 catcode 12,这样 TeX 就不会尝试考虑它们的嵌套。如果没有这个,{
一行中的 a 和}
另一行中的 a 可能会带来灾难性的后果。百分号被设为 other,这样它就不会掩盖行尾。
我看不出有任何明显的理由改变 catcode #
;然而,当被吸收到宏的参数中时,这可以避免其“加倍”。
其他特殊字符没有问题,因为所有找到的标记都将被丢弃。如果我们想逐字打印,则需要对它们进行处理。
该宏\oldcodes@
恢复已改变的类别代码。
现在最重要的宏。
首先\begincomment
执行\newcodes@
并设置行尾机制来传递字符编号 10(以双帽符号表示)^^J
。然后\comment@
执行。
它的工作是什么?它期待^^J
(直到行尾),使参数成为介于两者之间的任何值;然后它传递\comment@@
参数,然后传递参数,然后传递人物 \endcomment
,第一类代码12,其他11,以 结尾\comment@@@
。
使用\lowercase
,因此!
在执行任何其他操作之前,其参数中的每个字符都会更改为反斜杠;由于没有出现大写字符,因此这是唯一的变化。请记住,\lowercase
只会影响人物并保持控制序列不变。
因此,宏\comment@@
被定义为具有由 (不是宏) 分隔的参数\endcomment
。它的主要目的是丢弃参数,但它也会这样做\futurelet\next\comment@@@
。
让我们看一个有助于讨论其他宏的例子:
\begincomment
foo
\endcomment
在对 进行类别代码和 的分配后\endlinechar
,\comment@
发现。行尾尚未被标记化,但现在将在\comment@
查找其参数时进行标记。在本例中它是空的,所以
\comment@@!endcomment\comment@@@
被放置在输入流中(我用!endcomment
上面描述的 11 个字符表示)。(空)参数被丢弃,输入流将具有
\futurelet\next\comment@@@\comment@@@
然后\next
将 设置为等于\comment@@@
并\comment@@@
展开。它的定义是收集第一个标记之前的任何内容作为参数\comment@@@
。在本例中,什么都没有。然后检查 的含义\next
。
在这种情况下,\next
与 相同\comment@@@
,因此\next
设置为\comment@
,并跳过错误分支。然后\next
,即\comment@
执行 ,将看到
foo^^J
并执行基本相同的操作(尝试遵循它)。在此之后,
\comment@!endcomment^^J
保留在输入流中。这被转换成
\comment@@!endcomment!endcomment\comment@@@
并且参数为空(如果某个字符在同一行\comment@@
之前,则不会为空。\endcomment
第一的 !endcomment
在处理分隔参数的过程中被删除,并且
\futurelet\next\comment@@@!endcomment\comment@@@
剩余。现在\next
设置为!
(当然,实际上是 catcode 12 反斜杠),并且参数\comment@@@
被吸收并丢弃。由于\next
是不是 \comment@@@
,残局\oldcodes@\endlinechar`\^^M
执行。
要点。
当定义一个宏时,其参数文本和替换文本中的标记已经是内部格式,并且不以任何方式依赖于类别代码的后续变化。
当扩展带有分隔参数的宏时,分隔符将与参数一起从输入流中删除,并且被删除的标记将被宏的替换文本替换。
相反,\futurelet
永远不会删除令牌。
当\lowercase
改变一个字符时,它不会改变类别代码。因此
{\lccode`!=`\\ \lowercase{!}}
一个会得到类别代码 12 反斜杠。请注意,在组结束时,\lccode
of将恢复为其先前的值(通常为 0)。执行相同操作的等效方法是!
\begingroup\lccode`!=`\\ \lowercase{\endgroup!}
因为\lowercase
不会解释它找到的任何标记;所以\endgroup
将被执行后 !
已更改为反斜杠。
我可能会写
\begingroup\lccode`\!=`\\ \lowercase{\endgroup
\def\comment@#1^^J{\comment@@#1!endcomment\comment@@@}%
\def\comment@@#1!endcomment{\futurelet\next\comment@@@}%
}% end of \lowercase
\def\comment@@@#1\comment@@@{%
\ifx\next\comment@@@
\let\next=\comment@
\else
\def\next{\oldcodes@\endlinechar=`\^^M\relax}%
\fi
\next
}
无需担心括号问题。