理解 \newif 定义时遇到问题

理解 \newif 定义时遇到问题

阅读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

  1. 第一个扩展应该是

    \@ifdefinable{\iffoo}{\@newif\iffoo}
    

    因此检查是否已经定义了\iffoo,如果没有,则传递\iffoo\@newif。这就是我的问题开始的地方。

  2. \@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。所以必须有两个,对吧?然后省略,对吧?那么为什么要费心放三个呢?

  3. 现在\@if。如上所述,我们有:

    \@if\iffoo{true}
    

    因此扩展应该是:

    \csname\expandafter\if@\string\iffootrue\endcsname
    

    叶子\expandafter保持\if@原样,并扩展\string,使得\iffootrue变成iffootrue,因为\escapechar-1<0;所以我们得到:

    \csname\if@iffootrue\endcsname
    

    正确的?

  4. 最后,该行将的大写和的大写{\uccode‘1=‘i \uccode‘2=‘f \uppercase{\gdef\if@12{}}}都改为,可能是为了确保中的不会被误认为,因为我们在 中,但由于和不能放在控制序列中,因此将其拆分,然后 将其转换为和。因此,这里是:它被扩展为,只剩下:i1f2\if@\uppercase\if@if\makeatletter12if\uppercase\if@{}

    \csname footrue \endcsname
    

    (或)。所以我们理解了中的foofalse的实际原因:延迟 的扩展,由于 是,所以 是全局的,因此 被定义在一个组中以将 保留为本地的,但被设置为全局的,以便在那些 s 中重新使用。\expandafter\csname…\endcsname\if@\gdef\uccode\csname…\endcsname

  5. 最后,当然,我们希望\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@删除if并留下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

相关内容