1use std::rc::Rc;
9
10use crate::Engine;
11use crate::cmd::Cmd;
12use crate::hash::NO_CS;
13use crate::input::{Level, TokKind};
14use crate::nodes::NodeRef;
15use crate::primitives_gen as pg;
16use crate::save::GroupCode;
17use crate::token::Token;
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub enum Mode {
22 None,
24 Vertical,
26 InternalVertical,
28 Horizontal,
30 RestrictedHorizontal,
32 DisplayMath,
34 Math,
36}
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub enum ModeKind {
41 None,
43 Vertical,
45 Horizontal,
47 Math,
49}
50
51impl Mode {
52 pub fn abs_kind(self) -> ModeKind {
54 match self {
55 Mode::None => ModeKind::None,
56 Mode::Vertical | Mode::InternalVertical => ModeKind::Vertical,
57 Mode::Horizontal | Mode::RestrictedHorizontal => ModeKind::Horizontal,
58 Mode::DisplayMath | Mode::Math => ModeKind::Math,
59 }
60 }
61
62 pub fn is_inner(self) -> bool {
64 matches!(
65 self,
66 Mode::InternalVertical | Mode::RestrictedHorizontal | Mode::Math
67 )
68 }
69
70 pub fn is_privileged(self) -> bool {
72 matches!(self, Mode::Vertical | Mode::Horizontal | Mode::DisplayMath)
73 }
74}
75
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub enum BoxContext {
79 Shift(i32),
82 SetBox(u32),
84 GlobalSetBox(u32),
86 Shipout,
88 Leaders(u16),
90 Lua,
92}
93
94#[derive(Clone, Debug)]
96pub struct ListState {
97 pub mode: Mode,
99 pub head: NodeRef,
101 pub tail: NodeRef,
103 pub prev_graf: i32,
105 pub mode_line: i32,
108 pub prev_depth: i32,
110 pub space_factor: i32,
112 pub clang: i32,
114 pub incompleat_noad: NodeRef,
116 pub math_style: i32,
118 pub delim_ptr: NodeRef,
121}
122
123pub const IGNORE_DEPTH: i32 = -65_536_000;
126
127impl Engine {
128 pub fn cur_list(&self) -> &ListState {
130 self.nest.last().expect("nest is never empty")
131 }
132
133 pub fn cur_list_mut(&mut self) -> &mut ListState {
135 self.nest.last_mut().expect("nest is never empty")
136 }
137
138 pub fn push_nest(&mut self, mode: Mode) {
140 let head = self.nodes.new_temp();
141 let cl = ListState {
142 mode,
143 head,
144 tail: head,
145 prev_graf: 0,
146 mode_line: self.input.line(),
147 prev_depth: IGNORE_DEPTH,
148 space_factor: 1000,
149 clang: 0,
150 incompleat_noad: NodeRef::NULL,
151 math_style: 0,
152 delim_ptr: NodeRef::NULL,
153 };
154 self.nest.push(cl);
155 self.mode = mode;
156 }
157
158 pub fn pop_nest(&mut self) {
160 if let Some(cl) = self.nest.pop() {
161 self.nodes.free(cl.head);
162 }
163 self.mode = self.cur_list().mode;
164 }
165
166 pub fn tail_append(&mut self, p: NodeRef) {
168 let tail = self.cur_list().tail;
169 self.nodes.link(tail, p);
170 self.cur_list_mut().tail = p;
171 }
172
173 pub fn print_mode(&mut self, m: Mode) {
175 let s = match m {
176 Mode::Vertical => "vertical mode",
177 Mode::Horizontal => "horizontal mode",
178 Mode::DisplayMath => "display math mode",
179 Mode::None => "no mode",
180 Mode::InternalVertical => "internal vertical mode",
181 Mode::RestrictedHorizontal => "restricted horizontal mode",
182 Mode::Math => "math mode",
183 };
184 self.printer.print_str(s);
185 }
186
187 pub fn main_control(&mut self) {
190 let every_job = self.eqtb.toks_param(pg::assign_toks::EVERYJOB).clone();
191 if !every_job.is_empty() {
192 self.begin_token_list(every_job, TokKind::EveryJobText);
193 }
194 self.main_loop();
195 }
196
197 pub(crate) fn resume_main_loop(&mut self) {
200 self.main_loop();
201 }
202
203 pub fn run_local(&mut self, list: Option<crate::token::TokenList>) {
207 let end: crate::token::TokenList = Rc::from(vec![Token::cs(self.cs.end_local)]);
208 let depth = self.input.stack.len();
210 self.begin_token_list(end, TokKind::Inserted);
211 if let Some(l) = list {
212 self.begin_token_list(l, TokKind::Inserted);
213 }
214 let _ = depth;
215 self.local_level += 1;
216 self.main_loop();
217 self.local_level -= 1;
218 }
219
220 pub fn push_local_marker(&mut self) {
224 let end: crate::token::TokenList = Rc::from(vec![Token::cs(self.cs.end_local)]);
225 self.begin_token_list(end, TokKind::Inserted);
226 }
227
228 pub fn scan_list(&mut self) -> NodeRef {
232 self.lua_scanned_box = None;
233 let level = self.save.cur_level;
234 self.scan_box(BoxContext::Lua);
235 if self.lua_scanned_box.is_none() && self.save.cur_level > level {
236 let waiting = std::mem::replace(&mut self.lua_scan_waiting, true);
237 self.run_local_loop();
238 self.lua_scan_waiting = waiting;
239 }
240 self.lua_scanned_box.take().unwrap_or(NodeRef::NULL)
241 }
242
243 pub fn run_local_loop(&mut self) {
245 self.local_level += 1;
246 self.main_loop();
247 self.local_level -= 1;
248 }
249
250 #[inline]
253 pub fn check_double_free(&mut self) {
254 if let Some((t, p)) = self.nodes.double_free {
255 let name = crate::nodes::type_name(t);
256 self.luatex_fatal(
257 "nodes",
258 &format!("attempt to double-free {name} node {p}, ignored"),
259 );
260 }
261 }
262
263 fn main_loop(&mut self) {
264 loop {
265 if self.loop_hook.is_some()
267 && self.local_level == 0
268 && let Some(h) = self.loop_hook.take()
269 {
270 h(self);
271 }
272 self.profile_tick();
273 self.exec_context = true;
274 self.get_x_token();
275 self.exec_context = false;
276 loop {
277 if self.eqtb.int(pg::assign_int::TRACINGCOMMANDS) > 0 {
279 self.show_cur_cmd_chr();
280 }
281 match self.dispatch() {
282 Next::BigSwitch => {
283 self.check_double_free();
284 break;
285 }
286 Next::Reswitch => continue,
287 Next::Return => return,
288 }
289 }
290 }
291 }
292
293 fn dispatch(&mut self) -> Next {
295 use ModeKind as K;
296 let kind = self.mode.abs_kind();
297 let cmd = self.cur_cmd;
298 match (kind, cmd) {
299 (K::Horizontal, Cmd::Letter | Cmd::OtherChar | Cmd::CharGiven) => {
300 self.main_loop_char();
301 Next::Reswitch
302 }
303 (K::Horizontal, Cmd::CharNum) => {
304 self.scan_char_num();
305 self.cur_chr = self.cur_val as u32;
306 self.main_loop_char();
307 Next::Reswitch
308 }
309 (K::Horizontal, Cmd::Boundary) => {
310 self.append_boundary();
311 Next::BigSwitch
312 }
313 (K::Horizontal, Cmd::Spacer) => {
314 self.app_space_main();
315 Next::BigSwitch
316 }
317 (K::Horizontal | K::Math, Cmd::ExSpace) => {
318 self.app_explicit_space();
319 Next::BigSwitch
320 }
321 (K::Vertical, Cmd::Spacer) if self.local_level > 0 => {
325 self.append_normal_space();
326 Next::BigSwitch
327 }
328 (_, Cmd::Relax) | (K::Vertical | K::Math, Cmd::Spacer) => Next::BigSwitch,
329 (K::Math, Cmd::Boundary) => Next::BigSwitch,
330 (_, Cmd::IgnoreSpaces) => {
331 if self.cur_chr == 0 {
332 self.exec_context = true;
335 self.get_nonblank_noncall();
336 self.exec_context = false;
337 Next::Reswitch
338 } else {
339 Next::BigSwitch
341 }
342 }
343 (K::Vertical, Cmd::Stop) => {
344 if self.its_all_over() {
345 Next::Return
346 } else {
347 Next::BigSwitch
348 }
349 }
350 (K::Vertical, Cmd::Vmove)
351 | (K::Horizontal | K::Math, Cmd::Hmove)
352 | (_, Cmd::LastItem)
353 | (_, Cmd::MacParam) => {
354 self.report_illegal_case();
355 Next::BigSwitch
356 }
357 (_, c) if c > Cmd::MAX_NON_PREFIXED => {
358 self.prefixed_command();
359 Next::BigSwitch
360 }
361 (_, Cmd::BeginGroup) => {
362 self.new_save_level(GroupCode::SemiSimple);
363 self.reset_local_counts(true);
364 Next::BigSwitch
365 }
366 (_, Cmd::EndGroup) => {
367 if self.save.cur_group == GroupCode::SemiSimple {
368 self.end_group_text_dirs();
369 self.unsave();
370 } else {
371 self.off_save();
372 }
373 Next::BigSwitch
374 }
375 (_, Cmd::LeftBrace) if kind != K::Math => {
376 self.new_save_level(GroupCode::Simple);
377 self.reset_local_counts(true);
378 Next::BigSwitch
379 }
380 (_, Cmd::RightBrace) => {
381 self.handle_right_brace();
382 Next::BigSwitch
383 }
384 (_, Cmd::AfterAssignment) => {
385 self.get_token();
386 self.after_token = self.cur_tok;
387 Next::BigSwitch
388 }
389 (_, Cmd::AfterGroup) => {
390 self.get_token();
391 self.save_for_after(self.cur_tok);
392 Next::BigSwitch
393 }
394 (_, Cmd::InStream) => {
395 self.open_or_close_in();
396 Next::BigSwitch
397 }
398 (_, Cmd::Message) => {
399 self.issue_message();
400 Next::BigSwitch
401 }
402 (_, Cmd::CaseShift) => {
403 self.shift_case();
404 Next::BigSwitch
405 }
406 (_, Cmd::Xray) => {
407 self.show_whatever();
408 Next::BigSwitch
409 }
410 (_, Cmd::Extension)
411 if self.cur_chr == crate::misc::ext::END_LOCAL_CONTROL && self.local_level > 0 =>
412 {
413 Next::Return
414 }
415 (_, Cmd::Extension) => {
416 self.do_extension();
417 Next::BigSwitch
418 }
419 (_, Cmd::Normal) => {
420 self.do_normal();
421 Next::BigSwitch
422 }
423 (_, Cmd::LuaFunctionCall | Cmd::LuaBytecodeCall | Cmd::LuaCall) => {
424 let n = if self.cur_cmd == Cmd::LuaFunctionCall
425 || self.cur_cmd == Cmd::LuaBytecodeCall
426 {
427 self.scan_int();
428 self.cur_val
429 } else {
430 self.cur_chr as i32
431 };
432 if self.cur_cmd == Cmd::LuaBytecodeCall {
433 self.call_lua_bytecode(n);
434 } else {
435 self.call_lua_function(n);
436 }
437 Next::BigSwitch
438 }
439 (_, Cmd::PartokenName) => {
440 self.partoken_name();
441 Next::BigSwitch
442 }
443 _ => {
444 self.typeset_command();
445 Next::BigSwitch
446 }
447 }
448 }
449
450 pub fn report_illegal_case(&mut self) {
453 self.you_cant();
454 self.help(&[
455 "Sorry, but I'm not programmed to handle this case;",
456 "I'll just pretend that you didn't ask for it.",
457 "If you're in the wrong mode, you might be able to",
458 "return to the right one by typing `I}' or `I$' or `I\\par'.",
459 ]);
460 self.error();
461 }
462
463 pub fn you_cant(&mut self) {
465 self.print_err("You can't use `");
466 self.print_cmd_chr(self.cur_cmd, self.cur_chr);
467 self.printer.print_str("' in ");
468 self.print_mode(self.mode);
469 }
470
471 pub fn privileged(&mut self) -> bool {
474 if self.mode.is_privileged() {
475 true
476 } else {
477 self.report_illegal_case();
478 false
479 }
480 }
481
482 pub fn insert_dollar_sign(&mut self) {
484 self.back_input();
485 self.cur_tok = Token::char(Cmd::MathShift, '$' as u32);
486 self.print_err("Missing $ inserted");
487 self.help(&[
488 "I've inserted a begin-math/end-math symbol since I think",
489 "you left one out. Proceed, with fingers crossed.",
490 ]);
491 self.ins_error();
492 }
493
494 fn its_all_over(&mut self) -> bool {
497 if self.privileged() {
498 let cl = self.cur_list();
499 if self.page_is_empty() && cl.head == cl.tail && self.dead_cycles == 0 {
500 return true;
501 }
502 self.back_input();
503 let b = self.nodes.new_null_box(true);
504 let hsize = self.eqtb.dimen(pg::assign_dimen::HSIZE);
505 if let Some(bd) = self.nodes.box_data_mut(b) {
506 bd.width = hsize;
507 }
508 self.tail_append(b);
509 let g = self.nodes.new_glue(
510 crate::glue::Glue {
511 stretch: crate::glue::Glue::fixed(0).stretch + retex_arith::UNITY,
512 stretch_order: crate::glue::FILL,
513 ..crate::glue::Glue::ZERO
514 },
515 0,
516 );
517 self.tail_append(g);
518 let p = self.nodes.new_penalty(-0o10000000000, 0);
519 self.tail_append(p);
520 self.build_page_from("end");
521 }
522 false
523 }
524
525 pub fn handle_right_brace(&mut self) {
527 match self.save.cur_group {
528 GroupCode::Simple => {
529 self.end_group_text_dirs();
530 self.unsave();
531 }
532 GroupCode::BottomLevel => {
533 self.print_err("Too many }'s");
534 self.help(&[
535 "You've closed more groups than you opened.",
536 "Such booboos are generally harmless, so keep going.",
537 ]);
538 self.error();
539 }
540 GroupCode::SemiSimple | GroupCode::MathShift | GroupCode::MathLeft => {
541 self.extra_right_brace()
542 }
543 _ => self.handle_right_brace_typesetting(),
544 }
545 }
546
547 fn extra_right_brace(&mut self) {
548 self.print_err("Extra }, or forgotten ");
549 match self.save.cur_group {
550 GroupCode::SemiSimple => self.print_esc_str("endgroup"),
551 GroupCode::MathShift => self.printer.print_char('$' as u32),
552 _ => self.print_esc_str("right"),
553 }
554 self.help(&[
555 "I've deleted a group-closing symbol because it seems to be",
556 "spurious, as in `$x}$'. But perhaps the } is legitimate and",
557 "you forgot something else, as in `\\hbox{$x}'. In such cases",
558 "the way to recover is to insert both the forgotten and the",
559 "deleted material, e.g., by typing `I$}'.",
560 ]);
561 self.error();
562 self.input.align_state += 1;
563 }
564
565 pub fn off_save(&mut self) {
567 if self.save.cur_group == GroupCode::BottomLevel {
568 self.print_err("Extra ");
570 self.print_cmd_chr(self.cur_cmd, self.cur_chr);
571 self.help(&["Things are pretty mixed up, but I think the worst is over."]);
572 self.error();
573 return;
574 }
575 self.back_input();
576 let mut list: Vec<Token> = Vec::new();
577 self.print_err("Missing ");
578 match self.save.cur_group {
579 GroupCode::SemiSimple => {
580 list.push(Token::cs(self.cs.frozen_end_group));
581 self.print_esc_str("endgroup");
582 }
583 GroupCode::MathShift => {
584 list.push(Token::char(Cmd::MathShift, '$' as u32));
585 self.printer.print_char('$' as u32);
586 }
587 GroupCode::MathLeft => {
588 list.push(Token::cs(self.cs.frozen_right));
589 list.push(Token::other('.' as u32));
590 self.print_esc_str("right.");
591 }
592 _ => {
593 list.push(Token::RIGHT_BRACE);
594 self.printer.print_char('}' as u32);
595 }
596 }
597 self.printer.print_str(" inserted");
598 self.ins_list(Rc::from(list));
599 self.help(&[
600 "I've inserted something that you may have forgotten.",
601 "(See the <inserted text> above.)",
602 "With luck, this will get me unwedged. But if you",
603 "really didn't forget anything, try typing `2' now; then",
604 "my insertion and my current dilemma will both disappear.",
605 ]);
606 self.error();
607 }
608
609 fn issue_message(&mut self) {
611 let c = self.cur_chr;
612 let toks = self.scan_toks_expanded();
613 let s = self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), 10_000_000));
614 if c == 0 {
615 self.record_note(&String::from_utf8_lossy(&s));
616 if self.printer.term_offset + s.len() > self.printer.max_print_line.saturating_sub(2) {
617 self.printer.print_ln();
618 } else if self.printer.term_offset > 0 || self.printer.file_offset > 0 {
619 self.printer.print_char(' ' as u32);
620 }
621 self.printer.print_bytes(&s);
622 self.printer.update_terminal();
623 } else {
624 self.print_err("");
625 self.printer.print_bytes(&s);
626 let err_help = self.eqtb.toks_param(pg::assign_toks::ERRHELP);
627 if !err_help.is_empty() {
628 self.err.use_err_help = true;
629 } else if self.long_help_seen {
630 self.help(&["(That was another \\errmessage.)"]);
631 } else {
632 if self.err.interaction < crate::error::Interaction::ErrorStop {
633 self.long_help_seen = true;
634 }
635 self.help(&[
636 "This error message was generated by an \\errmessage",
637 "command, so I can't give any explicit help.",
638 "Pretend that you're Hercule Poirot: Examine all clues,",
639 "and deduce the truth by order and method.",
640 ]);
641 }
642 self.error();
643 self.err.use_err_help = false;
644 }
645 }
646
647 fn shift_case(&mut self) {
649 let b = self.cur_chr;
650 let toks = self.scan_general_text();
651 let upper = b == pg::def_char_code::UCCODE + UC_LC_OFFSET;
652 let mut out: Vec<Token> = Vec::with_capacity(toks.len());
653 for &t in toks.iter() {
654 if t.is_cs() {
655 if let crate::hash::CsKind::Active(c) = *self.hash.kind(t.cs_id()) {
656 let m = self.case_map(c, upper);
657 if m != 0 {
658 out.push(Token::cs(self.hash.active(m)));
659 continue;
660 }
661 }
662 out.push(t);
663 } else {
664 let c = t.chr();
665 let m = self.case_map(c, upper);
666 if m != 0 {
667 out.push(Token::char(t.cmd(), m));
668 } else {
669 out.push(t);
670 }
671 }
672 }
673 self.back_list(Rc::from(out));
674 }
675
676 fn case_map(&self, c: u32, upper: bool) -> u32 {
677 (if upper {
678 self.eqtb.uc.get(c)
679 } else {
680 self.eqtb.lc.get(c)
681 }) as u32
682 }
683
684 pub fn final_cleanup(&mut self) {
686 if self.figures.report {
687 eprint!("{}", self.figures.summary());
688 }
689 let c = self.cur_chr;
690 if c != 1 {
691 self.eqtb.int_par[pg::assign_int::NEWLINECHAR as usize].val = -1;
692 self.printer.new_line_char = -1;
693 }
694 if self.job_name.is_none() {
695 self.open_log_file();
696 }
697 while self.input.stack.len() > 1 {
698 match self.input.stack.last() {
699 Some(Level::Tokens(_)) => self.end_token_list(),
700 _ => self.end_file_reading(),
701 }
702 }
703 while self.input.open_parens > 0 {
704 self.printer.print_char(')' as u32);
706 self.input.open_parens -= 1;
707 }
708 if self.save.cur_level > crate::eqtb::LEVEL_ONE {
709 self.printer.print_nl("(");
710 self.print_esc_str("end occurred ");
711 self.printer.print_str("inside a group at level ");
712 self.printer
713 .print_int(i64::from(self.save.cur_level - crate::eqtb::LEVEL_ONE));
714 self.printer.print_char(')' as u32);
715 self.show_save_groups();
716 }
717 while !self.cond.stack.is_empty() {
718 self.printer.print_nl("(");
719 self.print_esc_str("end occurred ");
720 self.printer.print_str("when ");
721 self.print_cmd_chr(Cmd::IfTest, self.cond.cur_if);
722 if self.cond.if_line != 0 {
723 self.printer.print_str(" on line ");
724 self.printer.print_int(self.cond.if_line.into());
725 }
726 self.printer.print_str(" was incomplete)");
727 self.pop_cond();
728 }
729 if self.err.history != crate::error::History::Spotless
730 && (self.err.history == crate::error::History::WarningIssued
731 || self.err.interaction < crate::error::Interaction::ErrorStop)
732 && self.printer.selector == crate::print::Selector::TermAndLog
733 {
734 self.printer.selector = crate::print::Selector::TermOnly;
735 self.printer
736 .print_nl("(see the transcript file for additional information)");
737 self.printer.selector = crate::print::Selector::TermAndLog;
738 }
739 if c == 1 {
740 if self.ini_version {
741 self.dump_requested = true;
742 return;
743 }
744 self.printer
745 .print_nl("(\\dump is performed only by INITEX)");
746 }
747 }
748
749 fn partoken_name(&mut self) {
752 self.get_token();
753 if self.cur_cs != NO_CS {
754 self.par_token_cs = self.cur_cs;
755 }
756 }
757}
758
759pub const UC_LC_OFFSET: u32 = 333_280;
762
763pub enum Next {
765 BigSwitch,
767 Reswitch,
769 Return,
771}