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