这是源于以下问题的答案理解 \newif 定义时遇到问题经过@egreg。
@egreg 说
让我们回顾一下
\expandafter\expandafter\expandafter
\def\@if\iffoo{true}{\let\iffoo=\iftrue}%
的定义\@if
:
\def\@if#1#2{\csname\expandafter\if@\string#1#2\endcsname}
第一个\expandafter
展开第三个,这又导致 的展开\@if
,它有两个参数;在这种情况下,它们是
#1<-\iffoo
#2<-true
但据我了解,其工作方式\def
是,它对命令名称后的非参数字符(即,除 、 、... 之外的字符)很敏感#1
,#2
因此#9
(取自 TeXbook 第 202 页)如果您有,\def\cs #1. #2\par{…}
则\cs
需要以格式给出的参数#1. #2
,您需要将.␣
两者分开,例如
\cs You owe \$5.00. Pay it.\par
给出
#1 <- You owe \$5.00
#2 <- Pay it.
哪里(引用 TeXbook)
\5.00 中的句号不会停止
#1
,因为 TeX 会继续运行,直到找到紧接着空格的句号。
而上面\def\@if#1#2
不是\def\@if#1{#2}
,而且我可以想象后者不会起作用,因为它会解释为只接受单一参数的思考#2
的定义。\@if#1
\@if
#1
我的第二个困难是理解以下部分
因此我们只剩下
\expandafter\def\csname\expandafter\if@\string\iffoo true\endcsname
现在剩下的部分\expandafter
触发了扩展\csname
,我记得,它会进行详尽的扩展,直到找到匹配的\endcsname
。因此,为了知道将生成什么控制序列名称,我们必须跟踪 之后所有标记的扩展\csname
。
第一个标记是\expandafter
,这会导致 的扩展\string
;因为\escapechar
是-1
,我们剩下
\if@ iffootrue\endcsname
但我不明白为什么\string
无论是在重新分配的情况下\escapechar=-1
还是在\escapechar
尚未重新分配的情况下都会产生这种影响。
参照 TeXbook
\string<token>
TeX 首先读取<token>
不带扩展的。如果出现控制序列标记,则其\string
扩展由控制序列名称组成(\escapechar
如果控制序列不仅仅是一个活动字符,则包括转义字符)。
所以我只能猜测,如果它取代了\
之前iffoo
\iffoo
- 为什么
\string
当 是控制序列时会产生这种效果\iffoo
,因此(根据我对 TeXbook 的误解)应该将其转换为其名称,即\iffoo
(或者是iffoo
,在这种情况下为什么需要\escapechar
更改)。或者, - 如果
\
被替换,因为它是一个“控制序列标记”,而不是“控制序列”,那么它去了哪里-1
?
最后,在下一节中@egreg 说
由于其定义,
\if@
删除i
和f
并留下footrue
;所以我们有
\def\footrue{\let\iffoo=\iftrue}
我认为其\if@
定义如下:
{\uccode‘1=‘i \uccode‘2=‘f \uppercase{\gdef\if@12{}}} % ‘if’ required
并且我理解第一部分将的大写值设置为和1
,i
但我不明白为什么此部分包含在单独的括号中,也不明白为什么此定义起到删除和的作用,因为如果最初应用它似乎是未定义的,因为不是有效命令,或者将期望和作为固定参数(不是和),它将消除(因为它返回一个空字符串)。2
f
i
f
\uppercase
\GDEF
1
2
i
f
如果这看起来太深奥,我深表歉意,但我真的想理解为什么这些定义会这样起作用,我真的不知道我哪里出错了。我原本打算在原始答案的评论中提出这个问题,但@egreg 很有帮助地指出,我的问题太广泛了,评论不适合讨论这个问题。
答案1
Saying\newif\iffoo
生成以下标记列表:
\count@\escapechar \escapechar\m@ne
\expandafter\expandafter\expandafter\def\@if\iffoo{true}{\let\iffoo=\iftrue}
\expandafter\expandafter\expandafter\def\@if\iffoo{false}{\let\iffoo=\iffalse}
\@if\iffoo{false}
\escapechar\count@
接下来,添加空格(或换行符)只是为了分隔标记;定义中没有,因此不生成任何标记。
我还记得辅助宏的定义\@if
\def\@if#1#2{\csname\expandafter\if@\string#1#2\endcsname}
和\if@
{\uccode‘1=‘i \uccode‘2=‘f \uppercase{\gdef\if@12{}}} % ‘if’ required
做什么\if@
?
TeX 打开一个组,其中设置了和\uccode
的新值。然后它执行。该操作将1
2
\uppercase
字符标记以大写形式显示,而不更改类别代码。生成的标记列表将放回到输入流中,并且
\gdef\if@ if{}
其中i
和f
的类别代码为 12(类似1
和2
)。空格只是为了清晰起见,没有空格标记。因此,宏\if@
希望在它之后看到该对字符标记或发出错误并吞掉它们,而不会产生任何结果。接下来,}
恢复和\uccode
的值。i
f
步骤1
执行赋值\count@\escapechar
和\escapechar\m@ne
。具体来说,escapechar 设置为 -1,这意味着\string\cs
将扩展为cs
(每个类别代码为 12 的字符),因为 -1 不是有效的字符代码。
第2步
\expandafter\expandafter\expandafter\def\@if\iffoo{true}{\let\iffoo=\iftrue}
TeX 跳过一个标记并扩展下一个标记,即\expandafter
,因此会跳过另一个标记并扩展下一个标记。它是\@if
,它接受两个参数。宏后面没有{
,因此第一个参数是以下(非空格)标记,即\iffoo
;后面跟着 a {
,因此第二个参数是所有标记,直到匹配的 ,包括}
,因此{true}
。回想一下,TeX 会从未分隔的参数中剥离一对括号(如果这不会留下不匹配的括号,也会从分隔的参数中剥离一对括号)。所以参数#2
是true
。\if@\iffoo{true}
用替换文本替换
\csname\expandafter\if@\string\iffoo true\endcsname
再次强调,这里没有空格,我留它只是为了分隔 token。这个 token 列表中有 10 个 token。
步骤3
两个\expandafter
消失了,替换文本已取代\if@
及其参数,因此我们有
\expandafter\def\csname\expandafter\if@\string\iffoo true\endcsname{\let\iffoo=\iftrue}
TeX 跳过\def
并扩展\csname
。这最终将扩展为一个符号标记,该标记又通过扩展匹配的内容而构建\endcsname
步骤4
让我们看看 token 列表是什么样的\endcsname
:
\expandafter\if@\string\iffoo true
(记住,那里没有空格)。TeX 跳过\if@
并扩展\string
。这会产生五个字符标记iffoo
(因为\escapechar
是 -1),所有标记的类别代码均为 12。因此,我们剩下
\if@ iffootrue
和\if@
被扩展。所需的标记被找到并吞掉,所以我们得到footrue
。现在 TeX 完成\csname...\endcsname
构造,生成标记\footrue
。
步骤5
输入流有
\def\footrue{\let\iffoo\iftrue}
已执行并删除。
第 6 步
TeX 现在发现
\expandafter\expandafter\expandafter\def\@if\iffoo{false}{\let\iffoo=\iffalse}
和之前完全一样,但最终的标记列表是
\def\foofalse{\let\iffoo\iffalse}
已执行并删除。
步骤7
现在我们有
\@if\iffoo{false}
和之前一样,只生成
\let\iffoo\iffalse
因此条件一开始就是错误的。
步骤8
最后的代币
\escapechar\count@
重置\escapechar
为开始之前的值\newif\iffoo
。
答案2
逆向思考:
{\uccode‘1=‘i \uccode‘2=‘f \uppercase{\gdef\if@12{}}} % ‘if’ required
\uppercase
适用于标记,除字符标记之外的任何标记均保持不变。字符标记会生成新的字符标记,其 catcode 与之前相同,但字符代码是通过查找 uccode 获得的(如果 uccode 非零)
所以\gdef
大写\gdef
不是\GDEF
,你会得到
\gdef\if@ if{}
它定义\if@
为不接受任何参数并且扩展为不包含任何内容但会吞噬 catcode 12 i 和 f(或生成错误)。
就像这样:
\catcode`i=12
\catcode`f=12
\def\if@if{}
但这不起作用,因为f
in\def
需要是 catcode 11,并且ini
也是如此f
\if@
该构造用括号括起来,因为您不希望在文档的其余部分将 1 的大写字母显示为 i。
我不确定我是否理解了你的\string
问题。\string\iffoo
使 catcode 序列有 12 个标记\
i
f
f
o
o
,但第一个\
标记是 指定的任何字符,\escapechar
如果该字符代码不是合法字符,则为空,因此-1
这里\string\iffoo
生成iffoo
。
答案3
Knuth 的宏\newif
非常神秘。我花了整整 402 页在我的“TeXbook 纳鲁比”并对此做出解释。但我们可以问一下,是否没有更直接、更易理解的解决方案来解决此问题并得到相同的结果。我的意思是,它存在。例如:
\def\newif#1{\expandafter\newifA\string#1\relax#1}
\edef\tmp{\string\if}
\expandafter\def\expandafter\newifA\tmp#1\relax#2{%
\newifB#2{\csname#1true\endcsname}{\csname#1false\endcsname}%
}
\def\newifB#1#2#3{% #1=\iffoo #2=\footrue #3=\foofalse
\let#1=\iffalse
\expandafter\def#2{\let#1=\iftrue}%
\expandafter\def#3{\let#1=\iffalse}%
}