为什么“未定义的控制序列”后面会有“文本行包含无效字符”的提示,并且“Q”需要进一步输入?

为什么“未定义的控制序列”后面会有“文本行包含无效字符”的提示,并且“Q”需要进一步输入?
(base) MacBook-Pro-2:pdftex zmx$ latex
This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017) (preloaded format=latex)
 restricted \write18 enabled.
**\s^EH
entering extended mode
LaTeX2e <2017-04-15>
Babel <3.10> and hyphenation patterns for 84 language(s) loaded.
! Undefined control sequence.
<*> \s
      ^^EH
? 1
! Text line contains an invalid character.
<*> \s^^E
         H
? Q
OK, entering \batchmode

上面的例子中,^E 在 MacOS 上是 ctrl+e。显然它是一个无效字符,所以当我输入“1”时,它输出了一条错误消息“文本行包含无效字符”。然后我输入“Q”,它似乎没有开始运行,而是要求进一步输入。

我的问题是为什么在“Q”之后需要进一步输入?

实际上,我正在为我的项目调试 pdftex。在 pdftex 1.40.18 上,如果您输入:

\s^EH

1

那么就会出现段错误。 在此处输入图片描述

如果有人对这个错误感兴趣,请随时与我讨论。

答案1

  1. 原始发帖人:您能给我发电子邮件至 karl@tug.org 吗?您发现了原始 TeX 中的一个错误,DEK 肯定会想把圣塞里夫银行的一大笔钱转给您 :)。

  2. “Q 之后的进一步输入”和“分段错误”具有相同的根本原因:在这种不寻常的交互序列中,TeX 的数据陷入了不一致的状态。

  3. 我已向 TL svn r55767 提交了修复程序(感谢 David Fuchs)。它适用于除 LuaTeX 之外的所有 TeX 变体,LuaTeX 必须单独修复。经过更多测试后,我预计 Akira 将在他的 w32tex 发行版中推出新的二进制文件,供任何可以使用并想尝试的人使用。TL 二进制文件不会为此更新。

  4. 罪魁祸首是您的输入导致在交互 = 批处理模式时调用模块 83 <获取用户的建议...>。但这绝不应该发生。随后发生了混乱。

  5. 需要“H”(或任何文本)来导致 web2c 中的崩溃,因为该崩溃是由于尝试将文本写入关闭的 \write 流而发生的。(选择器从 16(no_print)减少到 15,对应于 \write15。就像我说的,造成严重破坏。)

  6. 根据记录,我可以通过运行 tex -ini <invalid.in 来重现该错误,其中 invalid.in 是一个包含以下四行的文本文件:

    \catcode`\^=7 \catcode`\^^?=15 \s^^?E
    1
    q
    v
    

很高兴你找到了它。

答案2

作为TeX 版本 3.141592653 和 Metafont 版本 2.71828182,该错误已修复。本答案的其余部分仅适用于以前的版本。


注意:我刚刚发现 Metafont 中也存在此错误。请参阅此答案的结尾。

为了记录的目的,这里准确说明了导致错误发生的原因,因为我花了一段时间才弄清楚。

关键的是两个整数变量selectorinteraction。我们先来关注interaction第一个,因为它比较简单。它用来控制 TeX 是否停止与用户交互,它有四个可能的值:

  • interaction= error_stop_mode= 3 时,如果发生错误(§82、§530),或者如果\pausing设置为正值(§363),或者如果\read 用于从终端获取输入(§484),或者如果interrupt在某些点为非零[在扫描标记列表之后(§324),在读取一行输入之后(§343),以及在处理连字期间(§753、§911)],TeX 将停止。

  • interaction= scroll_mode= 2 时,TeX 不会在发生非致命错误时停止,除非问题是找不到文件,在这种情况下 TeX 仍会提示用户输入新文件名(§530)。

  • interaction= nonstop_mode= 1 时,TeX 不会停止,除非发生致命错误,或者进入需要用户输入的情况——即,如果\end文件中不存在命令(§360),如果命令要求从终端输入(§484),或者找不到文件(§530)。(这些情况被视为致命情况,但如果是或则\read 不会 。)interactionscroll_modeerror_stop_mode

  • interaction= batch_mode= 0 时,TeX 的行为与 时 interaction一样nonstop_mode除了省略终端输出之外 (§75、§90、§92、§1328)。这很重要。

请注意,用户交互级别会随着 值的interaction 增加而增加。最初,interaction设置为error_stop_mode(§74)。

