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