Skip to main content

retex_engine/
input.rs

1//! The input stack and the tokenizer (*TeX: The Program* §300–§365).
2//!
3//! TeX reads from a stack of input levels. Each level is either a text
4//! source read line by line (a file, the terminal, a `\read` stream, a
5//! `\scantokens` pseudo-file or lines printed from Lua) or a token list (a
6//! macro body, a parameter, backed-up tokens, `\everypar`, …). `get_next`
7//! produces the next token from the top level, converting characters into
8//! tokens according to their category codes.
9
10use std::rc::Rc;
11
12use crate::Engine;
13use crate::cmd::Cmd;
14use crate::hash::{CsId, NO_CS, NULL_CS};
15use crate::token::{Token, TokenList};
16use crate::utf8x;
17
18/// The scanner state of a text level (`state`).
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub enum LexState {
21    /// In the middle of a line.
22    MidLine,
23    /// Skipping blanks after a control word or space.
24    SkipBlanks,
25    /// At the beginning of a line.
26    NewLine,
27}
28
29/// The kind of a token-list input level (`token_type`).
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub enum TokKind {
32    /// A macro parameter.
33    Parameter,
34    /// The `u` part of an alignment template.
35    UTemplate,
36    /// The `v` part of an alignment template.
37    VTemplate,
38    /// Tokens backed up by `back_input`.
39    BackedUp,
40    /// A backed-up character (LuaTeX distinguishes it in context lines).
41    BackedUpChar,
42    /// Tokens inserted during error recovery or by TeX itself.
43    Inserted,
44    /// A macro body.
45    Macro,
46    /// `\output`.
47    OutputText,
48    /// `\everypar`.
49    EveryParText,
50    /// `\everymath`.
51    EveryMathText,
52    /// `\everydisplay`.
53    EveryDisplayText,
54    /// `\everyhbox`.
55    EveryHboxText,
56    /// `\everyvbox`.
57    EveryVboxText,
58    /// `\everyjob`.
59    EveryJobText,
60    /// `\everycr`.
61    EveryCrText,
62    /// A `\mark` text.
63    MarkText,
64    /// `\everyeof`.
65    EveryEofText,
66    /// A `\write` text.
67    WriteText,
68    /// Tokens produced by Lua (`token.put_next`, `tex.runtoks`, …).
69    LocalText,
70}
71
72/// The category-code regime of a line printed from Lua.
73#[derive(Clone, Copy, Debug, PartialEq, Eq)]
74pub enum CatMode {
75    /// The current catcode table.
76    Current,
77    /// A specific `\catcodetable`.
78    Table(u32),
79    /// `-2`: everything other (12) except space (10), as `\the\toks`.
80    String,
81    /// All characters get this category (`tex.cprint`, `tex.write` uses
82    /// [`CatMode::String`]).
83    Single(u8),
84}
85
86/// How a line printed from Lua is fed to the scanner.
87#[derive(Clone, Copy, Debug, PartialEq, Eq)]
88pub enum LuaLineKind {
89    /// `tex.print`: a full line with `\endlinechar`.
90    Print,
91    /// `tex.sprint`: a partial line, no `\endlinechar`, mid-line state.
92    Sprint,
93}
94
95/// One item printed from Lua during a `\directlua` call.
96#[derive(Clone, Debug)]
97pub enum LuaItem {
98    /// A line of text.
99    Text {
100        /// The bytes (UTF-8).
101        text: Vec<u8>,
102        /// How it is scanned.
103        kind: LuaLineKind,
104        /// Its catcode regime.
105        catmode: CatMode,
106    },
107    /// Tokens injected directly.
108    Tokens(Vec<Token>),
109}
110
111/// Where the lines of a text level come from.
112#[derive(Clone, Debug)]
113pub enum LineSource {
114    /// A file held in memory, with the byte offset of the next line.
115    File {
116        /// The file content.
117        data: Rc<[u8]>,
118        /// Offset of the next unread line.
119        pos: usize,
120    },
121    /// The terminal (or the command line for the first line).
122    Terminal,
123    /// A `\read` stream: the level holds one line only.
124    Read(u32),
125    /// Lines of a `\scantokens` pseudo-file or of Lua `tex.print`.
126    Lines {
127        /// The remaining lines.
128        lines: std::collections::VecDeque<Vec<u8>>,
129        /// For Lua lines: the catcode table to use, if any.
130        catcodes: Option<u32>,
131        /// Whether this is a pseudo-file (`\scantokens`) that counts lines.
132        pseudo: bool,
133    },
134    /// Output of `tex.print` and friends.
135    Lua(std::collections::VecDeque<LuaItem>),
136}
137
138/// A text input level.
139#[derive(Clone, Debug)]
140pub struct TextLevel {
141    /// The scanner state.
142    pub state: LexState,
143    /// The characters of the current line, including the end-of-line
144    /// character if one was appended.
145    pub buf: Vec<u32>,
146    /// Index of the next character in `buf`.
147    pub loc: usize,
148    /// Where further lines come from.
149    pub source: LineSource,
150    /// The current line number in this source.
151    pub line: i32,
152    /// The printable name of the file (for context lines and `(` … `)`).
153    pub name: Vec<u8>,
154    /// Whether this level was opened by `\input` (prints parentheses).
155    pub is_input_file: bool,
156    /// Whether `\everyeof` has already been inserted for this file.
157    pub eof_seen: bool,
158    /// The group level when the file was opened (e-TeX `grp_stack`).
159    pub grp_boundary: usize,
160    /// The conditional depth when the file was opened (e-TeX `if_stack`).
161    pub if_depth: usize,
162    /// The catcode regime of the current line.
163    pub catmode: CatMode,
164    /// The SyncTeX tag and line of this level: those of the file, or tag
165    /// 0 and the number of lines read for other text (observed).
166    pub sync: (u32, i32),
167    /// The file's id for the profiler and origins (see
168    /// [`crate::profile`]); 0 until given.
169    pub profile_file: u32,
170}
171
172/// A token-list input level.
173#[derive(Clone, Debug)]
174pub struct TokLevel {
175    /// The tokens.
176    pub list: TokenList,
177    /// Index of the next token.
178    pub loc: usize,
179    /// The kind of list.
180    pub kind: TokKind,
181    /// Start of this macro's parameters in the parameter stack.
182    pub param_start: usize,
183    /// For macros: the control sequence being expanded.
184    pub name: CsId,
185}
186
187/// An input level.
188#[derive(Clone, Debug)]
189pub enum Level {
190    /// Reading characters from lines of text.
191    Text(Box<TextLevel>),
192    /// Reading a token list.
193    Tokens(TokLevel),
194}
195
196/// What the scanner is doing, for runaway diagnostics (`scanner_status`).
197#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
198pub enum ScannerStatus {
199    /// Nothing special.
200    Normal,
201    /// Skipping conditional text.
202    Skipping,
203    /// Scanning a macro definition.
204    Defining,
205    /// Scanning macro arguments.
206    Matching,
207    /// Scanning an alignment preamble.
208    Aligning,
209    /// Scanning a balanced text (`\message`, `\write`, …).
210    Absorbing,
211}
212
213/// The number of input levels above the terminal's (LuaTeX's
214/// `stack_size`, observed).
215const INPUT_STACK_SIZE: usize = 10_000;
216
217/// LuaTeX's limit on nested text input levels (`max_in_open`, observed:
218/// "text input levels=15").
219pub const MAX_IN_OPEN: usize = 15;
220
221/// Whether a text level counts towards [`MAX_IN_OPEN`]: files,
222/// `\scantokens` pseudo-files, `\read` streams and the output of Lua
223/// code (observed: a `tex.print` with 15 files open overflows), but not
224/// the terminal.
225fn counts_as_open(source: &LineSource) -> bool {
226    match source {
227        LineSource::File { .. } | LineSource::Read(_) | LineSource::Lua(_) => true,
228        LineSource::Lines { pseudo, .. } => *pseudo,
229        _ => false,
230    }
231}
232
233/// The most one-token lists kept for reuse by `back_input`.
234const SPARE_TOKENS: usize = 16;
235
236/// The input stack.
237#[derive(Debug)]
238pub struct InputState {
239    /// All levels; the last one is the current input (`cur_input`).
240    pub stack: Vec<Level>,
241    /// Arguments of macros being expanded.
242    pub param_stack: Vec<TokenList>,
243    /// Brace balance for alignments (`align_state`).
244    pub align_state: i32,
245    /// What the scanner is doing.
246    pub scanner_status: ScannerStatus,
247    /// The macro or definition being scanned, for diagnostics.
248    pub warning_index: CsId,
249    /// Number of open parentheses printed for input files.
250    pub open_parens: usize,
251    /// Whether `\endinput` was seen in the current file.
252    pub force_eof: bool,
253    /// Line number of the most recent `\read` line, etc.
254    pub last_line: i32,
255    /// Text levels open on files, `\scantokens` pseudo-files and `\read`
256    /// streams (TeX's `in_open`).
257    pub in_open: usize,
258    /// One-token lists of backed-up levels that ended, for `back_input` to
259    /// reuse (it runs very often; this saves an allocation each time).
260    pub spare_tokens: Vec<TokenList>,
261}
262
263impl Default for InputState {
264    fn default() -> Self {
265        InputState {
266            stack: Vec::new(),
267            param_stack: Vec::new(),
268            align_state: 1_000_000,
269            scanner_status: ScannerStatus::Normal,
270            warning_index: NO_CS,
271            open_parens: 0,
272            force_eof: false,
273            last_line: 0,
274            in_open: 0,
275            spare_tokens: Vec::new(),
276        }
277    }
278}
279
280/// `no_expand_flag`: the modifier of the special `\relax` produced by
281/// `\noexpand` (TeX uses 257; LuaTeX uses a value outside the char range).
282pub const NO_EXPAND_FLAG: u32 = 0x11_0001;
283
284impl InputState {
285    /// Number of text levels that are input files (TeX's `in_open`).
286    pub fn in_open(&self) -> usize {
287        self.stack
288            .iter()
289            .filter(|l| matches!(l, Level::Text(t) if t.is_input_file))
290            .count()
291    }
292
293    /// The innermost text level, if any.
294    pub fn cur_text(&self) -> Option<&TextLevel> {
295        self.stack.iter().rev().find_map(|l| match l {
296            Level::Text(t) => Some(&**t),
297            Level::Tokens(_) => None,
298        })
299    }
300
301    /// The current line number (`line`): the line of the innermost file.
302    pub fn line(&self) -> i32 {
303        self.stack
304            .iter()
305            .rev()
306            .find_map(|l| match l {
307                Level::Text(t) if !matches!(t.source, LineSource::Read(_)) => Some(t.line),
308                _ => None,
309            })
310            .unwrap_or(0)
311    }
312
313    /// Whether the current level is a token list.
314    pub fn in_token_list(&self) -> bool {
315        matches!(self.stack.last(), Some(Level::Tokens(_)))
316    }
317
318    /// Whether we are reading from the terminal (`terminal_input`).
319    pub fn terminal_input(&self) -> bool {
320        matches!(self.stack.last(), Some(Level::Text(t)) if matches!(t.source, LineSource::Terminal))
321    }
322}
323
324/// Splits the next line off `data[pos..]`. Lines end at LF, CR or CR LF.
325/// Returns the line bytes and the new position, or `None` at end of data.
326pub fn next_line(data: &[u8], pos: usize) -> Option<(&[u8], usize)> {
327    if pos >= data.len() {
328        return None;
329    }
330    let rest = &data[pos..];
331    match rest.iter().position(|&b| b == b'\n' || b == b'\r') {
332        Some(i) => {
333            let mut next = pos + i + 1;
334            if rest[i] == b'\r' && rest.get(i + 1) == Some(&b'\n') {
335                next += 1;
336            }
337            Some((&rest[..i], next))
338        }
339        None => Some((rest, data.len())),
340    }
341}
342
343/// Decodes a line of input into characters, removing trailing spaces
344/// (TeX's `input_ln`; tabs are kept, as in LuaTeX).
345pub fn decode_line(bytes: &[u8]) -> Vec<u32> {
346    let mut end = bytes.len();
347    while end > 0 && bytes[end - 1] == b' ' {
348        end -= 1;
349    }
350    utf8x::chars_marking_invalid(&bytes[..end])
351}
352
353fn is_hex(c: u32) -> bool {
354    matches!(c, 0x30..=0x39 | 0x61..=0x66)
355}
356
357fn hex_val(c: u32) -> u32 {
358    if c <= 0x39 { c - 0x30 } else { c - 0x61 + 10 }
359}
360
361/// Result of looking for a `^^` sequence at a position.
362enum Expanded {
363    /// No expanded character here.
364    None,
365    /// An expanded character of `len` input characters.
366    Char { c: u32, len: usize },
367    /// A malformed `^^^^`/`^^^^^^` sequence; `short` if the line ends
368    /// before the digits could (LuaTeX's "…, end of input").
369    Bad { six: bool, short: bool },
370}
371
372/// Examines `buf[k..]` for an expanded character introduced by the
373/// superscript character `sup` at `buf[k]` (the caller checks its catcode).
374fn expanded_at(buf: &[u32], k: usize) -> Expanded {
375    let sup = buf[k];
376    if buf.get(k + 1) != Some(&sup) {
377        return Expanded::None;
378    }
379    // LuaTeX: ^^^^^^ + six hex digits, or ^^^^ + four hex digits.
380    let run = buf[k..].iter().take(6).take_while(|&&c| c == sup).count();
381    if run >= 4 {
382        let six = run == 6;
383        let n = if six { 6 } else { 4 };
384        let start = k + run;
385        let digits = buf.get(start..start + n);
386        return match digits {
387            Some(d) if d.iter().all(|&c| is_hex(c)) => {
388                let c = d.iter().fold(0u32, |acc, &c| acc * 16 + hex_val(c));
389                if c > 0x10_FFFF {
390                    Expanded::Bad { six, short: false }
391                } else {
392                    Expanded::Char { c, len: run + n }
393                }
394            }
395            _ => Expanded::Bad {
396                six,
397                short: start + n > buf.len(),
398            },
399        };
400    }
401    // Classic TeX ^^ notation.
402    let Some(&c) = buf.get(k + 2) else {
403        return Expanded::None;
404    };
405    if c >= 0o200 {
406        return Expanded::None;
407    }
408    if is_hex(c)
409        && let Some(&cc) = buf.get(k + 3)
410        && is_hex(cc)
411    {
412        return Expanded::Char {
413            c: hex_val(c) * 16 + hex_val(cc),
414            len: 4,
415        };
416    }
417    Expanded::Char {
418        c: if c < 0o100 { c + 0o100 } else { c - 0o100 },
419        len: 3,
420    }
421}
422
423impl Engine {
424    // ----- input levels ---------------------------------------------------
425
426    /// Pushes a token list onto the input stack (`begin_token_list`).
427    pub fn begin_token_list(&mut self, list: TokenList, kind: TokKind) {
428        // tex.web §323: with \tracingmacros>1, token lists that TeX
429        // inserts itself are shown (`\everypar->...`).
430        let name = match kind {
431            TokKind::OutputText => Some("output"),
432            TokKind::EveryParText => Some("everypar"),
433            TokKind::EveryMathText => Some("everymath"),
434            TokKind::EveryDisplayText => Some("everydisplay"),
435            TokKind::EveryHboxText => Some("everyhbox"),
436            TokKind::EveryVboxText => Some("everyvbox"),
437            TokKind::EveryJobText => Some("everyjob"),
438            TokKind::EveryCrText => Some("everycr"),
439            TokKind::EveryEofText => Some("everyeof"),
440            TokKind::MarkText => Some("mark"),
441            TokKind::WriteText => Some("write"),
442            _ => None,
443        };
444        if let Some(name) = name
445            && self
446                .eqtb
447                .int(crate::primitives_gen::assign_int::TRACINGMACROS)
448                > 1
449        {
450            self.begin_diagnostic();
451            self.printer.print_nl("");
452            self.print_esc_str(name);
453            self.printer.print_str("->");
454            self.token_show_list(&list, 0, list.len(), 10_000_000);
455            self.end_diagnostic(false);
456        }
457        self.push_input(Level::Tokens(TokLevel {
458            list,
459            loc: 0,
460            kind,
461            param_start: self.input.param_stack.len(),
462            name: NO_CS,
463        }));
464    }
465
466    /// Pushes an input level (`push_input`), within LuaTeX's input stack
467    /// size.
468    fn push_input(&mut self, level: Level) {
469        if self.input.stack.len() > INPUT_STACK_SIZE {
470            self.overflow("input stack size", INPUT_STACK_SIZE as i64);
471        }
472        self.input.stack.push(level);
473    }
474
475    /// Pushes a macro body with its parameters (already on the parameter
476    /// stack starting at `param_start`).
477    pub fn begin_macro(&mut self, list: TokenList, start: usize, name: CsId, param_start: usize) {
478        self.push_input(Level::Tokens(TokLevel {
479            list,
480            loc: start,
481            kind: TokKind::Macro,
482            param_start,
483            name,
484        }));
485    }
486
487    /// Inserts a token list for immediate reading (`ins_list`).
488    pub fn ins_list(&mut self, list: TokenList) {
489        self.begin_token_list(list, TokKind::Inserted);
490    }
491
492    /// Pops the current token-list level (`end_token_list`).
493    pub fn end_token_list(&mut self) {
494        if let Some(Level::Tokens(t)) = self.input.stack.pop() {
495            if matches!(t.kind, TokKind::BackedUp | TokKind::Inserted)
496                && t.list.len() == 1
497                && Rc::strong_count(&t.list) == 1
498                && self.input.spare_tokens.len() < SPARE_TOKENS
499            {
500                self.input.spare_tokens.push(t.list);
501                self.check_interrupt();
502                return;
503            }
504            match t.kind {
505                TokKind::Macro => {
506                    self.input.param_stack.truncate(t.param_start);
507                }
508                TokKind::UTemplate => {
509                    if self.input.align_state > 500_000 {
510                        self.input.align_state = 0;
511                    } else {
512                        self.fatal_error("(interwoven alignment preambles are not allowed)");
513                    }
514                }
515                _ => {}
516            }
517        } else {
518            debug_assert!(false, "end_token_list on a text level");
519        }
520        self.check_interrupt();
521    }
522
523    /// Puts the current token back into the input (`back_input`).
524    pub fn back_input(&mut self) {
525        // Pop exhausted token lists first (but not v templates).
526        while let Some(Level::Tokens(t)) = self.input.stack.last() {
527            if t.loc < t.list.len() || t.kind == TokKind::VTemplate {
528                break;
529            }
530            self.end_token_list();
531        }
532        let tok = self.cur_tok;
533        if tok.0 < crate::token::CS_TOKEN_FLAG {
534            let cmd = tok.cmd_code();
535            if cmd == Cmd::RightBrace as u32 {
536                self.input.align_state += 1;
537            } else if cmd == Cmd::LeftBrace as u32 {
538                self.input.align_state -= 1;
539            }
540        }
541        let list = match self.input.spare_tokens.pop() {
542            Some(mut list) => {
543                // Only this level held it.
544                if let Some(slot) = Rc::get_mut(&mut list) {
545                    slot[0] = tok;
546                    list
547                } else {
548                    Rc::from([tok])
549                }
550            }
551            None => Rc::from([tok]),
552        };
553        self.push_input(Level::Tokens(TokLevel {
554            list,
555            loc: 0,
556            kind: TokKind::BackedUp,
557            param_start: self.input.param_stack.len(),
558            name: NO_CS,
559        }));
560    }
561
562    /// Backs up the current token and marks it as inserted
563    /// (`back_error` uses this through `ins_error`).
564    pub fn back_input_inserted(&mut self) {
565        self.back_input();
566        if let Some(Level::Tokens(t)) = self.input.stack.last_mut() {
567            t.kind = TokKind::Inserted;
568        }
569    }
570
571    /// Pushes a text level reading `source`. Files, pseudo-files and
572    /// `\read` streams nest at most [`MAX_IN_OPEN`] deep, as in LuaTeX.
573    pub fn begin_file_reading(&mut self, source: LineSource, name: Vec<u8>, is_input_file: bool) {
574        if counts_as_open(&source) {
575            self.check_in_open();
576            self.input.in_open += 1;
577        }
578        self.push_input(Level::Text(Box::new(TextLevel {
579            state: LexState::NewLine,
580            buf: Vec::new(),
581            loc: 0,
582            source,
583            line: 0,
584            name,
585            is_input_file,
586            eof_seen: false,
587            grp_boundary: self.save.cur_boundary,
588            if_depth: self.cond.stack.len(),
589            catmode: CatMode::Current,
590            sync: (0, 0),
591            profile_file: 0,
592        })));
593        self.sync_input_changed();
594    }
595
596    /// Stops if no further file can be opened (`begin_file_reading`
597    /// checks this before `\input` shows the file name).
598    pub(crate) fn check_in_open(&mut self) {
599        if self.input.in_open >= MAX_IN_OPEN {
600            self.overflow("text input levels", MAX_IN_OPEN as i64);
601        }
602    }
603
604    /// Pops the current text level (`end_file_reading`).
605    pub fn end_file_reading(&mut self) {
606        let lvl = self.input.stack.pop();
607        if let Some(Level::Text(t)) = lvl
608            && counts_as_open(&t.source)
609        {
610            self.input.in_open = self.input.in_open.saturating_sub(1);
611        }
612        self.sync_input_changed();
613    }
614
615    /// Reads the next line of the current text level into its buffer,
616    /// appending `\endlinechar`. Returns `false` at end of source.
617    fn read_next_line(&mut self) -> bool {
618        self.check_begun();
619        let end_line_char = self
620            .eqtb
621            .int(crate::primitives_gen::assign_int::ENDLINECHAR);
622        let Some(Level::Text(t)) = self.input.stack.last_mut() else {
623            return false;
624        };
625        // \scantextokens text gets no \endlinechar after its last line.
626        let mut eol = true;
627        let line: Vec<u32> = match &mut t.source {
628            LineSource::File { data, pos } => match next_line(data, *pos) {
629                Some((bytes, next)) => {
630                    let l = decode_line(bytes);
631                    *pos = next;
632                    l
633                }
634                None => return false,
635            },
636            LineSource::Lines { lines, pseudo, .. } => match lines.pop_front() {
637                Some(bytes) => {
638                    eol = *pseudo || !lines.is_empty();
639                    decode_line(&bytes)
640                }
641                None => return false,
642            },
643            LineSource::Lua(items) => {
644                let last = items.len() == 1;
645                match items.pop_front() {
646                    Some(LuaItem::Text {
647                        text,
648                        kind,
649                        catmode,
650                    }) => {
651                        t.catmode = catmode;
652                        t.loc = 0;
653                        t.sync.1 += 1;
654                        // Observed: each printed line, `tex.sprint` string
655                        // or token item is a line of the Lua level for
656                        // `\inputlineno`, counted from 1.
657                        t.line += 1;
658                        if kind == LuaLineKind::Sprint {
659                            t.buf = utf8x::chars(&text);
660                            t.state = LexState::MidLine;
661                            self.sync_input_changed();
662                            return true;
663                        }
664                        t.buf = decode_line(&text);
665                        t.state = LexState::NewLine;
666                        if !last && (0..=0x10_FFFF).contains(&end_line_char) {
667                            t.buf.push(end_line_char as u32);
668                        }
669                        self.sync_input_changed();
670                        return true;
671                    }
672                    Some(LuaItem::Tokens(toks)) => {
673                        t.buf.clear();
674                        t.loc = 0;
675                        t.line += 1;
676                        self.pending_lua_tokens = Some(toks);
677                        return true;
678                    }
679                    None => return false,
680                }
681            }
682            LineSource::Terminal | LineSource::Read(_) => return false,
683        };
684        t.buf = line;
685        if eol && (0..=0x10_FFFF).contains(&end_line_char) {
686            t.buf.push(end_line_char as u32);
687        }
688        t.loc = 0;
689        t.line += 1;
690        t.sync.1 = t.line;
691        self.sync_input_changed();
692        true
693    }
694
695    // ----- the tokenizer ---------------------------------------------------
696
697    /// Sets `cur_cmd`, `cur_chr` and `cur_cs` to the next token
698    /// (`get_next`).
699    #[inline]
700    pub fn get_next(&mut self) {
701        // Fast path: the next token of a token list, when it needs none of
702        // the special handling below (parameters, the end of the list,
703        // outer macros, \noexpand markers, alignment entries).
704        if let Some(Level::Tokens(t)) = self.input.stack.last_mut()
705            && let Some(&tok) = t.list.get(t.loc)
706        {
707            if tok.is_cs() {
708                let cs = tok.cs_id();
709                let m = self.eqtb.meaning(cs);
710                if m.cmd < Cmd::TabMark || (m.cmd > Cmd::CarRet && !m.cmd.is_outer_or_special()) {
711                    t.loc += 1;
712                    self.cur_cs = cs;
713                    self.cur_cmd = m.cmd;
714                    self.cur_chr = m.chr;
715                    return;
716                }
717            } else {
718                let cmd = tok.cmd();
719                if cmd > Cmd::CarRet || cmd < Cmd::MathShift {
720                    t.loc += 1;
721                    match cmd {
722                        Cmd::LeftBrace => self.input.align_state += 1,
723                        Cmd::RightBrace => self.input.align_state -= 1,
724                        _ => {}
725                    }
726                    self.cur_cs = NO_CS;
727                    self.cur_cmd = cmd;
728                    self.cur_chr = tok.chr();
729                    return;
730                }
731            }
732        }
733        self.get_next_slow();
734    }
735
736    /// [`get_next`](Self::get_next) for everything but the fast path.
737    #[inline(never)]
738    fn get_next_slow(&mut self) {
739        'restart: loop {
740            self.cur_cs = NO_CS;
741            let Some(top) = self.input.stack.last_mut() else {
742                // Nothing left to read: behave like the end of the terminal.
743                self.cur_cmd = Cmd::Relax;
744                self.cur_chr = 0;
745                self.fatal_error("*** (job aborted, no legal \\end found)");
746            };
747            match top {
748                Level::Tokens(t) => {
749                    if t.loc < t.list.len() {
750                        let tok = t.list[t.loc];
751                        t.loc += 1;
752                        if tok.is_cs() {
753                            let cs = tok.cs_id();
754                            self.cur_cs = cs;
755                            let m = self.eqtb.meaning(cs);
756                            self.cur_cmd = m.cmd;
757                            self.cur_chr = m.chr;
758                            if self.cur_cmd.is_outer_or_special() {
759                                if self.cur_cmd == Cmd::DontExpand {
760                                    // The next token is a cs whose expansion
761                                    // is suppressed.
762                                    let next = t.list[t.loc];
763                                    t.loc = t.list.len();
764                                    let cs = next.cs_id();
765                                    self.cur_cs = cs;
766                                    let m = self.eqtb.meaning(cs);
767                                    self.cur_cmd = m.cmd;
768                                    self.cur_chr = m.chr;
769                                    if self.cur_cmd > Cmd::MAX_COMMAND {
770                                        self.cur_cmd = Cmd::Relax;
771                                        self.cur_chr = NO_EXPAND_FLAG;
772                                    }
773                                } else {
774                                    self.check_outer_validity();
775                                }
776                            }
777                        } else {
778                            self.cur_cmd = tok.cmd();
779                            self.cur_chr = tok.chr();
780                            match self.cur_cmd {
781                                Cmd::LeftBrace => self.input.align_state += 1,
782                                Cmd::RightBrace => self.input.align_state -= 1,
783                                Cmd::CarRet => {
784                                    // out_param: insert the macro parameter.
785                                    // Lua code can make out_param tokens
786                                    // outside macros (token.set_macro with
787                                    // a character of category 5); they
788                                    // stand for no parameter and vanish.
789                                    let p = (self.cur_chr as usize)
790                                        .checked_sub(1)
791                                        .and_then(|i| self.input.param_stack.get(t.param_start + i))
792                                        .cloned();
793                                    if let Some(p) = p {
794                                        self.begin_token_list(p, TokKind::Parameter);
795                                    }
796                                    continue 'restart;
797                                }
798                                _ => {}
799                            }
800                        }
801                    } else {
802                        self.end_token_list();
803                        continue 'restart;
804                    }
805                }
806                Level::Text(_) => {
807                    if !self.get_next_from_text() {
808                        continue 'restart;
809                    }
810                }
811            }
812            // If an alignment entry has just ended, take appropriate action.
813            if self.cur_cmd <= Cmd::CarRet
814                && self.cur_cmd >= Cmd::TabMark
815                && self.input.align_state == 0
816            {
817                self.insert_v_template();
818                continue 'restart;
819            }
820            return;
821        }
822    }
823
824    /// Tokenizes from the current text level. Returns `false` if the caller
825    /// must restart (a level was popped or an error occurred).
826    fn get_next_from_text(&mut self) -> bool {
827        loop {
828            // `switch`: get the next character, reading a new line if needed.
829            let (c, catmode, state) = {
830                let Some(Level::Text(t)) = self.input.stack.last_mut() else {
831                    return false;
832                };
833                if t.loc < t.buf.len() {
834                    let c = t.buf[t.loc];
835                    t.loc += 1;
836                    (c, t.catmode, t.state)
837                } else {
838                    if matches!(t.source, LineSource::Read(_)) {
839                        // End of a \read line: TeX's null token
840                        // (`cur_cmd = cur_chr = 0`) tells `read_toks`.
841                        self.cur_cmd = Cmd::Relax;
842                        self.cur_chr = 0;
843                        return true;
844                    }
845                    if !self.next_line_or_pop() {
846                        return false;
847                    }
848                    continue;
849                }
850            };
851            let c = if utf8x::is_raw(c) {
852                self.invalid_utf8_sequence()
853            } else {
854                c
855            };
856            self.cur_chr = c;
857            // `reswitch`
858            let mut c = c;
859            loop {
860                let cat = self.catcode_in_mode(c, catmode);
861                self.cur_cmd = Cmd::from_u8(cat);
862                match (cat, state) {
863                    // Ignored characters, and spaces at the start of lines or
864                    // after control words.
865                    (9, _) | (10, LexState::SkipBlanks | LexState::NewLine) => break,
866                    (0, _) => {
867                        self.scan_control_sequence();
868                        return true;
869                    }
870                    (13, _) => {
871                        let cs = self.hash.active(c);
872                        self.cur_cs = cs;
873                        let m = self.eqtb.meaning(cs);
874                        self.cur_cmd = m.cmd;
875                        self.cur_chr = m.chr;
876                        self.set_state(LexState::MidLine);
877                        if self.cur_cmd.is_outer_or_special() {
878                            self.check_outer_validity();
879                        }
880                        return true;
881                    }
882                    (7, _) => {
883                        match self.expanded_in_line() {
884                            Expanded::Char { c: nc, len } => {
885                                if let Some(Level::Text(t)) = self.input.stack.last_mut() {
886                                    t.loc += len - 1;
887                                }
888                                c = nc;
889                                self.cur_chr = c;
890                                continue; // reswitch
891                            }
892                            Expanded::Bad { six, short } => {
893                                self.print_err(match (six, short) {
894                                    (true, false) => "^^^^^^ needs six hex digits",
895                                    (true, true) => "^^^^^^ needs six hex digits, end of input",
896                                    (false, false) => "^^^^ needs four hex digits",
897                                    (false, true) => "^^^^ needs four hex digits, end of input",
898                                });
899                                self.help(&[]);
900                                self.error();
901                                // Observed: LuaTeX then reads the first three
902                                // superscript characters the classic way
903                                // (`^^^` is U+001E).
904                                if let Some(Level::Text(t)) = self.input.stack.last_mut() {
905                                    t.loc += 2;
906                                }
907                                c = if c < 0o100 { c + 0o100 } else { c - 0o100 };
908                                self.cur_chr = c;
909                                continue; // reswitch
910                            }
911                            Expanded::None => {
912                                self.set_state(LexState::MidLine);
913                                return true;
914                            }
915                        }
916                    }
917                    (15, _) => {
918                        self.print_err("Text line contains an invalid character");
919                        self.help(&[
920                            "A funny symbol that I can't read has just been input.",
921                            "Continue, and I'll forget that it ever happened.",
922                        ]);
923                        self.err.deletions_allowed = false;
924                        self.error();
925                        self.err.deletions_allowed = true;
926                        return false;
927                    }
928                    (10, LexState::MidLine) => {
929                        self.set_state(LexState::SkipBlanks);
930                        self.cur_chr = ' ' as u32;
931                        return true;
932                    }
933                    (5, LexState::MidLine) => {
934                        self.finish_line();
935                        self.cur_cmd = Cmd::Spacer;
936                        self.cur_chr = ' ' as u32;
937                        return true;
938                    }
939                    (5, LexState::SkipBlanks) | (14, _) => {
940                        self.finish_line();
941                        break;
942                    }
943                    (5, LexState::NewLine) => {
944                        self.finish_line();
945                        let par = self.cs.par;
946                        self.cur_cs = par;
947                        let m = self.eqtb.meaning(par);
948                        self.cur_cmd = m.cmd;
949                        self.cur_chr = m.chr;
950                        if self.cur_cmd.is_outer_or_special() {
951                            self.check_outer_validity();
952                        }
953                        return true;
954                    }
955                    (1, s) => {
956                        if s != LexState::MidLine {
957                            self.set_state(LexState::MidLine);
958                        }
959                        self.input.align_state += 1;
960                        return true;
961                    }
962                    (2, s) => {
963                        if s != LexState::MidLine {
964                            self.set_state(LexState::MidLine);
965                        }
966                        self.input.align_state -= 1;
967                        return true;
968                    }
969                    (_, s) => {
970                        if s != LexState::MidLine {
971                            self.set_state(LexState::MidLine);
972                        }
973                        return true;
974                    }
975                }
976            }
977        }
978    }
979
980    /// Reports the invalid byte just read from the current line and
981    /// skips it with the bytes that follow it, three in all (observed:
982    /// LuaTeX replaces that many bytes, up to the end of the line, by
983    /// U+FFFD, which is returned).
984    #[cold]
985    fn invalid_utf8_sequence(&mut self) -> u32 {
986        // The context shows the line up to the invalid byte.
987        if let Some(Level::Text(t)) = self.input.stack.last_mut() {
988            t.loc -= 1;
989        }
990        self.print_err("String contains an invalid utf-8 sequence");
991        self.help(&[
992            "A funny symbol that I can't read has just been (re)read.",
993            "Just continue, I'll change it to 0xFFFD.",
994        ]);
995        self.error();
996        if let Some(Level::Text(t)) = self.input.stack.last_mut() {
997            let mut bytes = 0;
998            while bytes < 3 && t.loc < t.buf.len() {
999                let c = t.buf[t.loc];
1000                bytes += if utf8x::is_raw(c) { 1 } else { utf8x::len(c) };
1001                t.loc += 1;
1002            }
1003        }
1004        0xFFFD
1005    }
1006
1007    /// The category code of `c` under a line's catcode regime.
1008    #[inline]
1009    pub fn catcode_in_mode(&self, c: u32, mode: CatMode) -> u8 {
1010        match mode {
1011            CatMode::Current => self.eqtb.catcode(c),
1012            CatMode::Table(t) => self.eqtb.catcode_in(t, c),
1013            CatMode::String => {
1014                if c == ' ' as u32 {
1015                    10
1016                } else {
1017                    12
1018                }
1019            }
1020            CatMode::Single(k) => k,
1021        }
1022    }
1023
1024    /// Pushes the output collected from a Lua call as a new input level.
1025    pub fn push_lua_output(&mut self, items: Vec<LuaItem>) {
1026        if items.is_empty() {
1027            return;
1028        }
1029        self.begin_file_reading(
1030            LineSource::Lua(items.into_iter().collect()),
1031            Vec::new(),
1032            false,
1033        );
1034        if let Some(Level::Text(t)) = self.input.stack.last_mut() {
1035            t.line = self.input_line_for_lua;
1036        }
1037    }
1038
1039    fn set_state(&mut self, s: LexState) {
1040        if let Some(Level::Text(t)) = self.input.stack.last_mut() {
1041            t.state = s;
1042        }
1043    }
1044
1045    fn finish_line(&mut self) {
1046        if let Some(Level::Text(t)) = self.input.stack.last_mut() {
1047            t.loc = t.buf.len();
1048        }
1049    }
1050
1051    /// Checks for an expanded character at the character just read.
1052    fn expanded_in_line(&self) -> Expanded {
1053        let Some(Level::Text(t)) = self.input.stack.last() else {
1054            return Expanded::None;
1055        };
1056        expanded_at(&t.buf, t.loc - 1)
1057    }
1058
1059    /// Moves to the next line of the current text level, or pops the level
1060    /// at its end (TeX §360–§362). Returns `false` when the caller must
1061    /// restart from the top of `get_next`.
1062    fn next_line_or_pop(&mut self) -> bool {
1063        let (is_terminal, is_read, is_file) = match self.input.stack.last() {
1064            Some(Level::Text(t)) => (
1065                matches!(t.source, LineSource::Terminal),
1066                matches!(t.source, LineSource::Read(_)),
1067                t.is_input_file,
1068            ),
1069            _ => return false,
1070        };
1071        if is_read {
1072            return false;
1073        }
1074        if is_terminal {
1075            // The command line (or terminal input) is exhausted.
1076            if self.input.stack.len() > 1 {
1077                // Text inserted during error recovery.
1078                self.end_file_reading();
1079                return false;
1080            }
1081            self.terminal_exhausted();
1082            return false;
1083        }
1084        let is_lua = matches!(
1085            self.input.stack.last(),
1086            Some(Level::Text(t)) if matches!(t.source, LineSource::Lua(_))
1087        );
1088        if !self.input.force_eof && self.read_next_line() {
1089            if let Some(toks) = self.pending_lua_tokens.take() {
1090                self.begin_token_list(std::rc::Rc::from(toks), TokKind::Inserted);
1091                return false;
1092            }
1093            if !is_lua {
1094                self.set_state(LexState::NewLine);
1095            }
1096            return true;
1097        }
1098        // End of file (or \endinput).
1099        let is_pseudo = matches!(
1100            self.input.stack.last(),
1101            Some(Level::Text(t)) if matches!(t.source, LineSource::Lines { pseudo: true, .. })
1102        );
1103        if !self.input.force_eof && (is_file || is_pseudo) {
1104            // e-TeX: insert \everyeof once, for files and \scantokens
1105            // pseudo-files.
1106            let every_eof = self
1107                .eqtb
1108                .toks_param(crate::primitives_gen::assign_toks::EVERYEOF)
1109                .clone();
1110            let seen = match self.input.stack.last_mut() {
1111                Some(Level::Text(t)) => std::mem::replace(&mut t.eof_seen, true),
1112                _ => true,
1113            };
1114            if !seen && !every_eof.is_empty() {
1115                if let Some(Level::Text(t)) = self.input.stack.last_mut() {
1116                    t.buf.clear();
1117                    t.loc = 0;
1118                }
1119                self.begin_token_list(every_eof, TokKind::EveryEofText);
1120                return false;
1121            }
1122        }
1123        if is_file {
1124            self.printer.print_char(')' as u32);
1125            if is_pseudo {
1126                // Observed: a traced \scantokens pseudo-file closes with
1127                // two parentheses.
1128                self.printer.print_char(')' as u32);
1129            }
1130            self.input.open_parens -= 1;
1131            self.printer.update_terminal();
1132            self.file_closed_checks();
1133        }
1134        self.input.force_eof = false;
1135        // Output printed from Lua and \scantextokens text may end anywhere;
1136        // only files and \scantokens pseudo-files are checked.
1137        let check = match self.input.stack.last() {
1138            Some(Level::Text(t)) => match &t.source {
1139                LineSource::Lua(_) => false,
1140                LineSource::Lines { pseudo, .. } => *pseudo,
1141                _ => true,
1142            },
1143            _ => true,
1144        };
1145        self.end_file_reading();
1146        if check {
1147            self.check_outer_validity();
1148        }
1149        false
1150    }
1151
1152    /// Scans a control sequence name after an escape character (§354).
1153    fn scan_control_sequence(&mut self) {
1154        'start_cs: loop {
1155            let Some(Level::Text(t)) = self.input.stack.last() else {
1156                return;
1157            };
1158            let len = t.buf.len();
1159            let loc = t.loc;
1160            let catmode = t.catmode;
1161            if loc >= len {
1162                self.cur_cs = NULL_CS;
1163                break;
1164            }
1165            let mut k = loc;
1166            let mut ch = t.buf[k];
1167            let mut cat = self.catcode_in_mode(ch, catmode);
1168            k += 1;
1169            let state = if cat == 11 || cat == 10 {
1170                LexState::SkipBlanks
1171            } else {
1172                LexState::MidLine
1173            };
1174            if cat == 11 && k < len {
1175                // Scan ahead until finding a nonletter.
1176                loop {
1177                    ch = t.buf[k];
1178                    cat = self.catcode_in_mode(ch, catmode);
1179                    k += 1;
1180                    if cat != 11 || k >= len {
1181                        break;
1182                    }
1183                }
1184                if self.reduce_expanded(k, ch, cat) {
1185                    continue 'start_cs;
1186                }
1187                if cat != 11 {
1188                    k -= 1;
1189                }
1190                if k > loc + 1 {
1191                    let mut name = Vec::with_capacity(k - loc);
1192                    if let Some(Level::Text(t)) = self.input.stack.last() {
1193                        for &c in &t.buf[loc..k] {
1194                            utf8x::push(&mut name, c);
1195                        }
1196                    }
1197                    self.cur_cs = self.hash.lookup_or_insert(&name);
1198                    if let Some(Level::Text(t)) = self.input.stack.last_mut() {
1199                        t.loc = k;
1200                        t.state = state;
1201                    }
1202                    break;
1203                }
1204            } else if self.reduce_expanded(k, ch, cat) {
1205                continue 'start_cs;
1206            }
1207            let c = match self.input.stack.last() {
1208                Some(Level::Text(t)) => t.buf[loc],
1209                _ => return,
1210            };
1211            self.cur_cs = self.hash.single(c);
1212            if let Some(Level::Text(t)) = self.input.stack.last_mut() {
1213                t.loc = loc + 1;
1214                t.state = state;
1215            }
1216            break;
1217        }
1218        let m = self.eqtb.meaning(self.cur_cs);
1219        self.cur_cmd = m.cmd;
1220        self.cur_chr = m.chr;
1221        if self.cur_cmd.is_outer_or_special() {
1222            self.check_outer_validity();
1223        }
1224    }
1225
1226    /// If an expanded code (`^^A`, `^^df`, `^^^^xxxx`) starts at
1227    /// `buf[k-1]` (whose character is `ch` with category `cat`), reduces it
1228    /// in the buffer and returns `true` (§355).
1229    fn reduce_expanded(&mut self, k: usize, ch: u32, cat: u8) -> bool {
1230        if cat != 7 {
1231            return false;
1232        }
1233        let Some(Level::Text(t)) = self.input.stack.last_mut() else {
1234            return false;
1235        };
1236        if t.buf.get(k) != Some(&ch) {
1237            return false;
1238        }
1239        match expanded_at(&t.buf, k - 1) {
1240            Expanded::Char { c, len } => {
1241                t.buf[k - 1] = c;
1242                t.buf.drain(k..k - 1 + len);
1243                true
1244            }
1245            _ => false,
1246        }
1247    }
1248
1249    /// Gets the next token, setting `cur_tok` too (`get_token`).
1250    pub fn get_token(&mut self) {
1251        self.err.no_new_control_sequence = false;
1252        self.get_next();
1253        self.err.no_new_control_sequence = true;
1254        self.cur_tok = self.make_cur_tok();
1255    }
1256
1257    /// The token corresponding to `cur_cmd`/`cur_chr`/`cur_cs`.
1258    #[inline]
1259    pub fn make_cur_tok(&self) -> Token {
1260        if self.cur_cs == NO_CS {
1261            Token::char(self.cur_cmd, self.cur_chr)
1262        } else {
1263            Token::cs(self.cur_cs)
1264        }
1265    }
1266}
1267
1268#[cfg(test)]
1269mod tests {
1270    use super::*;
1271
1272    #[test]
1273    fn line_splitting() {
1274        let data = b"ab  \r\ncd\rx\n\nlast";
1275        let mut pos = 0;
1276        let mut lines = Vec::new();
1277        while let Some((l, next)) = next_line(data, pos) {
1278            lines.push(decode_line(l));
1279            pos = next;
1280        }
1281        let strs: Vec<String> = lines
1282            .iter()
1283            .map(|l| l.iter().map(|&c| char::from_u32(c).unwrap()).collect())
1284            .collect();
1285        assert_eq!(strs, vec!["ab", "cd", "x", "", "last"]);
1286        assert_eq!(decode_line(b"x\t "), vec!['x' as u32, 9]);
1287    }
1288
1289    #[test]
1290    fn expanded_chars() {
1291        let s = |x: &str| x.chars().map(|c| c as u32).collect::<Vec<_>>();
1292        assert!(matches!(
1293            expanded_at(&s("^^A"), 0),
1294            Expanded::Char { c: 1, len: 3 }
1295        ));
1296        assert!(matches!(
1297            expanded_at(&s("^^e9"), 0),
1298            Expanded::Char { c: 0xE9, len: 4 }
1299        ));
1300        assert!(matches!(
1301            expanded_at(&s("^^?"), 0),
1302            Expanded::Char { c: 127, len: 3 }
1303        ));
1304        assert!(matches!(
1305            expanded_at(&s("^^^^00e9"), 0),
1306            Expanded::Char { c: 0xE9, len: 8 }
1307        ));
1308        assert!(matches!(
1309            expanded_at(&s("^^^^^^01f600"), 0),
1310            Expanded::Char {
1311                c: 0x1F600,
1312                len: 12
1313            }
1314        ));
1315        assert!(matches!(
1316            expanded_at(&s("^^^^zz"), 0),
1317            Expanded::Bad {
1318                six: false,
1319                short: true
1320            }
1321        ));
1322        assert!(matches!(expanded_at(&s("^x"), 0), Expanded::None));
1323    }
1324}