\expandafter 与 \uppercase

\expandafter 与 \uppercase

考虑以下纯 TeX 手稿:

\expandafter\expandafter\expandafter\meaning%
    \expandafter\uppercase\expandafter{a}%
\bye

使用 pdftex 编译时,生成的 pdf 由以下文本组成:

\大写

不过,我希望输出文本是

字母A

原因如下。据我所知,TeX 应该按如下方式处理手稿。

  1. 第一个\expandafter从输入流中删除,原来的第二个也\expandafter被移除并推入空栈。
  2. 原来第三个\expandafter从输入流中被移除,\meaning并被推送到上述堆栈上。
  3. 原本第四个\expandafter从输入流中被移除,\uppercase并被推送到堆栈上。
  4. 最后一次\expandafter出现的项从输入流中删除,{并将其推送到堆栈上。
  5. a已扩展,但由于没有什么可扩展,所以什么也没有发生,并且a仍然保留在输入流中。
  6. 堆栈会弹出,直到为空,并将每个弹出的标记添加到输入流的前面。结果输入流为\expandafter\meaning\uppercase{a}\bye
  7. \expandafter从输入流中移除,\meaning并被移除并推送到现在空的堆栈上。
  8. \uppercase被扩展。这会将输入流转换为A\bye
  9. 堆栈会弹出,直到为空,并将每个弹出的标记添加到输入流的前面。结果输入流为\meaning A\bye
  10. \meaning扩展,包括打印

    字母A

    到 dvi 文件。输入流然后只是\bye

  11. pdftex 停止。

* 当前的问题与这个,尽管标题相似。

答案1

第 8 步是错误的。\uppercase不可展开。尝试\edef\test{\uppercase{a}} \show\test查看。

答案2

在阅读了这个问题之后,我开始编写一个“TeX 调试器”,它或许可以帮助阐明此类问题(也受到了先前的问题)。目前它只是在各个点转储 TeX 的内部状态,并显示它。如果有兴趣,我在这里展示它的当前(非常粗糙)版本。(我认为它大约完成了 2%,但我可能低估了剩余的工作量。)

截图/录音: 我喜欢的用户界面

不幸的是我的 UI 技能不太好而且它还没有完成,所以它不是不言自明的并且需要评论。:-) 您可以在链接中看到 TeX 对以下输入文件所做的事情(基本上等同于问题中的文件):

\expandafter\expandafter\expandafter\meaning\expandafter\uppercase\expandafter{a}
\end

让我们用\expandafter下标标记输入中的 s,以便我们可以在后面的注释中更好地引用它们(这里的下标只是为了注释而添加的;它们不是 TeX 处理的一部分):

\expandafter₁\expandafter₂\expandafter₃\meaning\expandafter₄\uppercase\expandafter₅{a}

