考虑以下纯 TeX 手稿:
\expandafter\expandafter\expandafter\meaning%
\expandafter\uppercase\expandafter{a}%
\bye
使用 pdftex 编译时,生成的 pdf 由以下文本组成:
\大写
不过,我希望输出文本是
字母A
原因如下。据我所知,TeX 应该按如下方式处理手稿。
- 第一个
\expandafter
从输入流中删除,原来的第二个也\expandafter
被移除并推入空栈。 - 原来第三个
\expandafter
从输入流中被移除,\meaning
并被推送到上述堆栈上。 - 原本第四个
\expandafter
从输入流中被移除,\uppercase
并被推送到堆栈上。 - 最后一次
\expandafter
出现的项从输入流中删除,{
并将其推送到堆栈上。 a
已扩展,但由于没有什么可扩展,所以什么也没有发生,并且a
仍然保留在输入流中。- 堆栈会弹出,直到为空,并将每个弹出的标记添加到输入流的前面。结果输入流为
\expandafter\meaning\uppercase{a}\bye
。 \expandafter
从输入流中移除,\meaning
并被移除并推送到现在空的堆栈上。\uppercase
被扩展。这会将输入流转换为A\bye
。- 堆栈会弹出,直到为空,并将每个弹出的标记添加到输入流的前面。结果输入流为
\meaning A\bye
。 \meaning
扩展,包括打印字母A
到 dvi 文件。输入流然后只是
\bye
。- 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 会执行以下操作:
↳ 读取
\expandafter₁
,并(因为它是可扩展的)调用内部函数expand
开始按照 执行操作\expandafter₁
。关联。此时,尚未读取的标记:\expandafter₂\expandafter₃\meaning\expandafter₄\uppercase\expandafter₅{a}
↳ ↳ 根据 执行操作
\expandafter₁
,将 token 保存\expandafter₂
为“之后要扩展”的 token,读取\expandafter₃
,并且(\expandafter₃
再次扩展)expand
再次调用以开始根据 执行操作\expandafter₃
。关联。此时,函数堆栈中的标记:\expandafter₂
。尚未读取的标记:\meaning\expandafter₄\uppercase\expandafter₅{a}
。↳ ↳ ↳ 根据 执行操作
\expandafter₃
,将令牌保存\meaning
为要“之后扩展”的令牌,读取\expandafter₄
,并且(\expandafter₄
再次扩展)expand
再次调用以开始根据 执行操作\expandafter₄
。关联. 函数堆栈中的标记:\expandafter₂
和\meaning
. 尚未读取的标记:\uppercase\expandafter₅{a}
.↳ ↳ ↳ ↳ 根据 执行操作
\expandafter₄
,保存\uppercase
为要“扩展”的令牌,读取\expandafter₅
,并(\expandafter₅
再次可扩展)调用expand again
开始根据 执行操作\expandafter₅
。关联。函数堆栈中的标记:\expandafter₂
、、\meaning
和\uppercase
。尚未读取的标记:{a}
。↳ ↳ ↳ ↵ 按照 执行
\expandafter₅
,保存{
为要“扩展”的标记,读取a
,并且(因为a
不可扩展),不递归,并将这些标记放回堆栈。(按照 执行完成\expandafter₅
。)关联. 函数堆栈中的标记:\expandafter₂
,\meaning
,\uppercase
。 需要读取的标记:{₁
,a₁₁
,}
。 (此处下标表示类别代码;它们是TeX 的一部分。)↳ ↳ ↵
\uppercase
在步骤 4 中保存的现在被推回到输入流的前面。(按照 完成操作\expandafter₄
。)关联. 函数堆栈中的标记:\expandafter₂
和.\meaning
要读取的标记:\uppercase
,,,,。{₁
a₁₁
}
↳ ↵
\meaning
在步骤 3 中保存的现在被推回到输入流的前面。(按照 完成操作\expandafter₃
。)关联. 函数堆栈中的标记:\expandafter₂
. 要读取的标记:\meaning
,\uppercase
,{₁
,a₁₁
,}
。↵
\expandafter₂
在步骤 2 中保存的 现已被推回到输入流的前面。(按照 完成操作\expandafter₁
。)关联. 函数堆栈中的标记:无。要读取的标记:\expandafter₂
,,,,,,\meaning
\uppercase
{₁
a₁₁
}
↳ 读取
\expandafter₂
,并(因为它是可扩展的)调用函数expand
开始按照 执行操作\expandafter₂
。关联. 需要读取的标记:\meaning
,\uppercase
,{₁
,a₁₁
,}
。↵ 按照 执行
\expandafter₂
,将标记保存\meaning
为要“扩展”的标记,读取标记\uppercase
,并且(因为\uppercase
不可扩展),不递归,并将这些标记放回堆栈。(按照 执行完成\expandafter₂
。)关联.要读取的标记:\meaning
,,,,,。[\uppercase
{₁
a₁₁
}
笔记:这是问题中分析错误的一步;你可以在 UI 中看到这一点,而不必事先知道它是否\uppercase
可扩展:你可以判断它不可扩展,因为expand
没有被调用。或者至少,你可以判断 UI 是否足够直观,目前它还不够直观,但数据在那里。]↳ 读取
\meaning
,并(因为它是可扩展的)调用函数expand
开始按照 执行操作\meaning
。关联. 需要读取的标记:\uppercase
,{₁
,a₁₁
,}
。↵ 按照 执行
\meaning
,读取下一个标记\uppercase
,并将相应的标记推到输入流的前面。(按照 执行完毕\meaning
。)关联。要读取的标记:,,,,,,,,,,,,,,。\₁₂
这些最终会被排版为相应的字符(这就是您在 PDF 中看到的原因)。(在此之后,TeX 会将前导处理到输出例程,依此类推。)u₁₂
p₁₂
p₁₂
e₁₂
r₁₂
c₁₂
a₁₂
s₁₂
e₁₂
{₁
a₁₁
}
\uppercasea
\end
我通过查看显示的数据“生成”了上述评论,我希望上述内容清晰明了,但我的最终目标是它不应该被需要,并且 UI 应该是不言自明的。
我不知道是否还有其他人会对这个“调试器”感兴趣。我打算把它放在一边,至少在接下来的几周内不使用它。然后当我/如果我回到它的时候,在它配得上这个名字之前,有很多限制需要消除:
- 改进用户界面并以更好的方式呈现所有信息。
- 使其适应其他 TeX 引擎(这应该相对容易,至少对于
pdflatex
和xelatex
...甚至对于来说都是如此lualatex
,但是它已经有许多回调(并且还在增长),因此这种事情在那里可能不太必要)。 - 涵盖 TeX 的其他部分:现在它只阐明了 TeX 的一小部分:宏(和一些其他)扩展(即对函数的调用
expand
)。 - 使其更快:目前它大大降低了 TeX 的速度。(TeX 非常快,所以这不是问题,这是件好事。)例如,可以通过只“调试” TeX 执行的一部分而不是其整个生命周期来实现这一点。
- [最具挑战性的] 它被称为“调试器”,但它只是节目TeX 正在做什么并且不允许您修改它:如果能够的话就好了。
与此同时,如果其他人有兴趣或者愿意贡献/接管,所有的代码都是可用的在 Github 上。它基本上是一个 Python 脚本,从内部gdb
运行调试版本tex
(用于转储数据)和 HTML/JavaScript“应用程序”(用于加载和交互显示转储的数据)。