Skip to main content

retex_engine/
error.rs

1//! Error reporting and recovery (*TeX: The Program* §71–§98, §311–§320).
2//!
3//! Errors print `! message.`, the input context and help text exactly as
4//! TeX does. Fatal errors end the run by unwinding to the top-level driver
5//! with a [`JumpOut`] payload (TeX's `jump_out`). `std::panic::resume_unwind`
6//! is used, so no panic hook runs and nothing is printed. This is the only
7//! non-local exit in the engine.
8
9use crate::Engine;
10use crate::input::{Level, LineSource, TokKind};
11use crate::print::Selector;
12
13/// Interaction level (`interaction`).
14#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
15pub enum Interaction {
16    /// `\batchmode`.
17    Batch = 0,
18    /// `\nonstopmode`.
19    NonStop = 1,
20    /// `\scrollmode`.
21    Scroll = 2,
22    /// `\errorstopmode`.
23    ErrorStop = 3,
24}
25
26impl Interaction {
27    /// The level with numeric value `n` (0–3).
28    pub fn from_i32(n: i32) -> Interaction {
29        match n {
30            0 => Interaction::Batch,
31            1 => Interaction::NonStop,
32            2 => Interaction::Scroll,
33            _ => Interaction::ErrorStop,
34        }
35    }
36}
37
38/// How bad things are (`history`).
39#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
40pub enum History {
41    /// No problems.
42    Spotless,
43    /// `begin_diagnostic` was called with warnings.
44    WarningIssued,
45    /// An error was reported.
46    ErrorMessageIssued,
47    /// The run was aborted.
48    FatalErrorStop,
49}
50
51/// Payload of the unwind that ends a run (TeX's `jump_out`).
52#[derive(Debug, Clone, Copy)]
53pub struct JumpOut;
54
55/// Error-handling state.
56#[derive(Debug)]
57pub struct ErrorState {
58    /// The interaction level.
59    pub interaction: Interaction,
60    /// The run's history.
61    pub history: History,
62    /// Number of errors since the last paragraph end (`error_count`).
63    pub error_count: i32,
64    /// Help lines for the next error, top line first.
65    pub help: Vec<&'static str>,
66    /// Owned help lines (used instead of `help` when non-empty).
67    pub help_owned: Vec<String>,
68    /// Whether `\errhelp` should be shown instead of the help lines.
69    pub use_err_help: bool,
70    /// Whether deletions are allowed in error recovery.
71    pub deletions_allowed: bool,
72    /// Whether `\setbox` is allowed here.
73    pub set_box_allowed: bool,
74    /// `no_new_control_sequence`: lookups must not create new names.
75    pub no_new_control_sequence: bool,
76    /// The selector saved by `begin_diagnostic`.
77    pub old_setting: Option<Selector>,
78    /// Whether `file:line:error` style messages are requested.
79    pub file_line_error: bool,
80    /// `-halt-on-error`: the first error ends the run.
81    pub halt_on_error: bool,
82    /// Whether a `\par` should not be inserted (`OK_to_interrupt`).
83    pub interrupt: bool,
84}
85
86impl Default for ErrorState {
87    fn default() -> Self {
88        ErrorState {
89            interaction: Interaction::ErrorStop,
90            history: History::FatalErrorStop,
91            error_count: 0,
92            help: Vec::new(),
93            help_owned: Vec::new(),
94            use_err_help: false,
95            deletions_allowed: true,
96            set_box_allowed: true,
97            no_new_control_sequence: true,
98            old_setting: None,
99            file_line_error: false,
100            halt_on_error: false,
101            interrupt: false,
102        }
103    }
104}
105
106impl Engine {
107    /// Starts an error message (`print_err`).
108    pub fn print_err(&mut self, msg: &str) {
109        self.printer.begin_line();
110        if !(self.err.file_line_error && self.print_file_line()) {
111            self.printer.print_str("! ");
112        }
113        if self.diagnostics.is_some() {
114            self.printer.capture = Some(Vec::new());
115        }
116        self.printer.print_str(msg);
117    }
118
119    /// Starts collecting a warning message (default output mode).
120    pub fn diag_capture_start(&mut self) {
121        if self.diagnostics.is_some() {
122            self.printer.capture = Some(Vec::new());
123        }
124    }
125
126    /// The innermost input file: name, line number, line text and the
127    /// reading position in it.
128    pub fn source_location(&self) -> Option<(String, u32, String, usize)> {
129        let elc = self
130            .eqtb
131            .int(crate::primitives_gen::assign_int::ENDLINECHAR);
132        self.input.stack.iter().rev().find_map(|l| match l {
133            crate::input::Level::Text(t) if t.is_input_file => {
134                let mut chars: &[u32] = &t.buf;
135                if let Some((&last, rest)) = chars.split_last()
136                    && i64::from(last) == i64::from(elc)
137                {
138                    chars = rest;
139                }
140                let text: String = chars.iter().filter_map(|&c| char::from_u32(c)).collect();
141                Some((
142                    String::from_utf8_lossy(&t.name).into_owned(),
143                    t.line.max(0) as u32,
144                    text,
145                    t.loc.min(chars.len()),
146                ))
147            }
148            _ => None,
149        })
150    }
151
152    /// Records the captured warning text as a diagnostic located at `line`
153    /// of the current file (or its current line).
154    pub fn diag_capture_warning(&mut self, line: Option<u32>) {
155        let Some(text) = self.printer.capture.take() else {
156            return;
157        };
158        let message = String::from_utf8_lossy(&text).trim().replace('\n', " ");
159        let mut d = crate::diag::Diagnostic::new(crate::diag::Severity::Warning, message);
160        if let Some((file, cur, _, _)) = self.source_location() {
161            d.file = Some(file);
162            d.line = Some(line.unwrap_or(cur));
163        }
164        if let Some(list) = self.diagnostics.as_mut() {
165            list.push(d);
166        }
167    }
168
169    /// Records the error being reported (default output mode).
170    fn record_error(&mut self) {
171        let Some(text) = self.printer.capture.take() else {
172            return;
173        };
174        let mut message = String::from_utf8_lossy(&text).trim().replace('\n', " ");
175        if message == "Undefined control sequence" && self.cur_cs != crate::hash::NO_CS {
176            let name = self.print_to_string(|e| e.print_cs(e.cur_cs));
177            message = format!(
178                "Undefined control sequence {}",
179                String::from_utf8_lossy(&name).trim_end()
180            );
181        }
182        let mut d = crate::diag::Diagnostic::new(crate::diag::Severity::Error, message);
183        d.facts = self.error_facts(&d.message);
184        if let Some((file, line, source, column)) = self.source_location() {
185            d.file = Some(file);
186            d.line = Some(line);
187            d.source = Some(source);
188            d.column = Some(column);
189        }
190        d.help = if self.err.help_owned.is_empty() {
191            self.err.help.iter().map(|s| (*s).to_owned()).collect()
192        } else {
193            self.err.help_owned.clone()
194        };
195        if let Some(list) = self.diagnostics.as_mut() {
196            list.push(d);
197        }
198    }
199
200    /// Facts about the error being reported, for the explanations of the
201    /// default output mode (see [`crate::diag::Diagnostic::facts`]).
202    fn error_facts(&mut self, message: &str) -> Vec<(String, String)> {
203        let mut facts = Vec::new();
204        if message.starts_with("Undefined control sequence") && self.cur_cs != crate::hash::NO_CS {
205            let name = self.hash.name(self.cur_cs).to_vec();
206            let similar = self.similar_defined_names(&name);
207            if !similar.is_empty() {
208                facts.push(("similar".to_owned(), similar.join(" ")));
209            }
210        }
211        if message.starts_with("Missing $ inserted") {
212            let tok = if self.cur_cmd == crate::cmd::Cmd::ParEnd {
213                // `\par` under any name (`\tex_par:D`, an empty line).
214                "\\par".to_owned()
215            } else if self.cur_cs != crate::hash::NO_CS {
216                let cs = self.cur_cs;
217                String::from_utf8_lossy(&self.print_to_string(|e| e.print_cs(cs)))
218                    .trim_end()
219                    .to_owned()
220            } else {
221                char::from_u32(self.cur_chr)
222                    .map(String::from)
223                    .unwrap_or_default()
224            };
225            facts.push(("token".to_owned(), tok));
226            if self.mode.abs_kind() == crate::maincontrol::ModeKind::Math {
227                let line = self.cur_list().mode_line.unsigned_abs();
228                facts.push(("math_line".to_owned(), line.to_string()));
229            }
230        }
231        facts
232    }
233
234    /// Defined control sequences whose names are close to `name` (a
235    /// typo), best first, at most three; internal names (with `@`, `_` or
236    /// `:`) only when `name` looks internal too.
237    fn similar_defined_names(&self, name: &[u8]) -> Vec<String> {
238        let internal = |n: &[u8]| n.iter().any(|&b| matches!(b, b'@' | b'_' | b':'));
239        let max = if name.len() < 4 { 1 } else { 2 };
240        let mut found: Vec<(usize, Vec<u8>)> = self
241            .hash
242            .iter_named()
243            .filter(|(n, _)| {
244                n.len() > 1
245                    && n.iter()
246                        .all(|b| !b.is_ascii_whitespace() && !b.is_ascii_control())
247            })
248            .filter(|(n, _)| !internal(n) || internal(name))
249            .filter(|(_, id)| self.eqtb.meaning(*id).cmd != crate::cmd::Cmd::UndefinedCs)
250            .filter_map(|(n, _)| {
251                crate::diag::edit_distance_within(name, n, max).map(|d| (d, n.to_vec()))
252            })
253            .filter(|(d, _)| *d > 0)
254            .collect();
255        found.sort();
256        found.dedup();
257        found
258            .into_iter()
259            .take(3)
260            .map(|(_, n)| format!("\\{}", String::from_utf8_lossy(&n)))
261            .collect()
262    }
263
264    /// Starts an error message given as bytes.
265    pub fn print_err_bytes(&mut self, msg: &[u8]) {
266        self.print_err("");
267        self.printer.print_bytes(msg);
268    }
269
270    /// Prints `file:line: ` for `-file-line-error`; returns `false` if no
271    /// file is open.
272    fn print_file_line(&mut self) -> bool {
273        let found = self.input.stack.iter().rev().find_map(|l| match l {
274            Level::Text(t) if t.is_input_file => Some((t.name.clone(), t.line)),
275            _ => None,
276        });
277        match found {
278            Some((name, line)) => {
279                self.printer.print_bytes(&name);
280                self.printer.print_char(':' as u32);
281                self.printer.print_int(line.into());
282                self.printer.print_str(": ");
283                true
284            }
285            None => false,
286        }
287    }
288
289    /// Sets the help lines for the next error (top line first).
290    pub fn help(&mut self, lines: &[&'static str]) {
291        self.err.help.clear();
292        self.err.help.extend_from_slice(lines);
293        self.err.help_owned.clear();
294    }
295
296    /// Sets owned help lines.
297    pub fn help_owned(&mut self, lines: Vec<String>) {
298        self.err.help.clear();
299        self.err.help_owned = lines;
300    }
301
302    /// Completes an error message: prints the context and help, and
303    /// interacts with the user if appropriate (`error`, §82).
304    pub fn error(&mut self) {
305        self.record_error();
306        if self.err.history < History::ErrorMessageIssued {
307            self.err.history = History::ErrorMessageIssued;
308        }
309        self.printer.print_char('.' as u32);
310        self.show_context();
311        if self.err.halt_on_error {
312            self.halt_on_error_stop();
313        }
314        if self.err.interaction == Interaction::ErrorStop {
315            // No interactive terminal support: behave like scroll mode, but
316            // a real terminal session would ask the user here.
317            self.terminal_error_stop();
318        }
319        self.err.error_count += 1;
320        if self.err.error_count == 100 {
321            self.printer
322                .print_nl("(That makes 100 errors; please try again.)");
323            self.err.history = History::FatalErrorStop;
324            self.jump_out();
325        }
326        // Put the help message on the transcript file.
327        let sel = self.printer.selector;
328        if self.err.interaction > Interaction::Batch {
329            self.printer.selector = match sel {
330                Selector::TermAndLog => Selector::LogOnly,
331                Selector::TermOnly => Selector::NoPrint,
332                s => s,
333            };
334        }
335        if self.err.use_err_help {
336            self.printer.print_ln();
337            self.give_err_help();
338        } else {
339            let lines: Vec<String> = if self.err.help_owned.is_empty() {
340                self.err.help.iter().map(|s| s.to_string()).collect()
341            } else {
342                self.err.help_owned.clone()
343            };
344            let empty = lines.is_empty();
345            for l in lines {
346                self.printer.print_nl(&l);
347            }
348            // LuaTeX does not add an empty line when there is no help and
349            // the context line was wrapped at max_print_line.
350            if empty {
351                self.printer.begin_line();
352            } else {
353                self.printer.print_ln();
354            }
355        }
356        if self.err.use_err_help {
357            self.printer.print_ln();
358        }
359        self.err.help.clear();
360        self.err.help_owned.clear();
361        if self.err.interaction > Interaction::Batch {
362            self.printer.selector = sel;
363        }
364        self.printer.print_ln();
365    }
366
367    /// Reads a line from the terminal, echoing it to the log
368    /// (`term_input`, §71). End of input is fatal, as in TeX.
369    pub fn term_input(&mut self) -> Vec<u32> {
370        self.prompted = true;
371        if self.printer.term_quiet {
372            let prompt = std::mem::take(&mut self.printer.quiet_line);
373            self.show_before_prompt(&String::from_utf8_lossy(&prompt));
374        }
375        self.printer.update_terminal();
376        let mut line = String::new();
377        match std::io::stdin().read_line(&mut line) {
378            Ok(0) | Err(_) => self.fatal_error("End of file on the terminal!"),
379            Ok(_) => {}
380        }
381        let chars = crate::input::decode_line(line.trim_end_matches(['\n', '\r']).as_bytes());
382        self.printer.term_offset = 0;
383        let old = self.printer.selector;
384        self.printer.selector = match old {
385            Selector::TermAndLog => Selector::LogOnly,
386            Selector::TermOnly => Selector::NoPrint,
387            s => s,
388        };
389        for &c in &chars {
390            self.printer.print_char(c);
391        }
392        self.printer.print_ln();
393        self.printer.selector = old;
394        chars
395    }
396
397    /// Prints a prompt and reads a line (`prompt_input`).
398    pub fn prompt_input(&mut self, prompt: &str) -> Vec<u32> {
399        self.printer.print_str(prompt);
400        self.term_input()
401    }
402
403    /// Default output mode: calls the prompt hook with the diagnostics not
404    /// shown yet and the prompt.
405    fn show_before_prompt(&mut self, prompt: &str) -> bool {
406        let Some(mut hook) = self.prompt_hook.take() else {
407            return true;
408        };
409        let list = self.diagnostics.as_deref().unwrap_or(&[]);
410        let from = self.diagnostics_shown.min(list.len());
411        let shown = hook(&list[from..], prompt);
412        self.diagnostics_shown = list.len();
413        self.prompt_hook = Some(hook);
414        shown
415    }
416
417    /// Asks the user what to do about an error (§84–§93). In the default
418    /// output mode, the error is shown as a diagnostic and TeX's dialog
419    /// then runs on the terminal as usual.
420    fn terminal_error_stop(&mut self) {
421        let quiet = self.printer.term_quiet;
422        if quiet && self.err.interaction == Interaction::ErrorStop {
423            if !self.show_before_prompt("") {
424                return;
425            }
426            self.printer.term_quiet = false;
427            self.printer.quiet_line.clear();
428        }
429        self.terminal_error_dialog();
430        self.printer.term_quiet = quiet;
431    }
432
433    fn terminal_error_dialog(&mut self) {
434        loop {
435            if self.err.interaction != Interaction::ErrorStop {
436                return;
437            }
438            self.clear_for_error_prompt();
439            let line = self.prompt_input("? ");
440            let Some(&c0) = line.first() else {
441                return;
442            };
443            let c = char::from_u32(c0).unwrap_or(' ').to_ascii_uppercase();
444            match c {
445                '0'..='9' if self.err.deletions_allowed => {
446                    let mut n = c as u32 - '0' as u32;
447                    if let Some(&d) = line.get(1)
448                        && (0x30..=0x39).contains(&d)
449                    {
450                        n = n * 10 + (d - 0x30);
451                    }
452                    let (s1, s2, s3, s4) = (
453                        self.cur_tok,
454                        self.cur_cmd,
455                        self.cur_chr,
456                        self.input.align_state,
457                    );
458                    self.input.align_state = 1_000_000;
459                    for _ in 0..n {
460                        self.get_token();
461                    }
462                    self.cur_tok = s1;
463                    self.cur_cmd = s2;
464                    self.cur_chr = s3;
465                    self.input.align_state = s4;
466                    self.help(&[
467                        "I have just deleted some text, as you asked.",
468                        "You can now delete more, or insert, or whatever.",
469                    ]);
470                    self.show_context();
471                    continue;
472                }
473                'E' => {
474                    let file = self.input.stack.iter().rev().find_map(|l| match l {
475                        Level::Text(t) if t.is_input_file => Some((t.name.clone(), t.line)),
476                        _ => None,
477                    });
478                    if let Some((name, line)) = file {
479                        self.printer.print_nl("You want to edit file ");
480                        self.printer.print_bytes(&name);
481                        self.printer.print_str(" at line ");
482                        self.printer.print_int(line.into());
483                        self.edit_request = Some((name, line));
484                        self.err.interaction = Interaction::Scroll;
485                        self.jump_out();
486                    }
487                }
488                'H' => {
489                    if self.err.use_err_help {
490                        self.give_err_help();
491                        self.err.use_err_help = false;
492                    } else {
493                        let lines: Vec<String> = if !self.err.help_owned.is_empty() {
494                            self.err.help_owned.clone()
495                        } else if !self.err.help.is_empty() {
496                            self.err.help.iter().map(|s| s.to_string()).collect()
497                        } else {
498                            vec![
499                                "Sorry, I don't know how to help in this situation.".into(),
500                                "Maybe you should try asking a human?".into(),
501                            ]
502                        };
503                        for l in lines {
504                            self.printer.print_str(&l);
505                            self.printer.print_ln();
506                        }
507                    }
508                    self.help(&[
509                        "Sorry, I already gave what help I could...",
510                        "Maybe you should try asking a human?",
511                        "An error might have occurred before I noticed any problems.",
512                        "``If all else fails, read the instructions.''",
513                    ]);
514                    continue;
515                }
516                'I' => {
517                    // Insert material from the terminal.
518                    let text: Vec<u32> = if line.len() > 1 {
519                        let mut v = vec![' ' as u32];
520                        v.extend_from_slice(&line[1..]);
521                        v
522                    } else {
523                        self.prompt_input("insert>")
524                    };
525                    self.begin_file_reading(crate::input::LineSource::Terminal, Vec::new(), false);
526                    if let Some(Level::Text(t)) = self.input.stack.last_mut() {
527                        t.buf = text;
528                        t.loc = 0;
529                        t.state = crate::input::LexState::MidLine;
530                    }
531                    return;
532                }
533                'Q' | 'R' | 'S' => {
534                    self.err.error_count = 0;
535                    self.printer.print_str("OK, entering ");
536                    match c {
537                        'Q' => {
538                            self.err.interaction = Interaction::Batch;
539                            self.print_esc_str("batchmode");
540                            self.printer.selector = match self.printer.selector {
541                                Selector::TermAndLog => Selector::LogOnly,
542                                _ => Selector::NoPrint,
543                            };
544                        }
545                        'R' => {
546                            self.err.interaction = Interaction::NonStop;
547                            self.print_esc_str("nonstopmode");
548                        }
549                        _ => {
550                            self.err.interaction = Interaction::Scroll;
551                            self.print_esc_str("scrollmode");
552                        }
553                    }
554                    self.printer.print_str("...");
555                    self.printer.print_ln();
556                    self.printer.update_terminal();
557                    return;
558                }
559                'X' => {
560                    self.err.interaction = Interaction::Scroll;
561                    self.jump_out();
562                }
563                _ => {}
564            }
565            // Print the menu of available options.
566            self.printer
567                .print_str("Type <return> to proceed, S to scroll future error messages,");
568            self.printer
569                .print_nl("R to run without stopping, Q to run quietly,");
570            self.printer.print_nl("I to insert something, ");
571            if self
572                .input
573                .stack
574                .iter()
575                .any(|l| matches!(l, Level::Text(t) if t.is_input_file))
576            {
577                self.printer.print_str("E to edit your file,");
578            }
579            if self.err.deletions_allowed {
580                self.printer
581                    .print_nl("1 or ... or 9 to ignore the next 1 to 9 tokens of input,");
582            }
583            self.printer.print_nl("H for help, X to quit.");
584        }
585    }
586
587    /// Pops finished terminal levels before prompting
588    /// (`clear_for_error_prompt`, §330).
589    fn clear_for_error_prompt(&mut self) {
590        while self.input.stack.len() > 1 {
591            match self.input.stack.last() {
592                Some(Level::Text(t))
593                    if matches!(t.source, LineSource::Terminal) && t.loc >= t.buf.len() =>
594                {
595                    self.end_file_reading();
596                }
597                _ => break,
598            }
599        }
600        self.printer.print_ln();
601    }
602
603    /// Prints `\errhelp` (`give_err_help`).
604    pub fn give_err_help(&mut self) {
605        let list = self
606            .eqtb
607            .toks_param(crate::primitives_gen::assign_toks::ERRHELP)
608            .clone();
609        self.token_show_list(&list, 0, list.len(), 10_000_000);
610    }
611
612    /// Reports a fatal error and ends the run (`fatal_error`).
613    pub fn fatal_error(&mut self, s: &str) -> ! {
614        self.normalize_selector();
615        self.print_err("Emergency stop");
616        self.help_owned(vec![s.to_owned()]);
617        self.succumb();
618    }
619
620    /// A LuaTeX fatal error of category `category` (as LuaTeX prints them:
621    /// `! error:  (category): message`, then `!  ==> Fatal error
622    /// occurred, …`); ends the run without the transcript line.
623    pub fn luatex_fatal(&mut self, category: &str, msg: &str) -> ! {
624        self.normalize_selector();
625        self.printer.print_nl("! error:  (");
626        self.printer.print_str(category);
627        self.printer.print_str("): ");
628        self.printer.print_str(msg);
629        let pdf = self.eqtb.int(crate::primitives_gen::assign_int::OUTPUTMODE) > 0;
630        self.printer.print_nl(if pdf {
631            "!  ==> Fatal error occurred, bad output PDF file produced!"
632        } else {
633            "!  ==> Fatal error occurred, bad output DVI file produced!"
634        });
635        self.printer.print_ln();
636        self.err.history = History::FatalErrorStop;
637        for n in 0..128u32 {
638            if self.printer.write_open(n) {
639                self.printer.close_write(n);
640            }
641        }
642        if self.total_pages == 0 {
643            self.printer.print_str("No pages of output.");
644            self.printer.print_ln();
645        }
646        if self.printer.log_opened() {
647            self.printer.close_log();
648        }
649        self.printer.selector = Selector::TermOnly;
650        self.printer.flush();
651        std::panic::resume_unwind(Box::new(JumpOut));
652    }
653
654    /// Ends the run after the first error with `-halt-on-error`: as
655    /// LuaTeX does, without the help text, with its fatal-error line, and
656    /// with the transcript line.
657    fn halt_on_error_stop(&mut self) -> ! {
658        let pdf = self.eqtb.int(crate::primitives_gen::assign_int::OUTPUTMODE) > 0;
659        self.printer.print_nl(if pdf {
660            "!  ==> Fatal error occurred, bad output PDF file produced!"
661        } else {
662            "!  ==> Fatal error occurred, bad output DVI file produced!"
663        });
664        self.printer.print_ln();
665        self.err.history = History::FatalErrorStop;
666        for n in 0..128u32 {
667            if self.printer.write_open(n) {
668                self.printer.close_write(n);
669            }
670        }
671        if self.total_pages == 0 {
672            self.printer.print_str("No pages of output.");
673            self.printer.print_ln();
674        }
675        // Observed: the log (only) ends with an empty line.
676        if self.printer.log_opened() {
677            let sel = self.printer.selector;
678            self.printer.selector = Selector::LogOnly;
679            self.printer.print_ln();
680            self.printer.selector = sel;
681        }
682        self.finish_log();
683        std::panic::resume_unwind(Box::new(JumpOut));
684    }
685
686    /// Reports that a capacity was exceeded and ends the run (`overflow`).
687    pub fn overflow(&mut self, s: &str, n: i64) -> ! {
688        self.normalize_selector();
689        self.print_err("TeX capacity exceeded, sorry [");
690        self.printer.print_str(s);
691        self.printer.print_char('=' as u32);
692        self.printer.print_int(n);
693        self.printer.print_char(']' as u32);
694        self.help(&[
695            "If you really absolutely need more capacity,",
696            "you can ask a wizard to enlarge me.",
697        ]);
698        self.succumb();
699    }
700
701    /// Reports an internal inconsistency (`confusion`).
702    pub fn confusion(&mut self, s: &str) -> ! {
703        self.normalize_selector();
704        if self.err.history < History::ErrorMessageIssued {
705            self.print_err("This can't happen (");
706            self.printer.print_str(s);
707            self.printer.print_char(')' as u32);
708            self.help(&["I'm broken. Please show this to someone who can fix"]);
709        } else {
710            self.print_err("I can't go on meeting you like this");
711            self.help(&[
712                "One of your faux pas seems to have wounded me deeply...",
713                "in fact, I'm barely conscious. Please fix it and try again.",
714            ]);
715        }
716        self.succumb();
717    }
718
719    fn succumb(&mut self) -> ! {
720        if self.err.interaction == Interaction::ErrorStop {
721            self.err.interaction = Interaction::Scroll;
722        }
723        if self.printer.log_opened() {
724            self.error();
725        }
726        self.err.history = History::FatalErrorStop;
727        self.jump_out();
728    }
729
730    /// Ends the run (`jump_out`): closes files and unwinds to the driver.
731    pub fn jump_out(&mut self) -> ! {
732        if self.prompt_hook.is_some() {
733            // Out of an error dialog (`X`): the default mode stays quiet.
734            self.printer.term_quiet = true;
735        }
736        self.close_files_and_terminate();
737        std::panic::resume_unwind(Box::new(JumpOut));
738    }
739
740    /// Makes sure output goes somewhere sensible (`normalize_selector`).
741    pub fn normalize_selector(&mut self) {
742        self.printer.selector = if self.printer.log_opened() {
743            Selector::TermAndLog
744        } else {
745            Selector::TermOnly
746        };
747        if self.job_name.is_none() {
748            self.open_log_file();
749        }
750        if self.err.interaction == Interaction::Batch {
751            self.printer.selector = match self.printer.selector {
752                Selector::TermAndLog => Selector::LogOnly,
753                _ => Selector::NoPrint,
754            };
755        }
756    }
757
758    /// Hook for user interrupts; retex does not support them.
759    pub fn check_interrupt(&mut self) {}
760
761    /// `back_error`: backs up the current token and reports an error.
762    pub fn back_error(&mut self) {
763        self.err.interrupt = false;
764        self.back_input();
765        self.err.interrupt = true;
766        self.error();
767    }
768
769    /// `ins_error`: like `back_error` but the token is marked inserted.
770    pub fn ins_error(&mut self) {
771        self.err.interrupt = false;
772        self.back_input_inserted();
773        self.err.interrupt = true;
774        self.error();
775    }
776
777    /// A LuaTeX warning (`warning  (category): message` on a line of its
778    /// own); it counts as a warning for the final "see the transcript"
779    /// note.
780    pub fn luatex_warning(&mut self, category: &str, msg: &str) {
781        self.printer.luatex_warning(category, msg);
782        if self.err.history == History::Spotless {
783            self.err.history = History::WarningIssued;
784        }
785    }
786
787    /// Starts diagnostic output (`begin_diagnostic`): goes to the log only
788    /// unless `\tracingonline > 0`.
789    pub fn begin_diagnostic(&mut self) {
790        self.err.old_setting = Some(self.printer.selector);
791        let online = self
792            .eqtb
793            .int(crate::primitives_gen::assign_int::TRACINGONLINE);
794        if online <= 0 && self.printer.selector == Selector::TermAndLog {
795            self.printer.selector = Selector::LogOnly;
796            if self.err.history == History::Spotless {
797                self.err.history = History::WarningIssued;
798            }
799        }
800    }
801
802    /// Ends diagnostic output (`end_diagnostic`).
803    pub fn end_diagnostic(&mut self, blank_line: bool) {
804        self.printer.print_nl("");
805        if blank_line {
806            self.printer.print_ln();
807        }
808        if let Some(s) = self.err.old_setting.take() {
809            self.printer.selector = s;
810        }
811    }
812
813    /// Shows where the scanner is (`show_context`, §311).
814    pub fn show_context(&mut self) {
815        let error_context_lines = self
816            .eqtb
817            .int(crate::primitives_gen::assign_int::ERRORCONTEXTLINES);
818        let input_ptr = self.input.stack.len().saturating_sub(1);
819        let mut base_ptr = input_ptr;
820        let mut nn: i32 = -1;
821        loop {
822            let (is_text, bottom) = match &self.input.stack.get(base_ptr) {
823                Some(Level::Text(t)) => (
824                    true,
825                    t.is_input_file || matches!(t.source, LineSource::File { .. }) || base_ptr == 0,
826                ),
827                Some(Level::Tokens(_)) => (false, false),
828                None => (true, true),
829            };
830            let bottom_line = is_text && bottom;
831            if base_ptr == input_ptr || bottom_line || nn < error_context_lines {
832                // Display the current context, omitting backed-up token
833                // lists that have already been read.
834                let show = match self.input.stack.get(base_ptr) {
835                    Some(Level::Tokens(t)) => {
836                        base_ptr == input_ptr
837                            || !matches!(t.kind, TokKind::BackedUp | TokKind::BackedUpChar)
838                            || t.loc < t.list.len()
839                    }
840                    _ => true,
841                };
842                if show {
843                    self.printer.term_just_wrapped = false;
844                    self.display_context(base_ptr);
845                    nn += 1;
846                    // LuaTeX's terminal shows an empty line after a context
847                    // line that filled the terminal line exactly, when more
848                    // context follows (the log does not).
849                    if self.printer.term_just_wrapped
850                        && !(bottom_line || base_ptr == 0)
851                        && matches!(
852                            self.printer.selector,
853                            crate::print::Selector::TermAndLog | crate::print::Selector::TermOnly
854                        )
855                    {
856                        self.printer.term_raw(b"\n");
857                    }
858                }
859            } else if nn == error_context_lines {
860                self.printer.print_nl("...");
861                nn += 1;
862            }
863            if bottom_line || base_ptr == 0 {
864                break;
865            }
866            base_ptr -= 1;
867        }
868    }
869
870    fn display_context(&mut self, base_ptr: usize) {
871        let old_setting = self.printer.selector;
872        self.printer.tally = 0;
873        let level = self.input.stack[base_ptr].clone();
874        let l: usize;
875        match &level {
876            Level::Text(t) => {
877                match &t.source {
878                    LineSource::Terminal => {
879                        if base_ptr == 0 {
880                            self.printer.print_nl("<*>");
881                        } else {
882                            self.printer.print_nl("<insert> ");
883                        }
884                    }
885                    LineSource::Read(n) => {
886                        self.printer.print_nl("<read ");
887                        if *n == 16 {
888                            self.printer.print_char('*' as u32);
889                        } else {
890                            self.printer.print_int(i64::from(*n));
891                        }
892                        self.printer.print_char('>' as u32);
893                    }
894                    _ => {
895                        self.printer.print_nl("l.");
896                        self.printer.print_int(t.line.into());
897                    }
898                }
899                self.printer.print_char(' ' as u32);
900                // Pseudoprint the line.
901                l = self.begin_pseudoprint();
902                let end_line_char = self
903                    .eqtb
904                    .int(crate::primitives_gen::assign_int::ENDLINECHAR);
905                let mut j = t.buf.len();
906                if j > 0 && t.buf[j - 1] as i32 == end_line_char {
907                    j -= 1;
908                }
909                for i in 0..j {
910                    if i == t.loc {
911                        self.set_trick_count();
912                    }
913                    let c = t.buf[i];
914                    if crate::utf8x::is_raw(c) {
915                        // Observed: an invalid byte that looks like the
916                        // start of a sequence is shown with the bytes that
917                        // would complete it; other ones are not shown.
918                        let n = match c & 0xFF {
919                            0xC2..=0xDF => 2,
920                            0xE0..=0xEF => 3,
921                            0xF0..=0xF4 => 4,
922                            _ => 0,
923                        };
924                        let mut bytes = Vec::new();
925                        for &e in t.buf.get(i..j).unwrap_or_default() {
926                            if bytes.len() >= n {
927                                break;
928                            }
929                            crate::utf8x::push_entry(&mut bytes, e);
930                        }
931                        bytes.truncate(n);
932                        self.printer.pseudo_unit(&bytes);
933                    } else {
934                        self.printer.print_char(c);
935                    }
936                }
937            }
938            Level::Tokens(t) => {
939                match t.kind {
940                    TokKind::Parameter => self.printer.print_nl("<argument> "),
941                    TokKind::UTemplate | TokKind::VTemplate => self.printer.print_nl("<template> "),
942                    TokKind::BackedUp | TokKind::BackedUpChar => {
943                        if t.loc >= t.list.len() {
944                            self.printer.print_nl("<recently read> ")
945                        } else {
946                            self.printer.print_nl("<to be read again> ")
947                        }
948                    }
949                    TokKind::Inserted => self.printer.print_nl("<inserted text> "),
950                    TokKind::Macro => {
951                        self.printer.print_ln();
952                        self.print_cs(t.name);
953                    }
954                    TokKind::OutputText => self.printer.print_nl("<output> "),
955                    TokKind::EveryParText => self.printer.print_nl("<everypar> "),
956                    TokKind::EveryMathText => self.printer.print_nl("<everymath> "),
957                    TokKind::EveryDisplayText => self.printer.print_nl("<everydisplay> "),
958                    TokKind::EveryHboxText => self.printer.print_nl("<everyhbox> "),
959                    TokKind::EveryVboxText => self.printer.print_nl("<everyvbox> "),
960                    TokKind::EveryJobText => self.printer.print_nl("<everyjob> "),
961                    TokKind::EveryCrText => self.printer.print_nl("<everycr> "),
962                    TokKind::MarkText => self.printer.print_nl("<mark> "),
963                    TokKind::EveryEofText => self.printer.print_nl("<everyeof> "),
964                    TokKind::WriteText => self.printer.print_nl("<write> "),
965                    TokKind::LocalText => self.printer.print_nl("<local> "),
966                }
967                l = self.begin_pseudoprint();
968                // For macros the list starts with the parameter text; TeX
969                // shows the whole definition (parameters and body).
970                self.show_token_list_ctx(&t.list, t.loc, 100_000);
971            }
972        }
973        self.printer.selector = old_setting;
974        self.print_two_lines(l);
975    }
976
977    fn begin_pseudoprint(&mut self) -> usize {
978        let l = self.printer.tally;
979        self.printer.tally = 0;
980        self.printer.selector = Selector::Pseudo;
981        self.printer.trick_count = 1_000_000;
982        self.printer.trick_buf.clear();
983        self.printer
984            .trick_buf
985            .resize(self.printer.error_line, vec![b' ']);
986        l
987    }
988
989    /// TeX's `set_trick_count`.
990    pub fn set_trick_count(&mut self) {
991        let p = &mut self.printer;
992        p.first_count = p.tally;
993        p.trick_count = p.tally + 1 + p.error_line - p.half_error_line;
994        if p.trick_count < p.error_line {
995            p.trick_count = p.error_line;
996        }
997    }
998
999    fn print_two_lines(&mut self, l: usize) {
1000        if self.printer.trick_count == 1_000_000 {
1001            self.set_trick_count();
1002        }
1003        let p = &self.printer;
1004        let (tally, first_count, trick_count, half, error_line) = (
1005            p.tally,
1006            p.first_count,
1007            p.trick_count,
1008            p.half_error_line,
1009            p.error_line,
1010        );
1011        let m = if tally < trick_count {
1012            tally - first_count
1013        } else {
1014            trick_count - first_count
1015        };
1016        let (start, n) = if l + first_count <= half {
1017            (0, l + first_count)
1018        } else {
1019            self.printer.print_plain("...");
1020            (l + first_count - half + 3, half)
1021        };
1022        let buf = self.printer.trick_buf.clone();
1023        let mut bytes = Vec::new();
1024        for q in start..first_count {
1025            bytes.extend_from_slice(&buf[q % error_line]);
1026        }
1027        self.print_raw_bytes(&bytes);
1028        self.printer.print_ln();
1029        for _ in 0..n {
1030            self.printer.print_char(' ' as u32);
1031        }
1032        let p_end = if m + n <= error_line {
1033            first_count + m
1034        } else {
1035            first_count + (error_line - n - 3)
1036        };
1037        bytes.clear();
1038        for q in first_count..p_end {
1039            bytes.extend_from_slice(&buf[q % error_line]);
1040        }
1041        self.print_raw_bytes(&bytes);
1042        if m + n > error_line {
1043            self.printer.print_plain("...");
1044        }
1045    }
1046
1047    /// Prints already-converted bytes from the pseudo buffer, decoding
1048    /// complete UTF-8 sequences so that line lengths stay correct.
1049    fn print_raw_bytes(&mut self, bytes: &[u8]) {
1050        let mut i = 0;
1051        while i < bytes.len() {
1052            let (c, n) = crate::utf8x::decode(bytes, i);
1053            if n == 1 && c >= 0x80 {
1054                self.printer.print_raw_byte(c as u8);
1055            } else {
1056                self.printer.print_char(c);
1057            }
1058            i += n;
1059        }
1060    }
1061}