1use std::rc::Rc;
5
6use crate::Engine;
7use crate::cmd::Cmd;
8use crate::hash::{CsId, NO_CS};
9use crate::input::{Level, ScannerStatus, TokKind};
10use crate::primitives_gen::assign_int as ip;
11use crate::token::{Token, TokenList};
12
13pub const EXPAND_DEPTH: u32 = 10000;
15
16pub mod cs_name_codes {
18 pub const CSNAME: u32 = 0;
20 pub const LAST_NAMED_CS: u32 = 1;
22 pub const BEGIN_CSNAME: u32 = 2;
24}
25
26#[inline]
28fn is_match_or_end(t: Token) -> bool {
29 !t.is_cs() && (t.cmd_code() == Cmd::ParEnd as u32 || t.cmd_code() == Cmd::Stop as u32)
30}
31
32#[inline]
35fn is_brace(t: Token) -> bool {
36 !t.is_cs() && (t.cmd_code() == Cmd::LeftBrace as u32 || t.cmd_code() == Cmd::RightBrace as u32)
37}
38
39#[inline]
40fn is_left_brace(t: Token) -> bool {
41 !t.is_cs() && t.cmd_code() == Cmd::LeftBrace as u32
42}
43
44impl Engine {
45 pub fn get_x_token(&mut self) {
48 self.get_next();
49 if self.cur_cmd > Cmd::MAX_COMMAND {
50 self.get_x_token_expanding();
51 } else {
52 self.cur_tok = self.make_cur_tok();
53 }
54 }
55
56 #[inline(never)]
59 fn get_x_token_expanding(&mut self) {
60 loop {
61 if self.cur_cmd >= Cmd::Call {
62 if self.cur_cmd < Cmd::EndTemplate {
63 self.macro_call();
64 } else {
65 self.cur_cs = self.cs.frozen_endv;
66 self.cur_cmd = Cmd::Endv;
67 break;
68 }
69 } else {
70 self.expand();
71 }
72 self.get_next();
73 if self.cur_cmd <= Cmd::MAX_COMMAND {
74 break;
75 }
76 }
77 self.cur_tok = self.make_cur_tok();
78 }
79
80 pub fn x_token(&mut self) {
82 while self.cur_cmd > Cmd::MAX_COMMAND {
83 if self.cur_cmd == Cmd::EndTemplate {
84 self.cur_cs = self.cs.frozen_endv;
87 self.cur_cmd = Cmd::Endv;
88 break;
89 }
90 self.expand();
91 self.get_next();
92 }
93 self.cur_tok = self.make_cur_tok();
94 }
95
96 pub fn expand(&mut self) {
98 let cv = self.cur_val;
99 let cvl = self.cur_val_level;
100 let cg = self.cur_glue;
101 self.expand_depth += 1;
102 if self.expand_depth >= EXPAND_DEPTH {
103 self.overflow("expansion depth", i64::from(EXPAND_DEPTH));
104 }
105 loop {
106 if self.cur_cmd < Cmd::Call {
107 if self.eqtb.int(ip::TRACINGCOMMANDS) > 1 {
108 self.show_cur_cmd_chr();
109 }
110 match self.cur_cmd {
111 Cmd::TopBotMark => self.insert_mark_text(),
112 Cmd::ExpandAfter => {
113 if self.cur_chr == 0 {
114 self.get_token();
116 let t = self.cur_tok;
117 self.get_token();
118 if self.cur_cmd > Cmd::MAX_COMMAND {
119 self.expand();
120 } else {
121 self.back_input();
122 }
123 self.cur_tok = t;
124 self.back_input();
125 } else {
126 self.get_token();
128 if self.cur_cmd == Cmd::IfTest
129 && self.cur_chr != crate::cond::if_codes::IF_CASE
130 {
131 self.cur_chr += crate::cond::UNLESS_CODE;
132 continue; }
134 self.print_err("You can't use `");
135 self.print_esc_str("unless");
136 self.printer.print_str("' before `");
137 self.print_cmd_chr(self.cur_cmd, self.cur_chr);
138 self.printer.print_char('\'' as u32);
139 self.help(&["Continue, and I'll forget that it ever happened."]);
140 self.back_error();
141 }
142 }
143 Cmd::NoExpand => {
144 if self.cur_chr == 0 {
145 self.no_expand();
146 } else {
147 self.primitive_cmd();
148 if self.cur_cmd > Cmd::MAX_COMMAND && self.cur_cmd != Cmd::NoExpand {
149 continue;
150 }
151 if self.cur_cmd <= Cmd::MAX_COMMAND {
152 self.back_input();
153 }
154 }
155 }
156 Cmd::CsName => self.manufacture_cs_name(),
157 Cmd::Convert => self.conv_toks(),
158 Cmd::The => self.ins_the_toks(),
159 Cmd::IfTest => self.conditional(),
160 Cmd::FiOrElse => self.fi_or_else(),
161 Cmd::Input => self.input_command(),
162 Cmd::LuaExpandableCall | Cmd::LuaLocalCall => self.lua_expandable_call(),
163 Cmd::Variable | Cmd::Feedback => self.backend_expandable(),
164 _ => {
165 self.print_err("Undefined control sequence");
166 self.help(&[
167 "The control sequence at the end of the top line",
168 "of your error message was never \\def'ed. If you have",
169 "misspelled it (e.g., `\\hobx'), type `I' and the correct",
170 "spelling (e.g., `I\\hbox'). Otherwise just continue,",
171 "and I'll forget about whatever was undefined.",
172 ]);
173 self.error();
174 }
175 }
176 } else if self.cur_cmd < Cmd::EndTemplate {
177 self.macro_call();
178 } else {
179 self.cur_tok = Token::cs(self.cs.frozen_endv);
180 self.back_input();
181 }
182 break;
183 }
184 self.expand_depth -= 1;
185 self.cur_val = cv;
186 self.cur_val_level = cvl;
187 self.cur_glue = cg;
188 }
189
190 fn no_expand(&mut self) {
192 let save = self.input.scanner_status;
193 self.input.scanner_status = ScannerStatus::Normal;
194 self.get_token();
195 self.input.scanner_status = save;
196 let t = self.cur_tok;
197 if t.is_cs() {
198 while let Some(Level::Tokens(l)) = self.input.stack.last() {
200 if l.loc < l.list.len() || l.kind == TokKind::VTemplate {
201 break;
202 }
203 self.end_token_list();
204 }
205 let list: TokenList = Rc::from(vec![Token::cs(self.cs.frozen_dont_expand), t]);
206 self.begin_token_list(list, TokKind::BackedUp);
207 } else {
208 self.back_input();
209 }
210 }
211
212 fn primitive_cmd(&mut self) {
215 let save = self.input.scanner_status;
216 self.input.scanner_status = ScannerStatus::Normal;
217 self.get_token();
218 self.input.scanner_status = save;
219 if self.cur_cs != NO_CS
220 && let Some(p) = crate::primitives::lookup(self.hash.name(self.cur_cs))
221 {
222 self.cur_cmd = p.cmd;
223 self.cur_chr = p.chr;
224 let frozen = self.frozen_primitive(self.cur_cs);
226 self.cur_cs = frozen;
227 self.cur_tok = Token::cs(frozen);
228 if self.cur_cmd > Cmd::MAX_COMMAND {
229 return;
230 }
231 return;
232 }
233 self.cur_cmd = Cmd::Relax;
234 self.cur_chr = 0;
235 self.back_input();
236 self.cur_cmd = Cmd::NoExpand;
237 }
238
239 pub(crate) fn lookup_named_cs(&mut self, name: &[u8]) -> CsId {
243 let cs = if name.is_empty() {
244 NO_CS
245 } else {
246 match self.hash.lookup(name) {
247 Some(cs) if self.eqtb.meaning(cs).cmd != Cmd::UndefinedCs => cs,
248 _ => crate::hash::UNDEFINED_CS,
249 }
250 };
251 self.last_cs_name = cs;
252 cs
253 }
254
255 fn manufacture_cs_name(&mut self) {
257 if self.cur_chr == cs_name_codes::LAST_NAMED_CS {
258 if self.last_cs_name != NO_CS {
259 self.cur_tok = Token::cs(self.last_cs_name);
260 self.back_input();
261 }
262 return;
263 }
264 let begin = self.cur_chr == cs_name_codes::BEGIN_CSNAME;
265 let e = self.cond.is_in_csname;
266 self.cond.is_in_csname = true;
267 let mut name: Vec<u8> = Vec::new();
268 loop {
269 self.get_x_token();
270 if self.cur_cs != NO_CS {
271 break;
272 }
273 crate::utf8x::push(&mut name, self.cur_chr);
274 }
275 if self.cur_cmd != Cmd::EndCsName {
276 self.complain_missing_endcsname();
277 }
278 self.cond.is_in_csname = e;
279 if begin {
280 let cs = self.lookup_named_cs(&name);
281 if cs != NO_CS && cs != crate::hash::UNDEFINED_CS {
282 self.cur_tok = Token::cs(cs);
283 self.back_input();
284 }
285 return;
286 }
287 let cs = self.hash.lookup_or_insert(&name);
288 self.last_cs_name = cs;
289 if self.eqtb.meaning(cs).cmd == Cmd::UndefinedCs {
290 self.eq_define(
291 crate::eqtb::Loc::Cs(cs),
292 crate::eqtb::Val::Meaning(crate::eqtb::Meaning::prim(Cmd::Relax, RELAX_CHR)),
293 );
294 }
295 self.cur_tok = Token::cs(cs);
296 self.back_input();
297 }
298
299 pub fn complain_missing_endcsname(&mut self) {
301 self.print_err("Missing ");
302 self.print_esc_str("endcsname");
303 self.printer.print_str(" inserted");
304 self.help(&[
305 "The control sequence marked <to be read again> should",
306 "not appear between \\csname and \\endcsname.",
307 ]);
308 self.back_error();
309 }
310
311 pub fn insert_relax(&mut self) {
313 self.cur_tok = Token::cs(self.cur_cs);
314 self.back_input();
315 self.cur_tok = Token::cs(self.cs.frozen_relax);
316 self.back_input();
317 if let Some(Level::Tokens(t)) = self.input.stack.last_mut() {
318 t.kind = TokKind::Inserted;
319 }
320 }
321
322 fn input_command(&mut self) {
324 match self.cur_chr {
325 1 => self.input.force_eof = true,
326 2 | 3 => self.pseudo_start(self.cur_chr == 3),
327 _ => {
328 if self.files.name_in_progress {
329 self.insert_relax();
330 } else {
331 self.start_input();
332 }
333 }
334 }
335 }
336
337 fn insert_mark_text(&mut self) {
339 let mut code = self.cur_chr;
340 let class = if code >= 5 {
341 code -= 5;
342 self.scan_register_num();
343 self.cur_val as u32
344 } else {
345 0
346 };
347 if let Some(list) = self.marks.get(class, code)
348 && !list.is_empty()
349 {
350 self.begin_token_list(list, TokKind::MarkText);
351 }
352 }
353
354 pub fn macro_call(&mut self) {
356 self.check_snapshot();
357 if let Some(mac) = &self.eqtb.meaning(self.cur_cs).mac
359 && mac.tokens[0] == Token::END_MATCH
360 && self.eqtb.int(ip::TRACINGMACROS) <= 0
361 {
362 let tokens = mac.tokens.clone();
363 while let Some(Level::Tokens(t)) = self.input.stack.last() {
364 if t.loc < t.list.len() || t.kind == TokKind::VTemplate {
365 break;
366 }
367 self.end_token_list();
368 }
369 let param_start = self.input.param_stack.len();
370 self.begin_macro(tokens, 1, self.cur_cs, param_start);
371 return;
372 }
373 let saved_runaway = std::mem::take(&mut self.runaway_list);
377 self.macro_call_with_args();
378 self.runaway_list = saved_runaway;
379 }
380
381 fn macro_call_with_args(&mut self) {
384 let save_scanner_status = self.input.scanner_status;
385 let save_warning_index = self.input.warning_index;
386 self.input.warning_index = self.cur_cs;
387 let Some(mac) = self.eqtb.meaning(self.cur_cs).mac.clone() else {
388 self.input.scanner_status = save_scanner_status;
389 self.input.warning_index = save_warning_index;
390 return;
391 };
392 let tokens = mac.tokens.clone();
393 if self.eqtb.int(ip::TRACINGMACROS) > 0 {
394 self.begin_diagnostic();
395 self.printer.print_ln();
396 self.print_input_level();
397 self.print_cs(self.input.warning_index);
398 self.token_show_list(&tokens, 0, tokens.len(), 10_000_000);
399 self.end_diagnostic(false);
400 }
401 let mut r = 0usize;
402 let mut pstack: Vec<TokenList> = std::mem::take(&mut self.param_scratch);
404 pstack.clear();
405 if tokens[0] != Token::END_MATCH {
406 self.input.scanner_status = ScannerStatus::Matching;
407 let mut unbalance: i32 = 0;
408 self.long_state = self.cur_cmd;
409 if self.long_state >= Cmd::OuterCall {
410 self.long_state = if self.long_state == Cmd::OuterCall {
411 Cmd::Call
412 } else {
413 Cmd::LongCall
414 };
415 }
416 let par_token = Token::cs(self.cs.par);
417 loop {
418 self.runaway_list = std::mem::take(&mut self.arg_scratch);
423 self.runaway_list.clear();
424 let s: Option<usize>;
425 let mut match_chr = 0u32;
426 let mut m = 0i32;
427 let mut rbrace_ptr = 0usize;
428 if !is_match_or_end(tokens[r]) || tokens[r] == Token::END_MATCH {
429 s = None;
430 } else {
431 match_chr = tokens[r].chr();
432 s = Some(r + 1);
433 r += 1;
434 }
435 'cont: loop {
436 self.get_token();
437 if self.cur_tok == tokens[r] {
438 r += 1;
439 if is_match_or_end(tokens[r]) {
440 if is_left_brace(self.cur_tok) {
441 self.input.align_state -= 1;
442 }
443 break 'cont; }
445 continue 'cont;
446 }
447 if s != Some(r) {
449 match s {
450 None => {
451 self.print_err("Use of ");
452 self.sprint_cs(self.input.warning_index);
453 self.printer.print_str(" doesn't match its definition");
454 self.help(&[
455 "If you say, e.g., `\\def\\a1{...}', then you must always",
456 "put `1' after `\\a', since control sequence names are",
457 "made up of letters only. The macro here has not been",
458 "followed by the required stuff, so I'm ignoring it.",
459 ]);
460 self.error();
461 self.runaway_list.clear();
462 self.input.scanner_status = save_scanner_status;
463 self.input.warning_index = save_warning_index;
464 return;
465 }
466 Some(s) => {
467 let mut t = s;
468 loop {
469 self.runaway_list.push(tokens[t]);
470 m += 1;
471 let mut u = t + 1;
472 let mut v = s;
473 let mut resumed = false;
474 loop {
475 if u == r {
476 if self.cur_tok != tokens[v] {
477 break;
478 }
479 r = v + 1;
480 resumed = true;
481 break;
482 }
483 if tokens[u] != tokens[v] {
484 break;
485 }
486 u += 1;
487 v += 1;
488 }
489 if resumed {
490 continue 'cont;
491 }
492 t += 1;
493 if t == r {
494 break;
495 }
496 }
497 r = s;
498 }
499 }
500 }
501 if self.cur_tok == par_token && self.long_state != Cmd::LongCall {
502 self.report_runaway_argument();
503 self.input.align_state -= unbalance;
504 self.input.scanner_status = save_scanner_status;
505 self.input.warning_index = save_warning_index;
506 return;
507 }
508 if is_brace(self.cur_tok) {
509 if is_left_brace(self.cur_tok) {
510 unbalance = 1;
512 loop {
513 self.runaway_list.push(self.cur_tok);
514 self.get_token();
515 if self.cur_tok == par_token && self.long_state != Cmd::LongCall {
516 self.report_runaway_argument();
517 self.input.align_state -= unbalance;
518 self.input.scanner_status = save_scanner_status;
519 self.input.warning_index = save_warning_index;
520 return;
521 }
522 if is_brace(self.cur_tok) {
523 if is_left_brace(self.cur_tok) {
524 unbalance += 1;
525 } else {
526 unbalance -= 1;
527 if unbalance == 0 {
528 break;
529 }
530 }
531 }
532 }
533 rbrace_ptr = self.runaway_list.len();
534 self.runaway_list.push(self.cur_tok);
535 } else {
536 self.back_input();
538 self.print_err("Argument of ");
539 self.sprint_cs(self.input.warning_index);
540 self.printer.print_str(" has an extra }");
541 self.help(&[
542 "I've run across a `}' that doesn't seem to match anything.",
543 "For example, `\\def\\a#1{...}' and `\\a}' would produce",
544 "this error. If you simply proceed now, the `\\par' that",
545 "I've just inserted will cause me to report a runaway",
546 "argument that might be the root of the problem. But if",
547 "your `}' was spurious, just type `2' and it will go away.",
548 ]);
549 self.input.align_state += 1;
550 self.long_state = Cmd::Call;
551 self.cur_tok = par_token;
552 self.ins_error();
553 continue 'cont;
554 }
555 } else {
556 if self.cur_tok == Token::SPACE && is_match_or_end(tokens[r]) {
557 continue 'cont;
558 }
559 self.runaway_list.push(self.cur_tok);
560 }
561 m += 1;
562 if !is_match_or_end(tokens[r]) {
563 continue 'cont;
564 }
565 break 'cont;
566 }
567 if s.is_some() {
569 let param: TokenList = if m == 1
571 && self.runaway_list.last().is_some_and(|&t| is_brace(t))
572 && rbrace_ptr > 0
573 {
574 Rc::from(&self.runaway_list[1..rbrace_ptr])
575 } else {
576 Rc::from(&self.runaway_list[..])
577 };
578 pstack.push(param);
579 let param = &pstack[pstack.len() - 1];
580 if self.eqtb.int(ip::TRACINGMACROS) > 0 {
581 self.begin_diagnostic();
582 if !self.print_input_level() {
583 self.printer.begin_line();
584 }
585 self.printer.print_char(match_chr);
586 self.printer.print_int(pstack.len() as i64);
587 self.printer.print_str("<-");
588 self.show_token_list(param, 0, param.len(), 1000);
589 self.end_diagnostic(false);
590 }
591 }
592 self.arg_scratch = std::mem::take(&mut self.runaway_list);
593 if tokens[r] == Token::END_MATCH {
594 break;
595 }
596 }
597 }
598 while let Some(Level::Tokens(t)) = self.input.stack.last() {
600 if t.loc < t.list.len() || t.kind == TokKind::VTemplate {
601 break;
602 }
603 self.end_token_list();
604 }
605 let param_start = self.input.param_stack.len();
606 self.input.param_stack.append(&mut pstack);
607 self.param_scratch = pstack;
608 self.begin_macro(tokens, r + 1, self.input.warning_index, param_start);
609 self.input.scanner_status = save_scanner_status;
610 self.input.warning_index = save_warning_index;
611 }
612
613 fn report_runaway_argument(&mut self) {
615 if self.long_state == Cmd::Call {
616 self.runaway();
617 self.print_err("Paragraph ended before ");
618 self.sprint_cs(self.input.warning_index);
619 self.printer.print_str(" was complete");
620 self.help(&[
621 "I suspect you've forgotten a `}', causing me to apply this",
622 "control sequence to too much text. How can we recover?",
623 "My plan is to forget the whole thing and hope for the best.",
624 ]);
625 self.back_error();
626 }
627 }
628
629 pub fn runaway(&mut self) {
631 let what = match self.input.scanner_status {
632 ScannerStatus::Defining => "definition",
633 ScannerStatus::Matching => "argument",
634 ScannerStatus::Aligning => "preamble",
635 ScannerStatus::Absorbing => "text",
636 _ => return,
637 };
638 self.printer.print_nl("Runaway ");
639 self.printer.print_str(what);
640 self.printer.print_char('?' as u32);
641 self.printer.print_ln();
642 let list = std::mem::take(&mut self.runaway_list);
643 let limit = self.printer.error_line.saturating_sub(10);
644 self.show_token_list(&list, 0, list.len(), limit as i64);
645 self.runaway_list = list;
646 }
647
648 pub fn check_outer_validity(&mut self) {
651 if self.input.scanner_status == ScannerStatus::Normal {
652 return;
653 }
654 if self.eqtb.int(ip::SUPPRESSOUTERERROR) > 0 && self.cur_cs != NO_CS {
655 return;
656 }
657 self.err.deletions_allowed = false;
658 if self.cur_cs != NO_CS {
660 let reread = match self.input.stack.last() {
661 Some(Level::Tokens(_)) => true,
662 Some(Level::Text(t)) => !matches!(
663 t.source,
664 crate::input::LineSource::Terminal | crate::input::LineSource::Read(_)
665 ),
666 None => true,
667 };
668 if reread {
669 let list: TokenList = Rc::from(vec![Token::cs(self.cur_cs)]);
670 self.back_list(list);
671 }
672 self.cur_cmd = Cmd::Spacer;
673 self.cur_chr = ' ' as u32;
674 }
675 if self.input.scanner_status > ScannerStatus::Skipping {
676 self.runaway();
677 if self.cur_cs == NO_CS {
678 self.print_err("File ended");
679 } else {
680 self.cur_cs = NO_CS;
681 self.print_err("Forbidden control sequence found");
682 }
683 self.printer.print_str(" while scanning ");
684 let mut ins: Vec<Token> = Vec::new();
685 match self.input.scanner_status {
686 ScannerStatus::Defining => {
687 self.printer.print_str("definition");
688 ins.push(Token::RIGHT_BRACE);
689 }
690 ScannerStatus::Matching => {
691 self.printer.print_str("use");
692 ins.push(Token::cs(self.cs.par));
693 self.long_state = Cmd::OuterCall;
694 }
695 ScannerStatus::Aligning => {
696 self.printer.print_str("preamble");
697 ins.push(Token::cs(self.cs.frozen_cr));
698 ins.push(Token::RIGHT_BRACE);
699 self.input.align_state = -1_000_000;
700 }
701 _ => {
702 self.printer.print_str("text");
703 ins.push(Token::RIGHT_BRACE);
704 }
705 }
706 self.ins_list(Rc::from(ins));
707 self.printer.print_str(" of ");
708 self.sprint_cs(self.input.warning_index);
709 self.help(&[
710 "I suspect you have forgotten a `}', causing me",
711 "to read past where you wanted me to stop.",
712 "I'll try to recover; but if the error is serious,",
713 "you'd better type `E' or `X' now and fix your file.",
714 ]);
715 self.error();
716 } else {
717 self.print_err("Incomplete ");
718 self.print_cmd_chr(Cmd::IfTest, self.cond.cur_if);
719 self.printer.print_str("; all text was ignored after line ");
720 self.printer.print_int(self.cond.skip_line.into());
721 let third = if self.cur_cs != NO_CS {
722 self.cur_cs = NO_CS;
723 "the matching `\\fi'. I've inserted a `\\fi'; this might work."
724 } else {
725 "The file ended while I was skipping conditional text."
726 };
727 self.help(&[
728 "A forbidden control sequence occurred in skipped text.",
729 "This kind of error happens when you say `\\if...' and forget",
730 third,
731 ]);
732 self.cur_tok = Token::cs(self.cs.frozen_fi);
733 self.ins_error();
734 }
735 self.err.deletions_allowed = true;
736 }
737
738 fn frozen_primitive(&mut self, cs: CsId) -> CsId {
741 let name = self.hash.name(cs).to_vec();
742 if let Some(&f) = self.frozen_prims.get(&name) {
743 return f;
744 }
745 let f = self.hash.new_frozen(&name);
746 if let Some(p) = crate::primitives::lookup(&name) {
747 self.eqtb.ensure_cs(f);
748 self.eqtb.cs[f.0 as usize].val = crate::eqtb::Meaning::prim(p.cmd, p.chr);
749 }
750 self.frozen_prims.insert(name, f);
751 f
752 }
753}
754
755pub const RELAX_CHR: u32 = 0x11_0000;