访问 TeX 的内部标记

访问 TeX 的内部标记

第 23.7 节source3.pdf列出了所有可能的标记,比如常见的字符和控制序列,以及一些 TeX 的内部标记。

  1. 扩展\the\font结果产生的标记看起来与用于选择当前字体的命令(例如\tenrm)相同,但形状不同。
  2. 当在评估条件之前遇到条件的结束符时,会插入一个“frozen” \relax,它的形状与原语不同(但含义相同) 。\fi
  3. 扩展\noexpand <令牌>(当。。。的时候<令牌>是可扩展的)会产生一个内部标记,暂时显示为\notexpanded: <令牌>,其形状与<令牌>并且其含义不同于\relax
  4. \outer endtemplate:当预览下一个标记时可能会遇到;这会扩展为另一个内部标记,即对齐模板的结尾。
  5. 棘手的编程可能会访问冻结的\endwrite
  6. 一些冻结的代币只能在交互式会话中访问: \cr,,,,,。\right\endgroup\fi\inaccessible

我怎样才能生成这些标记,是通过\let将它们与另一个控制序列、包含一个控制序列的宏进行关联,还是仅仅以某种形式“看到”它们?

答案1

更新:这个问题在新版本的 (PDF)TeX 中已经修复(通过\noexpand对 进行无操作\endwrite),但可能存在另一个分段错误。详情请参阅https://topanswers.xyz/tex?q=5286#a5394


公平地说,也许 Bruno Le Floch(LaTeX3 团队的成员,可能也是在 interface3.pdf 中写了那一段的人)比我先发现了这一点。

错误消息 - 引发 SIGSEGV 的最短代码 - Code Golf Stack Exchange

准确地说,纠正下面的评论:\show ⟨\endwrite⟩两者\meaning ⟨\endwrite⟩都会不是Knuth TeX 中的分段错误——前者显示

> \endwrite=\outer macro:
.

但在 PDFTeX 中却如此。只有\let ⟨some assignable thing⟩ ⟨\endwrite⟩(或⟨\endwrite⟩直接执行)分段错误 Knuth TeX。


不知道为什么没有人发布有关该\endwrite令牌的答案,但它就在这里。

%! TEX program = pdflatex
\documentclass{article}
\begin{document}
\ExplSyntaxOn

% starting state of the input stream:
%     \weird } \endwrite ...

\def \weird {   \expandafter \weirda \expandafter { \iffalse } \fi }

%   → \weirda { } \endwrite ...

\def \weirda #1  {  \expandafter \weirdb  \noexpand }

%         \endwrite cannot be grabbed as #2 so we \noexpand it
%   → \weirdb <safe \endwrite token>

\def \weirdb #1 {  \iffalse { \fi } #1   \edef \mycontainendwrite { \noexpand #1 } }

% after it's grabbed once it becomes "unsafe" again
%   → } \endwrite \edef \mycontainendwrite { \noexpand \endwrite } ...

% we need to restore the `} \endwrite` tokens otherwise it's an error

\setbox0=\vbox{
    \write 16{\weird}
}

\tex_shipout:D\box0

\nonstopmode
\show \mycontainendwrite
\errorstopmode

\expandafter \let \expandafter \myendwrite \mycontainendwrite

\nonstopmode
\show \myendwrite  % no output in LuaLaTeX, segmentation fault in pdflatex and XeLaTeX and latex
\errorstopmode

\ExplSyntaxOff
\end{document}

基本上,如果您这样做\write <some number> {<some token list>},令牌列表将未扩展地存储在“whatsit”中,并且当盒子被运出时,例如,会执行\shipout \box 0 \somemoretokens ...类似于的操作。\immediate \write <the number> { <the token list> } \endwrite \somemoretokens ...

(*)如下所述,\immediate\write这也是这样。上次我尝试时没有发生这种情况——可能是我的一些失误。

要将endwrite存储在某个地方,请在之后注入它\write(因为里面的内容是纯可扩展的,所以不能存储它)。

