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