阅读http://profs.sci.univr.it/~gregorio/introtex.pdf,我在第75页看到了的定义\newif
:
\def\newif#1{\@ifdefinable{#1}{\@newif#1}}
\def\@newif#1{\count@\escapechar \escapechar\m@ne
\expandafter\expandafter\expandafter
\def\@if#1{true}{\let#1=\iftrue}%
\expandafter\expandafter\expandafter
\def\@if#1{false}{\let#1=\iffalse}%
\@if#1{false}\escapechar\count@} % the condition starts out false
\def\@if#1#2{\csname\expandafter\if@\string#1#2\endcsname}
{\uccode‘1=‘i \uccode‘2=‘f \uppercase{\gdef\if@12{}}} % ‘if’ required
我试图通过遵循它的扩展来理解它的工作原理。想象一下我输入\newif\iffoo
。
第一个扩展应该是
\@ifdefinable{\iffoo}{\@newif\iffoo}
因此检查是否已经定义了
\iffoo
,如果没有,则传递\iffoo
给\@newif
。这就是我的问题开始的地方。\@newif
应扩展为:\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@
现在
\count@
是一个计数器(\countdef\count@=255
),所以是\m@ne
(\countdef\m@ne=22 \m@ne=-1
),并且\escapechar
包含的代码点\
被定义为\escapechar=\\
(我不能把反引号放在后面,=
因为网站的标记会带来麻烦)。所以\count@\escapechar
,我猜,给出了\count@
该代码,\escapechar\m@ne
并使\escapechar
-1
。那是以后的事了,在\if@
。然后我们有了 s 的序列\expandafter
。现在第一个让 TeX 跳过第二个,并查看第三个,这让它跳过\def
并扩展\@if
,我们稍后会讲到;然后 TeX(我猜)记得它已经跳过了一个\expandafter
,返回到它并执行它;这意味着\def<expansion of \@if\iffoo{true}>{\let\iffoo=\iftrue}
被延迟,我们输入一个新的 s 序列\expandafter
;第一个被跳过并存储起来,因为剩下的\expandafter
延迟了刚才提到的\def
;所以第二个被执行,第三个因此被存储起来,所以我们得到一个\def
应该重新定义\if@
但不可能的执行!所以 扩展的定义\@if\iffoo{true}
发生\let\iffoo=\iftrue
在\expandafter
第一个序列的第二个执行之前,但是为什么呢?好吧,在这个假设下,我们开始扩展第二个\@if
并将其扩展定义为\let\iffoo=\iffalse
,然后我们得到一/二\expandafter
;如果有两个,它们应该被省略,这让我想知道为什么如果两次都放一个就足够了,那么为什么要费心两次都放三个,而如果有一个,我们得到\count@
然后\escapechar
,所以我们有一个无用的指令,即在开头重复一个,\count@\escapechar
。所以必须有两个,对吧?然后省略,对吧?那么为什么要费心放三个呢?现在
\@if
。如上所述,我们有:\@if\iffoo{true}
因此扩展应该是:
\csname\expandafter\if@\string\iffootrue\endcsname
叶子
\expandafter
保持\if@
原样,并扩展\string
,使得\iffootrue
变成iffootrue
,因为\escapechar
是-1<0
;所以我们得到:\csname\if@iffootrue\endcsname
正确的?
最后,该行将的大写和的大写
{\uccode‘1=‘i \uccode‘2=‘f \uppercase{\gdef\if@12{}}}
都改为,可能是为了确保中的不会被误认为,因为我们在 中,但由于和不能放在控制序列中,因此将其拆分,然后 将其转换为和。因此,这里是:它被扩展为,只剩下:i
1
f
2
\if@
\uppercase
\if@if
\makeatletter
1
2
i
f
\uppercase
\if@
{}
\csname footrue \endcsname
(或)。所以我们理解了中的
foofalse
的实际原因:延迟 的扩展,由于 是,所以 是全局的,因此 被定义在一个组中以将 保留为本地的,但被设置为全局的,以便在那些 s 中重新使用。\expandafter
\csname…\endcsname
\if@
\gdef
\uccode
\csname…\endcsname
最后,当然,我们希望
\escapechar
回到它的原始值,该值存储在中\count@
,并在一切结束时恢复。
经过所有这些思考,我似乎只剩下一个问号:为什么\expandafters
每个\if@
控制序列定义有三个?所以问题最终是:
我说的有什么问题吗?问题是什么?为什么\expandafter
上面有这三个“s”?
答案1
让我们看看
\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
我使用 TeX 常用的方法来显示分配了哪些参数。所以我们剩下
\expandafter\def\csname\expandafter\if@\string\iffoo true\endcsname
现在,余下的\expandafter
将触发 的扩展\csname
,我记得,它会进行详尽的扩展,直到找到匹配的\endcsname
。因此,为了知道将生成什么控制序列名称,我们必须跟踪 之后所有标记的扩展\csname
。
第一个标记是\expandafter
,这导致 的扩展\string
;由于\escapechar
是 -1,所以我们剩下
\if@ iffootrue\endcsname
由于其定义,\if@
删除i
和f
并留下footrue
;所以我们有
\def\footrue{\let\iffoo=\iftrue}
现在定义的程序是相同的\foofalse
。
的定义\if@
是间接的,因为它后面必须跟着类别代码为 12 的i
和,因为由 产生的所有字符标记都有类别代码 12(空格除外)。f
\string
您认为 TeX 会记住已扩展 的想法是错误的\expandafter
:此标记在触发其后的第二个标记的扩展后就消失了。另外,说它们两个互相省略也是不正确的。
注 1:在几个地方有一个明显的空格,但实际上没有;它只是为了区分一个标记与另一个标记。值得注意的是,在 中有一个,\string\iffoo true
在 中有一个\if@ iffootrue
。
注 2:这是不是LaTeX 内核中的定义\newif
,其中使用了简化版本,它不检查之后的控制序列是否\newif
具有以 开头的名称if
(如书中所述\newif
)。
\newif
只是为了好玩,下面是如何在 中实现的expl3
。当然,这只是一个练习,用来表明人们可以获得“更清晰”的编程。
\documentclass{article}
\usepackage{xparse,l3regex}
\ExplSyntaxOn
\NewDocumentCommand{\xnewif}{ m }
{
\xnewif_newif:N #1
}
\tl_new:N \l__xnewif_name_tl
\msg_new:nnnn { xnewif } { bad~input }
{ #2 }
{ The~argument~\token_to_str:N #1 is~#2;~fix~it }
\cs_new_protected:Npn \xnewif_newif:N #1
{
\cs_if_exist:NTF #1
{% the control sequence is already defined, stop
\msg_error:nnnn { xnewif } { bad~input } { #1 } { already~defined }
}
{% the control sequence is undefined, go on
\__xnewif_newif_do:N #1
}
}
\cs_new_protected:Npn \__xnewif_newif_do:N #1
{
\tl_set:Nx \l__xnewif_name_tl { \cs_to_str:N #1 }
\regex_replace_once:nnNTF { \A if } { } \l__xnewif_name_tl
{% the name of the control sequence starts with `if', go on
\cs_gset_eq:NN #1 \if_false: % start globally false
\cs_new:cpn { \l__xnewif_name_tl true } % define \if...true
{
\cs_set_eq:NN #1 \if_true:
}
\cs_new:cpn { \l__xnewif_name_tl false } % define \if...false
{
\cs_set_eq:NN #1 \if_false:
}
}
{% the name of the control sequence doesn't start with `if', stop
\msg_error:nnnn { xnewif } { bad~input } { #1 } { not~starting~with~`if' }
}
}
\ExplSyntaxOff
% Test
\xnewif\iffoo
\show\iffoo
\footrue
\show\iffoo
\foofalse
\show\iffoo
\xnewif\isss
\xnewif\iffoo
这是终端上的输出
> \iffoo=\iffalse.
l.50 \show\iffoo
?
> \iffoo=\iftrue.
l.52 \show\iffoo
?
> \iffoo=\iffalse.
l.54 \show\iffoo
?
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! xnewif error: "bad input"
!
! not starting with `if'
!
! See the xnewif documentation for further information.
!
! For immediate help type H <return>.
!...............................................
l.56 \xnewif\isss
?
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! xnewif error: "bad input"
!
! already defined
!
! See the xnewif documentation for further information.
!
! For immediate help type H <return>.
!...............................................
l.57 \xnewif\iffoo