有趣的是,它会产生分段错误。(我不知道它是否可被利用。)它只是一个空指针取消引用,在正常环境下它必然会导致 SIGSEGV,因此与最近修复的任意位置内存读取月份值相比,它没那么“有趣”。

LuaLaTeX 上的输出是

> \mycontainendwrite=macro:
->\endwrite .
l.37 \show \mycontainendwrite
                           


> \myendwrite=\outer macro:
.
l.43 \show \myendwrite

其余的是

> \mycontainendwrite=macro:
->\endwrite .
l.37 \show \mycontainendwrite
                             

Segmentation fault (core dumped)

(分段错误是由 引起的\let,而不是\show。)


其余标记的附注。您可以阅读texdoc tex并稍作调整以弄清楚它们。

首先触发一个导致生成 token 的错误。然后“猜测”输入流中接下来的 token 是什么。

例如,对于\inaccessible可以通过执行\def后跟非控制序列来访问的令牌。

$ pdflatex
This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022/Arch Linux) (preloaded format=pdflatex)
 restricted \write18 enabled.
**\def a
entering extended mode
LaTeX2e  patch level 1
L3 programming layer 
! Missing control sequence inserted.
 
                \inaccessible 
 \def a
          
? I\a{}\show
> \inaccessible=undefined.
 \inaccessible 
                              
 \def a
          
? I\show
> the letter a.
 a
                 
 \def a
          
? 

下划线部分是输入的文本。其余部分是从 pdflatex 输出的。

您可以推断,当\def a执行时,TeX 会打印一条错误消息,将用户带到命令提示符,然后继续执行,就好像\def刚刚执行并\inaccessible a在输入流中跟随一样。

如果你I\a{}\show在这里输入的话,执行的内容就会变成\def\a{}\show\inaccessible a你所期望的那样。

这个令牌在 lualatex 中具有.tok0x20010001(或536936449),但我认为无论如何都没有任何方法可以根据其 tok 值创建令牌。

或者(出于学术目的),也可以使用 Lua 中的错误钩子来故意触发错误以创建无法访问的令牌,但是如果你在同一个段落中重复这个操作 100 次,TeX 就会停止

\directlua{
luatexbase.add_to_callback("show_error_hook",
  function()
    luatexbase.remove_from_callback("show_error_hook", "get inaccessible token")
    inaccessible_token=token.get_next()
    token.put_next(inaccessible_token)
  end,
  "get inaccessible token"
)
}

\ExplSyntaxOn

\batchmode
\def a{}
\errorstopmode

在线尝试一下! (链接到一些在线代码执行服务进行演示并查看结果。)


补充说明。

第四点 代\endtemplate

如果您执行以下操作(类似于获取\endwrite令牌的技巧,但不需要大括号破解)

