在哪里可以找到有关 \futurelet 的恶劣行为的记录?

在哪里可以找到有关 \futurelet 的恶劣行为的记录?

直到最近,我才认为

\futurelet\testtoken<token1><token2>...

具有以下效果:控制序列\testtoken\let并且从输入流<token2>\futurelet\testtoken剥离,因此 TeX 继续使用<token1><token2>...

我的这个答案我观察到(在 TH 的帮助下),这对于 catcodes 来说并不完全正确,并且在这个很好的答案Philippe 解释说,嵌套比对也会出现严重错误。为了完整起见,我在这里给出两个简短的例子。

\def\activateA{\catcode`A=\active}
{\activateA
 \gdefA{undocumented behaviour?}
}
An \futurelet\testtoken\activateA A
\bye

预期输出为“未记录的行为?”,但只有\futurelet\testtoken删除 时才会出现此输出。使用\futurelet,输出仅为“A”。原因似乎是\futurelet修复了最后一个 的 catcode A。(仍然会得到\testtoken=the letter A。)

第二个例子更长一些并且更复杂一些。

\def\begintestalign{\show\testtoken
    $\vcenter\bgroup\halign\bgroup##&##\cr}
\def\endtestalign{\egroup\egroup$}
\halign{#&#\cr
a & \futurelet\testtoken
    \begintestalign
         & c  \cr
    test & de \cr
    \endtestalign \cr
}
\bye

预期输出是(并且人们期望\testtoken=alignment tab character &),但是得到的却是\testtoken=\outer endtemplate,然后

! Emergency stop.
<recently read> \endtemplate 

<template> \endtemplate 

l.7          &
               c  \cr
!  ==> Fatal error occurred, no output PDF file produced!

我不能完全理解这里发生了什么,但我的主要问题是:我在哪里可以找到\futurelet记录的这种行为?

(老实说,我真的很想拥有一个\futurelet能够按照预期行事的行为;但是实现的行为相当卑鄙,我不知道这是一个错误还是一个功能。)

答案1

您的问题实际上有两个部分之间的相互作用有点奇怪。

对于第一个例子,这种行为是完全合理的。正如其他人所观察到的,A被标记化并被赋予类别代码 11。一旦字符标记被标记化,它就永远不会丢失其类别代码(e-TeX 的\detokenize扩展除外)。

第二个例子就复杂多了。我最初的反应是应该按照你的意愿工作。不幸的是,TeX 的处理\halign方式非常奇怪。让我引用TeXbook,第 248 页。

您必须小心使用&\span\cr因为即使 TeX 的扫描仪未扩展宏,它们也会拦截这些标记。例如,如果您\let\x=\span在对齐条目中间说“ ”,TeX 会认为“ \span”结束了条目,因此\x将等于#模板中“ ”后面的第一个标记。您可以\span通过将其放在括号中来隐藏它;例如,“ {\global\let\x=\span}”。(附录 D 解释了如何避免\global这种情况。)

这非常令人惊讶,但它解释了您看到的行为。也就是说,当 TeX&在寻找标记 to \letto时遇到时\testtoken,它会看到 a &,因此插入模板的其余部分(在本例中为空),然后插入\endtemplate— 导致对齐条目的内容排版在“未设置”框中的命令(意味着尚未设置粘合)。

答案2

在我看来,第一个例子中显示的行为是可以理解的,即“正确”。TeXbook 对\futurelet宏的描述如下(第 207 页):

TEX 还允许构造\futurelet\cs<token1><token2>,其效果为\let\cs = <token2><token1><token2>

所以我们谈论的是代币,它们已经分配了 catcode。在您的示例中A是 ,<token2>并且必须由 TeX 读取才能将其分配给命令序列\testtoken。宏\activateA尚未执行,因此 A 的 catcode 仍然是。 几时放回输入流 (这是这里的重点) catcode 的改变\activeA不再对其产生影响。

因此实际上\futurelet\cs<token1><token2>并不具有 100% 相同的效果,\let\cs = <token2><token1><token2>因为在第二种情况下<token1>仍然可以修改有效的 catcodes 以读取第二个<token2>

你的第二个例子更加棘手。恕我直言,你正在成为在单元格内容之后(和之前)自动插入代码的受害者。因此,当执行时,背后\futurelet确实有一个。这是一种行为,不应归咎于\outer endtemplate\halign\futurelet如你所知我对此的了解\halign仍然有限,所以我无法举例说明由此产生的错误。

我想说,如果有这种行为的记录,那就是TeXbook\futurelet。我不认为 eTeX以任何方式改变了的行为。

答案3

免责声明: 我没有参考资料来支持我下面的主张。

让我从一个简单的例子开始:

\def\showtoken{\show\testtoken}
\halign{#b\cr
  a\futurelet\testtoken\showtoken\cr}

当它读取第一个单元格时,TeX 会看到不可扩展的标记a,因此它知道没有\omit。然后它会在 之前插入模板的部分#,即什么也没有,并准备在单元格完成时插入模板的部分#,即之后b

然后它读取\futurelet,并执行它。因此 TeX 会查找三个标记。第一个和第二个是\testtoken\showtoken。第三个是不是 \cr:当 TeX 看到\cr此上下文时,它会插入它迫切想要插入的内容:模板的末尾,b。所以我们有。\futurelet \testtoken \showtoken b然后\testtoken是,然后我们通过宏看到。\letbb\showtoken

\futurelet和的有趣组合\afterassignment允许您提前预览两个标记,并且我们检查\futurelet确实读取了标记\testtokenbc并分配\testtoken=c

\def\showtoken{\show\testtoken}
\halign{#bcd\cr
  a\afterassignment\showtoken\futurelet\testtoken\cr}

现在删除cd模板。TeX 应该会打印在你的终端上

> \testtoken=\outer endtemplate:
.
\showtoken ->\show \testtoken 

<to be read again> 
                   b
<to be read again> 
                   \endtemplate 
<template> b\endtemplate 

l.3 ...assignment\showtoken\futurelet\testtoken\cr
                                                  }

如您所见,TeX 在单元格末尾插入的标记以 结尾\endtemplate,这是一些内部标记。即使删除b,我们也会看到\futurelet\testtoken内部\endtemplate,以及以下标记}。然后 TeX 崩溃了。

对此的一个变体是抓住\endtemplate使用\let,然后重做它以关闭模板。玩弄下面的代码,我注意到如果我们在 的最后位置放置除\testtoken(包含)以外的任何其他东西,TeX 就会崩溃。我不知道为什么。\endtemplate\showdotoken

\def\showdotoken{\show\testtoken \testtoken}
\halign{#\cr
  a\afterassignment\showdotoken\let\testtoken\cr
}

似乎每当我们尝试在对齐中的单元结束后伸出手时,TeX 都会崩溃:\halign下面的两个都会崩溃。

\halign{#\cr
  a\futurelet\testtoken\cr}
\halign{#&#\cr
  a\futurelet\testtoken & text\cr}

回到亨德里克的例子。

\def\begintestalign{\show\testtoken
    $\vcenter\bgroup\halign\bgroup##&##\cr}
\def\endtestalign{\egroup\egroup$}
\halign{#&#\cr
a & \futurelet\testtoken
    \begintestalign
         & c  \cr
    test & de \cr
    \endtestalign \cr
}

需要\futurelet三个标记:\testtoken\begintestalign和 TeX 等待插入的材料,只要它看到 或&\cr\crcr),即什么都没有,后面跟着内部的\endtemplate。因此,&已经转换为\endtemplate(也许部分\begin-next-template)第一个\halign。然后\begintestalign读取,创建一个新的内部\halign,并\halign等待一个新的&……它看到一个旧的&,已经属于封闭的\halign

这一切可能与 TeXbook 第 299 页中提到的错误有关:

不允许交织对齐前言。

如果您通过狡猾的手段获得了此消息,您就会明白,您将不值得任何同情。

答案4

我将此修复程序作为单独的答案和社区维基发布,希望人们能够改进它。

由于亨德里克的第二个问题发生在\futurelet读取&并触发外部对齐单元的结尾时,因此我们的想法是制作&一个活动字符。大多数情况下,我们会将\let其设置为隐式对齐选项卡\ampImplicit。在使用之前\futurelet,我们将定义更改&为无害的内容,并在分配后恢复通常的定义\futurelet

下面的代码排版正如 Hendrik 所期望的那样,但 是\testtoken,即我们定义为 之前的\relax安全控制序列。如果我们足够在意,我们可以用 替换。&\futurelet\let&\relax\def&{something recognizable}

\let\ampImplicit&
\catcode`\&=13\relax
\let&\ampImplicit

\def\makeampImplicit{\iffalse{\fi \let&\ampImplicit \iffalse}\fi }
\def\safefuturelet{\relax
  \iffalse{\fi \let&\relax \iffalse}\fi 
  \afterassignment\makeampImplicit
  \futurelet
}

% Hendrik's code, with `\futurelet` replaced by `\safefuturelet`.

\def\begintestalign{\show\testtoken
    $\vcenter\bgroup\halign\bgroup##&##\cr}
\def\endtestalign{\egroup\egroup$}
\halign{#&#\cr
a & \safefuturelet\testtoken
    \begintestalign
         & c  \cr
    test & de \cr
    \endtestalign \cr
}

\bye

&我已经测试了在使用时使 处于活动状态的想法amsmath,它\halign在内部使用。我们当然可以在加载包后更改 catcode,但我想看看如果它处于活动状态并从一开始就让其处于非活动状态,会发生什么情况。到目前为止,我发现这amsmath会因为\csname ... & ... \endcsname构造而中断:如果是显式对齐选项卡,则有效&,但当它是活动字符时,则无效,让其处于对齐选项卡。可以通过在加载包之前定义为&13扩展为的宏&4,然后恢复为“隐式字符”定义来修复此问题,以便对齐前言按预期工作。

\documentclass{article}

\def\ampMacro{&}
\let\ampImplicit&
\catcode`\&=13\relax

\let &\ampMacro
\usepackage{amsmath}
\let&\ampImplicit

\begin{document}
\begin{align}
  x^2+y^2 &= z^2 \\
  \intertext{but in general,}
  (a-b)^2 &\neq a^2 - b^2 
\end{align}
\end{document}

相关内容