Skip to main content

retex_engine/
expand.rs

1//! Expansion (*TeX: The Program* §366–§401, with e-TeX and LuaTeX
2//! additions): `expand`, macro calls, `\csname`, `\noexpand`, `\input`.
3
4use 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
13/// Maximum expansion nesting before LuaTeX gives up.
14pub const EXPAND_DEPTH: u32 = 10000;
15
16/// `cs_name` modifiers.
17pub mod cs_name_codes {
18    /// `\csname`.
19    pub const CSNAME: u32 = 0;
20    /// `\lastnamedcs`.
21    pub const LAST_NAMED_CS: u32 = 1;
22    /// `\begincsname`.
23    pub const BEGIN_CSNAME: u32 = 2;
24}
25
26/// Whether `t` is a `match` or `end_match` token.
27#[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/// Whether `t` is a left or right brace character token
33/// (`cur_tok < right_brace_limit`).
34#[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    /// Gets the next token, expanding macros and other expandable commands
46    /// (`get_x_token`).
47    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    /// [`get_x_token`](Self::get_x_token) once an expandable token has been
57    /// read.
58    #[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                // Primitive expansion (\expandafter, \csname, \ifnum, …)
71                // may expand macros whose bodies are not executed.
72                let exec = std::mem::replace(&mut self.exec_context, false);
73                self.expand();
74                self.exec_context = exec;
75            }
76            self.get_next();
77            if self.cur_cmd <= Cmd::MAX_COMMAND {
78                break;
79            }
80        }
81        self.cur_tok = self.make_cur_tok();
82    }
83
84    /// Expands the current token while it is expandable (`x_token`).
85    pub fn x_token(&mut self) {
86        while self.cur_cmd > Cmd::MAX_COMMAND {
87            if self.cur_cmd == Cmd::EndTemplate {
88                // LuaTeX converts `\endtemplate` in place (as
89                // `get_x_token` does) instead of backing up `\endv`.
90                self.cur_cs = self.cs.frozen_endv;
91                self.cur_cmd = Cmd::Endv;
92                break;
93            }
94            self.expand();
95            self.get_next();
96        }
97        self.cur_tok = self.make_cur_tok();
98    }
99
100    /// Expands the current (expandable) command (`expand`).
101    pub fn expand(&mut self) {
102        let cv = self.cur_val;
103        let cvl = self.cur_val_level;
104        let cg = self.cur_glue;
105        // Only this expansion is a single step: expansions nested in it
106        // (a number scanned by `\romannumeral`, …) are not.
107        let one_step = std::mem::take(&mut self.one_step);
108        self.expand_depth += 1;
109        if self.expand_depth >= EXPAND_DEPTH {
110            self.overflow("expansion depth", i64::from(EXPAND_DEPTH));
111        }
112        loop {
113            if self.cur_cmd < Cmd::Call {
114                if self.eqtb.int(ip::TRACINGCOMMANDS) > 1 {
115                    self.show_cur_cmd_chr();
116                }
117                match self.cur_cmd {
118                    Cmd::TopBotMark => self.insert_mark_text(),
119                    Cmd::ExpandAfter => {
120                        if self.cur_chr == 0 {
121                            // \expandafter
122                            self.get_token();
123                            let t = self.cur_tok;
124                            self.get_token();
125                            if self.cur_cmd > Cmd::MAX_COMMAND {
126                                self.one_step = true;
127                                self.expand();
128                            } else {
129                                self.back_input();
130                            }
131                            self.cur_tok = t;
132                            self.back_input();
133                        } else {
134                            // \unless
135                            self.get_token();
136                            if self.cur_cmd == Cmd::IfTest
137                                && self.cur_chr != crate::cond::if_codes::IF_CASE
138                            {
139                                self.cur_chr += crate::cond::UNLESS_CODE;
140                                continue; // reswitch
141                            }
142                            self.print_err("You can't use `");
143                            self.print_esc_str("unless");
144                            self.printer.print_str("' before `");
145                            self.print_cmd_chr(self.cur_cmd, self.cur_chr);
146                            self.printer.print_char('\'' as u32);
147                            self.help(&["Continue, and I'll forget that it ever happened."]);
148                            self.back_error();
149                        }
150                    }
151                    Cmd::NoExpand => {
152                        if self.cur_chr == 0 {
153                            self.no_expand();
154                        } else {
155                            self.primitive_cmd();
156                            if self.cur_cmd > Cmd::MAX_COMMAND && self.cur_cmd != Cmd::NoExpand {
157                                continue;
158                            }
159                            if self.cur_cmd <= Cmd::MAX_COMMAND {
160                                self.back_input();
161                            }
162                        }
163                    }
164                    Cmd::CsName => self.manufacture_cs_name(),
165                    Cmd::Convert => self.conv_toks(),
166                    Cmd::The => self.ins_the_toks(),
167                    Cmd::IfTest => self.conditional(),
168                    Cmd::FiOrElse => self.fi_or_else(),
169                    Cmd::Input => self.input_command(),
170                    Cmd::LuaExpandableCall | Cmd::LuaLocalCall => self.lua_expandable_call(),
171                    Cmd::Variable | Cmd::Feedback => self.backend_expandable(),
172                    _ => {
173                        self.print_err("Undefined control sequence");
174                        self.help(&[
175                            "The control sequence at the end of the top line",
176                            "of your error message was never \\def'ed. If you have",
177                            "misspelled it (e.g., `\\hobx'), type `I' and the correct",
178                            "spelling (e.g., `I\\hbox'). Otherwise just continue,",
179                            "and I'll forget about whatever was undefined.",
180                        ]);
181                        self.error();
182                    }
183                }
184            } else if self.cur_cmd < Cmd::EndTemplate {
185                self.one_step = one_step;
186                self.macro_call();
187            } else {
188                self.cur_tok = Token::cs(self.cs.frozen_endv);
189                self.back_input();
190            }
191            break;
192        }
193        self.expand_depth -= 1;
194        self.cur_val = cv;
195        self.cur_val_level = cvl;
196        self.cur_glue = cg;
197    }
198
199    /// `\noexpand`: makes the next token unexpandable once (§369).
200    fn no_expand(&mut self) {
201        let save = self.input.scanner_status;
202        self.input.scanner_status = ScannerStatus::Normal;
203        self.get_token();
204        self.input.scanner_status = save;
205        let t = self.cur_tok;
206        if t.is_cs() {
207            // Pop exhausted lists as back_input would.
208            while let Some(Level::Tokens(l)) = self.input.stack.last() {
209                if l.loc < l.list.len() || l.kind == TokKind::VTemplate {
210                    break;
211                }
212                self.end_token_list();
213            }
214            let list: TokenList = Rc::from(vec![Token::cs(self.cs.frozen_dont_expand), t]);
215            self.begin_token_list(list, TokKind::BackedUp);
216        } else {
217            self.back_input();
218        }
219    }
220
221    /// `\primitive`: replaces the next control sequence by the primitive of
222    /// that name, if there is one (LuaTeX).
223    fn primitive_cmd(&mut self) {
224        let save = self.input.scanner_status;
225        self.input.scanner_status = ScannerStatus::Normal;
226        self.get_token();
227        self.input.scanner_status = save;
228        if self.cur_cs != NO_CS
229            && let Some(p) = crate::primitives::lookup(self.hash.name(self.cur_cs))
230        {
231            self.cur_cmd = p.cmd;
232            self.cur_chr = p.chr;
233            // The frozen copy carries the primitive meaning.
234            let frozen = self.frozen_primitive(self.cur_cs);
235            self.cur_cs = frozen;
236            self.cur_tok = Token::cs(frozen);
237            if self.cur_cmd > Cmd::MAX_COMMAND {
238                return;
239            }
240            return;
241        }
242        self.cur_cmd = Cmd::Relax;
243        self.cur_chr = 0;
244        self.back_input();
245        self.cur_cmd = Cmd::NoExpand;
246    }
247
248    /// Looks up the control sequence named by `\ifcsname` or
249    /// `\begincsname` and records it for `\lastnamedcs`: an undefined
250    /// name gives the undefined control sequence, an empty name none.
251    pub(crate) fn lookup_named_cs(&mut self, name: &[u8]) -> CsId {
252        let cs = if name.is_empty() {
253            NO_CS
254        } else {
255            match self.hash.lookup(name) {
256                Some(cs) if self.eqtb.meaning(cs).cmd != Cmd::UndefinedCs => cs,
257                _ => crate::hash::UNDEFINED_CS,
258            }
259        };
260        self.last_cs_name = cs;
261        cs
262    }
263
264    /// Builds the control sequence for `\csname…\endcsname` (§372).
265    fn manufacture_cs_name(&mut self) {
266        if self.cur_chr == cs_name_codes::LAST_NAMED_CS {
267            if self.last_cs_name != NO_CS {
268                self.cur_tok = Token::cs(self.last_cs_name);
269                self.back_input();
270            }
271            return;
272        }
273        let begin = self.cur_chr == cs_name_codes::BEGIN_CSNAME;
274        let e = self.cond.is_in_csname;
275        self.cond.is_in_csname = true;
276        let mut name: Vec<u8> = Vec::new();
277        loop {
278            self.get_x_token();
279            if self.cur_cs != NO_CS {
280                break;
281            }
282            crate::utf8x::push(&mut name, self.cur_chr);
283        }
284        if self.cur_cmd != Cmd::EndCsName {
285            self.complain_missing_endcsname();
286        }
287        self.cond.is_in_csname = e;
288        if begin {
289            let cs = self.lookup_named_cs(&name);
290            if cs != NO_CS && cs != crate::hash::UNDEFINED_CS {
291                self.cur_tok = Token::cs(cs);
292                self.back_input();
293            }
294            return;
295        }
296        let cs = self.hash.lookup_or_insert(&name);
297        self.last_cs_name = cs;
298        if self.eqtb.meaning(cs).cmd == Cmd::UndefinedCs {
299            self.eq_define(
300                crate::eqtb::Loc::Cs(cs),
301                crate::eqtb::Val::Meaning(crate::eqtb::Meaning::prim(Cmd::Relax, RELAX_CHR)),
302            );
303        }
304        self.cur_tok = Token::cs(cs);
305        self.back_input();
306    }
307
308    /// Reports a missing `\endcsname`.
309    pub fn complain_missing_endcsname(&mut self) {
310        self.print_err("Missing ");
311        self.print_esc_str("endcsname");
312        self.printer.print_str(" inserted");
313        self.help(&[
314            "The control sequence marked <to be read again> should",
315            "not appear between \\csname and \\endcsname.",
316        ]);
317        self.back_error();
318    }
319
320    /// Inserts `\relax` after the current token (`insert_relax`).
321    pub fn insert_relax(&mut self) {
322        self.cur_tok = Token::cs(self.cur_cs);
323        self.back_input();
324        self.cur_tok = Token::cs(self.cs.frozen_relax);
325        self.back_input();
326        if let Some(Level::Tokens(t)) = self.input.stack.last_mut() {
327            t.kind = TokKind::Inserted;
328        }
329    }
330
331    /// `\input`, `\endinput`, `\scantokens`, `\scantextokens`.
332    fn input_command(&mut self) {
333        match self.cur_chr {
334            1 => self.input.force_eof = true,
335            2 | 3 => self.pseudo_start(self.cur_chr == 3),
336            _ => {
337                if self.files.name_in_progress {
338                    self.insert_relax();
339                } else {
340                    self.start_input();
341                }
342            }
343        }
344    }
345
346    /// Inserts the text of a mark (`top_bot_mark`, §386).
347    fn insert_mark_text(&mut self) {
348        let mut code = self.cur_chr;
349        let class = if code >= 5 {
350            code -= 5;
351            self.scan_register_num();
352            self.cur_val as u32
353        } else {
354            0
355        };
356        if let Some(list) = self.marks.get(class, code)
357            && !list.is_empty()
358        {
359            self.begin_token_list(list, TokKind::MarkText);
360        }
361    }
362
363    /// Calls a user macro (`macro_call`, §389).
364    pub fn macro_call(&mut self) {
365        let one_step = std::mem::take(&mut self.one_step);
366        self.check_snapshot();
367        if self.macro_profile.is_some() {
368            self.profile_macro_call(self.cur_cs);
369        }
370        // Fast path: a macro without parameters, not traced.
371        if let Some(mac) = &self.eqtb.meaning(self.cur_cs).mac
372            && mac.tokens[0] == Token::END_MATCH
373            && self.eqtb.int(ip::TRACINGMACROS) <= 0
374        {
375            let tokens = mac.tokens.clone();
376            // A native implementation (`crate::native`); 1 = known to
377            // have none.
378            let native = (mac.native.get() != 1).then(|| Rc::clone(mac));
379            while let Some(Level::Tokens(t)) = self.input.stack.last() {
380                if t.loc < t.list.len() || t.kind == TokKind::VTemplate {
381                    break;
382                }
383                self.end_token_list();
384            }
385            if let Some(mac) = native
386                && let Some(i) = self.native_of(&mac, self.cur_cs)
387                && self.run_native(i, &[], one_step)
388            {
389                return;
390            }
391            let param_start = self.input.param_stack.len();
392            self.begin_macro(tokens, 1, self.cur_cs, param_start);
393            return;
394        }
395        // The argument being scanned is kept in `runaway_list` while
396        // tokens are read, to be shown if it runs away; an enclosing
397        // scan's list is put back afterwards.
398        let saved_runaway = std::mem::take(&mut self.runaway_list);
399        self.macro_call_with_args(one_step);
400        self.runaway_list = saved_runaway;
401    }
402
403    /// [`macro_call`](Self::macro_call) for a macro with parameters or
404    /// traced; `one_step` if its body will not be expanded further (see
405    /// [`Engine::one_step`]).
406    fn macro_call_with_args(&mut self, one_step: bool) {
407        let save_scanner_status = self.input.scanner_status;
408        let save_warning_index = self.input.warning_index;
409        let save_cs = self.cur_cs;
410        self.input.warning_index = self.cur_cs;
411        let Some(mac) = self.eqtb.meaning(self.cur_cs).mac.clone() else {
412            self.input.scanner_status = save_scanner_status;
413            self.input.warning_index = save_warning_index;
414            return;
415        };
416        let tokens = mac.tokens.clone();
417        if self.eqtb.int(ip::TRACINGMACROS) > 0 {
418            self.begin_diagnostic();
419            self.printer.print_ln();
420            self.print_input_level();
421            self.print_cs(self.input.warning_index);
422            self.token_show_list(&tokens, 0, tokens.len(), 10_000_000);
423            self.end_diagnostic(false);
424        }
425        let mut r = 0usize;
426        // The arguments, in a buffer reused from call to call.
427        let mut pstack: Vec<TokenList> = std::mem::take(&mut self.param_scratch);
428        pstack.clear();
429        if tokens[0] != Token::END_MATCH {
430            self.input.scanner_status = ScannerStatus::Matching;
431            let mut unbalance: i32 = 0;
432            self.long_state = self.cur_cmd;
433            if self.long_state >= Cmd::OuterCall {
434                self.long_state = if self.long_state == Cmd::OuterCall {
435                    Cmd::Call
436                } else {
437                    Cmd::LongCall
438                };
439            }
440            let par_token = Token::cs(self.cs.par);
441            loop {
442                // One parameter (or the leading compulsory delimiter). The
443                // scratch buffer is reused from call to call.
444                // The argument is built in `runaway_list`, where a runaway
445                // error (in get_token) shows it.
446                self.runaway_list = std::mem::take(&mut self.arg_scratch);
447                self.runaway_list.clear();
448                let s: Option<usize>;
449                let mut match_chr = 0u32;
450                let mut m = 0i32;
451                let mut rbrace_ptr = 0usize;
452                if !is_match_or_end(tokens[r]) || tokens[r] == Token::END_MATCH {
453                    s = None;
454                } else {
455                    match_chr = tokens[r].chr();
456                    s = Some(r + 1);
457                    r += 1;
458                }
459                'cont: loop {
460                    self.get_token();
461                    if self.cur_tok == tokens[r] {
462                        r += 1;
463                        if is_match_or_end(tokens[r]) {
464                            if is_left_brace(self.cur_tok) {
465                                self.input.align_state -= 1;
466                            }
467                            break 'cont; // found
468                        }
469                        continue 'cont;
470                    }
471                    // Contribute recently matched tokens.
472                    if s != Some(r) {
473                        match s {
474                            None => {
475                                self.print_err("Use of ");
476                                self.sprint_cs(self.input.warning_index);
477                                self.printer.print_str(" doesn't match its definition");
478                                self.help(&[
479                                    "If you say, e.g., `\\def\\a1{...}', then you must always",
480                                    "put `1' after `\\a', since control sequence names are",
481                                    "made up of letters only. The macro here has not been",
482                                    "followed by the required stuff, so I'm ignoring it.",
483                                ]);
484                                self.error();
485                                self.runaway_list.clear();
486                                self.input.scanner_status = save_scanner_status;
487                                self.input.warning_index = save_warning_index;
488                                return;
489                            }
490                            Some(s) => {
491                                let mut t = s;
492                                loop {
493                                    self.runaway_list.push(tokens[t]);
494                                    m += 1;
495                                    let mut u = t + 1;
496                                    let mut v = s;
497                                    let mut resumed = false;
498                                    loop {
499                                        if u == r {
500                                            if self.cur_tok != tokens[v] {
501                                                break;
502                                            }
503                                            r = v + 1;
504                                            resumed = true;
505                                            break;
506                                        }
507                                        if tokens[u] != tokens[v] {
508                                            break;
509                                        }
510                                        u += 1;
511                                        v += 1;
512                                    }
513                                    if resumed {
514                                        continue 'cont;
515                                    }
516                                    t += 1;
517                                    if t == r {
518                                        break;
519                                    }
520                                }
521                                r = s;
522                            }
523                        }
524                    }
525                    if self.cur_tok == par_token && self.long_state != Cmd::LongCall {
526                        self.report_runaway_argument();
527                        self.input.align_state -= unbalance;
528                        self.input.scanner_status = save_scanner_status;
529                        self.input.warning_index = save_warning_index;
530                        return;
531                    }
532                    if is_brace(self.cur_tok) {
533                        if is_left_brace(self.cur_tok) {
534                            // Contribute an entire group.
535                            unbalance = 1;
536                            loop {
537                                self.runaway_list.push(self.cur_tok);
538                                self.get_token();
539                                if self.cur_tok == par_token && self.long_state != Cmd::LongCall {
540                                    self.report_runaway_argument();
541                                    self.input.align_state -= unbalance;
542                                    self.input.scanner_status = save_scanner_status;
543                                    self.input.warning_index = save_warning_index;
544                                    return;
545                                }
546                                if is_brace(self.cur_tok) {
547                                    if is_left_brace(self.cur_tok) {
548                                        unbalance += 1;
549                                    } else {
550                                        unbalance -= 1;
551                                        if unbalance == 0 {
552                                            break;
553                                        }
554                                    }
555                                }
556                            }
557                            rbrace_ptr = self.runaway_list.len();
558                            self.runaway_list.push(self.cur_tok);
559                        } else {
560                            // An extra right brace.
561                            self.back_input();
562                            self.print_err("Argument of ");
563                            self.sprint_cs(self.input.warning_index);
564                            self.printer.print_str(" has an extra }");
565                            self.help(&[
566                                "I've run across a `}' that doesn't seem to match anything.",
567                                "For example, `\\def\\a#1{...}' and `\\a}' would produce",
568                                "this error. If you simply proceed now, the `\\par' that",
569                                "I've just inserted will cause me to report a runaway",
570                                "argument that might be the root of the problem. But if",
571                                "your `}' was spurious, just type `2' and it will go away.",
572                            ]);
573                            self.input.align_state += 1;
574                            self.long_state = Cmd::Call;
575                            self.cur_tok = par_token;
576                            self.ins_error();
577                            continue 'cont;
578                        }
579                    } else {
580                        if self.cur_tok == Token::SPACE && is_match_or_end(tokens[r]) {
581                            continue 'cont;
582                        }
583                        self.runaway_list.push(self.cur_tok);
584                    }
585                    m += 1;
586                    if !is_match_or_end(tokens[r]) {
587                        continue 'cont;
588                    }
589                    break 'cont;
590                }
591                // found:
592                if s.is_some() {
593                    // Tidy up the parameter just scanned.
594                    let param: TokenList = if m == 1
595                        && self.runaway_list.last().is_some_and(|&t| is_brace(t))
596                        && rbrace_ptr > 0
597                    {
598                        Rc::from(&self.runaway_list[1..rbrace_ptr])
599                    } else {
600                        Rc::from(&self.runaway_list[..])
601                    };
602                    pstack.push(param);
603                    let param = &pstack[pstack.len() - 1];
604                    if self.eqtb.int(ip::TRACINGMACROS) > 0 {
605                        self.begin_diagnostic();
606                        if !self.print_input_level() {
607                            self.printer.begin_line();
608                        }
609                        self.printer.print_char(match_chr);
610                        self.printer.print_int(pstack.len() as i64);
611                        self.printer.print_str("<-");
612                        self.show_token_list(param, 0, param.len(), 1000);
613                        self.end_diagnostic(false);
614                    }
615                }
616                self.arg_scratch = std::mem::take(&mut self.runaway_list);
617                if tokens[r] == Token::END_MATCH {
618                    break;
619                }
620            }
621        }
622        // Feed the macro body and its parameters to the scanner.
623        while let Some(Level::Tokens(t)) = self.input.stack.last() {
624            if t.loc < t.list.len() || t.kind == TokKind::VTemplate {
625                break;
626            }
627            self.end_token_list();
628        }
629        if let Some(i) = self.native_of(&mac, save_cs)
630            && self.run_native(i, &pstack, one_step)
631        {
632            pstack.clear();
633            self.param_scratch = pstack;
634            self.input.scanner_status = save_scanner_status;
635            self.input.warning_index = save_warning_index;
636            return;
637        }
638        let param_start = self.input.param_stack.len();
639        self.input.param_stack.append(&mut pstack);
640        self.param_scratch = pstack;
641        self.begin_macro(tokens, r + 1, self.input.warning_index, param_start);
642        self.input.scanner_status = save_scanner_status;
643        self.input.warning_index = save_warning_index;
644    }
645
646    /// Reports a runaway argument ("Paragraph ended before …").
647    fn report_runaway_argument(&mut self) {
648        if self.long_state == Cmd::Call {
649            self.runaway();
650            self.print_err("Paragraph ended before ");
651            self.sprint_cs(self.input.warning_index);
652            self.printer.print_str(" was complete");
653            self.help(&[
654                "I suspect you've forgotten a `}', causing me to apply this",
655                "control sequence to too much text. How can we recover?",
656                "My plan is to forget the whole thing and hope for the best.",
657            ]);
658            self.back_error();
659        }
660    }
661
662    /// Prints the text that has run away (`runaway`).
663    pub fn runaway(&mut self) {
664        let what = match self.input.scanner_status {
665            ScannerStatus::Defining => "definition",
666            ScannerStatus::Matching => "argument",
667            ScannerStatus::Aligning => "preamble",
668            ScannerStatus::Absorbing => "text",
669            _ => return,
670        };
671        self.printer.print_nl("Runaway ");
672        self.printer.print_str(what);
673        self.printer.print_char('?' as u32);
674        self.printer.print_ln();
675        let list = std::mem::take(&mut self.runaway_list);
676        let limit = self.printer.error_line.saturating_sub(10);
677        self.show_token_list(&list, 0, list.len(), limit as i64);
678        self.runaway_list = list;
679    }
680
681    /// Checks whether an `\outer` macro or end of file is allowed here
682    /// (`check_outer_validity`, §336).
683    pub fn check_outer_validity(&mut self) {
684        if self.input.scanner_status == ScannerStatus::Normal {
685            return;
686        }
687        if self.eqtb.int(ip::SUPPRESSOUTERERROR) > 0 && self.cur_cs != NO_CS {
688            return;
689        }
690        self.err.deletions_allowed = false;
691        // Back up an outer control sequence so that it can be reread.
692        if self.cur_cs != NO_CS {
693            let reread = match self.input.stack.last() {
694                Some(Level::Tokens(_)) => true,
695                Some(Level::Text(t)) => !matches!(
696                    t.source,
697                    crate::input::LineSource::Terminal | crate::input::LineSource::Read(_)
698                ),
699                None => true,
700            };
701            if reread {
702                let list: TokenList = Rc::from(vec![Token::cs(self.cur_cs)]);
703                self.back_list(list);
704            }
705            self.cur_cmd = Cmd::Spacer;
706            self.cur_chr = ' ' as u32;
707        }
708        if self.input.scanner_status > ScannerStatus::Skipping {
709            self.runaway();
710            if self.cur_cs == NO_CS {
711                self.print_err("File ended");
712            } else {
713                self.cur_cs = NO_CS;
714                self.print_err("Forbidden control sequence found");
715            }
716            self.printer.print_str(" while scanning ");
717            let mut ins: Vec<Token> = Vec::new();
718            match self.input.scanner_status {
719                ScannerStatus::Defining => {
720                    self.printer.print_str("definition");
721                    ins.push(Token::RIGHT_BRACE);
722                }
723                ScannerStatus::Matching => {
724                    self.printer.print_str("use");
725                    ins.push(Token::cs(self.cs.par));
726                    self.long_state = Cmd::OuterCall;
727                }
728                ScannerStatus::Aligning => {
729                    self.printer.print_str("preamble");
730                    ins.push(Token::cs(self.cs.frozen_cr));
731                    ins.push(Token::RIGHT_BRACE);
732                    self.input.align_state = -1_000_000;
733                }
734                _ => {
735                    self.printer.print_str("text");
736                    ins.push(Token::RIGHT_BRACE);
737                }
738            }
739            self.ins_list(Rc::from(ins));
740            self.printer.print_str(" of ");
741            self.sprint_cs(self.input.warning_index);
742            self.help(&[
743                "I suspect you have forgotten a `}', causing me",
744                "to read past where you wanted me to stop.",
745                "I'll try to recover; but if the error is serious,",
746                "you'd better type `E' or `X' now and fix your file.",
747            ]);
748            self.error();
749        } else {
750            self.print_err("Incomplete ");
751            self.print_cmd_chr(Cmd::IfTest, self.cond.cur_if);
752            self.printer.print_str("; all text was ignored after line ");
753            self.printer.print_int(self.cond.skip_line.into());
754            let third = if self.cur_cs != NO_CS {
755                self.cur_cs = NO_CS;
756                "the matching `\\fi'. I've inserted a `\\fi'; this might work."
757            } else {
758                "The file ended while I was skipping conditional text."
759            };
760            self.help(&[
761                "A forbidden control sequence occurred in skipped text.",
762                "This kind of error happens when you say `\\if...' and forget",
763                third,
764            ]);
765            self.cur_tok = Token::cs(self.cs.frozen_fi);
766            self.ins_error();
767        }
768        self.err.deletions_allowed = true;
769    }
770
771    /// Returns the frozen control sequence that carries the primitive
772    /// meaning of the primitive named like `cs` (for `\primitive`).
773    fn frozen_primitive(&mut self, cs: CsId) -> CsId {
774        let name = self.hash.name(cs).to_vec();
775        if let Some(&f) = self.frozen_prims.get(&name) {
776            return f;
777        }
778        let f = self.hash.new_frozen(&name);
779        if let Some(p) = crate::primitives::lookup(&name) {
780            self.eqtb.ensure_cs(f);
781            self.eqtb.cs[f.0 as usize].val = crate::eqtb::Meaning::prim(p.cmd, p.chr);
782        }
783        self.frozen_prims.insert(name, f);
784        f
785    }
786}
787
788/// The modifier of `\relax` (LuaTeX uses one past the largest character).
789pub const RELAX_CHR: u32 = 0x11_0000;