TeX 基元的\output
行为几乎类似于 toks 寄存器,只不过它总是被括号括起来。例如,在 之后\output={\plainoutput}
, 的结果\showthe\output
为{\plainoutput}
,而不是我们天真地期望的(没有括号)。
括号内括号的状态有点奇怪。我遇到的具体问题是,当用显式或隐式结束组字符替换右括号时,该字符并非直接来自 TeX 的内部机制。
具体来说,用纯 TeX 编译以下示例会触发! Unbalanced output routine.
第 6 行的错误:第一种情况有效,但第二种情况无效。
\output{\plainoutput \afterassignment\next \let\next}
\hbox{} \vfill \penalty -10000
\def\Next{\next}
\output{\plainoutput \afterassignment\Next \let\next}
\hbox{} \vfill \penalty -10000
在这两种情况下,我都会重新定义输出例程,然后强制 TeX 调用它(使用\penalty-10000
)。我保留正常\plainoutput
例程,然后抓住 中的右括号,然后直接( )或间接( )\next
将其放回原处。在第一种直接情况下,TeX 会将右括号识别为来自例程,而在第二种情况下,TeX 不会,并且进入一种我输入的任何内容都不起作用的状态。\next
\Next
\output
为何两者的情况有所不同?
假设我在命令行中输入了上述代码,有什么办法可以恢复吗?
答案1
当有人说
\toks0={\plainoutput}\showthe\toks0
TeX 答案
> \plainoutput .
如果有人在这之后说
\output=\toks0
然后\showthe\output
给出
> \plainoutput .
如果有人说\hbox{}\penalty-10000
,那么错误消息
! Missing { inserted.
<to be read again>
\shipout
\plainoutput ->\shipout
\vbox {\makeheadline \pagebody \makefootline }\advan...
<*> \hbox{}\penalty-10000
发出。现在 TeX 处于内部垂直模式,正如\end
所产生
`! You can't use `\end' in internal vertical mode.`
但是输入}
并没有任何用处,因为当 TeX 不再解释任何标记时,我们就会陷入黑洞。
有趣的是,当赋值语句\output
的形式为
\output=<general text>
但不是当它像的时候\output=<token register>
。
添加括号的相关代码位于模块 1226 中,其中 Knuth 注释道“为了安全起见,我们在列表周围放置一对括号\output
。”,以及模块 1227。
一个值得研究的有趣模块是 1100,其结尾是输出组:接下来是模块 1026 中的内容。
我意识到这不是一个完整的答案:它只是表明最好不要对右括号进行修改。TeX 属于一种非常特殊的组,当它执行输出例程时,在此任务期间干扰它会非常危险。
我还发现了一个讨论comp.text.tex
似乎与这个问题有共同点。
答案2
如果你追踪中的代码tex.web
,你会发现重要的是例程 1025:
@ @<Fire up the user's output routine and |return|@>=
begin output_active:=true;
incr(dead_cycles);
push_nest; mode:=-vmode; prev_depth:=ignore_depth; mode_line:=-line;
begin_token_list(output_routine,output_text);
new_save_level(output_group); normal_paragraph;
scan_left_brace;
return;
end
与其他任何可以将左括号作为隐式 的情况一样{
,右括号必须是其本身或未隐藏在宏中的}
隐式。因此,隐藏在内部会以与任何其他情况完全相同的方式失败。(您可以使用 以任何其他方式测试 不是“特殊”的,效果很好。)}
}
\Next
}
\def\Next{\egroup}
您还会注意到,输出组实际上不是由启动的,{
而是明确编码的,因为 TeX 知道这里必须有一个组。扫描左括号找到一个(隐式的)左括号,但将其丢弃。
答案3
仔细研究tex.web
,我明白了
begin if (loc<>null) or
((token_type<>output_text)and(token_type<>backed_up)) then
@<Recover from an unbalanced output routine@>;
我不明白什么时候loc<>null
为真/假。但其余部分更容易理解:告诉token_type
我们标记来自哪里:它是由 TeX 插入的,还是<to be read again>
,等等。这里,output_text
是来自\output
标记参数的标记,其中包括结束括号,backed_up
是被 跳过的标记\expandafter
。我不知道为什么 Knuth 选择允许这两种类型的标记(嗯,output_text
很明显):任何其他类型的标记都会使测试为真,这会调用不平衡输出例程的代码。因此,要自己关闭输出例程,我们可以简单地使用\expandafter\egroup\empty
,如下所示:
\output
{%
\plainoutput % Plain TeX output routine
\expandafter \egroup % End the output routine
\let \next % Remove the closing brace inserted by TeX
}
\hbox{} \vfill \penalty -10000
如果令牌不是来自正确的地方,则运行以下代码
@ @<Recover from an unbalanced output routine@>=
begin print_err("Unbalanced output routine");
@.Unbalanced output routine@>
help2("Your sneaky output routine has problematic {'s and/or }'s.")@/
("I can't handle that very well; good luck."); error;
repeat get_token;
until loc=null;
end {loops forever if reading from a file, since |null=min_halfword<=0|}
出现错误后,TeX 会使用 获取标记get_token
,直到找到null
,据推测(根据注释)该标记无法从用户输入中获取,因此用户陷入困境。