如何理解The TeXbook第41页的“\appendroman#1#2#3”?

如何理解The TeXbook第41页的“\appendroman#1#2#3”?

第 41 页底部TeXbook,有一个疑问:

定义一个\appendroman具有三个参数的控制序列,使得\appendroman#1#2#3定义控制序列#1扩展为一个控制序列,其名称是控制序列的名称#2后跟用罗马数字表示的正整数的值#3。例如,假设\count20等于 30;那么\appendroman\a\TeX{\count20}应该具有与 相同的效果\def\a{\TeXxxx}

看到这里,我有了自己的答案,虽然我知道这个答案是不对的,那就是\def\appendroamn#1#2#3{\def#1{\#2#3}}。那本书的正确答案是

\def\gobble#1{} % remove one token
\def\appendroman#1#2#3{\expandafter\def\expandafter#1\expandafter
{\csname\expandafter\gobble\string#2\romannumeral#3\endcsname}}

有这么多\expandafters,这让我很困惑。我知道第一行发出一个命令\gobble,该命令定义了\gobble{ }什么都不产生。但是这里的第二行和第三行该如何理解?

答案1

总结我最喜欢读的是\expandafter教程\expandafter(拖船 9)


让我们看一下示例:代码

\appendroman\a\TeX{\count20}

扩展为

\expandafter\def\expandafter\a\expandafter
{\csname\expandafter\gobble\string\TeX\romannumeral\count20\endcsname}

现在,第一次\expandafter保存\def以供以后使用:我通过将标记放在括号中来表示这一点:

(\def)\expandafter\a\expandafter
{\csname\expandafter\gobble\string\TeX\romannumeral\count20\endcsname}

现在还有进一步的\expandafter,它再次保存下一个标记以供以后使用

(\def)(\a)\expandafter
{\csname\expandafter\gobble\string\TeX\romannumeral\count20\endcsname}

第三次同样的游戏,现在这一次支撑保存以供日后使用

(\def) (\a) ({) \csname\expandafter\gobble\string\TeX\romannumeral\count20\endcsname}

现在 TeX 会遇到并开始用和\csname之间的所有内容构建一个控制序列名称(直接丢弃后面的空格)\csname\endcsname\csname(直接丢弃但不是其他)。\csname...\endcsname是这样的一切被扩展,直到只剩下字符标记。因此,控件名称源自

\expandafter\gobble\string\TeX\romannumeral\count20

游戏继续:\expandafter保存\gobble以备后用,并\string返回\12 T12 e12 X12。但现在\gobble开始:它删除以下标记,即反斜杠(实际上是\escapechar),因此TeX只剩下 (所有类别代码为 12 的)。扩展继续,并\romannumeral\count20返回xxx(因为\count20假设等于 30)。因此,在\csname和之间完全扩展后,\endcsname我们现在有TeXxxx,它构建了控制序列标记\TeXxxx。但我们仍然有来自前一个 的保存的标记\expandafter

(\def) (\a) ({) \TeXxxx}

因此 TeX 现在返回并有效地找到

\def\a{\TeXxxx}

现在实际打印了什么?可能什么都没有。当您在文件中\a写入时,它会扩展为,现在又出现了一个奇怪的现象。我引用(TeX 书中的练习 7.7):“\a\TeXxxx\csname首次使用 定义控制序列时,该控制序列将被视为等同于 ,\relax直到重新定义为止。“这意味着

\appendroman\a\TeX{30}\a

没有打印任何内容,但

\def\TeXxxx{hello}
\appendroman\a\TeX{30}\a

您会在输出中发现“hello”。

答案2

下面列出了一系列步骤(重要:这些不是扩展步骤,因为它们是由 TeX 完成的,而是由 TeX 内部完成的更多处理步骤)。

需要注意的是,所有这些(除了最后一步,执行\def)都是在单个扩展步骤中发生的。情况就是这样,因为\expandafter会在一个步骤中扩展为紧随其后的第二个标记的扩展结果,从而\expandafter从流中移除 。 也会\csname在一个步骤中扩展,扩展所有可扩展的内容,直到它命中\endcsname(或者如果它命中了在扩展后不是字符标记的内容,则会引发低级错误),将其结果留在流中。

所以按照你的定义

\def\gobble#1{} % remove one token
\def\appendroman#1#2#3%
  {%
    \expandafter\def
    \expandafter#1%
    \expandafter{%
      \csname
        \expandafter\gobble
        \string#2\romannumeral#3%
      \endcsname
    }%
  }

我们将看一下该宏的单一用法。在下面的格式中,第一行是总结当前步骤的注释,然后可能是延迟信息,然后是未注释的行,即 TeX 检查的输入流。

% step 1: simple expansion
\appendroman\a\TeX{30}

% step 2: defer \expandafter<TOKEN>
\expandafter\def
\expandafter\a
\expandafter{%
  \csname
    \expandafter\gobble
    \string\TeX\romannumeral30%
  \endcsname
}

% step 3: defer \expandafter<TOKEN>
% Deferred expandafter-chain: \expandafter\def
\expandafter\a
\expandafter{%
  \csname
    \expandafter\gobble
    \string\TeX\romannumeral30%
  \endcsname
}

% step 4: defer \expandafter<TOKEN>
% Deferred expandafter-chain: \expandafter\def\expandafter\a
\expandafter{%
  \csname
    \expandafter\gobble
    \string\TeX\romannumeral30%
  \endcsname
}

% step 5: start \csname-expansion
% Deferred expandafter-chain: \expandafter\def\expandafter\a\expandafter{%
  \csname
    \expandafter\gobble
    \string\TeX\romannumeral30%
  \endcsname
}

% step 6: defer \expandafter<TOKEN>
% Deferred expandafter-chain: \expandafter\def\expandafter\a\expandafter{%
% \csname-expansion: \csname
    \expandafter\gobble
    \string\TeX\romannumeral30%
  \endcsname
}

% step 7: \string (turning the token \TeX into four tokens of category 12)
% Deferred expandafter-chain: \expandafter\def\expandafter\a\expandafter{%
% \csname-expansion: \csname
% Deferred expandafter-chain: \expandafter\gobble
    \string\TeX\romannumeral30%
  \endcsname
}

% step 8: inner expandafter-chain done, put back
% Deferred expandafter-chain: \expandafter\def\expandafter\a\expandafter{%
% \csname-expansion: \csname
% Deferred expandafter-chain: \expandafter\gobble
    \TeX\romannumeral30%
  \endcsname
}

% step 9: \gobble one token (category 12 \)
% Deferred expandafter-chain: \expandafter\def\expandafter\a\expandafter{%
% \csname-expansion: \csname
    \gobble
    \TeX\romannumeral30%
  \endcsname
}

% step 10: defer unexpandable tokens inside \csname
% Deferred expandafter-chain: \expandafter\def\expandafter\a\expandafter{%
% \csname-expansion: \csname
    TeX\romannumeral30%
  \endcsname
}

% step 11: \romannumeral expansion, start scanning for a number
% Deferred expandafter-chain: \expandafter\def\expandafter\a\expandafter{%
% \csname-expansion: \csname TeX
    \romannumeral30%
  \endcsname
}

% step 12: scanning number
% Deferred expandafter-chain: \expandafter\def\expandafter\a\expandafter{%
% \csname-expansion: \csname TeX
% \romannumeral-expansion: \romannumeral
    30%
  \endcsname
}

% step 13: scanning number
% Deferred expandafter-chain: \expandafter\def\expandafter\a\expandafter{%
% \csname-expansion: \csname TeX
% \romannumeral-expansion: \romannumeral 3
    0%
  \endcsname
}

% step 14: scanning number
% Deferred expandafter-chain: \expandafter\def\expandafter\a\expandafter{%
% \csname-expansion: \csname TeX
% \romannumeral-expansion: \romannumeral 30
  \endcsname
}

% step 15: \romannumeral hitting \endcsname, end of number put back result
% Deferred expandafter-chain: \expandafter\def\expandafter\a\expandafter{%
% \csname-expansion: \csname TeX
% \romannumeral-expansion: \romannumeral 30
  \endcsname
}

% step 16: defer unexpandable tokens inside \csname
% Deferred expandafter-chain: \expandafter\def\expandafter\a\expandafter{%
% \csname-expansion: \csname TeX
    xxx%
  \endcsname
}

% step 17: \endcsname (stop \csname building, put back result)
% Deferred expandafter-chain: \expandafter\def\expandafter\a\expandafter{%
% \csname-expansion: \csname TeXxxx
  \endcsname
}

% step 18: expandafter-chain done, put back
% Deferred expandafter-chain: \expandafter\def\expandafter\a\expandafter{%
  \TeXxxx
}

% step 19: expandafter-chain done, put back
% Deferred expandafter-chain: \expandafter\def\expandafter\a
  {%
  \TeXxxx
}

% step 20: expandafter-chain done, put back
% Deferred expandafter-chain: \expandafter\def
  \a{%
  \TeXxxx
}

% step 21: expandafter-chain done, put back
\def\a{%
  \TeXxxx
}

% step 22: execute \def (we don't examine the collecting of \def's arguments here)
% assign meaning \a=macro:->\TeXxxx.
%% done

答案3

已经有了很好的解释。我想强调一下 Knuth 解决方案的一个可能弱点,它假设\escapechar指向一个实际的字符。如果\escapechar超出字符范围,解决方案将失败,对于 TeX 来说从 0 到 255(0x10FFFF对于 Unicode 引擎则扩展到),因为\gobble会从控制序列名称中吃掉一个字符。

这是一个没有问题的解决方案,因为\escapechar在执行业务之前设置为负一,但是在一个组内部。

\def\appendroman#1#2#3{%
  % #1 = cs to be defined
  % #2 = cs to be appended a Roman numeral
  % #3 = integer (implicit or explicit)
  \begingroup\escapechar=-1 \expandafter\endgroup
  \expandafter\def\expandafter#1\expandafter{%
    \csname\string#2\romannumeral#3\endcsname
  }%
}

