答案1
原语\uppercase
不会扩展其参数。它所做的是获取范围内的每个字符标记a-z
并将其替换为大写字母。因此,当您发出
\uppercase{\romannumeral1986}
你正在\uppercase
对一组标记执行操作,其中第一个是原始的\romannumeral
,后跟1
,和9
,8
和和6
。这些都不是在乎的 26 个字符之一\uppercase
,所以它什么也不做。所以输出正是\romannumeral1986
。
正如你所看到的,如果你\expandafter
首先使用它来扩展论点,你就会得到预期的效果。
另外,使用解开包裹,也许看到它的实际作用会让我们更容易理解 TeX 的设计。
答案2
当读者第一次阅读第 7 章时,他们不应该知道接下来的所有内容,事实上,练习 7.9 被标记为双重危险。
危险弯角标记非常重要:第一次阅读时,应跳过危险弯角;第二次阅读时,可以查看危险弯角并跳过危险倍增的弯角。第三次(或第四次)阅读时,应能够面对危险倍增的部分。
如果您不理解开头的危险段落,请不要感到难过:您不应该理解。
现在来谈谈一些完全不同的事情:让我们根据 TeXbook 从前言到附录 J 的知识来解释一下发生了什么。
原语\uppercase
和\lowercase
的行为非常独特。首先,让我们看看它们的语法:
\uppercase
⟨一般文本⟩
\lowercase
⟨一般文本⟩
⟨一般文本⟩ → ⟨填充符⟩ {
⟨平衡文本⟩⟨右括号⟩
⟨填充符⟩ → ⟨可选空格⟩ | ⟨填充符⟩ ⟨\relax
可选空格⟩
最后,⟨平衡文本⟩是任何关于以下方面平衡的标记序列:明确的 {
和}
。
与其他需要⟨一般文本⟩的原语相比,这两个原语的区别在于,⟨平衡文本⟩被直接发送到“胃”(因此不发生宏扩展),其中标记以特殊方式处理:字符标记根据和转换为大写或小写\uccode
。\lccode
而任何其他标记保持不变。请注意,类别代码是不是已改变。
然后将这样获得的标记列表发送回“口”,在那里可以照常进行宏扩展。
由于\uccode
数字的为 0,因此它们不会发生变化\uppercase
。因此代码
\uppercase{\romannumeral1986}
只是一种低效的方式来获得相同的结果
\romannumeral1986
那么,如何从 1986 年获得“MCMLXXXVI”?⟨filler⟩ 就是答案,一旦你知道,当 TeX 寻找{
包含 ⟨balanced text⟩ 的 时,它会执行扩展,直到找到一些不符合空格标记的标记,如\relax
或{
。但是,空格标记和\relax
标记会被忽略,然后重新开始查找{
。如果 TeX 最终找不到{
,则会引发错误。
这就解释了为什么
\uppercase\expandafter{\romannumeral1986}
实际上产生了 token MCMLXXXVI
(类别代码均为 12)。事实上,在找到 a并丢弃 ⟨filler⟩\expandafter
的过程中, 被扩展,并且根据规则,在触发下一个 token 之后,其扩展为空,在本例中为 。因此,当该过程实际找到 时,⟨balanced text⟩ 已经变成{
\romannumeral
{
mcmlxxxvi
因此胃中的处理将发回令牌列表
MCMLXXXVI
将会被正常处理。
实际上练习 7.9 的答案应该是
\uppercase\expandafter{\romannumeral\year}
但这只是一个细节。
锻炼。解释一下
\uppercase\ifodd\day\expandafter\expandafter\fi{\romannumeral\year}
产生的原因以及为什么\expandafter
使用两个标记。