直到最近,我才认为
\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 \let
to时遇到时\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
是,然后我们通过宏看到。\let
b
b
\showtoken
\futurelet
和的有趣组合\afterassignment
允许您提前预览两个标记,并且我们检查\futurelet
确实读取了标记\testtoken
,b
和c
并分配\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}