1use crate::Engine;
10use crate::input::{Level, LineSource, TokKind};
11use crate::print::Selector;
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
15pub enum Interaction {
16 Batch = 0,
18 NonStop = 1,
20 Scroll = 2,
22 ErrorStop = 3,
24}
25
26impl Interaction {
27 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#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
40pub enum History {
41 Spotless,
43 WarningIssued,
45 ErrorMessageIssued,
47 FatalErrorStop,
49}
50
51#[derive(Debug, Clone, Copy)]
53pub struct JumpOut;
54
55#[derive(Debug)]
57pub struct ErrorState {
58 pub interaction: Interaction,
60 pub history: History,
62 pub error_count: i32,
64 pub help: Vec<&'static str>,
66 pub help_owned: Vec<String>,
68 pub use_err_help: bool,
70 pub deletions_allowed: bool,
72 pub set_box_allowed: bool,
74 pub no_new_control_sequence: bool,
76 pub old_setting: Option<Selector>,
78 pub file_line_error: bool,
80 pub halt_on_error: bool,
82 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 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 pub fn diag_capture_start(&mut self) {
121 if self.diagnostics.is_some() {
122 self.printer.capture = Some(Vec::new());
123 }
124 }
125
126 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 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 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 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 let Some(option) = bad_option(message) {
212 let similar = self.similar_options(option.as_bytes());
213 if !similar.is_empty() {
214 facts.push(("similar_options".to_owned(), similar.join(" ")));
215 }
216 }
217 if message.starts_with("Missing $ inserted") {
218 let tok = if self.cur_cmd == crate::cmd::Cmd::ParEnd {
219 "\\par".to_owned()
221 } else if self.cur_cs != crate::hash::NO_CS {
222 let cs = self.cur_cs;
223 String::from_utf8_lossy(&self.print_to_string(|e| e.print_cs(cs)))
224 .trim_end()
225 .to_owned()
226 } else {
227 char::from_u32(self.cur_chr)
228 .map(String::from)
229 .unwrap_or_default()
230 };
231 facts.push(("token".to_owned(), tok));
232 if self.mode.abs_kind() == crate::maincontrol::ModeKind::Math {
233 let line = self.cur_list().mode_line.unsigned_abs();
234 facts.push(("math_line".to_owned(), line.to_string()));
235 }
236 }
237 facts
238 }
239
240 fn similar_options(&self, option: &[u8]) -> Vec<String> {
245 let max = if option.len() < 5 { 1 } else { 2 };
246 let mut found: Vec<(usize, Vec<u8>)> = self
247 .hash
248 .iter_named()
249 .filter(|(_, id)| self.eqtb.meaning(*id).cmd != crate::cmd::Cmd::UndefinedCs)
250 .filter_map(|(n, _)| {
251 if let Some(o) = n.strip_prefix(b"ds@") {
252 Some(o)
253 } else if let Some(r) = n.strip_prefix(b"KV@") {
254 let k = &r[r.iter().position(|&b| b == b'@')? + 1..];
255 (!k.ends_with(b"@default")).then_some(k)
256 } else {
257 let r = n.strip_prefix(b"key code > ")?;
258 Some(&r[r.iter().rposition(|&b| b == b'/')? + 1..])
259 }
260 })
261 .filter(|o| !o.is_empty() && o.iter().all(|b| !b.is_ascii_whitespace()))
262 .filter_map(|o| {
263 crate::diag::edit_distance_within(option, o, max).map(|d| (d, o.to_vec()))
264 })
265 .filter(|(d, _)| *d > 0)
266 .collect();
267 found.sort();
268 found.dedup();
269 found
270 .into_iter()
271 .take(3)
272 .map(|(_, n)| String::from_utf8_lossy(&n).into_owned())
273 .collect()
274 }
275
276 fn similar_defined_names(&self, name: &[u8]) -> Vec<String> {
280 let internal = |n: &[u8]| n.iter().any(|&b| matches!(b, b'@' | b'_' | b':'));
281 let max = if name.len() < 4 { 1 } else { 2 };
282 let mut found: Vec<(usize, Vec<u8>)> = self
283 .hash
284 .iter_named()
285 .filter(|(n, _)| {
286 n.len() > 1
287 && n.iter()
288 .all(|b| !b.is_ascii_whitespace() && !b.is_ascii_control())
289 })
290 .filter(|(n, _)| !internal(n) || internal(name))
291 .filter(|(_, id)| self.eqtb.meaning(*id).cmd != crate::cmd::Cmd::UndefinedCs)
292 .filter_map(|(n, _)| {
293 crate::diag::edit_distance_within(name, n, max).map(|d| (d, n.to_vec()))
294 })
295 .filter(|(d, _)| *d > 0)
296 .collect();
297 found.sort();
298 found.dedup();
299 found
300 .into_iter()
301 .take(3)
302 .map(|(_, n)| format!("\\{}", String::from_utf8_lossy(&n)))
303 .collect()
304 }
305
306 pub fn print_err_bytes(&mut self, msg: &[u8]) {
308 self.print_err("");
309 self.printer.print_bytes(msg);
310 }
311
312 fn print_file_line(&mut self) -> bool {
315 let found = self.input.stack.iter().rev().find_map(|l| match l {
316 Level::Text(t) if t.is_input_file => Some((t.name.clone(), t.line)),
317 _ => None,
318 });
319 match found {
320 Some((name, line)) => {
321 self.printer.print_bytes(&name);
322 self.printer.print_char(':' as u32);
323 self.printer.print_int(line.into());
324 self.printer.print_str(": ");
325 true
326 }
327 None => false,
328 }
329 }
330
331 pub fn help(&mut self, lines: &[&'static str]) {
333 self.err.help.clear();
334 self.err.help.extend_from_slice(lines);
335 self.err.help_owned.clear();
336 }
337
338 pub fn help_owned(&mut self, lines: Vec<String>) {
340 self.err.help.clear();
341 self.err.help_owned = lines;
342 }
343
344 pub fn error(&mut self) {
347 self.record_error();
348 if self.err.history < History::ErrorMessageIssued {
349 self.err.history = History::ErrorMessageIssued;
350 }
351 self.printer.print_char('.' as u32);
352 self.show_context();
353 if self.err.halt_on_error {
354 self.halt_on_error_stop();
355 }
356 if self.err.interaction == Interaction::ErrorStop {
357 self.terminal_error_stop();
360 }
361 self.err.error_count += 1;
362 if self.err.error_count == 100 {
363 self.printer
364 .print_nl("(That makes 100 errors; please try again.)");
365 self.err.history = History::FatalErrorStop;
366 self.jump_out();
367 }
368 let sel = self.printer.selector;
370 if self.err.interaction > Interaction::Batch {
371 self.printer.selector = match sel {
372 Selector::TermAndLog => Selector::LogOnly,
373 Selector::TermOnly => Selector::NoPrint,
374 s => s,
375 };
376 }
377 if self.err.use_err_help {
378 self.printer.print_ln();
379 self.give_err_help();
380 } else {
381 let lines: Vec<String> = if self.err.help_owned.is_empty() {
382 self.err.help.iter().map(|s| s.to_string()).collect()
383 } else {
384 self.err.help_owned.clone()
385 };
386 let empty = lines.is_empty();
387 for l in lines {
388 self.printer.print_nl(&l);
389 }
390 if empty {
393 self.printer.begin_line();
394 } else {
395 self.printer.print_ln();
396 }
397 }
398 if self.err.use_err_help {
399 self.printer.print_ln();
400 }
401 self.err.help.clear();
402 self.err.help_owned.clear();
403 if self.err.interaction > Interaction::Batch {
404 self.printer.selector = sel;
405 }
406 self.printer.print_ln();
407 }
408
409 pub fn term_input(&mut self) -> Vec<u32> {
412 self.prompted = true;
413 if self.printer.term_quiet {
414 let prompt = std::mem::take(&mut self.printer.quiet_line);
415 self.show_before_prompt(&String::from_utf8_lossy(&prompt));
416 }
417 self.printer.update_terminal();
418 let mut line = String::new();
419 match std::io::stdin().read_line(&mut line) {
420 Ok(0) | Err(_) => self.fatal_error("End of file on the terminal!"),
421 Ok(_) => {}
422 }
423 let chars = crate::input::decode_line(line.trim_end_matches(['\n', '\r']).as_bytes());
424 self.printer.term_offset = 0;
425 let old = self.printer.selector;
426 self.printer.selector = match old {
427 Selector::TermAndLog => Selector::LogOnly,
428 Selector::TermOnly => Selector::NoPrint,
429 s => s,
430 };
431 for &c in &chars {
432 self.printer.print_char(c);
433 }
434 self.printer.print_ln();
435 self.printer.selector = old;
436 chars
437 }
438
439 pub fn prompt_input(&mut self, prompt: &str) -> Vec<u32> {
441 self.printer.print_str(prompt);
442 self.term_input()
443 }
444
445 fn show_before_prompt(&mut self, prompt: &str) -> bool {
448 let Some(mut hook) = self.prompt_hook.take() else {
449 return true;
450 };
451 let list = self.diagnostics.as_deref().unwrap_or(&[]);
452 let from = self.diagnostics_shown.min(list.len());
453 let shown = hook(&list[from..], prompt);
454 self.diagnostics_shown = list.len();
455 self.prompt_hook = Some(hook);
456 shown
457 }
458
459 fn terminal_error_stop(&mut self) {
463 let quiet = self.printer.term_quiet;
464 if quiet && self.err.interaction == Interaction::ErrorStop {
465 if !self.show_before_prompt("") {
466 return;
467 }
468 self.printer.term_quiet = false;
469 self.printer.quiet_line.clear();
470 }
471 self.terminal_error_dialog();
472 self.printer.term_quiet = quiet;
473 }
474
475 fn terminal_error_dialog(&mut self) {
476 loop {
477 if self.err.interaction != Interaction::ErrorStop {
478 return;
479 }
480 self.clear_for_error_prompt();
481 let line = self.prompt_input("? ");
482 let Some(&c0) = line.first() else {
483 return;
484 };
485 let c = char::from_u32(c0).unwrap_or(' ').to_ascii_uppercase();
486 match c {
487 '0'..='9' if self.err.deletions_allowed => {
488 let mut n = c as u32 - '0' as u32;
489 if let Some(&d) = line.get(1)
490 && (0x30..=0x39).contains(&d)
491 {
492 n = n * 10 + (d - 0x30);
493 }
494 let (s1, s2, s3, s4) = (
495 self.cur_tok,
496 self.cur_cmd,
497 self.cur_chr,
498 self.input.align_state,
499 );
500 self.input.align_state = 1_000_000;
501 for _ in 0..n {
502 self.get_token();
503 }
504 self.cur_tok = s1;
505 self.cur_cmd = s2;
506 self.cur_chr = s3;
507 self.input.align_state = s4;
508 self.help(&[
509 "I have just deleted some text, as you asked.",
510 "You can now delete more, or insert, or whatever.",
511 ]);
512 self.show_context();
513 continue;
514 }
515 'E' => {
516 let file = self.input.stack.iter().rev().find_map(|l| match l {
517 Level::Text(t) if t.is_input_file => Some((t.name.clone(), t.line)),
518 _ => None,
519 });
520 if let Some((name, line)) = file {
521 self.printer.print_nl("You want to edit file ");
522 self.printer.print_bytes(&name);
523 self.printer.print_str(" at line ");
524 self.printer.print_int(line.into());
525 self.edit_request = Some((name, line));
526 self.err.interaction = Interaction::Scroll;
527 self.jump_out();
528 }
529 }
530 'H' => {
531 if self.err.use_err_help {
532 self.give_err_help();
533 self.err.use_err_help = false;
534 } else {
535 let lines: Vec<String> = if !self.err.help_owned.is_empty() {
536 self.err.help_owned.clone()
537 } else if !self.err.help.is_empty() {
538 self.err.help.iter().map(|s| s.to_string()).collect()
539 } else {
540 vec![
541 "Sorry, I don't know how to help in this situation.".into(),
542 "Maybe you should try asking a human?".into(),
543 ]
544 };
545 for l in lines {
546 self.printer.print_str(&l);
547 self.printer.print_ln();
548 }
549 }
550 self.help(&[
551 "Sorry, I already gave what help I could...",
552 "Maybe you should try asking a human?",
553 "An error might have occurred before I noticed any problems.",
554 "``If all else fails, read the instructions.''",
555 ]);
556 continue;
557 }
558 'I' => {
559 let text: Vec<u32> = if line.len() > 1 {
561 let mut v = vec![' ' as u32];
562 v.extend_from_slice(&line[1..]);
563 v
564 } else {
565 self.prompt_input("insert>")
566 };
567 self.begin_file_reading(crate::input::LineSource::Terminal, Vec::new(), false);
568 if let Some(Level::Text(t)) = self.input.stack.last_mut() {
569 t.buf = text;
570 t.loc = 0;
571 t.state = crate::input::LexState::MidLine;
572 }
573 return;
574 }
575 'Q' | 'R' | 'S' => {
576 self.err.error_count = 0;
577 self.printer.print_str("OK, entering ");
578 match c {
579 'Q' => {
580 self.err.interaction = Interaction::Batch;
581 self.print_esc_str("batchmode");
582 self.printer.selector = match self.printer.selector {
583 Selector::TermAndLog => Selector::LogOnly,
584 _ => Selector::NoPrint,
585 };
586 }
587 'R' => {
588 self.err.interaction = Interaction::NonStop;
589 self.print_esc_str("nonstopmode");
590 }
591 _ => {
592 self.err.interaction = Interaction::Scroll;
593 self.print_esc_str("scrollmode");
594 }
595 }
596 self.printer.print_str("...");
597 self.printer.print_ln();
598 self.printer.update_terminal();
599 return;
600 }
601 'X' => {
602 self.err.interaction = Interaction::Scroll;
603 self.jump_out();
604 }
605 _ => {}
606 }
607 self.printer
609 .print_str("Type <return> to proceed, S to scroll future error messages,");
610 self.printer
611 .print_nl("R to run without stopping, Q to run quietly,");
612 self.printer.print_nl("I to insert something, ");
613 if self
614 .input
615 .stack
616 .iter()
617 .any(|l| matches!(l, Level::Text(t) if t.is_input_file))
618 {
619 self.printer.print_str("E to edit your file,");
620 }
621 if self.err.deletions_allowed {
622 self.printer
623 .print_nl("1 or ... or 9 to ignore the next 1 to 9 tokens of input,");
624 }
625 self.printer.print_nl("H for help, X to quit.");
626 }
627 }
628
629 fn clear_for_error_prompt(&mut self) {
632 while self.input.stack.len() > 1 {
633 match self.input.stack.last() {
634 Some(Level::Text(t))
635 if matches!(t.source, LineSource::Terminal) && t.loc >= t.buf.len() =>
636 {
637 self.end_file_reading();
638 }
639 _ => break,
640 }
641 }
642 self.printer.print_ln();
643 }
644
645 pub fn give_err_help(&mut self) {
647 let list = self
648 .eqtb
649 .toks_param(crate::primitives_gen::assign_toks::ERRHELP)
650 .clone();
651 self.token_show_list(&list, 0, list.len(), 10_000_000);
652 }
653
654 pub fn fatal_error(&mut self, s: &str) -> ! {
656 self.normalize_selector();
657 self.print_err("Emergency stop");
658 self.help_owned(vec![s.to_owned()]);
659 self.succumb();
660 }
661
662 pub fn luatex_fatal(&mut self, category: &str, msg: &str) -> ! {
666 self.normalize_selector();
667 self.printer.print_nl("! error: (");
668 self.printer.print_str(category);
669 self.printer.print_str("): ");
670 self.printer.print_str(msg);
671 let pdf = self.eqtb.int(crate::primitives_gen::assign_int::OUTPUTMODE) > 0;
672 self.printer.print_nl(if pdf {
673 "! ==> Fatal error occurred, bad output PDF file produced!"
674 } else {
675 "! ==> Fatal error occurred, bad output DVI file produced!"
676 });
677 self.printer.print_ln();
678 self.err.history = History::FatalErrorStop;
679 for n in 0..128u32 {
680 if self.printer.write_open(n) {
681 self.printer.close_write(n);
682 }
683 }
684 if self.total_pages == 0 {
685 self.printer.print_str("No pages of output.");
686 self.printer.print_ln();
687 } else if !pdf {
688 self.finish_output();
690 }
691 if self.printer.log_opened() {
692 self.printer.close_log();
693 }
694 self.printer.selector = Selector::TermOnly;
695 self.printer.flush();
696 std::panic::resume_unwind(Box::new(JumpOut));
697 }
698
699 fn halt_on_error_stop(&mut self) -> ! {
703 let pdf = self.eqtb.int(crate::primitives_gen::assign_int::OUTPUTMODE) > 0;
704 self.printer.print_nl(if pdf {
705 "! ==> Fatal error occurred, bad output PDF file produced!"
706 } else {
707 "! ==> Fatal error occurred, bad output DVI file produced!"
708 });
709 self.printer.print_ln();
710 self.err.history = History::FatalErrorStop;
711 for n in 0..128u32 {
712 if self.printer.write_open(n) {
713 self.printer.close_write(n);
714 }
715 }
716 if self.total_pages == 0 {
717 self.printer.print_str("No pages of output.");
718 self.printer.print_ln();
719 }
720 if self.printer.log_opened() {
722 let sel = self.printer.selector;
723 self.printer.selector = Selector::LogOnly;
724 self.printer.print_ln();
725 self.printer.selector = sel;
726 }
727 self.finish_log();
728 std::panic::resume_unwind(Box::new(JumpOut));
729 }
730
731 pub fn overflow(&mut self, s: &str, n: i64) -> ! {
733 self.normalize_selector();
734 self.print_err("TeX capacity exceeded, sorry [");
735 self.printer.print_str(s);
736 self.printer.print_char('=' as u32);
737 self.printer.print_int(n);
738 self.printer.print_char(']' as u32);
739 self.help(&[
740 "If you really absolutely need more capacity,",
741 "you can ask a wizard to enlarge me.",
742 ]);
743 self.succumb();
744 }
745
746 pub fn confusion(&mut self, s: &str) -> ! {
748 self.normalize_selector();
749 if self.err.history < History::ErrorMessageIssued {
750 self.print_err("This can't happen (");
751 self.printer.print_str(s);
752 self.printer.print_char(')' as u32);
753 self.help(&["I'm broken. Please show this to someone who can fix"]);
754 } else {
755 self.print_err("I can't go on meeting you like this");
756 self.help(&[
757 "One of your faux pas seems to have wounded me deeply...",
758 "in fact, I'm barely conscious. Please fix it and try again.",
759 ]);
760 }
761 self.succumb();
762 }
763
764 pub(crate) fn succumb(&mut self) -> ! {
765 if self.err.interaction == Interaction::ErrorStop {
766 self.err.interaction = Interaction::Scroll;
767 }
768 if self.printer.log_opened() {
769 self.error();
770 }
771 self.err.history = History::FatalErrorStop;
772 self.jump_out();
773 }
774
775 pub fn jump_out(&mut self) -> ! {
777 if self.prompt_hook.is_some() {
778 self.printer.term_quiet = true;
780 }
781 self.close_files_and_terminate();
782 std::panic::resume_unwind(Box::new(JumpOut));
783 }
784
785 pub fn normalize_selector(&mut self) {
787 self.printer.selector = if self.printer.log_opened() {
788 Selector::TermAndLog
789 } else {
790 Selector::TermOnly
791 };
792 if self.job_name.is_none() {
793 self.open_log_file();
794 }
795 if self.err.interaction == Interaction::Batch {
796 self.printer.selector = match self.printer.selector {
797 Selector::TermAndLog => Selector::LogOnly,
798 _ => Selector::NoPrint,
799 };
800 }
801 }
802
803 pub fn check_interrupt(&mut self) {}
805
806 pub fn back_error(&mut self) {
808 self.err.interrupt = false;
809 self.back_input();
810 self.err.interrupt = true;
811 self.error();
812 }
813
814 pub fn ins_error(&mut self) {
816 self.err.interrupt = false;
817 self.back_input_inserted();
818 self.err.interrupt = true;
819 self.error();
820 }
821
822 pub fn luatex_warning(&mut self, category: &str, msg: &str) {
826 self.printer.luatex_warning(category, msg);
827 if self.err.history == History::Spotless {
828 self.err.history = History::WarningIssued;
829 }
830 }
831
832 pub fn begin_diagnostic(&mut self) {
835 self.err.old_setting = Some(self.printer.selector);
836 let online = self
837 .eqtb
838 .int(crate::primitives_gen::assign_int::TRACINGONLINE);
839 if online <= 0 && self.printer.selector == Selector::TermAndLog {
840 self.printer.selector = Selector::LogOnly;
841 if self.err.history == History::Spotless {
842 self.err.history = History::WarningIssued;
843 }
844 }
845 }
846
847 pub fn end_diagnostic(&mut self, blank_line: bool) {
849 self.printer.print_nl("");
850 if blank_line {
851 self.printer.print_ln();
852 }
853 if let Some(s) = self.err.old_setting.take() {
854 self.printer.selector = s;
855 }
856 }
857
858 pub fn show_context(&mut self) {
860 let error_context_lines = self
861 .eqtb
862 .int(crate::primitives_gen::assign_int::ERRORCONTEXTLINES);
863 let input_ptr = self.input.stack.len().saturating_sub(1);
864 let mut base_ptr = input_ptr;
865 let mut nn: i32 = -1;
866 loop {
867 let (is_text, bottom) = match &self.input.stack.get(base_ptr) {
868 Some(Level::Text(t)) => (
869 true,
870 t.is_input_file || matches!(t.source, LineSource::File { .. }) || base_ptr == 0,
871 ),
872 Some(Level::Tokens(_)) => (false, false),
873 None => (true, true),
874 };
875 let bottom_line = is_text && bottom;
876 if base_ptr == input_ptr || bottom_line || nn < error_context_lines {
877 let show = match self.input.stack.get(base_ptr) {
880 Some(Level::Tokens(t)) => {
881 base_ptr == input_ptr
882 || !matches!(t.kind, TokKind::BackedUp | TokKind::BackedUpChar)
883 || t.loc < t.list.len()
884 }
885 _ => true,
886 };
887 if show {
888 self.printer.term_just_wrapped = false;
889 self.display_context(base_ptr);
890 nn += 1;
891 if self.printer.term_just_wrapped
895 && !(bottom_line || base_ptr == 0)
896 && matches!(
897 self.printer.selector,
898 crate::print::Selector::TermAndLog | crate::print::Selector::TermOnly
899 )
900 {
901 self.printer.term_raw(b"\n");
902 }
903 }
904 } else if nn == error_context_lines {
905 self.printer.print_nl("...");
906 nn += 1;
907 }
908 if bottom_line || base_ptr == 0 {
909 break;
910 }
911 base_ptr -= 1;
912 }
913 }
914
915 fn display_context(&mut self, base_ptr: usize) {
916 let old_setting = self.printer.selector;
917 self.printer.tally = 0;
918 let level = self.input.stack[base_ptr].clone();
919 let l: usize;
920 match &level {
921 Level::Text(t) => {
922 match &t.source {
923 LineSource::Terminal => {
924 if base_ptr == 0 {
925 self.printer.print_nl("<*>");
926 } else {
927 self.printer.print_nl("<insert> ");
928 }
929 }
930 LineSource::Read(n) => {
931 self.printer.print_nl("<read ");
932 if *n == 16 {
933 self.printer.print_char('*' as u32);
934 } else {
935 self.printer.print_int(i64::from(*n));
936 }
937 self.printer.print_char('>' as u32);
938 }
939 _ => {
940 self.printer.print_nl("l.");
941 self.printer.print_int(t.line.into());
942 }
943 }
944 self.printer.print_char(' ' as u32);
945 l = self.begin_pseudoprint();
947 let end_line_char = self
948 .eqtb
949 .int(crate::primitives_gen::assign_int::ENDLINECHAR);
950 let mut j = t.buf.len();
951 if j > 0 && t.buf[j - 1] as i32 == end_line_char {
952 j -= 1;
953 }
954 for i in 0..j {
955 if i == t.loc {
956 self.set_trick_count();
957 }
958 let c = t.buf[i];
959 if crate::utf8x::is_raw(c) {
960 let n = match c & 0xFF {
964 0xC2..=0xDF => 2,
965 0xE0..=0xEF => 3,
966 0xF0..=0xF4 => 4,
967 _ => 0,
968 };
969 let mut bytes = Vec::new();
970 for &e in t.buf.get(i..j).unwrap_or_default() {
971 if bytes.len() >= n {
972 break;
973 }
974 crate::utf8x::push_entry(&mut bytes, e);
975 }
976 bytes.truncate(n);
977 self.printer.pseudo_unit(&bytes);
978 } else {
979 self.printer.print_char(c);
980 }
981 }
982 }
983 Level::Tokens(t) => {
984 match t.kind {
985 TokKind::Parameter => self.printer.print_nl("<argument> "),
986 TokKind::UTemplate | TokKind::VTemplate => self.printer.print_nl("<template> "),
987 TokKind::BackedUp | TokKind::BackedUpChar => {
988 if t.loc >= t.list.len() {
989 self.printer.print_nl("<recently read> ")
990 } else {
991 self.printer.print_nl("<to be read again> ")
992 }
993 }
994 TokKind::Inserted => self.printer.print_nl("<inserted text> "),
995 TokKind::Macro => {
996 self.printer.print_ln();
997 self.print_cs(t.name);
998 }
999 TokKind::OutputText => self.printer.print_nl("<output> "),
1000 TokKind::EveryParText => self.printer.print_nl("<everypar> "),
1001 TokKind::EveryMathText => self.printer.print_nl("<everymath> "),
1002 TokKind::EveryDisplayText => self.printer.print_nl("<everydisplay> "),
1003 TokKind::EveryHboxText => self.printer.print_nl("<everyhbox> "),
1004 TokKind::EveryVboxText => self.printer.print_nl("<everyvbox> "),
1005 TokKind::EveryJobText => self.printer.print_nl("<everyjob> "),
1006 TokKind::EveryCrText => self.printer.print_nl("<everycr> "),
1007 TokKind::MarkText => self.printer.print_nl("<mark> "),
1008 TokKind::EveryEofText => self.printer.print_nl("<everyeof> "),
1009 TokKind::WriteText => self.printer.print_nl("<write> "),
1010 TokKind::LocalText => self.printer.print_nl("<local> "),
1011 }
1012 l = self.begin_pseudoprint();
1013 self.show_token_list_ctx(&t.list, t.loc, 100_000);
1016 }
1017 }
1018 self.printer.selector = old_setting;
1019 self.print_two_lines(l);
1020 }
1021
1022 fn begin_pseudoprint(&mut self) -> usize {
1023 let l = self.printer.tally;
1024 self.printer.tally = 0;
1025 self.printer.selector = Selector::Pseudo;
1026 self.printer.trick_count = 1_000_000;
1027 self.printer.trick_buf.clear();
1028 self.printer
1029 .trick_buf
1030 .resize(self.printer.error_line, vec![b' ']);
1031 l
1032 }
1033
1034 pub fn set_trick_count(&mut self) {
1036 let p = &mut self.printer;
1037 p.first_count = p.tally;
1038 p.trick_count = p.tally + 1 + p.error_line - p.half_error_line;
1039 if p.trick_count < p.error_line {
1040 p.trick_count = p.error_line;
1041 }
1042 }
1043
1044 fn print_two_lines(&mut self, l: usize) {
1045 if self.printer.trick_count == 1_000_000 {
1046 self.set_trick_count();
1047 }
1048 let p = &self.printer;
1049 let (tally, first_count, trick_count, half, error_line) = (
1050 p.tally,
1051 p.first_count,
1052 p.trick_count,
1053 p.half_error_line,
1054 p.error_line,
1055 );
1056 let m = if tally < trick_count {
1057 tally - first_count
1058 } else {
1059 trick_count - first_count
1060 };
1061 let (start, n) = if l + first_count <= half {
1062 (0, l + first_count)
1063 } else {
1064 self.printer.print_plain("...");
1065 (l + first_count - half + 3, half)
1066 };
1067 let buf = self.printer.trick_buf.clone();
1068 let mut bytes = Vec::new();
1069 for q in start..first_count {
1070 bytes.extend_from_slice(&buf[q % error_line]);
1071 }
1072 self.print_raw_bytes(&bytes);
1073 self.printer.print_ln();
1074 for _ in 0..n {
1075 self.printer.print_char(' ' as u32);
1076 }
1077 let p_end = if m + n <= error_line {
1078 first_count + m
1079 } else {
1080 first_count + (error_line - n - 3)
1081 };
1082 bytes.clear();
1083 for q in first_count..p_end {
1084 bytes.extend_from_slice(&buf[q % error_line]);
1085 }
1086 self.print_raw_bytes(&bytes);
1087 if m + n > error_line {
1088 self.printer.print_plain("...");
1089 }
1090 }
1091
1092 fn print_raw_bytes(&mut self, bytes: &[u8]) {
1095 let mut i = 0;
1096 while i < bytes.len() {
1097 let (c, n) = crate::utf8x::decode(bytes, i);
1098 if n == 1 && c >= 0x80 {
1099 self.printer.print_raw_byte(c as u8);
1100 } else {
1101 self.printer.print_char(c);
1102 }
1103 i += n;
1104 }
1105 }
1106}
1107
1108fn bad_option(message: &str) -> Option<String> {
1112 let m = message.split_once("Error: ").map_or(message, |(_, m)| m);
1113 if let Some(r) = m.strip_prefix("Unknown option ") {
1114 let r = r.strip_prefix(['`', '\''])?;
1115 return Some(r.split(['\'', '`']).next()?.to_owned());
1116 }
1117 if message.starts_with("Package keyval Error: ") {
1118 return m.split_once(" undefined").map(|(o, _)| o.to_owned());
1120 }
1121 None
1122}