use std::rc::Rc;
use crate::Engine;
use crate::cmd::Cmd;
use crate::hash::NO_CS;
use crate::input::{Level, TokKind};
use crate::nodes::NodeRef;
use crate::primitives_gen as pg;
use crate::save::GroupCode;
use crate::token::Token;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Mode {
None,
Vertical,
InternalVertical,
Horizontal,
RestrictedHorizontal,
DisplayMath,
Math,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ModeKind {
None,
Vertical,
Horizontal,
Math,
}
impl Mode {
pub fn abs_kind(self) -> ModeKind {
match self {
Mode::None => ModeKind::None,
Mode::Vertical | Mode::InternalVertical => ModeKind::Vertical,
Mode::Horizontal | Mode::RestrictedHorizontal => ModeKind::Horizontal,
Mode::DisplayMath | Mode::Math => ModeKind::Math,
}
}
pub fn is_inner(self) -> bool {
matches!(
self,
Mode::InternalVertical | Mode::RestrictedHorizontal | Mode::Math
)
}
pub fn is_privileged(self) -> bool {
matches!(self, Mode::Vertical | Mode::Horizontal | Mode::DisplayMath)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BoxContext {
Shift(i32),
SetBox(u32),
GlobalSetBox(u32),
Shipout,
Leaders(u16),
Lua,
}
#[derive(Clone, Debug)]
pub struct ListState {
pub mode: Mode,
pub head: NodeRef,
pub tail: NodeRef,
pub prev_graf: i32,
pub mode_line: i32,
pub prev_depth: i32,
pub space_factor: i32,
pub clang: i32,
pub incompleat_noad: NodeRef,
pub math_style: i32,
pub delim_ptr: NodeRef,
}
pub const IGNORE_DEPTH: i32 = -65_536_000;
impl Engine {
pub fn cur_list(&self) -> &ListState {
self.nest.last().expect("nest is never empty")
}
pub fn cur_list_mut(&mut self) -> &mut ListState {
self.nest.last_mut().expect("nest is never empty")
}
pub fn push_nest(&mut self, mode: Mode) {
let head = self.nodes.new_temp();
let cl = ListState {
mode,
head,
tail: head,
prev_graf: 0,
mode_line: self.input.line(),
prev_depth: IGNORE_DEPTH,
space_factor: 1000,
clang: 0,
incompleat_noad: NodeRef::NULL,
math_style: 0,
delim_ptr: NodeRef::NULL,
};
self.nest.push(cl);
self.mode = mode;
}
pub fn pop_nest(&mut self) {
if let Some(cl) = self.nest.pop() {
self.nodes.free(cl.head);
}
self.mode = self.cur_list().mode;
}
pub fn tail_append(&mut self, p: NodeRef) {
let tail = self.cur_list().tail;
self.nodes.link(tail, p);
self.cur_list_mut().tail = p;
}
pub fn print_mode(&mut self, m: Mode) {
let s = match m {
Mode::Vertical => "vertical mode",
Mode::Horizontal => "horizontal mode",
Mode::DisplayMath => "display math mode",
Mode::None => "no mode",
Mode::InternalVertical => "internal vertical mode",
Mode::RestrictedHorizontal => "restricted horizontal mode",
Mode::Math => "math mode",
};
self.printer.print_str(s);
}
pub fn main_control(&mut self) {
let every_job = self.eqtb.toks_param(pg::assign_toks::EVERYJOB).clone();
if !every_job.is_empty() {
self.begin_token_list(every_job, TokKind::EveryJobText);
}
self.main_loop();
}
pub fn run_local(&mut self, list: Option<crate::token::TokenList>) {
let end: crate::token::TokenList = Rc::from(vec![Token::cs(self.cs.end_local)]);
let depth = self.input.stack.len();
self.begin_token_list(end, TokKind::Inserted);
if let Some(l) = list {
self.begin_token_list(l, TokKind::Inserted);
}
let _ = depth;
self.local_level += 1;
self.main_loop();
self.local_level -= 1;
}
pub fn push_local_marker(&mut self) {
let end: crate::token::TokenList = Rc::from(vec![Token::cs(self.cs.end_local)]);
self.begin_token_list(end, TokKind::Inserted);
}
pub fn scan_list(&mut self) -> NodeRef {
self.lua_scanned_box = None;
let level = self.save.cur_level;
self.scan_box(BoxContext::Lua);
if self.lua_scanned_box.is_none() && self.save.cur_level > level {
let waiting = std::mem::replace(&mut self.lua_scan_waiting, true);
self.run_local_loop();
self.lua_scan_waiting = waiting;
}
self.lua_scanned_box.take().unwrap_or(NodeRef::NULL)
}
pub fn run_local_loop(&mut self) {
self.local_level += 1;
self.main_loop();
self.local_level -= 1;
}
#[inline]
pub fn check_double_free(&mut self) {
if let Some((t, p)) = self.nodes.double_free {
let name = crate::nodes::type_name(t);
self.luatex_fatal(
"nodes",
&format!("attempt to double-free {name} node {p}, ignored"),
);
}
}
fn main_loop(&mut self) {
loop {
self.get_x_token();
loop {
if self.eqtb.int(pg::assign_int::TRACINGCOMMANDS) > 0 {
self.show_cur_cmd_chr();
}
match self.dispatch() {
Next::BigSwitch => {
self.check_double_free();
break;
}
Next::Reswitch => continue,
Next::Return => return,
}
}
}
}
fn dispatch(&mut self) -> Next {
use ModeKind as K;
let kind = self.mode.abs_kind();
let cmd = self.cur_cmd;
match (kind, cmd) {
(K::Horizontal, Cmd::Letter | Cmd::OtherChar | Cmd::CharGiven) => {
self.main_loop_char();
Next::Reswitch
}
(K::Horizontal, Cmd::CharNum) => {
self.scan_char_num();
self.cur_chr = self.cur_val as u32;
self.main_loop_char();
Next::Reswitch
}
(K::Horizontal, Cmd::Boundary) => {
self.append_boundary();
Next::BigSwitch
}
(K::Horizontal, Cmd::Spacer) => {
self.app_space_main();
Next::BigSwitch
}
(K::Horizontal | K::Math, Cmd::ExSpace) => {
self.app_explicit_space();
Next::BigSwitch
}
(K::Vertical, Cmd::Spacer) if self.local_level > 0 => {
self.append_normal_space();
Next::BigSwitch
}
(_, Cmd::Relax) | (K::Vertical | K::Math, Cmd::Spacer) => Next::BigSwitch,
(K::Math, Cmd::Boundary) => Next::BigSwitch,
(_, Cmd::IgnoreSpaces) => {
if self.cur_chr == 0 {
self.get_nonblank_noncall();
Next::Reswitch
} else {
Next::BigSwitch
}
}
(K::Vertical, Cmd::Stop) => {
if self.its_all_over() {
Next::Return
} else {
Next::BigSwitch
}
}
(K::Vertical, Cmd::Vmove)
| (K::Horizontal | K::Math, Cmd::Hmove)
| (_, Cmd::LastItem)
| (_, Cmd::MacParam) => {
self.report_illegal_case();
Next::BigSwitch
}
(_, c) if c > Cmd::MAX_NON_PREFIXED => {
self.prefixed_command();
Next::BigSwitch
}
(_, Cmd::BeginGroup) => {
self.new_save_level(GroupCode::SemiSimple);
self.reset_local_counts(true);
Next::BigSwitch
}
(_, Cmd::EndGroup) => {
if self.save.cur_group == GroupCode::SemiSimple {
self.end_group_text_dirs();
self.unsave();
} else {
self.off_save();
}
Next::BigSwitch
}
(_, Cmd::LeftBrace) if kind != K::Math => {
self.new_save_level(GroupCode::Simple);
self.reset_local_counts(true);
Next::BigSwitch
}
(_, Cmd::RightBrace) => {
self.handle_right_brace();
Next::BigSwitch
}
(_, Cmd::AfterAssignment) => {
self.get_token();
self.after_token = self.cur_tok;
Next::BigSwitch
}
(_, Cmd::AfterGroup) => {
self.get_token();
self.save_for_after(self.cur_tok);
Next::BigSwitch
}
(_, Cmd::InStream) => {
self.open_or_close_in();
Next::BigSwitch
}
(_, Cmd::Message) => {
self.issue_message();
Next::BigSwitch
}
(_, Cmd::CaseShift) => {
self.shift_case();
Next::BigSwitch
}
(_, Cmd::Xray) => {
self.show_whatever();
Next::BigSwitch
}
(_, Cmd::Extension)
if self.cur_chr == crate::misc::ext::END_LOCAL_CONTROL && self.local_level > 0 =>
{
Next::Return
}
(_, Cmd::Extension) => {
self.do_extension();
Next::BigSwitch
}
(_, Cmd::Normal) => {
self.do_normal();
Next::BigSwitch
}
(_, Cmd::LuaFunctionCall | Cmd::LuaBytecodeCall | Cmd::LuaCall) => {
let n = if self.cur_cmd == Cmd::LuaFunctionCall
|| self.cur_cmd == Cmd::LuaBytecodeCall
{
self.scan_int();
self.cur_val
} else {
self.cur_chr as i32
};
if self.cur_cmd == Cmd::LuaBytecodeCall {
self.call_lua_bytecode(n);
} else {
self.call_lua_function(n);
}
Next::BigSwitch
}
(_, Cmd::PartokenName) => {
self.partoken_name();
Next::BigSwitch
}
_ => {
self.typeset_command();
Next::BigSwitch
}
}
}
pub fn report_illegal_case(&mut self) {
self.you_cant();
self.help(&[
"Sorry, but I'm not programmed to handle this case;",
"I'll just pretend that you didn't ask for it.",
"If you're in the wrong mode, you might be able to",
"return to the right one by typing `I}' or `I$' or `I\\par'.",
]);
self.error();
}
pub fn you_cant(&mut self) {
self.print_err("You can't use `");
self.print_cmd_chr(self.cur_cmd, self.cur_chr);
self.printer.print_str("' in ");
self.print_mode(self.mode);
}
pub fn privileged(&mut self) -> bool {
if self.mode.is_privileged() {
true
} else {
self.report_illegal_case();
false
}
}
pub fn insert_dollar_sign(&mut self) {
self.back_input();
self.cur_tok = Token::char(Cmd::MathShift, '$' as u32);
self.print_err("Missing $ inserted");
self.help(&[
"I've inserted a begin-math/end-math symbol since I think",
"you left one out. Proceed, with fingers crossed.",
]);
self.ins_error();
}
fn its_all_over(&mut self) -> bool {
if self.privileged() {
let cl = self.cur_list();
if self.page_is_empty() && cl.head == cl.tail && self.dead_cycles == 0 {
return true;
}
self.back_input();
let b = self.nodes.new_null_box(true);
let hsize = self.eqtb.dimen(pg::assign_dimen::HSIZE);
if let Some(bd) = self.nodes.box_data_mut(b) {
bd.width = hsize;
}
self.tail_append(b);
let g = self.nodes.new_glue(
crate::glue::Glue {
stretch: crate::glue::Glue::fixed(0).stretch + retex_arith::UNITY,
stretch_order: crate::glue::FILL,
..crate::glue::Glue::ZERO
},
0,
);
self.tail_append(g);
let p = self.nodes.new_penalty(-0o10000000000, 0);
self.tail_append(p);
self.build_page_from("end");
}
false
}
pub fn handle_right_brace(&mut self) {
match self.save.cur_group {
GroupCode::Simple => {
self.end_group_text_dirs();
self.unsave();
}
GroupCode::BottomLevel => {
self.print_err("Too many }'s");
self.help(&[
"You've closed more groups than you opened.",
"Such booboos are generally harmless, so keep going.",
]);
self.error();
}
GroupCode::SemiSimple | GroupCode::MathShift | GroupCode::MathLeft => {
self.extra_right_brace()
}
_ => self.handle_right_brace_typesetting(),
}
}
fn extra_right_brace(&mut self) {
self.print_err("Extra }, or forgotten ");
match self.save.cur_group {
GroupCode::SemiSimple => self.print_esc_str("endgroup"),
GroupCode::MathShift => self.printer.print_char('$' as u32),
_ => self.print_esc_str("right"),
}
self.help(&[
"I've deleted a group-closing symbol because it seems to be",
"spurious, as in `$x}$'. But perhaps the } is legitimate and",
"you forgot something else, as in `\\hbox{$x}'. In such cases",
"the way to recover is to insert both the forgotten and the",
"deleted material, e.g., by typing `I$}'.",
]);
self.error();
self.input.align_state += 1;
}
pub fn off_save(&mut self) {
if self.save.cur_group == GroupCode::BottomLevel {
self.print_err("Extra ");
self.print_cmd_chr(self.cur_cmd, self.cur_chr);
self.help(&["Things are pretty mixed up, but I think the worst is over."]);
self.error();
return;
}
self.back_input();
let mut list: Vec<Token> = Vec::new();
self.print_err("Missing ");
match self.save.cur_group {
GroupCode::SemiSimple => {
list.push(Token::cs(self.cs.frozen_end_group));
self.print_esc_str("endgroup");
}
GroupCode::MathShift => {
list.push(Token::char(Cmd::MathShift, '$' as u32));
self.printer.print_char('$' as u32);
}
GroupCode::MathLeft => {
list.push(Token::cs(self.cs.frozen_right));
list.push(Token::other('.' as u32));
self.print_esc_str("right.");
}
_ => {
list.push(Token::RIGHT_BRACE);
self.printer.print_char('}' as u32);
}
}
self.printer.print_str(" inserted");
self.ins_list(Rc::from(list));
self.help(&[
"I've inserted something that you may have forgotten.",
"(See the <inserted text> above.)",
"With luck, this will get me unwedged. But if you",
"really didn't forget anything, try typing `2' now; then",
"my insertion and my current dilemma will both disappear.",
]);
self.error();
}
fn issue_message(&mut self) {
let c = self.cur_chr;
let toks = self.scan_toks_expanded();
let s = self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), 10_000_000));
if c == 0 {
if self.printer.term_offset + s.len() > self.printer.max_print_line.saturating_sub(2) {
self.printer.print_ln();
} else if self.printer.term_offset > 0 || self.printer.file_offset > 0 {
self.printer.print_char(' ' as u32);
}
self.printer.print_bytes(&s);
self.printer.update_terminal();
} else {
self.print_err("");
self.printer.print_bytes(&s);
let err_help = self.eqtb.toks_param(pg::assign_toks::ERRHELP);
if !err_help.is_empty() {
self.err.use_err_help = true;
} else if self.long_help_seen {
self.help(&["(That was another \\errmessage.)"]);
} else {
if self.err.interaction < crate::error::Interaction::ErrorStop {
self.long_help_seen = true;
}
self.help(&[
"This error message was generated by an \\errmessage",
"command, so I can't give any explicit help.",
"Pretend that you're Hercule Poirot: Examine all clues,",
"and deduce the truth by order and method.",
]);
}
self.error();
self.err.use_err_help = false;
}
}
fn shift_case(&mut self) {
let b = self.cur_chr;
let toks = self.scan_general_text();
let upper = b == pg::def_char_code::UCCODE + UC_LC_OFFSET;
let mut out: Vec<Token> = Vec::with_capacity(toks.len());
for &t in toks.iter() {
if t.is_cs() {
if let crate::hash::CsKind::Active(c) = *self.hash.kind(t.cs_id()) {
let m = self.case_map(c, upper);
if m != 0 {
out.push(Token::cs(self.hash.active(m)));
continue;
}
}
out.push(t);
} else {
let c = t.chr();
let m = self.case_map(c, upper);
if m != 0 {
out.push(Token::char(t.cmd(), m));
} else {
out.push(t);
}
}
}
self.back_list(Rc::from(out));
}
fn case_map(&self, c: u32, upper: bool) -> u32 {
(if upper {
self.eqtb.uc.get(c)
} else {
self.eqtb.lc.get(c)
}) as u32
}
pub fn final_cleanup(&mut self) {
let c = self.cur_chr;
if c != 1 {
self.eqtb.int_par[pg::assign_int::NEWLINECHAR as usize].val = -1;
self.printer.new_line_char = -1;
}
if self.job_name.is_none() {
self.open_log_file();
}
while self.input.stack.len() > 1 {
match self.input.stack.last() {
Some(Level::Tokens(_)) => self.end_token_list(),
_ => self.end_file_reading(),
}
}
while self.input.open_parens > 0 {
self.printer.print_char(')' as u32);
self.input.open_parens -= 1;
}
if self.save.cur_level > crate::eqtb::LEVEL_ONE {
self.printer.print_nl("(");
self.print_esc_str("end occurred ");
self.printer.print_str("inside a group at level ");
self.printer
.print_int(i64::from(self.save.cur_level - crate::eqtb::LEVEL_ONE));
self.printer.print_char(')' as u32);
self.show_save_groups();
}
while !self.cond.stack.is_empty() {
self.printer.print_nl("(");
self.print_esc_str("end occurred ");
self.printer.print_str("when ");
self.print_cmd_chr(Cmd::IfTest, self.cond.cur_if);
if self.cond.if_line != 0 {
self.printer.print_str(" on line ");
self.printer.print_int(self.cond.if_line.into());
}
self.printer.print_str(" was incomplete)");
self.pop_cond();
}
if self.err.history != crate::error::History::Spotless
&& (self.err.history == crate::error::History::WarningIssued
|| self.err.interaction < crate::error::Interaction::ErrorStop)
&& self.printer.selector == crate::print::Selector::TermAndLog
{
self.printer.selector = crate::print::Selector::TermOnly;
self.printer
.print_nl("(see the transcript file for additional information)");
self.printer.selector = crate::print::Selector::TermAndLog;
}
if c == 1 {
if self.ini_version {
self.dump_requested = true;
return;
}
self.printer
.print_nl("(\\dump is performed only by INITEX)");
}
}
fn partoken_name(&mut self) {
self.get_token();
if self.cur_cs != NO_CS {
self.par_token_cs = self.cur_cs;
}
}
}
pub const UC_LC_OFFSET: u32 = 333_280;
pub enum Next {
BigSwitch,
Reswitch,
Return,
}