use crate::Engine;
use crate::input::{Level, LineSource, TokKind};
use crate::print::Selector;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Interaction {
Batch = 0,
NonStop = 1,
Scroll = 2,
ErrorStop = 3,
}
impl Interaction {
pub fn from_i32(n: i32) -> Interaction {
match n {
0 => Interaction::Batch,
1 => Interaction::NonStop,
2 => Interaction::Scroll,
_ => Interaction::ErrorStop,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum History {
Spotless,
WarningIssued,
ErrorMessageIssued,
FatalErrorStop,
}
#[derive(Debug, Clone, Copy)]
pub struct JumpOut;
#[derive(Debug)]
pub struct ErrorState {
pub interaction: Interaction,
pub history: History,
pub error_count: i32,
pub help: Vec<&'static str>,
pub help_owned: Vec<String>,
pub use_err_help: bool,
pub deletions_allowed: bool,
pub set_box_allowed: bool,
pub no_new_control_sequence: bool,
pub old_setting: Option<Selector>,
pub file_line_error: bool,
pub halt_on_error: bool,
pub interrupt: bool,
}
impl Default for ErrorState {
fn default() -> Self {
ErrorState {
interaction: Interaction::ErrorStop,
history: History::FatalErrorStop,
error_count: 0,
help: Vec::new(),
help_owned: Vec::new(),
use_err_help: false,
deletions_allowed: true,
set_box_allowed: true,
no_new_control_sequence: true,
old_setting: None,
file_line_error: false,
halt_on_error: false,
interrupt: false,
}
}
}
impl Engine {
pub fn print_err(&mut self, msg: &str) {
self.printer.begin_line();
if !(self.err.file_line_error && self.print_file_line()) {
self.printer.print_str("! ");
}
if self.diagnostics.is_some() {
self.printer.capture = Some(Vec::new());
}
self.printer.print_str(msg);
}
pub fn diag_capture_start(&mut self) {
if self.diagnostics.is_some() {
self.printer.capture = Some(Vec::new());
}
}
pub fn source_location(&self) -> Option<(String, u32, String, usize)> {
let elc = self
.eqtb
.int(crate::primitives_gen::assign_int::ENDLINECHAR);
self.input.stack.iter().rev().find_map(|l| match l {
crate::input::Level::Text(t) if t.is_input_file => {
let mut chars: &[u32] = &t.buf;
if let Some((&last, rest)) = chars.split_last()
&& i64::from(last) == i64::from(elc)
{
chars = rest;
}
let text: String = chars.iter().filter_map(|&c| char::from_u32(c)).collect();
Some((
String::from_utf8_lossy(&t.name).into_owned(),
t.line.max(0) as u32,
text,
t.loc.min(chars.len()),
))
}
_ => None,
})
}
pub fn diag_capture_warning(&mut self, line: Option<u32>) {
let Some(text) = self.printer.capture.take() else {
return;
};
let message = String::from_utf8_lossy(&text).trim().replace('\n', " ");
let mut d = crate::diag::Diagnostic::new(crate::diag::Severity::Warning, message);
if let Some((file, cur, _, _)) = self.source_location() {
d.file = Some(file);
d.line = Some(line.unwrap_or(cur));
}
if let Some(list) = self.diagnostics.as_mut() {
list.push(d);
}
}
fn record_error(&mut self) {
let Some(text) = self.printer.capture.take() else {
return;
};
let mut message = String::from_utf8_lossy(&text).trim().replace('\n', " ");
if message == "Undefined control sequence" && self.cur_cs != crate::hash::NO_CS {
let name = self.print_to_string(|e| e.print_cs(e.cur_cs));
message = format!(
"Undefined control sequence {}",
String::from_utf8_lossy(&name).trim_end()
);
}
let mut d = crate::diag::Diagnostic::new(crate::diag::Severity::Error, message);
d.facts = self.error_facts(&d.message);
if let Some((file, line, source, column)) = self.source_location() {
d.file = Some(file);
d.line = Some(line);
d.source = Some(source);
d.column = Some(column);
}
d.help = if self.err.help_owned.is_empty() {
self.err.help.iter().map(|s| (*s).to_owned()).collect()
} else {
self.err.help_owned.clone()
};
if let Some(list) = self.diagnostics.as_mut() {
list.push(d);
}
}
fn error_facts(&mut self, message: &str) -> Vec<(String, String)> {
let mut facts = Vec::new();
if message.starts_with("Undefined control sequence") && self.cur_cs != crate::hash::NO_CS {
let name = self.hash.name(self.cur_cs).to_vec();
let similar = self.similar_defined_names(&name);
if !similar.is_empty() {
facts.push(("similar".to_owned(), similar.join(" ")));
}
}
if let Some(option) = bad_option(message) {
let similar = self.similar_options(option.as_bytes());
if !similar.is_empty() {
facts.push(("similar_options".to_owned(), similar.join(" ")));
}
}
if message.starts_with("Missing $ inserted") {
let tok = if self.cur_cmd == crate::cmd::Cmd::ParEnd {
"\\par".to_owned()
} else if self.cur_cs != crate::hash::NO_CS {
let cs = self.cur_cs;
String::from_utf8_lossy(&self.print_to_string(|e| e.print_cs(cs)))
.trim_end()
.to_owned()
} else {
char::from_u32(self.cur_chr)
.map(String::from)
.unwrap_or_default()
};
facts.push(("token".to_owned(), tok));
if self.mode.abs_kind() == crate::maincontrol::ModeKind::Math {
let line = self.cur_list().mode_line.unsigned_abs();
facts.push(("math_line".to_owned(), line.to_string()));
}
}
facts
}
fn similar_options(&self, option: &[u8]) -> Vec<String> {
let max = if option.len() < 5 { 1 } else { 2 };
let mut found: Vec<(usize, Vec<u8>)> = self
.hash
.iter_named()
.filter(|(_, id)| self.eqtb.meaning(*id).cmd != crate::cmd::Cmd::UndefinedCs)
.filter_map(|(n, _)| {
if let Some(o) = n.strip_prefix(b"ds@") {
Some(o)
} else if let Some(r) = n.strip_prefix(b"KV@") {
let k = &r[r.iter().position(|&b| b == b'@')? + 1..];
(!k.ends_with(b"@default")).then_some(k)
} else {
let r = n.strip_prefix(b"key code > ")?;
Some(&r[r.iter().rposition(|&b| b == b'/')? + 1..])
}
})
.filter(|o| !o.is_empty() && o.iter().all(|b| !b.is_ascii_whitespace()))
.filter_map(|o| {
crate::diag::edit_distance_within(option, o, max).map(|d| (d, o.to_vec()))
})
.filter(|(d, _)| *d > 0)
.collect();
found.sort();
found.dedup();
found
.into_iter()
.take(3)
.map(|(_, n)| String::from_utf8_lossy(&n).into_owned())
.collect()
}
fn similar_defined_names(&self, name: &[u8]) -> Vec<String> {
let internal = |n: &[u8]| n.iter().any(|&b| matches!(b, b'@' | b'_' | b':'));
let max = if name.len() < 4 { 1 } else { 2 };
let mut found: Vec<(usize, Vec<u8>)> = self
.hash
.iter_named()
.filter(|(n, _)| {
n.len() > 1
&& n.iter()
.all(|b| !b.is_ascii_whitespace() && !b.is_ascii_control())
})
.filter(|(n, _)| !internal(n) || internal(name))
.filter(|(_, id)| self.eqtb.meaning(*id).cmd != crate::cmd::Cmd::UndefinedCs)
.filter_map(|(n, _)| {
crate::diag::edit_distance_within(name, n, max).map(|d| (d, n.to_vec()))
})
.filter(|(d, _)| *d > 0)
.collect();
found.sort();
found.dedup();
found
.into_iter()
.take(3)
.map(|(_, n)| format!("\\{}", String::from_utf8_lossy(&n)))
.collect()
}
pub fn print_err_bytes(&mut self, msg: &[u8]) {
self.print_err("");
self.printer.print_bytes(msg);
}
fn print_file_line(&mut self) -> bool {
let found = self.input.stack.iter().rev().find_map(|l| match l {
Level::Text(t) if t.is_input_file => Some((t.name.clone(), t.line)),
_ => None,
});
match found {
Some((name, line)) => {
self.printer.print_bytes(&name);
self.printer.print_char(':' as u32);
self.printer.print_int(line.into());
self.printer.print_str(": ");
true
}
None => false,
}
}
pub fn help(&mut self, lines: &[&'static str]) {
self.err.help.clear();
self.err.help.extend_from_slice(lines);
self.err.help_owned.clear();
}
pub fn help_owned(&mut self, lines: Vec<String>) {
self.err.help.clear();
self.err.help_owned = lines;
}
pub fn error(&mut self) {
self.record_error();
if self.err.history < History::ErrorMessageIssued {
self.err.history = History::ErrorMessageIssued;
}
self.printer.print_char('.' as u32);
self.show_context();
if self.err.halt_on_error {
self.halt_on_error_stop();
}
if self.err.interaction == Interaction::ErrorStop {
self.terminal_error_stop();
}
self.err.error_count += 1;
if self.err.error_count == 100 {
self.printer
.print_nl("(That makes 100 errors; please try again.)");
self.err.history = History::FatalErrorStop;
self.jump_out();
}
let sel = self.printer.selector;
if self.err.interaction > Interaction::Batch {
self.printer.selector = match sel {
Selector::TermAndLog => Selector::LogOnly,
Selector::TermOnly => Selector::NoPrint,
s => s,
};
}
if self.err.use_err_help {
self.printer.print_ln();
self.give_err_help();
} else {
let lines: Vec<String> = if self.err.help_owned.is_empty() {
self.err.help.iter().map(|s| s.to_string()).collect()
} else {
self.err.help_owned.clone()
};
let empty = lines.is_empty();
for l in lines {
self.printer.print_nl(&l);
}
if empty {
self.printer.begin_line();
} else {
self.printer.print_ln();
}
}
if self.err.use_err_help {
self.printer.print_ln();
}
self.err.help.clear();
self.err.help_owned.clear();
if self.err.interaction > Interaction::Batch {
self.printer.selector = sel;
}
self.printer.print_ln();
}
pub fn term_input(&mut self) -> Vec<u32> {
self.prompted = true;
if self.printer.term_quiet {
let prompt = std::mem::take(&mut self.printer.quiet_line);
self.show_before_prompt(&String::from_utf8_lossy(&prompt));
}
self.printer.update_terminal();
let mut line = String::new();
match std::io::stdin().read_line(&mut line) {
Ok(0) | Err(_) => self.fatal_error("End of file on the terminal!"),
Ok(_) => {}
}
let chars = crate::input::decode_line(line.trim_end_matches(['\n', '\r']).as_bytes());
self.printer.term_offset = 0;
let old = self.printer.selector;
self.printer.selector = match old {
Selector::TermAndLog => Selector::LogOnly,
Selector::TermOnly => Selector::NoPrint,
s => s,
};
for &c in &chars {
self.printer.print_char(c);
}
self.printer.print_ln();
self.printer.selector = old;
chars
}
pub fn prompt_input(&mut self, prompt: &str) -> Vec<u32> {
self.printer.print_str(prompt);
self.term_input()
}
fn show_before_prompt(&mut self, prompt: &str) -> bool {
let Some(mut hook) = self.prompt_hook.take() else {
return true;
};
let list = self.diagnostics.as_deref().unwrap_or(&[]);
let from = self.diagnostics_shown.min(list.len());
let shown = hook(&list[from..], prompt);
self.diagnostics_shown = list.len();
self.prompt_hook = Some(hook);
shown
}
fn terminal_error_stop(&mut self) {
let quiet = self.printer.term_quiet;
if quiet && self.err.interaction == Interaction::ErrorStop {
if !self.show_before_prompt("") {
return;
}
self.printer.term_quiet = false;
self.printer.quiet_line.clear();
}
self.terminal_error_dialog();
self.printer.term_quiet = quiet;
}
fn terminal_error_dialog(&mut self) {
loop {
if self.err.interaction != Interaction::ErrorStop {
return;
}
self.clear_for_error_prompt();
let line = self.prompt_input("? ");
let Some(&c0) = line.first() else {
return;
};
let c = char::from_u32(c0).unwrap_or(' ').to_ascii_uppercase();
match c {
'0'..='9' if self.err.deletions_allowed => {
let mut n = c as u32 - '0' as u32;
if let Some(&d) = line.get(1)
&& (0x30..=0x39).contains(&d)
{
n = n * 10 + (d - 0x30);
}
let (s1, s2, s3, s4) = (
self.cur_tok,
self.cur_cmd,
self.cur_chr,
self.input.align_state,
);
self.input.align_state = 1_000_000;
for _ in 0..n {
self.get_token();
}
self.cur_tok = s1;
self.cur_cmd = s2;
self.cur_chr = s3;
self.input.align_state = s4;
self.help(&[
"I have just deleted some text, as you asked.",
"You can now delete more, or insert, or whatever.",
]);
self.show_context();
continue;
}
'E' => {
let file = self.input.stack.iter().rev().find_map(|l| match l {
Level::Text(t) if t.is_input_file => Some((t.name.clone(), t.line)),
_ => None,
});
if let Some((name, line)) = file {
self.printer.print_nl("You want to edit file ");
self.printer.print_bytes(&name);
self.printer.print_str(" at line ");
self.printer.print_int(line.into());
self.edit_request = Some((name, line));
self.err.interaction = Interaction::Scroll;
self.jump_out();
}
}
'H' => {
if self.err.use_err_help {
self.give_err_help();
self.err.use_err_help = false;
} else {
let lines: Vec<String> = if !self.err.help_owned.is_empty() {
self.err.help_owned.clone()
} else if !self.err.help.is_empty() {
self.err.help.iter().map(|s| s.to_string()).collect()
} else {
vec![
"Sorry, I don't know how to help in this situation.".into(),
"Maybe you should try asking a human?".into(),
]
};
for l in lines {
self.printer.print_str(&l);
self.printer.print_ln();
}
}
self.help(&[
"Sorry, I already gave what help I could...",
"Maybe you should try asking a human?",
"An error might have occurred before I noticed any problems.",
"``If all else fails, read the instructions.''",
]);
continue;
}
'I' => {
let text: Vec<u32> = if line.len() > 1 {
let mut v = vec![' ' as u32];
v.extend_from_slice(&line[1..]);
v
} else {
self.prompt_input("insert>")
};
self.begin_file_reading(crate::input::LineSource::Terminal, Vec::new(), false);
if let Some(Level::Text(t)) = self.input.stack.last_mut() {
t.buf = text;
t.loc = 0;
t.state = crate::input::LexState::MidLine;
}
return;
}
'Q' | 'R' | 'S' => {
self.err.error_count = 0;
self.printer.print_str("OK, entering ");
match c {
'Q' => {
self.err.interaction = Interaction::Batch;
self.print_esc_str("batchmode");
self.printer.selector = match self.printer.selector {
Selector::TermAndLog => Selector::LogOnly,
_ => Selector::NoPrint,
};
}
'R' => {
self.err.interaction = Interaction::NonStop;
self.print_esc_str("nonstopmode");
}
_ => {
self.err.interaction = Interaction::Scroll;
self.print_esc_str("scrollmode");
}
}
self.printer.print_str("...");
self.printer.print_ln();
self.printer.update_terminal();
return;
}
'X' => {
self.err.interaction = Interaction::Scroll;
self.jump_out();
}
_ => {}
}
self.printer
.print_str("Type <return> to proceed, S to scroll future error messages,");
self.printer
.print_nl("R to run without stopping, Q to run quietly,");
self.printer.print_nl("I to insert something, ");
if self
.input
.stack
.iter()
.any(|l| matches!(l, Level::Text(t) if t.is_input_file))
{
self.printer.print_str("E to edit your file,");
}
if self.err.deletions_allowed {
self.printer
.print_nl("1 or ... or 9 to ignore the next 1 to 9 tokens of input,");
}
self.printer.print_nl("H for help, X to quit.");
}
}
fn clear_for_error_prompt(&mut self) {
while self.input.stack.len() > 1 {
match self.input.stack.last() {
Some(Level::Text(t))
if matches!(t.source, LineSource::Terminal) && t.loc >= t.buf.len() =>
{
self.end_file_reading();
}
_ => break,
}
}
self.printer.print_ln();
}
pub fn give_err_help(&mut self) {
let list = self
.eqtb
.toks_param(crate::primitives_gen::assign_toks::ERRHELP)
.clone();
self.token_show_list(&list, 0, list.len(), 10_000_000);
}
pub fn fatal_error(&mut self, s: &str) -> ! {
self.normalize_selector();
self.print_err("Emergency stop");
self.help_owned(vec![s.to_owned()]);
self.succumb();
}
pub fn luatex_fatal(&mut self, category: &str, msg: &str) -> ! {
self.normalize_selector();
self.printer.print_nl("! error: (");
self.printer.print_str(category);
self.printer.print_str("): ");
self.printer.print_str(msg);
let pdf = self.eqtb.int(crate::primitives_gen::assign_int::OUTPUTMODE) > 0;
self.printer.print_nl(if pdf {
"! ==> Fatal error occurred, bad output PDF file produced!"
} else {
"! ==> Fatal error occurred, bad output DVI file produced!"
});
self.printer.print_ln();
self.err.history = History::FatalErrorStop;
for n in 0..128u32 {
if self.printer.write_open(n) {
self.printer.close_write(n);
}
}
if self.total_pages == 0 {
self.printer.print_str("No pages of output.");
self.printer.print_ln();
} else if !pdf {
self.finish_output();
}
if self.printer.log_opened() {
self.printer.close_log();
}
self.printer.selector = Selector::TermOnly;
self.printer.flush();
std::panic::resume_unwind(Box::new(JumpOut));
}
fn halt_on_error_stop(&mut self) -> ! {
let pdf = self.eqtb.int(crate::primitives_gen::assign_int::OUTPUTMODE) > 0;
self.printer.print_nl(if pdf {
"! ==> Fatal error occurred, bad output PDF file produced!"
} else {
"! ==> Fatal error occurred, bad output DVI file produced!"
});
self.printer.print_ln();
self.err.history = History::FatalErrorStop;
for n in 0..128u32 {
if self.printer.write_open(n) {
self.printer.close_write(n);
}
}
if self.total_pages == 0 {
self.printer.print_str("No pages of output.");
self.printer.print_ln();
}
if self.printer.log_opened() {
let sel = self.printer.selector;
self.printer.selector = Selector::LogOnly;
self.printer.print_ln();
self.printer.selector = sel;
}
self.finish_log();
std::panic::resume_unwind(Box::new(JumpOut));
}
pub fn overflow(&mut self, s: &str, n: i64) -> ! {
self.normalize_selector();
self.print_err("TeX capacity exceeded, sorry [");
self.printer.print_str(s);
self.printer.print_char('=' as u32);
self.printer.print_int(n);
self.printer.print_char(']' as u32);
self.help(&[
"If you really absolutely need more capacity,",
"you can ask a wizard to enlarge me.",
]);
self.succumb();
}
pub fn confusion(&mut self, s: &str) -> ! {
self.normalize_selector();
if self.err.history < History::ErrorMessageIssued {
self.print_err("This can't happen (");
self.printer.print_str(s);
self.printer.print_char(')' as u32);
self.help(&["I'm broken. Please show this to someone who can fix"]);
} else {
self.print_err("I can't go on meeting you like this");
self.help(&[
"One of your faux pas seems to have wounded me deeply...",
"in fact, I'm barely conscious. Please fix it and try again.",
]);
}
self.succumb();
}
pub(crate) fn succumb(&mut self) -> ! {
if self.err.interaction == Interaction::ErrorStop {
self.err.interaction = Interaction::Scroll;
}
if self.printer.log_opened() {
self.error();
}
self.err.history = History::FatalErrorStop;
self.jump_out();
}
pub fn jump_out(&mut self) -> ! {
if self.prompt_hook.is_some() {
self.printer.term_quiet = true;
}
self.close_files_and_terminate();
std::panic::resume_unwind(Box::new(JumpOut));
}
pub fn normalize_selector(&mut self) {
self.printer.selector = if self.printer.log_opened() {
Selector::TermAndLog
} else {
Selector::TermOnly
};
if self.job_name.is_none() {
self.open_log_file();
}
if self.err.interaction == Interaction::Batch {
self.printer.selector = match self.printer.selector {
Selector::TermAndLog => Selector::LogOnly,
_ => Selector::NoPrint,
};
}
}
pub fn check_interrupt(&mut self) {}
pub fn back_error(&mut self) {
self.err.interrupt = false;
self.back_input();
self.err.interrupt = true;
self.error();
}
pub fn ins_error(&mut self) {
self.err.interrupt = false;
self.back_input_inserted();
self.err.interrupt = true;
self.error();
}
pub fn luatex_warning(&mut self, category: &str, msg: &str) {
self.printer.luatex_warning(category, msg);
if self.err.history == History::Spotless {
self.err.history = History::WarningIssued;
}
}
pub fn begin_diagnostic(&mut self) {
self.err.old_setting = Some(self.printer.selector);
let online = self
.eqtb
.int(crate::primitives_gen::assign_int::TRACINGONLINE);
if online <= 0 && self.printer.selector == Selector::TermAndLog {
self.printer.selector = Selector::LogOnly;
if self.err.history == History::Spotless {
self.err.history = History::WarningIssued;
}
}
}
pub fn end_diagnostic(&mut self, blank_line: bool) {
self.printer.print_nl("");
if blank_line {
self.printer.print_ln();
}
if let Some(s) = self.err.old_setting.take() {
self.printer.selector = s;
}
}
pub fn show_context(&mut self) {
let error_context_lines = self
.eqtb
.int(crate::primitives_gen::assign_int::ERRORCONTEXTLINES);
let input_ptr = self.input.stack.len().saturating_sub(1);
let mut base_ptr = input_ptr;
let mut nn: i32 = -1;
loop {
let (is_text, bottom) = match &self.input.stack.get(base_ptr) {
Some(Level::Text(t)) => (
true,
t.is_input_file || matches!(t.source, LineSource::File { .. }) || base_ptr == 0,
),
Some(Level::Tokens(_)) => (false, false),
None => (true, true),
};
let bottom_line = is_text && bottom;
if base_ptr == input_ptr || bottom_line || nn < error_context_lines {
let show = match self.input.stack.get(base_ptr) {
Some(Level::Tokens(t)) => {
base_ptr == input_ptr
|| !matches!(t.kind, TokKind::BackedUp | TokKind::BackedUpChar)
|| t.loc < t.list.len()
}
_ => true,
};
if show {
self.printer.term_just_wrapped = false;
self.display_context(base_ptr);
nn += 1;
if self.printer.term_just_wrapped
&& !(bottom_line || base_ptr == 0)
&& matches!(
self.printer.selector,
crate::print::Selector::TermAndLog | crate::print::Selector::TermOnly
)
{
self.printer.term_raw(b"\n");
}
}
} else if nn == error_context_lines {
self.printer.print_nl("...");
nn += 1;
}
if bottom_line || base_ptr == 0 {
break;
}
base_ptr -= 1;
}
}
fn display_context(&mut self, base_ptr: usize) {
let old_setting = self.printer.selector;
self.printer.tally = 0;
let level = self.input.stack[base_ptr].clone();
let l: usize;
match &level {
Level::Text(t) => {
match &t.source {
LineSource::Terminal => {
if base_ptr == 0 {
self.printer.print_nl("<*>");
} else {
self.printer.print_nl("<insert> ");
}
}
LineSource::Read(n) => {
self.printer.print_nl("<read ");
if *n == 16 {
self.printer.print_char('*' as u32);
} else {
self.printer.print_int(i64::from(*n));
}
self.printer.print_char('>' as u32);
}
_ => {
self.printer.print_nl("l.");
self.printer.print_int(t.line.into());
}
}
self.printer.print_char(' ' as u32);
l = self.begin_pseudoprint();
let end_line_char = self
.eqtb
.int(crate::primitives_gen::assign_int::ENDLINECHAR);
let mut j = t.buf.len();
if j > 0 && t.buf[j - 1] as i32 == end_line_char {
j -= 1;
}
for i in 0..j {
if i == t.loc {
self.set_trick_count();
}
let c = t.buf[i];
if crate::utf8x::is_raw(c) {
let n = match c & 0xFF {
0xC2..=0xDF => 2,
0xE0..=0xEF => 3,
0xF0..=0xF4 => 4,
_ => 0,
};
let mut bytes = Vec::new();
for &e in t.buf.get(i..j).unwrap_or_default() {
if bytes.len() >= n {
break;
}
crate::utf8x::push_entry(&mut bytes, e);
}
bytes.truncate(n);
self.printer.pseudo_unit(&bytes);
} else {
self.printer.print_char(c);
}
}
}
Level::Tokens(t) => {
match t.kind {
TokKind::Parameter => self.printer.print_nl("<argument> "),
TokKind::UTemplate | TokKind::VTemplate => self.printer.print_nl("<template> "),
TokKind::BackedUp | TokKind::BackedUpChar => {
if t.loc >= t.list.len() {
self.printer.print_nl("<recently read> ")
} else {
self.printer.print_nl("<to be read again> ")
}
}
TokKind::Inserted => self.printer.print_nl("<inserted text> "),
TokKind::Macro => {
self.printer.print_ln();
self.print_cs(t.name);
}
TokKind::OutputText => self.printer.print_nl("<output> "),
TokKind::EveryParText => self.printer.print_nl("<everypar> "),
TokKind::EveryMathText => self.printer.print_nl("<everymath> "),
TokKind::EveryDisplayText => self.printer.print_nl("<everydisplay> "),
TokKind::EveryHboxText => self.printer.print_nl("<everyhbox> "),
TokKind::EveryVboxText => self.printer.print_nl("<everyvbox> "),
TokKind::EveryJobText => self.printer.print_nl("<everyjob> "),
TokKind::EveryCrText => self.printer.print_nl("<everycr> "),
TokKind::MarkText => self.printer.print_nl("<mark> "),
TokKind::EveryEofText => self.printer.print_nl("<everyeof> "),
TokKind::WriteText => self.printer.print_nl("<write> "),
TokKind::LocalText => self.printer.print_nl("<local> "),
}
l = self.begin_pseudoprint();
self.show_token_list_ctx(&t.list, t.loc, 100_000);
}
}
self.printer.selector = old_setting;
self.print_two_lines(l);
}
fn begin_pseudoprint(&mut self) -> usize {
let l = self.printer.tally;
self.printer.tally = 0;
self.printer.selector = Selector::Pseudo;
self.printer.trick_count = 1_000_000;
self.printer.trick_buf.clear();
self.printer
.trick_buf
.resize(self.printer.error_line, vec![b' ']);
l
}
pub fn set_trick_count(&mut self) {
let p = &mut self.printer;
p.first_count = p.tally;
p.trick_count = p.tally + 1 + p.error_line - p.half_error_line;
if p.trick_count < p.error_line {
p.trick_count = p.error_line;
}
}
fn print_two_lines(&mut self, l: usize) {
if self.printer.trick_count == 1_000_000 {
self.set_trick_count();
}
let p = &self.printer;
let (tally, first_count, trick_count, half, error_line) = (
p.tally,
p.first_count,
p.trick_count,
p.half_error_line,
p.error_line,
);
let m = if tally < trick_count {
tally - first_count
} else {
trick_count - first_count
};
let (start, n) = if l + first_count <= half {
(0, l + first_count)
} else {
self.printer.print_plain("...");
(l + first_count - half + 3, half)
};
let buf = self.printer.trick_buf.clone();
let mut bytes = Vec::new();
for q in start..first_count {
bytes.extend_from_slice(&buf[q % error_line]);
}
self.print_raw_bytes(&bytes);
self.printer.print_ln();
for _ in 0..n {
self.printer.print_char(' ' as u32);
}
let p_end = if m + n <= error_line {
first_count + m
} else {
first_count + (error_line - n - 3)
};
bytes.clear();
for q in first_count..p_end {
bytes.extend_from_slice(&buf[q % error_line]);
}
self.print_raw_bytes(&bytes);
if m + n > error_line {
self.printer.print_plain("...");
}
}
fn print_raw_bytes(&mut self, bytes: &[u8]) {
let mut i = 0;
while i < bytes.len() {
let (c, n) = crate::utf8x::decode(bytes, i);
if n == 1 && c >= 0x80 {
self.printer.print_raw_byte(c as u8);
} else {
self.printer.print_char(c);
}
i += n;
}
}
}
fn bad_option(message: &str) -> Option<String> {
let m = message.split_once("Error: ").map_or(message, |(_, m)| m);
if let Some(r) = m.strip_prefix("Unknown option ") {
let r = r.strip_prefix(['`', '\''])?;
return Some(r.split(['\'', '`']).next()?.to_owned());
}
if message.starts_with("Package keyval Error: ") {
return m.split_once(" undefined").map(|(o, _)| o.to_owned());
}
None
}