因此,通过此输入行,TeX 会执行以下操作:

  1. ↳ 读取\expandafter₁,并(因为它是可扩展的)调用内部函数expand开始按照 执行操作\expandafter₁关联。此时,尚未读取的标记:\expandafter₂\expandafter₃\meaning\expandafter₄\uppercase\expandafter₅{a}

  2. ↳ ↳ 根据 执行操作\expandafter₁,将 token 保存\expandafter₂为“之后要扩展”的 token,读取\expandafter₃,并且(\expandafter₃再次扩展)expand再次调用以开始根据 执行操作\expandafter₃关联。此时,函数堆栈中的标记:\expandafter₂。尚未读取的标记:\meaning\expandafter₄\uppercase\expandafter₅{a}

  3. ↳ ↳ ↳ 根据 执行操作\expandafter₃,将令牌保存\meaning为要“之后扩展”的令牌,读取\expandafter₄,并且(\expandafter₄再次扩展)expand再次调用以开始根据 执行操作\expandafter₄关联. 函数堆栈中的标记:\expandafter₂\meaning. 尚未读取的标记:\uppercase\expandafter₅{a}.

  4. ↳ ↳ ↳ ↳ 根据 执行操作\expandafter₄,保存\uppercase为要“扩展”的令牌,读取\expandafter₅,并(\expandafter₅再次可扩展)调用expand again开始根据 执行操作\expandafter₅关联。函数堆栈中的标记:\expandafter₂、、\meaning\uppercase。尚未读取的标记:{a}

  5. ↳ ↳ ↳ ↵ 按照 执行\expandafter₅,保存{为要“扩展”的标记,读取a,并且(因为a不可扩展),不递归,并将这些标记放回堆栈。(按照 执行完成\expandafter₅。)关联. 函数堆栈中的标记:\expandafter₂\meaning\uppercase。 需要读取的标记:{₁a₁₁}。 (此处下标表示类别代码;它们TeX 的一部分。)

  6. ↳ ↳ ↵\uppercase在步骤 4 中保存的现在被推回到输入流的前面。(按照 完成操作\expandafter₄。)关联. 函数堆栈中的标记:\expandafter₂和.\meaning要读取的标记:\uppercase,,,,。{₁a₁₁}

  7. ↳ ↵\meaning在步骤 3 中保存的现在被推回到输入流的前面。(按照 完成操作\expandafter₃。)关联. 函数堆栈中的标记:\expandafter₂. 要读取的标记:\meaning\uppercase{₁a₁₁}

  8. \expandafter₂在步骤 2 中保存的 现已被推回到输入流的前面。(按照 完成操作\expandafter₁。)关联. 函数堆栈中的标记:无。要读取的标记:\expandafter₂,,,,,,\meaning\uppercase{₁a₁₁}

  9. ↳ 读取\expandafter₂,并(因为它是可扩展的)调用函数expand开始按照 执行操作\expandafter₂关联. 需要读取的标记:\meaning\uppercase{₁a₁₁}

  10. ↵ 按照 执行\expandafter₂,将标记保存\meaning为要“扩展”的标记,读取标记\uppercase,并且(因为\uppercase不可扩展),不递归,并将这些标记放回堆栈。(按照 执行完成\expandafter₂。)关联.要读取的标记:\meaning,,,,,。[\uppercase{₁a₁₁}笔记:这是问题中分析错误的一步;你可以在 UI 中看到这一点,而不必事先知道它是否\uppercase可扩展:你可以判断它不可扩展,因为expand没有被调用。或者至少,你可以判断 UI 是否足够直观,目前它还不够直观,但数据在那里。]

  11. ↳ 读取\meaning,并(因为它是可扩展的)调用函数expand开始按照 执行操作\meaning关联. 需要读取的标记:\uppercase{₁a₁₁}

  12. ↵ 按照 执行\meaning,读取下一个标记\uppercase,并将相应的标记推到输入流的前面。(按照 执行完毕\meaning。)关联。要读取的标记:,,,,,,,,,,,,,,。\₁₂这些最终会被排版为相应的字符(这就是您在 PDF 中看到的原因)。(在此之后,TeX 会将前导处理到输出例程,依此类推。)u₁₂p₁₂p₁₂e₁₂r₁₂c₁₂a₁₂s₁₂e₁₂{₁a₁₁}\uppercasea\end

我通过查看显示的数据“生成”了上述评论,我希望上述内容清晰明了,但我的最终目标是它不应该被需要,并且 UI 应该是不言自明的。


我不知道是否还有其他人会对这个“调试器”感兴趣。我打算把它放在一边,至少在接下来的几周内不使用它。然后当我/如果我回到它的时候,在它配得上这个名字之前,有很多限制需要消除:

  • 改进用户界面并以更好的方式呈现所有信息。
  • 使其适应其他 TeX 引擎(这应该相对容易,至少对于pdflatexxelatex...甚至对于来说都是如此lualatex,但是它已经有许多回调(并且还在增长),因此这种事情在那里可能不太必要)。
  • 涵盖 TeX 的其他部分:现在它只阐明了 TeX 的一小部分:宏(和一些其他)扩展(即对函数的调用expand)。
  • 使其更快:目前它大大降低了 TeX 的速度。(TeX 非常快,所以这不是问题,这是件好事。)例如,可以通过只“调试” TeX 执行的一部分而不是其整个生命周期来实现这一点。
  • [最具挑战性的] 它被称为“调试器”,但它只是节目TeX 正在做什么并且不允许您修改它:如果能够的话就好了。

与此同时,如果其他人有兴趣或者愿意贡献/接管,所有的代码都是可用的在 Github 上。它基本上是一个 Python 脚本,从内部gdb运行调试版本tex(用于转储数据)和 HTML/JavaScript“应用程序”(用于加载和交互显示转储的数据)。

相关内容