\def\weird{\expandafter \weirda\noexpand}
\def\weirda #1{\global\edef\container{\noexpand #1}#1}
{\setbox0=\vbox{\halign{#\cr\noexpand\weird\cr}}}

你可以获取外部 endtemplate 标记本身,而不仅仅是其含义。它具有属性

  • 字符串表示也\endtemplate与内部标记一样
  • 意思是\outer endtemplate:,或者[unknown command code! (151, 4)]:在 LuaTeX 上。(另一个错误……?)
  • 在外面。
  • 是可扩展的。
  • 如果\noexpand将其应用于它,其含义就变成\relax,就像普通的可扩展标记一样。

如果它被扩展一次,它就会变成具有以下属性的内部标记

  • 字符串表示法\endtemplate如上所述
  • 意思是end of alignment template
  • 不在外面。
  • 不可扩展。

第 3 点 notexpanded

书中提到,它的含义并不等同于正常的 的含义\relax。值得注意的是,它的含义可以分配给另一个 token,而新的 token 将无法扩展。

\noexpand此外,当应用于未定义的标记时,也会获得此含义。

\noexpand应用于不可扩展的 token(包括冻结的放松)时,它不是一个无操作,但只有在你

  • 将其重新定义为其他东西,那么输入流上的标记的含义就变成\relax
  • 应用\show,然后\notexpanded打印在控制台上。(即使不可扩展,也会\expandafter\show \noexpand\let显示)\notexpanded: let\let

演示。

% ======== assign meaning \a = \noexpand of y
\def\y {}
\expandafter \let \expandafter \a \noexpand \y

% ======== assign meaning \b = \noexpand of z
\expandafter \let \expandafter \b \noexpand \z

% ======== assign meaning \c = \noexpand of \let
\expandafter \let \expandafter \c \noexpand \let

% ======== \a is different from \relax
\ifx\a\relax \errmessage{this cannot happen} \fi

% ======== \a expanded once is different from \relax
\expandafter\ifx\a\relax \errmessage{this cannot happen} \fi

% ======== \a is different from \y
\ifx\a\y \errmessage{this cannot happen} \fi

% ======== \a expanded once remains the same i.e. it's not expandable, unlike the notexpanded token
\expandafter\ifx\a\a \else \errmessage{this cannot happen} \fi

% ======== \a and \b is the same
\ifx\a\b \else \errmessage{this cannot happen} \fi

% ======== \c meaning is \let
\ifx\c\let \else \errmessage{this cannot happen} \fi

\bye

答案2

DRF 等人同意 \endwrite 访问至少是一个错误,我们会将其报告给 Knuth(最终)。最终归结为访问 mem[0],如果 mem_in>0,这将超出范围,就像在 trip 测试中一样。无论如何,与此同时,我将其添加到https://tug.org/texmfbug/newbug.html#B155endwrite

DRF 奇迹:

我(目前)还不能确信没有类似的技巧可以让 \let 或 \futurelet 应用于 end_write_token,当 eq_destroy 认为旧值是 outer_call 时,这会导致麻烦(因此我们需要 another_null_list 修复)。但我对 TeX 的嘴和胃的复杂性还不够熟悉,无法判断 \let 和 \futurelet 是否存在类似的问题;所以也许值得向 [这里] 的超级黑客核实一下,他们一开始就想出了这个魔法?

如果有人想尝试一下并报告,请这样做。最好[电子邮件保护]以及这里。

此外,我不清楚这里讨论的其他内容是否可能是错误或只是奇怪之处。如果 TeX 没有崩溃,Knuth 不太可能想要改变任何东西,但很难说...

答案3

在我的实验中,我只能够产生 1、2、(部分通过\show)3、4,但不能产生最后两个。

1 号可以简单地生成

\font\tenrm=cmr10
{\tenrm \xdef\tmpa{\the\font}}
\show\tmpa                       % macro:->\tenrm
\expandafter\ifx\tmpa\tenrm y\fi % \ifx equivalent
\expandafter\def\expandafter\tmpb\tmpa{}
\expandafter\tmpb\tmpa
% \tmpb\tenrm                    % produces error: different shape

可以使用相同的测试生成数字 2 并验证其是否为异常:

\edef\tmpa{\ifnum0=0\else\fi}
\show\tmpa % macro:->\relax

对于 3,我只能在 just 'd 标记上\notexpanded:使用时看到。也许还有更多方法可以尝试它们\show\noexpand


\expandafter\show\noexpand\stuff
% > \stuff=\relax.
% <recently read> \notexpanded: \stuff

(这已经失控了:现在至少有两种异常变体\relax!)

数字 4 可以作为\futureleth/valign 内的控制序列

\halign{#\cr\global\futurelet\tmpa\relax\cr}
\show\tmpa % =\outer endtemplate:

这可以再次扩展以获得\endtemplate

\expandafter\let\expandafter\tmpb\tmpa
\show\tmpb % =end of alignment template
\expandafter\def\expandafter\tmpc\expandafter{\tmpa}
\show\tmpc % macro:->\endtemplate

相关内容