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 fn run_local(&mut self, list: Option<crate::token::TokenList>) {
201 let end: crate::token::TokenList = Rc::from(vec![Token::cs(self.cs.end_local)]);
202 let depth = self.input.stack.len();
204 self.begin_token_list(end, TokKind::Inserted);
205 if let Some(l) = list {
206 self.begin_token_list(l, TokKind::Inserted);
207 }
208 let _ = depth;
209 self.local_level += 1;
210 self.main_loop();
211 self.local_level -= 1;
212 }
213
214 pub fn push_local_marker(&mut self) {
218 let end: crate::token::TokenList = Rc::from(vec![Token::cs(self.cs.end_local)]);
219 self.begin_token_list(end, TokKind::Inserted);
220 }
221
222 pub fn scan_list(&mut self) -> NodeRef {
226 self.lua_scanned_box = None;
227 let level = self.save.cur_level;
228 self.scan_box(BoxContext::Lua);
229 if self.lua_scanned_box.is_none() && self.save.cur_level > level {
230 let waiting = std::mem::replace(&mut self.lua_scan_waiting, true);
231 self.run_local_loop();
232 self.lua_scan_waiting = waiting;
233 }
234 self.lua_scanned_box.take().unwrap_or(NodeRef::NULL)
235 }
236
237 pub fn run_local_loop(&mut self) {
239 self.local_level += 1;
240 self.main_loop();
241 self.local_level -= 1;
242 }
243
244 #[inline]
247 pub fn check_double_free(&mut self) {
248 if let Some((t, p)) = self.nodes.double_free {
249 let name = crate::nodes::type_name(t);
250 self.luatex_fatal(
251 "nodes",
252 &format!("attempt to double-free {name} node {p}, ignored"),
253 );
254 }
255 }
256
257 fn main_loop(&mut self) {
258 loop {
259 self.profile_tick();
261 self.exec_context = true;
262 self.get_x_token();
263 self.exec_context = false;
264 loop {
265 if self.eqtb.int(pg::assign_int::TRACINGCOMMANDS) > 0 {
267 self.show_cur_cmd_chr();
268 }
269 match self.dispatch() {
270 Next::BigSwitch => {
271 self.check_double_free();
272 break;
273 }
274 Next::Reswitch => continue,
275 Next::Return => return,
276 }
277 }
278 }
279 }
280
281 fn dispatch(&mut self) -> Next {
283 use ModeKind as K;
284 let kind = self.mode.abs_kind();
285 let cmd = self.cur_cmd;
286 match (kind, cmd) {
287 (K::Horizontal, Cmd::Letter | Cmd::OtherChar | Cmd::CharGiven) => {
288 self.main_loop_char();
289 Next::Reswitch
290 }
291 (K::Horizontal, Cmd::CharNum) => {
292 self.scan_char_num();
293 self.cur_chr = self.cur_val as u32;
294 self.main_loop_char();
295 Next::Reswitch
296 }
297 (K::Horizontal, Cmd::Boundary) => {
298 self.append_boundary();
299 Next::BigSwitch
300 }
301 (K::Horizontal, Cmd::Spacer) => {
302 self.app_space_main();
303 Next::BigSwitch
304 }
305 (K::Horizontal | K::Math, Cmd::ExSpace) => {
306 self.app_explicit_space();
307 Next::BigSwitch
308 }
309 (K::Vertical, Cmd::Spacer) if self.local_level > 0 => {
313 self.append_normal_space();
314 Next::BigSwitch
315 }
316 (_, Cmd::Relax) | (K::Vertical | K::Math, Cmd::Spacer) => Next::BigSwitch,
317 (K::Math, Cmd::Boundary) => Next::BigSwitch,
318 (_, Cmd::IgnoreSpaces) => {
319 if self.cur_chr == 0 {
320 self.exec_context = true;
323 self.get_nonblank_noncall();
324 self.exec_context = false;
325 Next::Reswitch
326 } else {
327 Next::BigSwitch
329 }
330 }
331 (K::Vertical, Cmd::Stop) => {
332 if self.its_all_over() {
333 Next::Return
334 } else {
335 Next::BigSwitch
336 }
337 }
338 (K::Vertical, Cmd::Vmove)
339 | (K::Horizontal | K::Math, Cmd::Hmove)
340 | (_, Cmd::LastItem)
341 | (_, Cmd::MacParam) => {
342 self.report_illegal_case();
343 Next::BigSwitch
344 }
345 (_, c) if c > Cmd::MAX_NON_PREFIXED => {
346 self.prefixed_command();
347 Next::BigSwitch
348 }
349 (_, Cmd::BeginGroup) => {
350 self.new_save_level(GroupCode::SemiSimple);
351 self.reset_local_counts(true);
352 Next::BigSwitch
353 }
354 (_, Cmd::EndGroup) => {
355 if self.save.cur_group == GroupCode::SemiSimple {
356 self.end_group_text_dirs();
357 self.unsave();
358 } else {
359 self.off_save();
360 }
361 Next::BigSwitch
362 }
363 (_, Cmd::LeftBrace) if kind != K::Math => {
364 self.new_save_level(GroupCode::Simple);
365 self.reset_local_counts(true);
366 Next::BigSwitch
367 }
368 (_, Cmd::RightBrace) => {
369 self.handle_right_brace();
370 Next::BigSwitch
371 }
372 (_, Cmd::AfterAssignment) => {
373 self.get_token();
374 self.after_token = self.cur_tok;
375 Next::BigSwitch
376 }
377 (_, Cmd::AfterGroup) => {
378 self.get_token();
379 self.save_for_after(self.cur_tok);
380 Next::BigSwitch
381 }
382 (_, Cmd::InStream) => {
383 self.open_or_close_in();
384 Next::BigSwitch
385 }
386 (_, Cmd::Message) => {
387 self.issue_message();
388 Next::BigSwitch
389 }
390 (_, Cmd::CaseShift) => {
391 self.shift_case();
392 Next::BigSwitch
393 }
394 (_, Cmd::Xray) => {
395 self.show_whatever();
396 Next::BigSwitch
397 }
398 (_, Cmd::Extension)
399 if self.cur_chr == crate::misc::ext::END_LOCAL_CONTROL && self.local_level > 0 =>
400 {
401 Next::Return
402 }
403 (_, Cmd::Extension) => {
404 self.do_extension();
405 Next::BigSwitch
406 }
407 (_, Cmd::Normal) => {
408 self.do_normal();
409 Next::BigSwitch
410 }
411 (_, Cmd::LuaFunctionCall | Cmd::LuaBytecodeCall | Cmd::LuaCall) => {
412 let n = if self.cur_cmd == Cmd::LuaFunctionCall
413 || self.cur_cmd == Cmd::LuaBytecodeCall
414 {
415 self.scan_int();
416 self.cur_val
417 } else {
418 self.cur_chr as i32
419 };
420 if self.cur_cmd == Cmd::LuaBytecodeCall {
421 self.call_lua_bytecode(n);
422 } else {
423 self.call_lua_function(n);
424 }
425 Next::BigSwitch
426 }
427 (_, Cmd::PartokenName) => {
428 self.partoken_name();
429 Next::BigSwitch
430 }
431 _ => {
432 self.typeset_command();
433 Next::BigSwitch
434 }
435 }
436 }
437
438 pub fn report_illegal_case(&mut self) {
441 self.you_cant();
442 self.help(&[
443 "Sorry, but I'm not programmed to handle this case;",
444 "I'll just pretend that you didn't ask for it.",
445 "If you're in the wrong mode, you might be able to",
446 "return to the right one by typing `I}' or `I$' or `I\\par'.",
447 ]);
448 self.error();
449 }
450
451 pub fn you_cant(&mut self) {
453 self.print_err("You can't use `");
454 self.print_cmd_chr(self.cur_cmd, self.cur_chr);
455 self.printer.print_str("' in ");
456 self.print_mode(self.mode);
457 }
458
459 pub fn privileged(&mut self) -> bool {
462 if self.mode.is_privileged() {
463 true
464 } else {
465 self.report_illegal_case();
466 false
467 }
468 }
469
470 pub fn insert_dollar_sign(&mut self) {
472 self.back_input();
473 self.cur_tok = Token::char(Cmd::MathShift, '$' as u32);
474 self.print_err("Missing $ inserted");
475 self.help(&[
476 "I've inserted a begin-math/end-math symbol since I think",
477 "you left one out. Proceed, with fingers crossed.",
478 ]);
479 self.ins_error();
480 }
481
482 fn its_all_over(&mut self) -> bool {
485 if self.privileged() {
486 let cl = self.cur_list();
487 if self.page_is_empty() && cl.head == cl.tail && self.dead_cycles == 0 {
488 return true;
489 }
490 self.back_input();
491 let b = self.nodes.new_null_box(true);
492 let hsize = self.eqtb.dimen(pg::assign_dimen::HSIZE);
493 if let Some(bd) = self.nodes.box_data_mut(b) {
494 bd.width = hsize;
495 }
496 self.tail_append(b);
497 let g = self.nodes.new_glue(
498 crate::glue::Glue {
499 stretch: crate::glue::Glue::fixed(0).stretch + retex_arith::UNITY,
500 stretch_order: crate::glue::FILL,
501 ..crate::glue::Glue::ZERO
502 },
503 0,
504 );
505 self.tail_append(g);
506 let p = self.nodes.new_penalty(-0o10000000000, 0);
507 self.tail_append(p);
508 self.build_page_from("end");
509 }
510 false
511 }
512
513 pub fn handle_right_brace(&mut self) {
515 match self.save.cur_group {
516 GroupCode::Simple => {
517 self.end_group_text_dirs();
518 self.unsave();
519 }
520 GroupCode::BottomLevel => {
521 self.print_err("Too many }'s");
522 self.help(&[
523 "You've closed more groups than you opened.",
524 "Such booboos are generally harmless, so keep going.",
525 ]);
526 self.error();
527 }
528 GroupCode::SemiSimple | GroupCode::MathShift | GroupCode::MathLeft => {
529 self.extra_right_brace()
530 }
531 _ => self.handle_right_brace_typesetting(),
532 }
533 }
534
535 fn extra_right_brace(&mut self) {
536 self.print_err("Extra }, or forgotten ");
537 match self.save.cur_group {
538 GroupCode::SemiSimple => self.print_esc_str("endgroup"),
539 GroupCode::MathShift => self.printer.print_char('$' as u32),
540 _ => self.print_esc_str("right"),
541 }
542 self.help(&[
543 "I've deleted a group-closing symbol because it seems to be",
544 "spurious, as in `$x}$'. But perhaps the } is legitimate and",
545 "you forgot something else, as in `\\hbox{$x}'. In such cases",
546 "the way to recover is to insert both the forgotten and the",
547 "deleted material, e.g., by typing `I$}'.",
548 ]);
549 self.error();
550 self.input.align_state += 1;
551 }
552
553 pub fn off_save(&mut self) {
555 if self.save.cur_group == GroupCode::BottomLevel {
556 self.print_err("Extra ");
558 self.print_cmd_chr(self.cur_cmd, self.cur_chr);
559 self.help(&["Things are pretty mixed up, but I think the worst is over."]);
560 self.error();
561 return;
562 }
563 self.back_input();
564 let mut list: Vec<Token> = Vec::new();
565 self.print_err("Missing ");
566 match self.save.cur_group {
567 GroupCode::SemiSimple => {
568 list.push(Token::cs(self.cs.frozen_end_group));
569 self.print_esc_str("endgroup");
570 }
571 GroupCode::MathShift => {
572 list.push(Token::char(Cmd::MathShift, '$' as u32));
573 self.printer.print_char('$' as u32);
574 }
575 GroupCode::MathLeft => {
576 list.push(Token::cs(self.cs.frozen_right));
577 list.push(Token::other('.' as u32));
578 self.print_esc_str("right.");
579 }
580 _ => {
581 list.push(Token::RIGHT_BRACE);
582 self.printer.print_char('}' as u32);
583 }
584 }
585 self.printer.print_str(" inserted");
586 self.ins_list(Rc::from(list));
587 self.help(&[
588 "I've inserted something that you may have forgotten.",
589 "(See the <inserted text> above.)",
590 "With luck, this will get me unwedged. But if you",
591 "really didn't forget anything, try typing `2' now; then",
592 "my insertion and my current dilemma will both disappear.",
593 ]);
594 self.error();
595 }
596
597 fn issue_message(&mut self) {
599 let c = self.cur_chr;
600 let toks = self.scan_toks_expanded();
601 let s = self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), 10_000_000));
602 if c == 0 {
603 self.record_note(&String::from_utf8_lossy(&s));
604 if self.printer.term_offset + s.len() > self.printer.max_print_line.saturating_sub(2) {
605 self.printer.print_ln();
606 } else if self.printer.term_offset > 0 || self.printer.file_offset > 0 {
607 self.printer.print_char(' ' as u32);
608 }
609 self.printer.print_bytes(&s);
610 self.printer.update_terminal();
611 } else {
612 self.print_err("");
613 self.printer.print_bytes(&s);
614 let err_help = self.eqtb.toks_param(pg::assign_toks::ERRHELP);
615 if !err_help.is_empty() {
616 self.err.use_err_help = true;
617 } else if self.long_help_seen {
618 self.help(&["(That was another \\errmessage.)"]);
619 } else {
620 if self.err.interaction < crate::error::Interaction::ErrorStop {
621 self.long_help_seen = true;
622 }
623 self.help(&[
624 "This error message was generated by an \\errmessage",
625 "command, so I can't give any explicit help.",
626 "Pretend that you're Hercule Poirot: Examine all clues,",
627 "and deduce the truth by order and method.",
628 ]);
629 }
630 self.error();
631 self.err.use_err_help = false;
632 }
633 }
634
635 fn shift_case(&mut self) {
637 let b = self.cur_chr;
638 let toks = self.scan_general_text();
639 let upper = b == pg::def_char_code::UCCODE + UC_LC_OFFSET;
640 let mut out: Vec<Token> = Vec::with_capacity(toks.len());
641 for &t in toks.iter() {
642 if t.is_cs() {
643 if let crate::hash::CsKind::Active(c) = *self.hash.kind(t.cs_id()) {
644 let m = self.case_map(c, upper);
645 if m != 0 {
646 out.push(Token::cs(self.hash.active(m)));
647 continue;
648 }
649 }
650 out.push(t);
651 } else {
652 let c = t.chr();
653 let m = self.case_map(c, upper);
654 if m != 0 {
655 out.push(Token::char(t.cmd(), m));
656 } else {
657 out.push(t);
658 }
659 }
660 }
661 self.back_list(Rc::from(out));
662 }
663
664 fn case_map(&self, c: u32, upper: bool) -> u32 {
665 (if upper {
666 self.eqtb.uc.get(c)
667 } else {
668 self.eqtb.lc.get(c)
669 }) as u32
670 }
671
672 pub fn final_cleanup(&mut self) {
674 let c = self.cur_chr;
675 if c != 1 {
676 self.eqtb.int_par[pg::assign_int::NEWLINECHAR as usize].val = -1;
677 self.printer.new_line_char = -1;
678 }
679 if self.job_name.is_none() {
680 self.open_log_file();
681 }
682 while self.input.stack.len() > 1 {
683 match self.input.stack.last() {
684 Some(Level::Tokens(_)) => self.end_token_list(),
685 _ => self.end_file_reading(),
686 }
687 }
688 while self.input.open_parens > 0 {
689 self.printer.print_char(')' as u32);
691 self.input.open_parens -= 1;
692 }
693 if self.save.cur_level > crate::eqtb::LEVEL_ONE {
694 self.printer.print_nl("(");
695 self.print_esc_str("end occurred ");
696 self.printer.print_str("inside a group at level ");
697 self.printer
698 .print_int(i64::from(self.save.cur_level - crate::eqtb::LEVEL_ONE));
699 self.printer.print_char(')' as u32);
700 self.show_save_groups();
701 }
702 while !self.cond.stack.is_empty() {
703 self.printer.print_nl("(");
704 self.print_esc_str("end occurred ");
705 self.printer.print_str("when ");
706 self.print_cmd_chr(Cmd::IfTest, self.cond.cur_if);
707 if self.cond.if_line != 0 {
708 self.printer.print_str(" on line ");
709 self.printer.print_int(self.cond.if_line.into());
710 }
711 self.printer.print_str(" was incomplete)");
712 self.pop_cond();
713 }
714 if self.err.history != crate::error::History::Spotless
715 && (self.err.history == crate::error::History::WarningIssued
716 || self.err.interaction < crate::error::Interaction::ErrorStop)
717 && self.printer.selector == crate::print::Selector::TermAndLog
718 {
719 self.printer.selector = crate::print::Selector::TermOnly;
720 self.printer
721 .print_nl("(see the transcript file for additional information)");
722 self.printer.selector = crate::print::Selector::TermAndLog;
723 }
724 if c == 1 {
725 if self.ini_version {
726 self.dump_requested = true;
727 return;
728 }
729 self.printer
730 .print_nl("(\\dump is performed only by INITEX)");
731 }
732 }
733
734 fn partoken_name(&mut self) {
737 self.get_token();
738 if self.cur_cs != NO_CS {
739 self.par_token_cs = self.cur_cs;
740 }
741 }
742}
743
744pub const UC_LC_OFFSET: u32 = 333_280;
747
748pub enum Next {
750 BigSwitch,
752 Reswitch,
754 Return,
756}