\appendroman\a\TeX{30} \show\a

\count20=18

\appendroman\a\TeX{\count20} \show\a

\showthe\escapechar

在控制台上

> \a=macro:
->\TeXxxx .
l.11 \appendroman\a\TeX{30} \show\a

?
> \a=macro:
->\TeXxviii .
l.15 \appendroman\a\TeX{\count20} \show\a

?
> 92.
l.17 \showthe\escapechar

说明运作符合预期,且\escapechar业务完成后价值得到恢复。

注意重要的后面有一个空格-1。如果忘记了,则\escapechar只有在链执行完其操作后才会对 进行赋值\expandafter。但无论如何,常量后面的空格是良好做法

诀窍在于,\expandafterbefore\endgroup开始扩展,最终触发 的扩展\csname,后者会进行完全扩展,直到找到。在第一个示例中,在、和完成工作\endcsname之后的输入流将是\expandafter\csname\rommannumeral

\endgroup\def\a{\TeXxxx}

剩下的部分将恢复集团成立时的原有\endgroup价值。\escapechar

替代解决方案是使用单个\expandafter,但使用\noexpand两次:

\def\appendroman#1#2#3{%
  % #1 = cs to be defined
  % #2 = cs to be appended a Roman numeral
  % #3 = integer (implicit or explicit)
  \begingroup\escapechar=-1
  \edef\x{\endgroup\def\noexpand#1{%
    \expandafter\noexpand\csname\string#2\romannumeral#3\endcsname
  }}\x
}

相同之处expl3

\ExplSyntaxOn
\cs_new_protected:Npn \appendroman #1 #2 #3
 {
  \cs_set:Npe #1 { \exp_not:c { \cs_to_str:N #2 \int_to_roman:n { #3 } } }
 }
\ExplSyntaxOff

相关内容