这个问题导致了一个新的方案的出现:
unravel
考虑以下 MWE test.tex
:
\documentclass[12pt]{article}
\begin{document}
\tracingassigns=1
\tracingmacros=1
\def\aaa{something}
\def\bbb{else \aaa, else}
\edef\ccc{third \bbb, level}
\tracingassigns=0
\tracingmacros=0
\end{document}
如果你用 - 构建它pdflatex test.tex
,那么在日志文件中test.log
,你会得到类似这样的内容(为便于阅读添加了换行符):
{into \tracingassigns=1}
{changing \tracingmacros=0}
{into \tracingmacros=1}
{changing \aaa=undefined}
{into \aaa=macro:->something}
{changing \bbb=undefined}
{into \bbb=macro:->else \aaa , else}
\bbb ->else \aaa , else
\aaa ->something
{changing \ccc=undefined}
{into \ccc=macro:->third else something, else, le\ETC.}
{changing \tracingassigns=1}
现在,这很好地解释了 (La)Tex 对于这个简短示例所做的扩展步骤 - 不幸的是,一旦你必须处理可能数百个这样的扩展,它就会变得极其难以阅读(对我来说),有些可能涉及排版过程。
所以我在想 - 构建一个应用程序应该不是太困难,它基本上逐行读取日志文件,并允许“逐步”浏览日志文件;我想象右箭头键盘键->会让你在日志中前进,左箭头键<-会让你后退;可能,也可以指定日志文件的行号作为起点。
然后,应用程序将简单地对“ ^{changing
”、“ ^{into
”以及可能的“ ^\\(.*)->(.*)
”做出反应;并将显示该行以及屏幕上其他位置的“当前”标记;因此在“更改”行,屏幕的额外部分将显示\aaa=undefined
;而在“进入”行,代码片段将更改为\aaa=macro:->something
。
我认为,仅凭这一功能,就能够更轻松地可视化和理解 (La)Tex 扩展过程(尤其是在“真实”文档中)。事实上,这样的应用程序甚至不需要功能齐全的 GUI - 我想ncurses
终端应用程序也能做到同样好(尽管在有限宽度的终端中显示长字符串存在问题)。
所以,我想知道——是否有类似的应用程序?
答案1
编辑:这个答案导致(事实上是)一个包:unravel
包裹,在 GitHub 上。它依赖于gtl
包裹,因此如果您想通过测试来帮助我,您也需要获取它。这两个包都是使用包expl3
( l3kernel
) 提供的 LaTeX3 编程语言和 中的一些扩展编写的l3experimental
。
我现在已经实现了 TeX 的大部分原语。缺少的部分是数学模式、表格(等\halign
)、自由裁量项(包括\-
)、输出例程\aftergroup
、、、、以及所有 XeTeX 和 LuaTeX好东西。同样不可避免的是,类别代码在第一次打开文件时是固定的,宏会破坏包,而左括号和右括号以外的开始组和结束组字符会引起麻烦。尽管有这些限制,但还是会向您展示 TeX 在浏览时所做的所有细节(在此之前,所有决定文件是否值得读取的工作)。注意:全速时,这需要几分钟和 20000 步。\letterspacefont
\pdfcopyfont
\pdfprimitive
\outer
\unravel{\documentclass{article}\relax}
article.cls
到目前为止,该unravel
软件包仅提供了一个非常简单的接口来监控 TeX 在扩展和排版过程中的活动。我们只能继续前进。让我们给出一个使用示例。将以下代码放入一个文件中,例如,然后在终端中filename.tex
运行。pdflatex filename.tex
\documentclass{article}
\usepackage{unravel}
\AtEndDocument{\message{Bye!}}
\begin{document}
\end{document}
在一小段欢迎信息之后,\unravel
将等待您的输入。您可以按 键逐个步骤执行enter
,也可以键入s20o1
("*s*croll 20 steps but still *o*utput") 然后enter
。在后一种情况下,输出是(类似于,取决于版本)以下内容。
======== Welcome to the unravel package ========
"<|" denotes the output to TeX's stomach.
"||" denotes tokens waiting to be used.
"|>" denotes tokens that we will act on.
Press <enter> to continue; 'h' <enter> for help.
|> \AtEndDocument {\message {Bye!}}
s20o1
[===== Step 1 =====]
\AtEndDocument = macro:->\g@addto@macro \@enddocumenthook .
|> \g@addto@macro \@enddocumenthook {\message {Bye!}}
[===== Step 2 =====]
\g@addto@macro = \long macro:#1#2->\begingroup \toks@ \expandafter {#1#2}\xdef
#1{\the \toks@ }\endgroup .
|> \begingroup \toks@ \expandafter {\@enddocumenthook \message {Bye!}}\xdef
|> \@enddocumenthook {\the \toks@ }\endgroup
[===== Step 3 =====] \begingroup = \begingroup.
<| \begingroup
|> \toks@ \expandafter {\@enddocumenthook \message {Bye!}}\xdef
|> \@enddocumenthook {\the \toks@ }\endgroup
[===== Step 4 =====] \toks@ = \toks0.
<| \begingroup
|| \toks@
|> \expandafter {\@enddocumenthook \message {Bye!}}\xdef \@enddocumenthook
|> {\the \toks@ }\endgroup
[===== Step 5 =====] \expandafter {.
<| \begingroup
|| \toks@ \expandafter {
|> \@enddocumenthook \message {Bye!}}\xdef \@enddocumenthook {\the \toks@
|> }\endgroup
[===== Step 6 =====] \@enddocumenthook = macro:->.
<| \begingroup
|| \toks@ \expandafter {
|> \message {Bye!}}\xdef \@enddocumenthook {\the \toks@ }\endgroup
[===== Step 7 =====] back_input: \expandafter {.
<| \begingroup
|| \toks@
|> {\message {Bye!}}\xdef \@enddocumenthook {\the \toks@ }\endgroup
[===== Step 8 =====] Set \toks@(\toks0)=\message {Bye!}.
<| \begingroup
|> \xdef \@enddocumenthook {\the \toks@ }\endgroup
[===== Step 9 =====] \xdef .
<| \begingroup
|| \xdef
|> \@enddocumenthook {\the \toks@ }\endgroup
[===== Step 10 =====] \@enddocumenthook .
<| \begingroup
|| \xdef \@enddocumenthook
|> {\the \toks@ }\endgroup
[===== Step 11 =====] {.
<| \begingroup
|| \xdef \@enddocumenthook {
|> \the \toks@ }\endgroup
[===== Step 12 =====] \the .
<| \begingroup
|| \xdef \@enddocumenthook {\the
|> \toks@ }\endgroup
[===== Step 13 =====] \toks@ .
<| \begingroup
|| \xdef \@enddocumenthook {\the \toks@
|> }\endgroup
[===== Step 14 =====] \message {Bye!}.
<| \begingroup
|| \xdef \@enddocumenthook {\message {Bye!}
|> }\endgroup
[===== Step 15 =====] }.
<| \begingroup
|| \xdef \@enddocumenthook {\message {Bye!}}
|> \endgroup
[===== Step 16 =====] Set \@enddocumenthook=macro:->\message {Bye!}.
<| \begingroup
|> \endgroup
[===== Step 17 =====] \endgroup = \endgroup.
<| \begingroup \endgroup
|>
<| \begingroup \endgroup
|>
Step 17 was the last!
当然,\AtEndDocument
命令的效果会发生,并且在编译结束时会出现一条消息“Bye!”
此示例不涉及任何复杂的扩展。若想在这个方向上获得更多乐趣,请尝试了解l3fp
表达式解析的工作原理...
\documentclass{article}
\usepackage{unravel}
\begin{document}
\ExplSyntaxOn
\unravel { \fp_eval:n { sin(2pi/3) } }
\ExplSyntaxOff
\end{document}
经过一些步骤后,我的终端上显示以下内容:
[===== Step 3334 =====] \exp_after:wN \__fp_pack:NNNNNwn
|| \exp_after:wN \__fp_to_decimal_dispatch:w
|| \tex_romannumeral:D -48
|| \tex_romannumeral:D
|| \exp_after:wN \__fp_parse_after:ww
|| \tex_romannumeral:D -48
|| \exp_after:wN \__fp_parse_until_test:NwN
|| \exp_after:wN \c_minus_one
|| \tex_romannumeral:D -48
|| \exp_after:wN \__fp_fixed_mul_after:wn
|| \int_use:N
|| \__int_eval:w -50000
|| \exp_after:wN \__fp_pack:NNNNNwn
|| \int_use:N
|| \__int_eval:w 499950000+05235*05235
|| \exp_after:wN \__fp_pack:NNNNNwn
|| \int_use:N
|| \__int_eval:w 499950000+05235*9877+9877*05235
|| \exp_after:wN \__fp_pack:NNNNNwn
|| \int_use:N
|| \__int_eval:w 499950000+05235*5598+9877*9877+5598*05235
|| \exp_after:wN \__fp_pack:NNNNNwn
|| \int_use:N
|| \__int_eval:w 499950000+05235*2990+9877*5598+5598*9877+2990*05235
|| \exp_after:wN \__fp_pack:NNNNNwn
|> \int_use:N \__int_eval:w \c__fp_trailing_shift_int
|> +9877*2990+5598*5598+2990*9877+(5598*2990+2990*5598+\__fp_fixed_mul:nnnnnnnw
n
|> {0000}{0000}{05235}{9877}{05235}{9877}{0000}{0000};{\exp_after:wN
|> \__fp_sin_series_aux:NNnww \exp_after:wN \__fp_fixed_to_float:wN
|> \__int_value:w \if_int_odd... (223 chars)
(我通过按下s3333
然后两次回车键获得了这个结果)。这是什么意思呢?好吧,在 的扩展过程中\fp_eval:n { sin(2pi/3) }
,TeX 找到了\exp_after:wN
(LaTeX3 中 的名称\expandafter
),保留了下一个标记\__fp_to_decimal_dispatch:w
以备稍后扩展,并扩展了后面的内容。接下来是\tex_romannumeral:D
(屏幕上标有 的部分中的下一个标记||
),这让 TeX 寻找一个数字。在进一步扩展一些标记之后,TeX 找到了(不完整的)数字-`0
(等于-48
),这让它进一步扩展,依此类推。屏幕上显示的快照是在 TeX 在这种嵌套扩展中深度为 24 级(该部分中的所有标记)时拍摄的,并且自该部分的||
最后一部分以来正朝着第 10 级前进,在跳过 后,命中(又名)。不用说,执行浮点计算的宏有点难以理解。\exp_after:wN
||
\__fp_fixed_mul_after:wn
\int_use:N
\the
一般来说,可以分为三部分:|>
表示输入中的标记,TeX 尚未见过的标记,或者在宏扩展之后重新插入的标记;||
表示存储以供以后使用的标记;<|
表示已到达 TeX 主循环(即已经历整个扩展机制)并对排版产生影响的命令。定义会立即执行。
实现unravel
过去和现在当然都很困难,我最终不得不经常依赖 TeX 源代码来了解 Knuth 是如何做各种事情的。特别是,条件语句的嵌套是一场噩梦。那些知道 TeX 源代码的人可能可以从诸如 或 之类的名称中看出其影响的痕迹\@@_scan_int:
。\@@_get_x_next:
此外,每个原语都有一个命令代码和一个字符代码,它们的值与 的值完全一致pdfTeX
。
我想探索的一个方向是将所有数据输出到 XML 文件(或其他文件),然后可以通过各种其他工具进行处理,也许可以提供更多的交互性。另一个有趣的方面是生成排版内容而不是屏幕诊断。这将允许在视觉上更区分开来,例如,在区域的各个部分之间||
,它还允许通过颜色区分类别代码,这在某些调试任务中可能很重要。在减少步骤的方向上,我想知道给出一个正则表达式并默默执行与与正则表达式匹配的命令相关的扩展是否会提供一个有用的过滤器。