selector变量控制 TeX 的各种文本打印例程将其输出发送到何处。在 TeX82 中,它有 22 个可能的值,范围从 0 到 21。当 0 ≤ selector≤ 15 时,它表示使用 打开的文件之一 \openout。15 以上的值selector具有以下含义:

  • selector= no_print= 16 时,打印无进展。

  • selector= term_only= 17 时,打印仅发送到终端。

  • selector= log_only= 18 时,打印仅转到成绩单文件。

  • selector= term_and_log= 19 时,打印转到终端和成绩单文件。

  • selector= pseudo= 20 时,字符将“打印”到缓冲区供程序使用show_context,这一过程称为“伪打印”(§315)。此设置与我们无关。

  • selector= new_string= 21 时,字符将附加到字符串内存中(如果还有空间)。此设置与我们无关。

最初selectorterm_only(§55、§1332),因为尚未打开任何笔录文件。

selector和的值interaction大多是独立的。但是,正如您所预料的,当 时interactionbatch_mode不应selectorterm_onlyterm_and_log。在一种情况下,它term_only 在 §535 中无条件地(虽然是暂时的,因为先前的值已在 §534 中保存)设置为 ,而不管interaction。但一般的想法是, 当且仅当> 时,selector将是term_only或;特别是,当= 时,将是或。term_and_loginteractionbatch_modeselectorterm_onlyterm_and_loginteractionerror_stop_mode

当 TeX 希望从终端读取一行时,它会调用term_input (通常通过prompt_input;参见§71)。此例程巧妙地利用了selector的可能值之间的数字关系,以便在适当的情况下回显输入行。该程序假定在输入 时selector必须是 或 term_only(其他值没有意义)。因此可以减少并无条件打印用户输入的行;如果是,它就变成 ,这是正确的,因为该行已经被回显了(由于终端的性质),如果是,它就变成,这是正确的,因为该行必须写入转录文件。term_and_logterm_inputterm_inputselectorselectorterm_onlyno_printselectorterm_and_loglog_only

现在让我们看一下该error例程。其顶层如下所示(§82):

procedure error;
  label continue, exit;
  var …;
  begin
    if interaction = error_stop_mode then
      ⟨Get the user’s advice and return⟩;
exit:
  end;

⟨获取用户的建议…⟩ 的提纲如下:

loop
  begin continue:
    clear_for_error_prompt;
    prompt_input("? ");
    if last = first then
      return;
    c ← buffer[first]
    if c ≥ "a" then
      c ← c + "A" − "a"; {convert to uppercase}
    ⟨Interpret code c and return if done⟩;
    ⟨Print the menu of available options⟩;
  end

⟨打印菜单...⟩ 部分听起来就是这样,不同之处在于,E如果没有打开输入文件,则不会列出键入以编辑输入文件的选项,并且如果 deletions_allowed为假,则不会列出键入数字以删除标记的选项(为了阻止超过两级的递归error)。

⟨解释代码…⟩ 中有趣的部分c是一个大case语句,它打开了 的值c。(无趣的部分实际上是 ⟨打印菜单…⟩。我把它移走了,以使整个循环流程更清晰。)在以下描述中,控制转移以大胆的

  1. 如果c是十进制数字,并且如果可以删除令牌,则删除用户指定的令牌数量,并且控制权归 continue

  2. 如果c"E",那么(在 TeX82 中)会告知用户要编辑哪个文件的哪一行,并且 TeX终止

  3. 如果c"H",则打印帮助信息,并且控制权归 continue

  4. 如果c"I",那么从终端读取一行输入作为 TeX 下一步要处理的内容,并且控制权归 exit通过return宏。

  5. 如果c"Q",则interaction变为batch_modeselector减少(以抑制终端输出),并且控制权归 exit

  6. 如果c"R",则interaction变为nonstop_mode并且控制权归 exit

  7. 如果c"S",则interaction变为scroll_mode并且控制权归 exit

  8. 如果c"X",则interaction变为scroll_mode和 TeX 终止

  9. 否则什么也不会发生;控制失败到⟨打印菜单...⟩我们回到循环顶部。

如果用于调试的代码没有被注释掉, 那么也会出现c= 的情况。"D"控制权归 continue然后。

