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