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