[关于案例 5、6、7 有一点值得注意:每次改变 都 interaction伴随着一条消息,说OK, entering,然后是新模式;例如,当你输入 时S,TeX 说 OK, entering scrollmode。然后程序执行print("..."),所以消息最终是 OK, entering scrollmode...。然而,在案例 5 中,selector在省略号之前递减,所以如果selector 是,它最终会进入转录文件,如果是,它最终会进入term_and_log任何地方;不会出现在终端上。Knuth 在他的第六个练习的答案中承认了这一点selectorterm_only...TeX:程序他在 TUGboat 上发表过(练习 这里,答案 这里)。

删除过程非常简单。首先,保存某些全局变量(、、和)的值。然后cur_tok将 设置 为 false — 这是停止不必要递归的另一种措施,因为如果发生中断并且为 true,可能会调用cur_cmd。 接下来,将设置为用户输入的数字。执行以下循环:cur_chralign_stateOK_to_interrupterrorOK_to_interruptc

while c > 0 do
  begin
    get_token; {one-level recursive call of error is possible}
    decr(c);
  end

因此,只需读取并忽略标记即可删除标记。get_token 就我们的目的而言,该过程可以视为与 相同get_next。递归可能发生,因为get_next可能导致error被调用。 中可能出现的大多数错误情况get_next最终会终止程序;它们是致命错误。但有一个直接调用error,当读取无效字符时发生(§346)。deletions_allowed 变量在调用之前设置为 false,之后设置为 true。

那么问题是什么?让我们考虑一下当您启动纯 TeX 并输入麻烦的输入时会发生什么。(我使用纯 TeX 是因为^^?已经被定为非法。)首先\s^^?E输入 以响应提示** 。由于输入的第一个字符是\(=  escape),TeX 将其视为常规代码(即,它不假设您想要\input一个名为 的文件\s^^?​E;参见§1337)。\s读取 ,TeX 尝试扩展名为 的控制序列s。由于\s没有定义,expand例程调用error(§370)。

此时,interactionerror_stop_mode并且selectorterm_only。(这就是为什么错误必须发生在输入的第一行;否则记录文件将被打开并selector发生变化。)§83 中的循环开始。然后您输入1(这是上面列出的情况 1),§88 开始执行,并由 get_next调用get_token。无效字符^^?(ASCII 代码 127 =  '177 =  "7F;参见附录 CTeXbook) 被读取,控制权移至 §346。该error例程被再次调用。

interaction和的值selector没有改变,因此错误对话框像以前一样输入。现在您输入Q。§86 中的代码运行;interaction 变为batch_mode,并selector减少到no_print。控制从error返回到get_next,跳过无效字符并读取E输入中的左侧字符。然后我们回到error;请记住我们处于情况 1,因此控制权上升到continue并且对话框循环再次开始。

此时,interactionisbatch_modeselectoris no_print= 16,并且我们处于§83中的循环顶部,应该执行仅有的如果 interaction= error_stop_mode。现在,所有拼图碎片都已就位。prompt_input宏首先尝试打印?;由于 的值为 ,因此什么也没有显示selector。然后prompt_input调用 term_input,它确实执行了input_ln(term_in, true);这就是为什么 TeX 等待输入,即使它应该处于批处理模式。无效字符后面必须有文本的原因是,否则 TeX 将遇到输入的结尾(在get_next,§360)并报告致命错误 [ *** (job aborted, no legal \end found)]。fatal_error过程 (§93) 调用 normalize_selector(§92),旨在避免我所描述的情况!

接下来,term_input减少selector;其值变为 15。如果您输入任何内容来响应不可见的?term_input则将尝试通过调用print中的每个字符来打印它buffer,最终将调用print_char。(简单练习:为什么不能直接term_input调用print_char ?)的值selector不是上面列举的六个重要值之一,因此print_char尝试打印到write_file[selector]。的元素write_file属于类型alpha_file,并且它们都不是实际的开放流,因此现在发生的情况取决于系统。在 Web2C 中,结果是putc将使用空指针作为其第二个参数进行调用(请参阅 fixwrites.c),这将导致分段错误。 ∎


现在我们知道了哪里出了问题,那么该如何修复呢?在 TeX Live 和最近的调整中,§83 被改为在循环开始时进行测试,因此现在看起来像

loop
  begin continue:
    if interaction ≠ error_stop_mode then
      return;
    clear_for_error_prompt;
    prompt_input("? ");
    if last = first then
      return;
    c ← buffer[first]
    if c ≥ "a" then
      c ← c + "A" − "a" {convert to uppercase}
    ⟨Interpret code c and return if done⟩;
  end

(看7 月 6 日提交的c。这里我没有像之前一样将⟨打印菜单…⟩从⟨解释代码…⟩中取出。)

在研究了原始代码后,我想出了以下替代解决方案。首先,我们更改error的顶层(§82),以便

if interaction = error_stop_mode then
  ⟨Get the user's advice and return⟩;

while interaction = error_stop_mode do
  ⟨Get the user's advice and return⟩;

然后我们将第 83 条改为

begin
  clear_for_error_prompt;
  prompt_input("? ");
  if last = first then
    return;
  c ← buffer[first];
  if c ≥ "a" then
    c ← c + "A" − "a"; {convert to uppercase}
  ⟨Interpret code c and return if done⟩;
continue:
end

还有其他更激进的选择。我们可以做同样的改变,但删除continue§83 中的标签,并将 ⟨Interpret code c…⟩ 改为类似

if (c ≥ "0") ∧ (c ≤ "9") ∧ deletions_allowed then
  ⟨Delete c − "0" tokens⟩
else
  if (c = "E") ∧ (base_ptr > 0) then
  else
    case c of
      debug "D"
        begin
          debug_help;
        end;
      gubed
      "H":
        ⟨Print the help information⟩;
      "I":
        ⟨Introduce new material from the terminal and return⟩;
      "Q", "R", "S":
        ⟨Change the interaction level and return⟩;
      "X":
        begin
          interaction ← scroll_mode;
          jump_out;
        end;
    othercases
      ⟨Print the menu of available options⟩
    endcases

已从goto continue删除代码、调试代码和帮助显示代码中删除。在我看来,这更糟糕,因为即使为c"E" 为数字,菜单也可能被打印,这一点并不明显。

除此以外,其他地方error也可以改变。我们可以做出 term_input或明确验证∈ { , }prompt_input的假设 。例如,可以将其扩展为selectorterm_onlyterm_and_logprompt_input(#)

begin
  if (selector ≠ term_only) ∧ (selector ≠ term_and_log) then
    confusion("selector");
  wake_up_terminal();
  print(#);
  term_input;
end

当然,只有当程序中还存在更多此类错误时,这才会有帮助。


附录:Metafont 和 TeX 共享许多编程,事实上它们的例程版本error几乎相同。因此,这个错误在两个程序中都发生并不奇怪。不过,Metafont 中的情况没有那么糟糕;不会发生分段错误。这次有问题的第一行是\1:=^Ax,其中^A是 control+ a。(任何无效字符都可以,但必须直接输入,因为 Metafont 没有与 TeX^^语法等效的语法。)您将收到错误

Improper `:=' will be changed to `='.

其余交互与之前一样。你输入1,Metafont 会谴责无效字符,然后你输入q,Metafont 会在进入批处理模式后等待输入。

当然还有其他方法可以导致错误。您可以说\1;^Ax,但您必须删除两个令牌,而不是一个。

上面关于 TeX 的大部分说明都适用于 Metafont,尽管许多部分编号不同。这种selector恶作剧不会发生,因为 Metafont 认为它介于 0 和 5 之间,如果不是,则不会执行任何操作。

答案3

tex我可以使用以下测试文档重现该行为

\tracingall
\catcode`\^^E=15
\s^^EH

如果我跑tex test,我会得到

This is TeX, Version 3.14159265 (TeX Live 2020) (preloaded format=tex)
(./testinv.tex
{vertical mode: \tracingstats}
{\tracingpages}
{\tracingoutput}
{\tracinglostchars}
{\tracingmacros}
{\tracingparagraphs}
{\tracingrestores}
{\showboxbreadth}
{\showboxdepth}
{\catcode}
{undefined}
! Undefined control sequence.
l.3 \s
      ^^EH
? 1
! Text line contains an invalid character.
l.3 \s^^E
         H
? q
OK, entering \batchmode

>

最后一行表示只有按下回车键才会出现的 shell 提示符。

为什么有人说这种行为在 TL 2020 中无法重现?好问题。直到 2018 年,LaTeX 才将几个字符(包括)设为无效,以捕获错误输入。当 UTF-8 成为默认输入编码并且现在不再在格式中分配类别代码 15^^E时,情况发生了变化。^^E

如果我击中r,我会得到

This is TeX, Version 3.14159265 (TeX Live 2020) (preloaded format=tex)
(./testinv.tex
{vertical mode: \tracingstats}
{\tracingpages}
{\tracingoutput}
{\tracinglostchars}
{\tracingmacros}
{\tracingparagraphs}
{\tracingrestores}
{\showboxbreadth}
{\showboxdepth}
{\catcode}
{undefined}
! Undefined control sequence.
l.3 \s
      ^^EH
? 1
! Text line contains an invalid character.
l.3 \s^^E
         H
? r
OK, entering \nonstopmode...
l.3 \s^^EH

?

这暗示无效字符确实被忽略并且1指令(忽略一个标记)尚未执行并且 TeX 仍在等待用户输入。

故事的寓意是:无效字符确实是无效的,在错误恢复期间删除令牌时不会被考虑。

另一方面,

? q
OK, entering \batchmode

行应该表示它的意思(但它不是,就像当r被击中时一样)。这可能是一个真正的错误。


较短的测试文件是

\s^^?H

执行操作时1q在提示符下查看日志文件,但再次按下回车键

This is TeX, Version 3.14159265 (TeX Live 2020) (preloaded format=tex 2020.4.17)  27 JUN 2020 10:34
**test
(./test.tex
! Undefined control sequence.
l.1 \s
      ^^?H
? 1
! Text line contains an invalid character.
l.1 \s^^?
         H
? q
OK, entering \batchmode...
l.1 \s^^?H
          
? )
! Emergency stop.
<*> test
           
*** (job aborted, no legal \end found)

No pages of output.

相关内容