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.check_figure() {
376            return;
377        }
378        if self.profiler.is_some() {
379            self.profile_call(self.cur_cs);
380        }
381        // Fast path: a macro without parameters, not traced.
382        if let Some(mac) = &self.eqtb.meaning(self.cur_cs).mac
383            && mac.tokens[0] == Token::END_MATCH
384            && self.eqtb.int(ip::TRACINGMACROS) <= 0
385        {
386            let tokens = mac.tokens.clone();
387            // A native implementation (`crate::native`); 1 = known to
388            // have none.
389            let native = (mac.native.get() != 1).then(|| Rc::clone(mac));
390            while let Some(Level::Tokens(t)) = self.input.stack.last() {
391                if t.loc < t.list.len() || t.kind == TokKind::VTemplate {
392                    break;
393                }
394                self.end_token_list();
395            }
396            if let Some(mac) = native
397                && let Some(i) = self.native_of(&mac, self.cur_cs)
398                && self.run_native(i, &[], one_step)
399            {
400                return;
401            }
402            let param_start = self.input.param_stack.len();
403            self.begin_macro(tokens, 1, self.cur_cs, param_start);
404            return;
405        }
406        // The argument being scanned is kept in `runaway_list` while
407        // tokens are read, to be shown if it runs away; an enclosing
408        // scan's list is put back afterwards.
409        let saved_runaway = std::mem::take(&mut self.runaway_list);
410        self.macro_call_with_args(one_step);
411        self.runaway_list = saved_runaway;
412    }
413
414    /// [`macro_call`](Self::macro_call) for a macro with parameters or
415    /// traced; `one_step` if its body will not be expanded further (see
416    /// [`Engine::one_step`]).
417    fn macro_call_with_args(&mut self, one_step: bool) {
418        let save_scanner_status = self.input.scanner_status;
419        let save_warning_index = self.input.warning_index;
420        let save_cs = self.cur_cs;
421        self.input.warning_index = self.cur_cs;
422        let Some(mac) = self.eqtb.meaning(self.cur_cs).mac.clone() else {
423            self.input.scanner_status = save_scanner_status;
424            self.input.warning_index = save_warning_index;
425            return;
426        };
427        let tokens = mac.tokens.clone();
428        if self.eqtb.int(ip::TRACINGMACROS) > 0 {
429            self.begin_diagnostic();
430            self.printer.print_ln();
431            self.print_input_level();
432            self.print_cs(self.input.warning_index);
433            self.token_show_list(&tokens, 0, tokens.len(), 10_000_000);
434            self.end_diagnostic(false);
435        }
436        let mut r = 0usize;
437        // The arguments, in a buffer reused from call to call.
438        let mut pstack: Vec<TokenList> = std::mem::take(&mut self.param_scratch);
439        pstack.clear();
440        if tokens[0] != Token::END_MATCH {
441            self.input.scanner_status = ScannerStatus::Matching;
442            let mut unbalance: i32 = 0;
443            self.long_state = self.cur_cmd;
444            if self.long_state >= Cmd::OuterCall {
445                self.long_state = if self.long_state == Cmd::OuterCall {
446                    Cmd::Call
447                } else {
448                    Cmd::LongCall
449                };
450            }
451            let par_token = Token::cs(self.cs.par);
452            loop {
453                // One parameter (or the leading compulsory delimiter). The
454                // scratch buffer is reused from call to call.
455                // The argument is built in `runaway_list`, where a runaway
456                // error (in get_token) shows it.
457                self.runaway_list = std::mem::take(&mut self.arg_scratch);
458                self.runaway_list.clear();
459                let s: Option<usize>;
460                let mut match_chr = 0u32;
461                let mut m = 0i32;
462                let mut rbrace_ptr = 0usize;
463                if !is_match_or_end(tokens[r]) || tokens[r] == Token::END_MATCH {
464                    s = None;
465                } else {
466                    match_chr = tokens[r].chr();
467                    s = Some(r + 1);
468                    r += 1;
469                }
470                'cont: loop {
471                    self.get_token();
472                    if self.cur_tok == tokens[r] {
473                        r += 1;
474                        if is_match_or_end(tokens[r]) {
475                            if is_left_brace(self.cur_tok) {
476                                self.input.align_state -= 1;
477                            }
478                            break 'cont; // found
479                        }
480                        continue 'cont;
481                    }
482                    // Contribute recently matched tokens.
483                    if s != Some(r) {
484                        match s {
485                            None => {
486                                self.print_err("Use of ");
487                                self.sprint_cs(self.input.warning_index);
488                                self.printer.print_str(" doesn't match its definition");
489                                self.help(&[
490                                    "If you say, e.g., `\\def\\a1{...}', then you must always",
491                                    "put `1' after `\\a', since control sequence names are",
492                                    "made up of letters only. The macro here has not been",
493                                    "followed by the required stuff, so I'm ignoring it.",
494                                ]);
495                                self.error();
496                                self.runaway_list.clear();
497                                self.input.scanner_status = save_scanner_status;
498                                self.input.warning_index = save_warning_index;
499                                return;
500                            }
501                            Some(s) => {
502                                let mut t = s;
503                                loop {
504                                    self.runaway_list.push(tokens[t]);
505                                    m += 1;
506                                    let mut u = t + 1;
507                                    let mut v = s;
508                                    let mut resumed = false;
509                                    loop {
510                                        if u == r {
511                                            if self.cur_tok != tokens[v] {
512                                                break;
513                                            }
514                                            r = v + 1;
515                                            resumed = true;
516                                            break;
517                                        }
518                                        if tokens[u] != tokens[v] {
519                                            break;
520                                        }
521                                        u += 1;
522                                        v += 1;
523                                    }
524                                    if resumed {
525                                        continue 'cont;
526                                    }
527                                    t += 1;
528                                    if t == r {
529                                        break;
530                                    }
531                                }
532                                r = s;
533                            }
534                        }
535                    }
536                    if self.cur_tok == par_token && self.long_state != Cmd::LongCall {
537                        self.report_runaway_argument();
538                        self.input.align_state -= unbalance;
539                        self.input.scanner_status = save_scanner_status;
540                        self.input.warning_index = save_warning_index;
541                        return;
542                    }
543                    if is_brace(self.cur_tok) {
544                        if is_left_brace(self.cur_tok) {
545                            // Contribute an entire group.
546                            unbalance = 1;
547                            loop {
548                                self.runaway_list.push(self.cur_tok);
549                                self.get_token();
550                                if self.cur_tok == par_token && self.long_state != Cmd::LongCall {
551                                    self.report_runaway_argument();
552                                    self.input.align_state -= unbalance;
553                                    self.input.scanner_status = save_scanner_status;
554                                    self.input.warning_index = save_warning_index;
555                                    return;
556                                }
557                                if is_brace(self.cur_tok) {
558                                    if is_left_brace(self.cur_tok) {
559                                        unbalance += 1;
560                                    } else {
561                                        unbalance -= 1;
562                                        if unbalance == 0 {
563                                            break;
564                                        }
565                                    }
566                                }
567                            }
568                            rbrace_ptr = self.runaway_list.len();
569                            self.runaway_list.push(self.cur_tok);
570                        } else {
571                            // An extra right brace.
572                            self.back_input();
573                            self.print_err("Argument of ");
574                            self.sprint_cs(self.input.warning_index);
575                            self.printer.print_str(" has an extra }");
576                            self.help(&[
577                                "I've run across a `}' that doesn't seem to match anything.",
578                                "For example, `\\def\\a#1{...}' and `\\a}' would produce",
579                                "this error. If you simply proceed now, the `\\par' that",
580                                "I've just inserted will cause me to report a runaway",
581                                "argument that might be the root of the problem. But if",
582                                "your `}' was spurious, just type `2' and it will go away.",
583                            ]);
584                            self.input.align_state += 1;
585                            self.long_state = Cmd::Call;
586                            self.cur_tok = par_token;
587                            self.ins_error();
588                            continue 'cont;
589                        }
590                    } else {
591                        if self.cur_tok == Token::SPACE && is_match_or_end(tokens[r]) {
592                            continue 'cont;
593                        }
594                        self.runaway_list.push(self.cur_tok);
595                    }
596                    m += 1;
597                    if !is_match_or_end(tokens[r]) {
598                        continue 'cont;
599                    }
600                    break 'cont;
601                }
602                // found:
603                if s.is_some() {
604                    // Tidy up the parameter just scanned.
605                    let param: TokenList = if m == 1
606                        && self.runaway_list.last().is_some_and(|&t| is_brace(t))
607                        && rbrace_ptr > 0
608                    {
609                        Rc::from(&self.runaway_list[1..rbrace_ptr])
610                    } else {
611                        Rc::from(&self.runaway_list[..])
612                    };
613                    pstack.push(param);
614                    let param = &pstack[pstack.len() - 1];
615                    if self.eqtb.int(ip::TRACINGMACROS) > 0 {
616                        self.begin_diagnostic();
617                        if !self.print_input_level() {
618                            self.printer.begin_line();
619                        }
620                        self.printer.print_char(match_chr);
621                        self.printer.print_int(pstack.len() as i64);
622                        self.printer.print_str("<-");
623                        self.show_token_list(param, 0, param.len(), 1000);
624                        self.end_diagnostic(false);
625                    }
626                }
627                self.arg_scratch = std::mem::take(&mut self.runaway_list);
628                if tokens[r] == Token::END_MATCH {
629                    break;
630                }
631            }
632        }
633        // Feed the macro body and its parameters to the scanner.
634        while let Some(Level::Tokens(t)) = self.input.stack.last() {
635            if t.loc < t.list.len() || t.kind == TokKind::VTemplate {
636                break;
637            }
638            self.end_token_list();
639        }
640        if let Some(i) = self.native_of(&mac, save_cs)
641            && self.run_native(i, &pstack, one_step)
642        {
643            pstack.clear();
644            self.param_scratch = pstack;
645            self.input.scanner_status = save_scanner_status;
646            self.input.warning_index = save_warning_index;
647            return;
648        }
649        let param_start = self.input.param_stack.len();
650        self.input.param_stack.append(&mut pstack);
651        self.param_scratch = pstack;
652        self.begin_macro(tokens, r + 1, self.input.warning_index, param_start);
653        self.input.scanner_status = save_scanner_status;
654        self.input.warning_index = save_warning_index;
655    }
656
657    /// Reports a runaway argument ("Paragraph ended before …").
658    fn report_runaway_argument(&mut self) {
659        if self.long_state == Cmd::Call {
660            self.runaway();
661            self.print_err("Paragraph ended before ");
662            self.sprint_cs(self.input.warning_index);
663            self.printer.print_str(" was complete");
664            self.help(&[
665                "I suspect you've forgotten a `}', causing me to apply this",
666                "control sequence to too much text. How can we recover?",
667                "My plan is to forget the whole thing and hope for the best.",
668            ]);
669            self.back_error();
670        }
671    }
672
673    /// Prints the text that has run away (`runaway`).
674    pub fn runaway(&mut self) {
675        let what = match self.input.scanner_status {
676            ScannerStatus::Defining => "definition",
677            ScannerStatus::Matching => "argument",
678            ScannerStatus::Aligning => "preamble",
679            ScannerStatus::Absorbing => "text",
680            _ => return,
681        };
682        self.printer.print_nl("Runaway ");
683        self.printer.print_str(what);
684        self.printer.print_char('?' as u32);
685        self.printer.print_ln();
686        let list = std::mem::take(&mut self.runaway_list);
687        let limit = self.printer.error_line.saturating_sub(10);
688        self.show_token_list(&list, 0, list.len(), limit as i64);
689        self.runaway_list = list;
690    }
691
692    /// Checks whether an `\outer` macro or end of file is allowed here
693    /// (`check_outer_validity`, §336).
694    pub fn check_outer_validity(&mut self) {
695        if self.input.scanner_status == ScannerStatus::Normal {
696            return;
697        }
698        if self.eqtb.int(ip::SUPPRESSOUTERERROR) > 0 && self.cur_cs != NO_CS {
699            return;
700        }
701        self.err.deletions_allowed = false;
702        // Back up an outer control sequence so that it can be reread.
703        if self.cur_cs != NO_CS {
704            let reread = match self.input.stack.last() {
705                Some(Level::Tokens(_)) => true,
706                Some(Level::Text(t)) => !matches!(
707                    t.source,
708                    crate::input::LineSource::Terminal | crate::input::LineSource::Read(_)
709                ),
710                None => true,
711            };
712            if reread {
713                let list: TokenList = Rc::from(vec![Token::cs(self.cur_cs)]);
714                self.back_list(list);
715            }
716            self.cur_cmd = Cmd::Spacer;
717            self.cur_chr = ' ' as u32;
718        }
719        if self.input.scanner_status > ScannerStatus::Skipping {
720            self.runaway();
721            if self.cur_cs == NO_CS {
722                self.print_err("File ended");
723            } else {
724                self.cur_cs = NO_CS;
725                self.print_err("Forbidden control sequence found");
726            }
727            self.printer.print_str(" while scanning ");
728            let mut ins: Vec<Token> = Vec::new();
729            match self.input.scanner_status {
730                ScannerStatus::Defining => {
731                    self.printer.print_str("definition");
732                    ins.push(Token::RIGHT_BRACE);
733                }
734                ScannerStatus::Matching => {
735                    self.printer.print_str("use");
736                    ins.push(Token::cs(self.cs.par));
737                    self.long_state = Cmd::OuterCall;
738                }
739                ScannerStatus::Aligning => {
740                    self.printer.print_str("preamble");
741                    ins.push(Token::cs(self.cs.frozen_cr));
742                    ins.push(Token::RIGHT_BRACE);
743                    self.input.align_state = -1_000_000;
744                }
745                _ => {
746                    self.printer.print_str("text");
747                    ins.push(Token::RIGHT_BRACE);
748                }
749            }
750            self.ins_list(Rc::from(ins));
751            self.printer.print_str(" of ");
752            self.sprint_cs(self.input.warning_index);
753            self.help(&[
754                "I suspect you have forgotten a `}', causing me",
755                "to read past where you wanted me to stop.",
756                "I'll try to recover; but if the error is serious,",
757                "you'd better type `E' or `X' now and fix your file.",
758            ]);
759            self.error();
760        } else {
761            self.print_err("Incomplete ");
762            self.print_cmd_chr(Cmd::IfTest, self.cond.cur_if);
763            self.printer.print_str("; all text was ignored after line ");
764            self.printer.print_int(self.cond.skip_line.into());
765            let third = if self.cur_cs != NO_CS {
766                self.cur_cs = NO_CS;
767                "the matching `\\fi'. I've inserted a `\\fi'; this might work."
768            } else {
769                "The file ended while I was skipping conditional text."
770            };
771            self.help(&[
772                "A forbidden control sequence occurred in skipped text.",
773                "This kind of error happens when you say `\\if...' and forget",
774                third,
775            ]);
776            self.cur_tok = Token::cs(self.cs.frozen_fi);
777            self.ins_error();
778        }
779        self.err.deletions_allowed = true;
780    }
781
782    /// Returns the frozen control sequence that carries the primitive
783    /// meaning of the primitive named like `cs` (for `\primitive`).
784    fn frozen_primitive(&mut self, cs: CsId) -> CsId {
785        let name = self.hash.name(cs).to_vec();
786        if let Some(&f) = self.frozen_prims.get(&name) {
787            return f;
788        }
789        let f = self.hash.new_frozen(&name);
790        if let Some(p) = crate::primitives::lookup(&name) {
791            self.eqtb.ensure_cs(f);
792            self.eqtb.cs[f.0 as usize].val = crate::eqtb::Meaning::prim(p.cmd, p.chr);
793        }
794        self.frozen_prims.insert(name, f);
795        f
796    }
797}
798
799/// The modifier of `\relax` (LuaTeX uses one past the largest character).
800pub const RELAX_CHR: u32 = 0x11_0000;