不平衡输出例程

不平衡输出例程

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. 为何两者的情况有所不同?

  2. 假设我在命令行中输入了上述代码,有什么办法可以恢复吗?

答案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,据推测(根据注释)该标记无法从用户输入中获取,因此用户陷入困境。

相关内容