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    /// Whether a `\par` should not be inserted (`OK_to_interrupt`).
81    pub interrupt: bool,
82}
83
84impl Default for ErrorState {
85    fn default() -> Self {
86        ErrorState {
87            interaction: Interaction::ErrorStop,
88            history: History::FatalErrorStop,
89            error_count: 0,
90            help: Vec::new(),
91            help_owned: Vec::new(),
92            use_err_help: false,
93            deletions_allowed: true,
94            set_box_allowed: true,
95            no_new_control_sequence: true,
96            old_setting: None,
97            file_line_error: false,
98            interrupt: false,
99        }
100    }
101}
102
103impl Engine {
104    /// Starts an error message (`print_err`).
105    pub fn print_err(&mut self, msg: &str) {
106        self.printer.begin_line();
107        if !(self.err.file_line_error && self.print_file_line()) {
108            self.printer.print_str("! ");
109        }
110        if self.diagnostics.is_some() {
111            self.printer.capture = Some(Vec::new());
112        }
113        self.printer.print_str(msg);
114    }
115
116    /// Starts collecting a warning message (default output mode).
117    pub fn diag_capture_start(&mut self) {
118        if self.diagnostics.is_some() {
119            self.printer.capture = Some(Vec::new());
120        }
121    }
122
123    /// The innermost input file: name, line number, line text and the
124    /// reading position in it.
125    pub fn source_location(&self) -> Option<(String, u32, String, usize)> {
126        let elc = self
127            .eqtb
128            .int(crate::primitives_gen::assign_int::ENDLINECHAR);
129        self.input.stack.iter().rev().find_map(|l| match l {
130            crate::input::Level::Text(t) if t.is_input_file => {
131                let mut chars: &[u32] = &t.buf;
132                if let Some((&last, rest)) = chars.split_last()
133                    && i64::from(last) == i64::from(elc)
134                {
135                    chars = rest;
136                }
137                let text: String = chars.iter().filter_map(|&c| char::from_u32(c)).collect();
138                Some((
139                    String::from_utf8_lossy(&t.name).into_owned(),
140                    t.line.max(0) as u32,
141                    text,
142                    t.loc.min(chars.len()),
143                ))
144            }
145            _ => None,
146        })
147    }
148
149    /// Records the captured warning text as a diagnostic located at `line`
150    /// of the current file (or its current line).
151    pub fn diag_capture_warning(&mut self, line: Option<u32>) {
152        let Some(text) = self.printer.capture.take() else {
153            return;
154        };
155        let message = String::from_utf8_lossy(&text).trim().replace('\n', " ");
156        let mut d = crate::diag::Diagnostic::new(crate::diag::Severity::Warning, message);
157        if let Some((file, cur, _, _)) = self.source_location() {
158            d.file = Some(file);
159            d.line = Some(line.unwrap_or(cur));
160        }
161        if let Some(list) = self.diagnostics.as_mut() {
162            list.push(d);
163        }
164    }
165
166    /// Records the error being reported (default output mode).
167    fn record_error(&mut self) {
168        let Some(text) = self.printer.capture.take() else {
169            return;
170        };
171        let mut message = String::from_utf8_lossy(&text).trim().replace('\n', " ");
172        if message == "Undefined control sequence" && self.cur_cs != crate::hash::NO_CS {
173            let name = self.print_to_string(|e| e.print_cs(e.cur_cs));
174            message = format!(
175                "Undefined control sequence {}",
176                String::from_utf8_lossy(&name).trim_end()
177            );
178        }
179        let mut d = crate::diag::Diagnostic::new(crate::diag::Severity::Error, message);
180        if let Some((file, line, source, column)) = self.source_location() {
181            d.file = Some(file);
182            d.line = Some(line);
183            d.source = Some(source);
184            d.column = Some(column);
185        }
186        d.help = if self.err.help_owned.is_empty() {
187            self.err.help.iter().map(|s| (*s).to_owned()).collect()
188        } else {
189            self.err.help_owned.clone()
190        };
191        if let Some(list) = self.diagnostics.as_mut() {
192            list.push(d);
193        }
194    }
195
196    /// Starts an error message given as bytes.
197    pub fn print_err_bytes(&mut self, msg: &[u8]) {
198        self.print_err("");
199        self.printer.print_bytes(msg);
200    }
201
202    /// Prints `file:line: ` for `-file-line-error`; returns `false` if no
203    /// file is open.
204    fn print_file_line(&mut self) -> bool {
205        let found = self.input.stack.iter().rev().find_map(|l| match l {
206            Level::Text(t) if t.is_input_file => Some((t.name.clone(), t.line)),
207            _ => None,
208        });
209        match found {
210            Some((name, line)) => {
211                self.printer.print_bytes(&name);
212                self.printer.print_char(':' as u32);
213                self.printer.print_int(line.into());
214                self.printer.print_str(": ");
215                true
216            }
217            None => false,
218        }
219    }
220
221    /// Sets the help lines for the next error (top line first).
222    pub fn help(&mut self, lines: &[&'static str]) {
223        self.err.help.clear();
224        self.err.help.extend_from_slice(lines);
225        self.err.help_owned.clear();
226    }
227
228    /// Sets owned help lines.
229    pub fn help_owned(&mut self, lines: Vec<String>) {
230        self.err.help.clear();
231        self.err.help_owned = lines;
232    }
233
234    /// Completes an error message: prints the context and help, and
235    /// interacts with the user if appropriate (`error`, §82).
236    pub fn error(&mut self) {
237        self.record_error();
238        if self.err.history < History::ErrorMessageIssued {
239            self.err.history = History::ErrorMessageIssued;
240        }
241        self.printer.print_char('.' as u32);
242        self.show_context();
243        if self.err.interaction == Interaction::ErrorStop {
244            // No interactive terminal support: behave like scroll mode, but
245            // a real terminal session would ask the user here.
246            self.terminal_error_stop();
247        }
248        self.err.error_count += 1;
249        if self.err.error_count == 100 {
250            self.printer
251                .print_nl("(That makes 100 errors; please try again.)");
252            self.err.history = History::FatalErrorStop;
253            self.jump_out();
254        }
255        // Put the help message on the transcript file.
256        let sel = self.printer.selector;
257        if self.err.interaction > Interaction::Batch {
258            self.printer.selector = match sel {
259                Selector::TermAndLog => Selector::LogOnly,
260                Selector::TermOnly => Selector::NoPrint,
261                s => s,
262            };
263        }
264        if self.err.use_err_help {
265            self.printer.print_ln();
266            self.give_err_help();
267        } else {
268            let lines: Vec<String> = if self.err.help_owned.is_empty() {
269                self.err.help.iter().map(|s| s.to_string()).collect()
270            } else {
271                self.err.help_owned.clone()
272            };
273            let empty = lines.is_empty();
274            for l in lines {
275                self.printer.print_nl(&l);
276            }
277            // LuaTeX does not add an empty line when there is no help and
278            // the context line was wrapped at max_print_line.
279            if empty {
280                self.printer.begin_line();
281            } else {
282                self.printer.print_ln();
283            }
284        }
285        if self.err.use_err_help {
286            self.printer.print_ln();
287        }
288        self.err.help.clear();
289        self.err.help_owned.clear();
290        if self.err.interaction > Interaction::Batch {
291            self.printer.selector = sel;
292        }
293        self.printer.print_ln();
294    }
295
296    /// Reads a line from the terminal, echoing it to the log
297    /// (`term_input`, §71). End of input is fatal, as in TeX.
298    pub fn term_input(&mut self) -> Vec<u32> {
299        self.printer.update_terminal();
300        let mut line = String::new();
301        match std::io::stdin().read_line(&mut line) {
302            Ok(0) | Err(_) => self.fatal_error("End of file on the terminal!"),
303            Ok(_) => {}
304        }
305        let chars = crate::input::decode_line(line.trim_end_matches(['\n', '\r']).as_bytes());
306        self.printer.term_offset = 0;
307        let old = self.printer.selector;
308        self.printer.selector = match old {
309            Selector::TermAndLog => Selector::LogOnly,
310            Selector::TermOnly => Selector::NoPrint,
311            s => s,
312        };
313        for &c in &chars {
314            self.printer.print_char(c);
315        }
316        self.printer.print_ln();
317        self.printer.selector = old;
318        chars
319    }
320
321    /// Prints a prompt and reads a line (`prompt_input`).
322    pub fn prompt_input(&mut self, prompt: &str) -> Vec<u32> {
323        self.printer.print_str(prompt);
324        self.term_input()
325    }
326
327    /// Asks the user what to do about an error (§84–§93).
328    fn terminal_error_stop(&mut self) {
329        loop {
330            if self.err.interaction != Interaction::ErrorStop {
331                return;
332            }
333            self.clear_for_error_prompt();
334            let line = self.prompt_input("? ");
335            let Some(&c0) = line.first() else {
336                return;
337            };
338            let c = char::from_u32(c0).unwrap_or(' ').to_ascii_uppercase();
339            match c {
340                '0'..='9' if self.err.deletions_allowed => {
341                    let mut n = c as u32 - '0' as u32;
342                    if let Some(&d) = line.get(1)
343                        && (0x30..=0x39).contains(&d)
344                    {
345                        n = n * 10 + (d - 0x30);
346                    }
347                    let (s1, s2, s3, s4) = (
348                        self.cur_tok,
349                        self.cur_cmd,
350                        self.cur_chr,
351                        self.input.align_state,
352                    );
353                    self.input.align_state = 1_000_000;
354                    for _ in 0..n {
355                        self.get_token();
356                    }
357                    self.cur_tok = s1;
358                    self.cur_cmd = s2;
359                    self.cur_chr = s3;
360                    self.input.align_state = s4;
361                    self.help(&[
362                        "I have just deleted some text, as you asked.",
363                        "You can now delete more, or insert, or whatever.",
364                    ]);
365                    self.show_context();
366                    continue;
367                }
368                'E' => {
369                    let file = self.input.stack.iter().rev().find_map(|l| match l {
370                        Level::Text(t) if t.is_input_file => Some((t.name.clone(), t.line)),
371                        _ => None,
372                    });
373                    if let Some((name, line)) = file {
374                        self.printer.print_nl("You want to edit file ");
375                        self.printer.print_bytes(&name);
376                        self.printer.print_str(" at line ");
377                        self.printer.print_int(line.into());
378                        self.err.interaction = Interaction::Scroll;
379                        self.jump_out();
380                    }
381                }
382                'H' => {
383                    if self.err.use_err_help {
384                        self.give_err_help();
385                        self.err.use_err_help = false;
386                    } else {
387                        let lines: Vec<String> = if !self.err.help_owned.is_empty() {
388                            self.err.help_owned.clone()
389                        } else if !self.err.help.is_empty() {
390                            self.err.help.iter().map(|s| s.to_string()).collect()
391                        } else {
392                            vec![
393                                "Sorry, I don't know how to help in this situation.".into(),
394                                "Maybe you should try asking a human?".into(),
395                            ]
396                        };
397                        for l in lines {
398                            self.printer.print_str(&l);
399                            self.printer.print_ln();
400                        }
401                    }
402                    self.help(&[
403                        "Sorry, I already gave what help I could...",
404                        "Maybe you should try asking a human?",
405                        "An error might have occurred before I noticed any problems.",
406                        "``If all else fails, read the instructions.''",
407                    ]);
408                    continue;
409                }
410                'I' => {
411                    // Insert material from the terminal.
412                    let text: Vec<u32> = if line.len() > 1 {
413                        let mut v = vec![' ' as u32];
414                        v.extend_from_slice(&line[1..]);
415                        v
416                    } else {
417                        self.prompt_input("insert>")
418                    };
419                    self.begin_file_reading(crate::input::LineSource::Terminal, Vec::new(), false);
420                    if let Some(Level::Text(t)) = self.input.stack.last_mut() {
421                        t.buf = text;
422                        t.loc = 0;
423                        t.state = crate::input::LexState::MidLine;
424                    }
425                    return;
426                }
427                'Q' | 'R' | 'S' => {
428                    self.err.error_count = 0;
429                    self.printer.print_str("OK, entering ");
430                    match c {
431                        'Q' => {
432                            self.err.interaction = Interaction::Batch;
433                            self.print_esc_str("batchmode");
434                            self.printer.selector = match self.printer.selector {
435                                Selector::TermAndLog => Selector::LogOnly,
436                                _ => Selector::NoPrint,
437                            };
438                        }
439                        'R' => {
440                            self.err.interaction = Interaction::NonStop;
441                            self.print_esc_str("nonstopmode");
442                        }
443                        _ => {
444                            self.err.interaction = Interaction::Scroll;
445                            self.print_esc_str("scrollmode");
446                        }
447                    }
448                    self.printer.print_str("...");
449                    self.printer.print_ln();
450                    self.printer.update_terminal();
451                    return;
452                }
453                'X' => {
454                    self.err.interaction = Interaction::Scroll;
455                    self.jump_out();
456                }
457                _ => {}
458            }
459            // Print the menu of available options.
460            self.printer
461                .print_str("Type <return> to proceed, S to scroll future error messages,");
462            self.printer
463                .print_nl("R to run without stopping, Q to run quietly,");
464            self.printer.print_nl("I to insert something, ");
465            if self
466                .input
467                .stack
468                .iter()
469                .any(|l| matches!(l, Level::Text(t) if t.is_input_file))
470            {
471                self.printer.print_str("E to edit your file,");
472            }
473            if self.err.deletions_allowed {
474                self.printer
475                    .print_nl("1 or ... or 9 to ignore the next 1 to 9 tokens of input,");
476            }
477            self.printer.print_nl("H for help, X to quit.");
478        }
479    }
480
481    /// Pops finished terminal levels before prompting
482    /// (`clear_for_error_prompt`, §330).
483    fn clear_for_error_prompt(&mut self) {
484        while self.input.stack.len() > 1 {
485            match self.input.stack.last() {
486                Some(Level::Text(t))
487                    if matches!(t.source, LineSource::Terminal) && t.loc >= t.buf.len() =>
488                {
489                    self.end_file_reading();
490                }
491                _ => break,
492            }
493        }
494        self.printer.print_ln();
495    }
496
497    /// Prints `\errhelp` (`give_err_help`).
498    pub fn give_err_help(&mut self) {
499        let list = self
500            .eqtb
501            .toks_param(crate::primitives_gen::assign_toks::ERRHELP)
502            .clone();
503        self.token_show_list(&list, 0, list.len(), 10_000_000);
504    }
505
506    /// Reports a fatal error and ends the run (`fatal_error`).
507    pub fn fatal_error(&mut self, s: &str) -> ! {
508        self.normalize_selector();
509        self.print_err("Emergency stop");
510        self.help_owned(vec![s.to_owned()]);
511        self.succumb();
512    }
513
514    /// A LuaTeX fatal error of category `category` (as LuaTeX prints them:
515    /// `! error:  (category): message`, then `!  ==> Fatal error
516    /// occurred, …`); ends the run without the transcript line.
517    pub fn luatex_fatal(&mut self, category: &str, msg: &str) -> ! {
518        self.normalize_selector();
519        self.printer.print_nl("! error:  (");
520        self.printer.print_str(category);
521        self.printer.print_str("): ");
522        self.printer.print_str(msg);
523        let pdf = self.eqtb.int(crate::primitives_gen::assign_int::OUTPUTMODE) > 0;
524        self.printer.print_nl(if pdf {
525            "!  ==> Fatal error occurred, bad output PDF file produced!"
526        } else {
527            "!  ==> Fatal error occurred, bad output DVI file produced!"
528        });
529        self.printer.print_ln();
530        self.err.history = History::FatalErrorStop;
531        for n in 0..128u32 {
532            if self.printer.write_open(n) {
533                self.printer.close_write(n);
534            }
535        }
536        if self.total_pages == 0 {
537            self.printer.print_str("No pages of output.");
538            self.printer.print_ln();
539        }
540        if self.printer.log_opened() {
541            self.printer.close_log();
542        }
543        self.printer.selector = Selector::TermOnly;
544        self.printer.flush();
545        std::panic::resume_unwind(Box::new(JumpOut));
546    }
547
548    /// Reports that a capacity was exceeded and ends the run (`overflow`).
549    pub fn overflow(&mut self, s: &str, n: i64) -> ! {
550        self.normalize_selector();
551        self.print_err("TeX capacity exceeded, sorry [");
552        self.printer.print_str(s);
553        self.printer.print_char('=' as u32);
554        self.printer.print_int(n);
555        self.printer.print_char(']' as u32);
556        self.help(&[
557            "If you really absolutely need more capacity,",
558            "you can ask a wizard to enlarge me.",
559        ]);
560        self.succumb();
561    }
562
563    /// Reports an internal inconsistency (`confusion`).
564    pub fn confusion(&mut self, s: &str) -> ! {
565        self.normalize_selector();
566        if self.err.history < History::ErrorMessageIssued {
567            self.print_err("This can't happen (");
568            self.printer.print_str(s);
569            self.printer.print_char(')' as u32);
570            self.help(&["I'm broken. Please show this to someone who can fix can fix"]);
571        } else {
572            self.print_err("I can't go on meeting you like this");
573            self.help(&[
574                "One of your faux pas seems to have wounded me deeply...",
575                "in fact, I'm barely conscious. Please fix it and try again.",
576            ]);
577        }
578        self.succumb();
579    }
580
581    fn succumb(&mut self) -> ! {
582        if self.err.interaction == Interaction::ErrorStop {
583            self.err.interaction = Interaction::Scroll;
584        }
585        if self.printer.log_opened() {
586            self.error();
587        }
588        self.err.history = History::FatalErrorStop;
589        self.jump_out();
590    }
591
592    /// Ends the run (`jump_out`): closes files and unwinds to the driver.
593    pub fn jump_out(&mut self) -> ! {
594        self.close_files_and_terminate();
595        std::panic::resume_unwind(Box::new(JumpOut));
596    }
597
598    /// Makes sure output goes somewhere sensible (`normalize_selector`).
599    pub fn normalize_selector(&mut self) {
600        self.printer.selector = if self.printer.log_opened() {
601            Selector::TermAndLog
602        } else {
603            Selector::TermOnly
604        };
605        if self.job_name.is_none() {
606            self.open_log_file();
607        }
608        if self.err.interaction == Interaction::Batch {
609            self.printer.selector = match self.printer.selector {
610                Selector::TermAndLog => Selector::LogOnly,
611                _ => Selector::NoPrint,
612            };
613        }
614    }
615
616    /// Hook for user interrupts; retex does not support them.
617    pub fn check_interrupt(&mut self) {}
618
619    /// `back_error`: backs up the current token and reports an error.
620    pub fn back_error(&mut self) {
621        self.err.interrupt = false;
622        self.back_input();
623        self.err.interrupt = true;
624        self.error();
625    }
626
627    /// `ins_error`: like `back_error` but the token is marked inserted.
628    pub fn ins_error(&mut self) {
629        self.err.interrupt = false;
630        self.back_input_inserted();
631        self.err.interrupt = true;
632        self.error();
633    }
634
635    /// A LuaTeX warning (`warning  (category): message` on a line of its
636    /// own); it counts as a warning for the final "see the transcript"
637    /// note.
638    pub fn luatex_warning(&mut self, category: &str, msg: &str) {
639        self.printer.luatex_warning(category, msg);
640        if self.err.history == History::Spotless {
641            self.err.history = History::WarningIssued;
642        }
643    }
644
645    /// Starts diagnostic output (`begin_diagnostic`): goes to the log only
646    /// unless `\tracingonline > 0`.
647    pub fn begin_diagnostic(&mut self) {
648        self.err.old_setting = Some(self.printer.selector);
649        let online = self
650            .eqtb
651            .int(crate::primitives_gen::assign_int::TRACINGONLINE);
652        if online <= 0 && self.printer.selector == Selector::TermAndLog {
653            self.printer.selector = Selector::LogOnly;
654            if self.err.history == History::Spotless {
655                self.err.history = History::WarningIssued;
656            }
657        }
658    }
659
660    /// Ends diagnostic output (`end_diagnostic`).
661    pub fn end_diagnostic(&mut self, blank_line: bool) {
662        self.printer.print_nl("");
663        if blank_line {
664            self.printer.print_ln();
665        }
666        if let Some(s) = self.err.old_setting.take() {
667            self.printer.selector = s;
668        }
669    }
670
671    /// Shows where the scanner is (`show_context`, §311).
672    pub fn show_context(&mut self) {
673        let error_context_lines = self
674            .eqtb
675            .int(crate::primitives_gen::assign_int::ERRORCONTEXTLINES);
676        let input_ptr = self.input.stack.len().saturating_sub(1);
677        let mut base_ptr = input_ptr;
678        let mut nn: i32 = -1;
679        loop {
680            let (is_text, bottom) = match &self.input.stack.get(base_ptr) {
681                Some(Level::Text(t)) => (
682                    true,
683                    t.is_input_file || matches!(t.source, LineSource::File { .. }) || base_ptr == 0,
684                ),
685                Some(Level::Tokens(_)) => (false, false),
686                None => (true, true),
687            };
688            let bottom_line = is_text && bottom;
689            if base_ptr == input_ptr || bottom_line || nn < error_context_lines {
690                // Display the current context, omitting backed-up token
691                // lists that have already been read.
692                let show = match self.input.stack.get(base_ptr) {
693                    Some(Level::Tokens(t)) => {
694                        base_ptr == input_ptr
695                            || !matches!(t.kind, TokKind::BackedUp | TokKind::BackedUpChar)
696                            || t.loc < t.list.len()
697                    }
698                    _ => true,
699                };
700                if show {
701                    self.printer.term_just_wrapped = false;
702                    self.display_context(base_ptr);
703                    nn += 1;
704                    // LuaTeX's terminal shows an empty line after a context
705                    // line that filled the terminal line exactly, when more
706                    // context follows (the log does not).
707                    if self.printer.term_just_wrapped
708                        && !(bottom_line || base_ptr == 0)
709                        && matches!(
710                            self.printer.selector,
711                            crate::print::Selector::TermAndLog | crate::print::Selector::TermOnly
712                        )
713                    {
714                        self.printer.term_raw(b"\n");
715                    }
716                }
717            } else if nn == error_context_lines {
718                self.printer.print_nl("...");
719                nn += 1;
720            }
721            if bottom_line || base_ptr == 0 {
722                break;
723            }
724            base_ptr -= 1;
725        }
726    }
727
728    fn display_context(&mut self, base_ptr: usize) {
729        let old_setting = self.printer.selector;
730        self.printer.tally = 0;
731        let level = self.input.stack[base_ptr].clone();
732        let l: usize;
733        match &level {
734            Level::Text(t) => {
735                match &t.source {
736                    LineSource::Terminal => {
737                        if base_ptr == 0 {
738                            self.printer.print_nl("<*>");
739                        } else {
740                            self.printer.print_nl("<insert> ");
741                        }
742                    }
743                    LineSource::Read(n) => {
744                        self.printer.print_nl("<read ");
745                        if *n == 16 {
746                            self.printer.print_char('*' as u32);
747                        } else {
748                            self.printer.print_int(i64::from(*n));
749                        }
750                        self.printer.print_char('>' as u32);
751                    }
752                    _ => {
753                        self.printer.print_nl("l.");
754                        self.printer.print_int(t.line.into());
755                    }
756                }
757                self.printer.print_char(' ' as u32);
758                // Pseudoprint the line.
759                l = self.begin_pseudoprint();
760                let end_line_char = self
761                    .eqtb
762                    .int(crate::primitives_gen::assign_int::ENDLINECHAR);
763                let mut j = t.buf.len();
764                if j > 0 && t.buf[j - 1] as i32 == end_line_char {
765                    j -= 1;
766                }
767                for i in 0..j {
768                    if i == t.loc {
769                        self.set_trick_count();
770                    }
771                    self.printer.print_char(t.buf[i]);
772                }
773            }
774            Level::Tokens(t) => {
775                match t.kind {
776                    TokKind::Parameter => self.printer.print_nl("<argument> "),
777                    TokKind::UTemplate | TokKind::VTemplate => self.printer.print_nl("<template> "),
778                    TokKind::BackedUp | TokKind::BackedUpChar => {
779                        if t.loc >= t.list.len() {
780                            self.printer.print_nl("<recently read> ")
781                        } else {
782                            self.printer.print_nl("<to be read again> ")
783                        }
784                    }
785                    TokKind::Inserted => self.printer.print_nl("<inserted text> "),
786                    TokKind::Macro => {
787                        self.printer.print_ln();
788                        self.print_cs(t.name);
789                    }
790                    TokKind::OutputText => self.printer.print_nl("<output> "),
791                    TokKind::EveryParText => self.printer.print_nl("<everypar> "),
792                    TokKind::EveryMathText => self.printer.print_nl("<everymath> "),
793                    TokKind::EveryDisplayText => self.printer.print_nl("<everydisplay> "),
794                    TokKind::EveryHboxText => self.printer.print_nl("<everyhbox> "),
795                    TokKind::EveryVboxText => self.printer.print_nl("<everyvbox> "),
796                    TokKind::EveryJobText => self.printer.print_nl("<everyjob> "),
797                    TokKind::EveryCrText => self.printer.print_nl("<everycr> "),
798                    TokKind::MarkText => self.printer.print_nl("<mark> "),
799                    TokKind::EveryEofText => self.printer.print_nl("<everyeof> "),
800                    TokKind::WriteText => self.printer.print_nl("<write> "),
801                    TokKind::LocalText => self.printer.print_nl("<local> "),
802                }
803                l = self.begin_pseudoprint();
804                // For macros the list starts with the parameter text; TeX
805                // shows the whole definition (parameters and body).
806                self.show_token_list_ctx(&t.list, t.loc, 100_000);
807            }
808        }
809        self.printer.selector = old_setting;
810        self.print_two_lines(l);
811    }
812
813    fn begin_pseudoprint(&mut self) -> usize {
814        let l = self.printer.tally;
815        self.printer.tally = 0;
816        self.printer.selector = Selector::Pseudo;
817        self.printer.trick_count = 1_000_000;
818        self.printer.trick_buf.clear();
819        self.printer.trick_buf.resize(self.printer.error_line, b' ');
820        l
821    }
822
823    /// TeX's `set_trick_count`.
824    pub fn set_trick_count(&mut self) {
825        let p = &mut self.printer;
826        p.first_count = p.tally;
827        p.trick_count = p.tally + 1 + p.error_line - p.half_error_line;
828        if p.trick_count < p.error_line {
829            p.trick_count = p.error_line;
830        }
831    }
832
833    fn print_two_lines(&mut self, l: usize) {
834        if self.printer.trick_count == 1_000_000 {
835            self.set_trick_count();
836        }
837        let p = &self.printer;
838        let (tally, first_count, trick_count, half, error_line) = (
839            p.tally,
840            p.first_count,
841            p.trick_count,
842            p.half_error_line,
843            p.error_line,
844        );
845        let m = if tally < trick_count {
846            tally - first_count
847        } else {
848            trick_count - first_count
849        };
850        let (start, n) = if l + first_count <= half {
851            (0, l + first_count)
852        } else {
853            self.printer.print_plain("...");
854            (l + first_count - half + 3, half)
855        };
856        let buf = self.printer.trick_buf.clone();
857        let mut bytes = Vec::new();
858        for q in start..first_count {
859            bytes.push(buf[q % error_line]);
860        }
861        self.print_raw_bytes(&bytes);
862        self.printer.print_ln();
863        for _ in 0..n {
864            self.printer.print_char(' ' as u32);
865        }
866        let p_end = if m + n <= error_line {
867            first_count + m
868        } else {
869            first_count + (error_line - n - 3)
870        };
871        bytes.clear();
872        for q in first_count..p_end {
873            bytes.push(buf[q % error_line]);
874        }
875        self.print_raw_bytes(&bytes);
876        if m + n > error_line {
877            self.printer.print_plain("...");
878        }
879    }
880
881    /// Prints already-converted bytes from the pseudo buffer, decoding
882    /// complete UTF-8 sequences so that line lengths stay correct.
883    fn print_raw_bytes(&mut self, bytes: &[u8]) {
884        let mut i = 0;
885        while i < bytes.len() {
886            let (c, n) = crate::utf8x::decode(bytes, i);
887            if c < 32 && c != 9 && c != 10 && c != 13 {
888                // A raw control byte inside the buffer: print as is.
889                self.printer.print_char(c);
890            } else {
891                self.printer.print_char(c);
892            }
893            i += n;
894        }
895    }
896}