comment-pln 代码实际上起什么作用?

comment-pln 代码实际上起什么作用?

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 反斜杠。请注意,在组结束时,\lccodeof将恢复为其先前的值(通常为 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
}

无需担心括号问题。

相关内容