Skip to main content

retex_engine/
scan.rs

1//! Scanning numbers, dimensions, glue, keywords and token lists
2//! (*TeX: The Program* §402–§486, e-TeX §1490ff, LuaTeX extensions).
3
4use std::rc::Rc;
5
6use retex_arith::{self as arith, MAX_DIMEN, UNITY};
7
8use crate::Engine;
9use crate::cmd::Cmd;
10use crate::eqtb::Loc;
11use crate::glue::{self, Glue};
12use crate::hash::NO_CS;
13use crate::input::{ScannerStatus, TokKind};
14use crate::primitives_gen as pg;
15use crate::token::{Token, TokenList};
16
17/// The level of a scanned value (`cur_val_level`).
18#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default)]
19pub enum ValLevel {
20    /// An integer.
21    #[default]
22    Int = 0,
23    /// A dimension.
24    Dimen = 1,
25    /// Glue.
26    Glue = 2,
27    /// Muglue.
28    Mu = 3,
29    /// A font identifier.
30    Ident = 4,
31    /// A token list.
32    Tok = 5,
33}
34
35/// Offset of register numbers in the modifier of register-like
36/// commands defined by `\countdef` etc. (smaller modifiers are parameters).
37pub const REG_OFFSET: u32 = 0x1_0000;
38
39/// `register` modifiers.
40#[allow(missing_docs)]
41pub mod reg {
42    pub const COUNT: u32 = 0;
43    pub const ATTRIBUTE: u32 = 1;
44    pub const DIMEN: u32 = 2;
45    pub const SKIP: u32 = 3;
46    pub const MUSKIP: u32 = 4;
47}
48
49/// `last_item` modifiers (LuaTeX numbering).
50#[allow(missing_docs)]
51pub mod last_item {
52    pub const LAST_PENALTY: u32 = 0;
53    pub const LAST_KERN: u32 = 2;
54    pub const LAST_SKIP: u32 = 3;
55    pub const LAST_NODE_TYPE: u32 = 4;
56    pub const INPUT_LINE_NO: u32 = 5;
57    pub const BADNESS: u32 = 6;
58    pub const LAST_SAVED_BOX_RESOURCE_INDEX: u32 = 7;
59    pub const LAST_SAVED_IMAGE_RESOURCE_INDEX: u32 = 8;
60    pub const LAST_SAVED_IMAGE_RESOURCE_PAGES: u32 = 9;
61    pub const LAST_X_POS: u32 = 10;
62    pub const LAST_Y_POS: u32 = 11;
63    pub const RANDOM_SEED: u32 = 12;
64    pub const LUATEX_VERSION: u32 = 13;
65    pub const ETEX_MINOR_VERSION: u32 = 14;
66    pub const ETEX_VERSION: u32 = 15;
67    pub const CURRENT_GROUP_LEVEL: u32 = 16;
68    pub const CURRENT_GROUP_TYPE: u32 = 17;
69    pub const CURRENT_IF_LEVEL: u32 = 18;
70    pub const CURRENT_IF_TYPE: u32 = 19;
71    pub const CURRENT_IF_BRANCH: u32 = 20;
72    pub const GLUE_STRETCH_ORDER: u32 = 21;
73    pub const GLUE_SHRINK_ORDER: u32 = 22;
74    pub const ETEX_GLUE_STRETCH_ORDER: u32 = 23;
75    pub const ETEX_GLUE_SHRINK_ORDER: u32 = 24;
76    pub const FONT_CHAR_WD: u32 = 25;
77    pub const FONT_CHAR_HT: u32 = 26;
78    pub const FONT_CHAR_DP: u32 = 27;
79    pub const FONT_CHAR_IC: u32 = 28;
80    pub const PAR_SHAPE_LENGTH: u32 = 29;
81    pub const PAR_SHAPE_INDENT: u32 = 30;
82    pub const PAR_SHAPE_DIMEN: u32 = 31;
83    pub const GLUE_STRETCH: u32 = 32;
84    pub const GLUE_SHRINK: u32 = 33;
85    pub const MU_TO_GLUE: u32 = 34;
86    pub const GLUE_TO_MU: u32 = 35;
87    pub const NUM_EXPR: u32 = 36;
88    pub const DIM_EXPR: u32 = 38;
89    pub const GLUE_EXPR: u32 = 39;
90    pub const MU_EXPR: u32 = 40;
91}
92
93/// The LuaTeX version reported by `\luatexversion`.
94pub const LUATEX_VERSION: i32 = 122;
95/// `\luatexrevision`.
96pub const LUATEX_REVISION: &str = "0";
97/// `\eTeXversion`.
98pub const ETEX_VERSION: i32 = 2;
99/// `\eTeXminorversion`.
100pub const ETEX_MINOR_VERSION: i32 = 6;
101/// `\eTeXrevision`.
102pub const ETEX_REVISION: &str = ".6";
103
104impl Engine {
105    // ----- small helpers ---------------------------------------------------
106
107    /// Gets the next non-blank non-call token (TeX §406).
108    pub fn get_nonblank_noncall(&mut self) {
109        loop {
110            self.get_x_token();
111            if self.cur_cmd != Cmd::Spacer {
112                break;
113            }
114        }
115    }
116
117    /// Gets the next non-blank token, expanding everything except
118    /// protected macros (e-TeX's `get_x_or_protected` loop, used where an
119    /// alignment looks for `\noalign` or `\omit`).
120    pub fn get_nonblank_or_protected(&mut self) {
121        loop {
122            self.get_token();
123            if self.cur_cmd <= Cmd::MAX_COMMAND {
124                if self.cur_cmd == Cmd::Spacer {
125                    continue;
126                }
127                return;
128            }
129            if self.cur_cmd >= Cmd::Call
130                && self.cur_cmd < Cmd::EndTemplate
131                && self
132                    .eqtb
133                    .meaning(self.cur_cs)
134                    .mac
135                    .as_ref()
136                    .is_some_and(|m| m.protected)
137            {
138                return;
139            }
140            self.expand();
141        }
142    }
143
144    /// Gets the next non-blank non-relax non-call token (§404).
145    pub fn get_nonblank_nonrelax_noncall(&mut self) {
146        loop {
147            self.get_x_token();
148            if self.cur_cmd != Cmd::Spacer && self.cur_cmd != Cmd::Relax {
149                break;
150            }
151        }
152    }
153
154    /// Scans an optional space (§443).
155    pub fn scan_optional_space(&mut self) {
156        self.get_x_token();
157        if self.cur_cmd != Cmd::Spacer {
158            self.back_input();
159        }
160    }
161
162    /// Scans a left brace, inserting one if missing (`scan_left_brace`).
163    pub fn scan_left_brace(&mut self) {
164        self.get_nonblank_nonrelax_noncall();
165        if self.cur_cmd != Cmd::LeftBrace {
166            self.print_err("Missing { inserted");
167            self.help(&[
168                "A left brace was mandatory here, so I've put one in.",
169                "You might want to delete and/or insert some corrections",
170                "so that I will find a matching right brace soon.",
171                // LuaTeX drops TeX82's parentheses around this line.
172                "If you're confused by all this, try typing `I}' now.",
173            ]);
174            self.back_error();
175            self.cur_tok = Token::LEFT_BRACE;
176            self.cur_cmd = Cmd::LeftBrace;
177            self.cur_chr = '{' as u32;
178            self.input.align_state += 1;
179        }
180    }
181
182    /// Scans an optional `=` (`scan_optional_equals`).
183    pub fn scan_optional_equals(&mut self) {
184        self.get_nonblank_noncall();
185        if self.cur_tok != Token::other('=' as u32) {
186            self.back_input();
187        }
188    }
189
190    /// Looks for a keyword (`scan_keyword`); letters match either case.
191    pub fn scan_keyword(&mut self, s: &str) -> bool {
192        // Keywords are short: the matched tokens stay on the stack.
193        let mut matched = [Token(0); 32];
194        let bytes = s.as_bytes();
195        if bytes.len() > matched.len() {
196            // A long keyword from Lua (`token.scan_keyword`).
197            return self.scan_long_keyword(bytes);
198        }
199        let mut k = 0;
200        while k < bytes.len() {
201            self.get_x_token();
202            let c = u32::from(bytes[k]);
203            let upper = if bytes[k].is_ascii_lowercase() {
204                c - 32
205            } else {
206                c
207            };
208            if self.cur_cs == NO_CS && (self.cur_chr == c || self.cur_chr == upper) {
209                matched[k] = self.cur_tok;
210                k += 1;
211            } else if self.cur_cmd != Cmd::Spacer || k > 0 {
212                self.back_input();
213                if k > 0 {
214                    self.back_list(Rc::from(&matched[..k]));
215                }
216                return false;
217            }
218        }
219        true
220    }
221
222    /// [`Engine::scan_keyword`] for keywords too long for its buffer.
223    #[cold]
224    fn scan_long_keyword(&mut self, bytes: &[u8]) -> bool {
225        let mut matched = Vec::new();
226        while matched.len() < bytes.len() {
227            self.get_x_token();
228            let b = bytes[matched.len()];
229            let c = u32::from(b);
230            let upper = if b.is_ascii_lowercase() { c - 32 } else { c };
231            if self.cur_cs == NO_CS && (self.cur_chr == c || self.cur_chr == upper) {
232                matched.push(self.cur_tok);
233            } else if self.cur_cmd != Cmd::Spacer || !matched.is_empty() {
234                self.back_input();
235                if !matched.is_empty() {
236                    self.back_list(Rc::from(matched));
237                }
238                return false;
239            }
240        }
241        true
242    }
243
244    /// Pushes a token list to be read again (`back_list`).
245    pub fn back_list(&mut self, list: TokenList) {
246        self.begin_token_list(list, TokKind::BackedUp);
247    }
248
249    /// Reports an inappropriate mu/non-mu mix (`mu_error`).
250    pub fn mu_error(&mut self) {
251        self.print_err("Incompatible glue units");
252        self.help(&["I'm going to assume that 1mu=1pt when they're mixed."]);
253        self.error();
254    }
255
256    // ----- integers ---------------------------------------------------------
257
258    /// Scans a signed integer into `cur_val` (`scan_int`).
259    #[inline]
260    pub fn scan_int(&mut self) {
261        self.get_nonblank_noncall();
262        // Fast path: an internal quantity without a sign.
263        if self.cur_cmd.is_internal() {
264            self.scan_something_internal(ValLevel::Int, false);
265        } else {
266            self.scan_int_from_first();
267        }
268    }
269
270    /// [`scan_int`](Self::scan_int) once the first non-blank token has been
271    /// read.
272    #[inline(never)]
273    fn scan_int_from_first(&mut self) {
274        let mut ok_so_far = true;
275        // Get the next non-blank non-sign token.
276        let mut negative = false;
277        let mut first = true;
278        loop {
279            if !first {
280                self.get_nonblank_noncall();
281            }
282            first = false;
283            if self.cur_tok == Token::other('-' as u32) {
284                negative = !negative;
285                self.cur_tok = Token::other('+' as u32);
286            }
287            if self.cur_tok != Token::other('+' as u32) {
288                break;
289            }
290        }
291        if self.cur_tok == Token::other('`' as u32) {
292            // An alphabetic constant.
293            self.get_token();
294            if self.cur_tok.0 < crate::token::CS_TOKEN_FLAG {
295                self.cur_val = self.cur_chr as i32;
296                if self.cur_cmd <= Cmd::RightBrace {
297                    if self.cur_cmd == Cmd::RightBrace {
298                        self.input.align_state += 1;
299                    } else {
300                        self.input.align_state -= 1;
301                    }
302                }
303            } else {
304                let cs = self.cur_tok.cs_id();
305                self.cur_val = match self.hash.kind(cs) {
306                    crate::hash::CsKind::Active(c) | crate::hash::CsKind::Single(c) => *c as i32,
307                    _ => 0x11_0000,
308                };
309            }
310            if self.cur_val > 0x10_FFFF {
311                self.print_err("Improper alphabetic constant");
312                self.help(&[
313                    "A one-character control sequence belongs after a ` mark.",
314                    "So I'm essentially inserting \\0 here.",
315                ]);
316                self.cur_val = '0' as i32;
317                self.back_error();
318            } else {
319                self.scan_optional_space();
320            }
321        } else if self.cur_cmd.is_internal() {
322            self.scan_something_internal(ValLevel::Int, false);
323        } else if self.cur_cmd == Cmd::MathStyle {
324            // LuaTeX: the style commands are numbers (0-7).
325            self.cur_val = self.cur_chr as i32;
326        } else {
327            // A numeric constant.
328            let mut radix: i64 = 10;
329            let mut m: i64 = 214_748_364;
330            if self.cur_tok == Token::other('\'' as u32) {
331                radix = 8;
332                m = 0o2000000000;
333                self.get_x_token();
334            } else if self.cur_tok == Token::other('"' as u32) {
335                radix = 16;
336                m = 0o1000000000;
337                self.get_x_token();
338            }
339            let mut vacuous = true;
340            let mut val: i64 = 0;
341            loop {
342                let t = self.cur_tok;
343                let d: i64 = if !t.is_cs()
344                    && t.cmd() == Cmd::OtherChar
345                    && (0x30..=0x39).contains(&t.chr())
346                    && i64::from(t.chr() - 0x30) < radix
347                {
348                    i64::from(t.chr() - 0x30)
349                } else if radix == 16
350                    && !t.is_cs()
351                    && (t.cmd() == Cmd::Letter || t.cmd() == Cmd::OtherChar)
352                    && (0x41..=0x46).contains(&t.chr())
353                {
354                    i64::from(t.chr() - 0x41 + 10)
355                } else {
356                    break;
357                };
358                vacuous = false;
359                if val >= m && (val > m || d > 7 || radix != 10) {
360                    if ok_so_far {
361                        self.print_err("Number too big");
362                        self.help(&[
363                            "I can only go up to 2147483647='17777777777=\"7FFFFFFF,",
364                            "so I'm using that number instead of yours.",
365                        ]);
366                        self.error();
367                        val = i64::from(arith::INFINITY);
368                        ok_so_far = false;
369                    }
370                } else {
371                    val = val * radix + d;
372                }
373                self.get_x_token();
374            }
375            self.cur_val = val as i32;
376            if vacuous {
377                self.missing_number();
378            } else if self.cur_cmd != Cmd::Spacer {
379                self.back_input();
380            }
381        }
382        if negative {
383            self.cur_val = self.cur_val.wrapping_neg();
384        }
385    }
386
387    /// Reports a missing number (TeX's "Express astonishment").
388    fn missing_number(&mut self) {
389        self.print_err("Missing number, treated as zero");
390        self.help(&[
391            "A number should have been here; I inserted `0'.",
392            "(If you can't figure out why I needed to see a number,",
393            "look up `weird error' in the index to The TeXbook.)",
394        ]);
395        self.back_error();
396    }
397
398    fn scan_limited(&mut self, max: i32, what: &str, help: &'static str) {
399        self.scan_int();
400        if self.cur_val < 0 || self.cur_val > max {
401            self.print_err(&format!("Bad {what} ("));
402            self.printer.print_int(self.cur_val.into());
403            self.printer.print_char(')' as u32);
404            self.help(&[help, "I changed this one to zero."]);
405            self.int_error_zero();
406        }
407    }
408
409    fn int_error_zero(&mut self) {
410        self.error();
411        self.cur_val = 0;
412    }
413
414    /// Scans a register number 0–65535 (LuaTeX's `scan_register_num`).
415    pub fn scan_register_num(&mut self) {
416        self.scan_limited(
417            65535,
418            "register code",
419            "A register code must be between 0 and 65535.",
420        );
421    }
422
423    /// Scans a character code 0–0x10FFFF (`scan_char_num`).
424    pub fn scan_char_num(&mut self) {
425        self.scan_limited(
426            0x10_FFFF,
427            "character code",
428            "A character code must be between 0 and 1114111.",
429        );
430    }
431
432    /// Scans a stream number for `\read`, `\openin`, `\ifeof` (0–15).
433    pub fn scan_four_bit_int(&mut self) {
434        self.scan_limited(
435            15,
436            "number",
437            "Since I expected to read a number between 0 and 15,",
438        );
439    }
440
441    /// Scans a stream number for `\ifeof` and `\read`; LuaTeX accepts any
442    /// integer here and treats out-of-range streams as closed.
443    pub fn scan_four_bit_int_or_more(&mut self) {
444        self.scan_int();
445    }
446
447    /// Scans a math character for `\mathchardef`, `\mathchar` and
448    /// `\mathaccent` (`scan_fifteen_bit_int`) and returns it as a
449    /// `math_given` value; `cur_val` is the number `\the` reports.
450    ///
451    /// Observed in LuaTeX 1.22: numbers above `"8000` (math active) are read as packed
452    /// math codes (class·2²¹ + family·2²⁴ + character, what `\mathcode`
453    /// reports for `\Umathcode`s), so `\mathchardef\x\mathcode`c` works;
454    /// a character beyond U+10FFFF is an "Invalid math code during >
455    /// 0x8000 mathcode fallback", a negative number a "Bad mathchar".
456    pub fn scan_math_given(&mut self) -> u32 {
457        self.scan_int();
458        let v = self.cur_val;
459        let g = if v < 0 {
460            self.print_err("Bad mathchar (");
461            self.printer.print_int(v.into());
462            self.printer.print_char(')' as u32);
463            0
464        } else if v <= 0x8000 {
465            return v as u32;
466        } else if crate::codes::math_char(v.into()) > 0x10FFFF {
467            self.print_err("Invalid math code during > 0x8000 mathcode fallback");
468            0
469        } else {
470            v as u32 | crate::codes::MATH_GIVEN_PACKED
471        };
472        if g == 0 {
473            self.help(&["I'm going to use 0 instead of that illegal code value."]);
474            self.error();
475        }
476        self.cur_val = crate::codes::math_given_report(g);
477        g
478    }
479
480    /// Scans a 27-bit delimiter code (`scan_twenty_seven_bit_int`).
481    pub fn scan_twenty_seven_bit_int(&mut self) {
482        self.scan_int();
483        if self.cur_val < 0 || self.cur_val > 0o777777777 {
484            self.print_err("Bad delimiter code (");
485            self.printer.print_int(self.cur_val.into());
486            self.printer.print_char(')' as u32);
487            self.help(&[
488                "A numeric delimiter code must be between 0 and 2^{27}-1.",
489                "I changed this one to zero.",
490            ]);
491            self.int_error_zero();
492        }
493    }
494
495    /// Scans a math family number 0–255 (LuaTeX).
496    pub fn scan_math_family(&mut self) {
497        self.scan_limited(255, "number", "A family number must be between 0 and 255.");
498    }
499
500    /// Scans a category code 0–15.
501    pub fn scan_category_code(&mut self) {
502        self.scan_limited(
503            15,
504            "category code",
505            "A category code must be between 0 and 15.",
506        );
507    }
508
509    // ----- dimensions --------------------------------------------------------
510
511    /// Scans a normal dimension (`scan_normal_dimen`).
512    pub fn scan_normal_dimen(&mut self) {
513        self.scan_dimen(false, false, false);
514    }
515
516    /// Scans a dimension (`scan_dimen`, §448). `mu`: units must be `mu`;
517    /// `inf`: infinite units allowed; `shortcut`: `cur_val` already holds
518    /// the integer part. Sets `cur_val`, and `cur_order` via the returned
519    /// order.
520    pub fn scan_dimen(&mut self, mu: bool, inf: bool, shortcut: bool) -> glue::GlueOrder {
521        let mut f: i32 = 0;
522        let mut arith_error = false;
523        let mut cur_order = glue::NORMAL;
524        let mut negative = false;
525        'attach_sign: {
526            if !shortcut {
527                negative = false;
528                loop {
529                    self.get_nonblank_noncall();
530                    if self.cur_tok == Token::other('-' as u32) {
531                        negative = !negative;
532                        self.cur_tok = Token::other('+' as u32);
533                    }
534                    if self.cur_tok != Token::other('+' as u32) {
535                        break;
536                    }
537                }
538                if self.cur_cmd.is_internal() {
539                    if mu {
540                        self.scan_something_internal(ValLevel::Mu, false);
541                        if self.cur_val_level >= ValLevel::Glue {
542                            self.cur_val = self.cur_glue.width;
543                        }
544                        if self.cur_val_level == ValLevel::Mu {
545                            break 'attach_sign;
546                        }
547                        if self.cur_val_level != ValLevel::Int {
548                            self.mu_error();
549                        }
550                    } else {
551                        self.scan_something_internal(ValLevel::Dimen, false);
552                        if self.cur_val_level == ValLevel::Dimen {
553                            break 'attach_sign;
554                        }
555                    }
556                } else {
557                    self.back_input();
558                    if self.cur_tok == Token::other(',' as u32) {
559                        self.cur_tok = Token::other('.' as u32);
560                    }
561                    let mut radix10 = true;
562                    if self.cur_tok != Token::other('.' as u32) {
563                        self.scan_int_radix(&mut radix10);
564                    } else {
565                        self.cur_val = 0;
566                    }
567                    if self.cur_tok == Token::other(',' as u32) {
568                        self.cur_tok = Token::other('.' as u32);
569                    }
570                    if radix10 && self.cur_tok == Token::other('.' as u32) {
571                        // Scan decimal fraction.
572                        let mut digits: Vec<u8> = Vec::new();
573                        self.get_token(); // the point token is being re-scanned
574                        loop {
575                            self.get_x_token();
576                            let t = self.cur_tok;
577                            if t.is_cs()
578                                || t.cmd() != Cmd::OtherChar
579                                || !(0x30..=0x39).contains(&t.chr())
580                            {
581                                break;
582                            }
583                            if digits.len() < 17 {
584                                digits.push((t.chr() - 0x30) as u8);
585                            }
586                        }
587                        f = arith::round_decimals(&digits);
588                        if self.cur_cmd != Cmd::Spacer {
589                            self.back_input();
590                        }
591                    }
592                }
593            }
594            if self.cur_val < 0 {
595                negative = !negative;
596                self.cur_val = self.cur_val.wrapping_neg();
597            }
598            // Scan units.
599            'attach_fraction: {
600                'done: {
601                    if inf && self.scan_keyword("fi") {
602                        cur_order = glue::FI;
603                        while self.scan_keyword("l") {
604                            if cur_order == glue::FILLL {
605                                break;
606                            }
607                            cur_order += 1;
608                        }
609                        break 'attach_fraction;
610                    }
611                    // Units that are internal dimensions.
612                    let save_cur_val = self.cur_val;
613                    self.get_nonblank_noncall();
614                    let v: i32;
615                    if !self.cur_cmd.is_internal() {
616                        self.back_input();
617                        if mu {
618                            // Scan for mu units.
619                            if !self.scan_keyword("mu") {
620                                self.print_err("Illegal unit of measure (");
621                                self.printer.print_str("mu inserted)");
622                                self.help(&[
623                                    "The unit of measurement in math glue must be mu.",
624                                    "To recover gracefully from this error, it's best to",
625                                    "delete the erroneous units; e.g., type `2' to delete",
626                                    "two letters. (See Chapter 27 of The TeXbook.)",
627                                ]);
628                                self.error();
629                            }
630                            break 'attach_fraction;
631                        }
632                        if self.scan_keyword("em") {
633                            v = self.quad(self.eqtb.cur_font.val);
634                            self.scan_optional_space();
635                        } else if self.scan_keyword("ex") {
636                            v = self.x_height(self.eqtb.cur_font.val);
637                            self.scan_optional_space();
638                        } else if self.scan_keyword("px") {
639                            v = self.eqtb.dimen(pg::assign_dimen::PXDIMEN);
640                            self.scan_optional_space();
641                        } else {
642                            // Not an internal unit.
643                            if self.scan_keyword("true") {
644                                self.prepare_mag();
645                                let mag = self.eqtb.int(pg::assign_int::MAG);
646                                if mag != 1000 {
647                                    match arith::xn_over_d(self.cur_val, 1000, mag) {
648                                        Ok(r) => {
649                                            self.cur_val = r.quotient;
650                                            f = ((1000 * i64::from(f)
651                                                + 0o200000 * i64::from(r.remainder))
652                                                / i64::from(mag))
653                                                as i32;
654                                        }
655                                        Err(_) => arith_error = true,
656                                    }
657                                    self.cur_val += f / 0o200000;
658                                    f %= 0o200000;
659                                }
660                            }
661                            if self.scan_keyword("pt") {
662                                break 'attach_fraction;
663                            }
664                            let (num, denom): (i32, i32) = if self.scan_keyword("in") {
665                                (7227, 100)
666                            } else if self.scan_keyword("pc") {
667                                (12, 1)
668                            } else if self.scan_keyword("cm") {
669                                (7227, 254)
670                            } else if self.scan_keyword("mm") {
671                                (7227, 2540)
672                            } else if self.scan_keyword("bp") {
673                                (7227, 7200)
674                            } else if self.scan_keyword("dd") {
675                                (1238, 1157)
676                            } else if self.scan_keyword("cc") {
677                                (14856, 1157)
678                            } else if self.scan_keyword("nd") {
679                                (685, 642)
680                            } else if self.scan_keyword("nc") {
681                                (1370, 107)
682                            } else if self.scan_keyword("sp") {
683                                break 'done;
684                            } else {
685                                self.print_err("Illegal unit of measure (");
686                                self.printer.print_str("pt inserted)");
687                                self.help(&[
688                                    "Dimensions can be in units of em, ex, in, pt, pc,",
689                                    "cm, mm, dd, cc, nd, nc, bp, or sp; but yours is a new one!",
690                                    "I'll assume that you meant to say pt, for printer's points.",
691                                    "To recover gracefully from this error, it's best to",
692                                    "delete the erroneous units; e.g., type `2' to delete",
693                                    "two letters. (See Chapter 27 of The TeXbook.)",
694                                ]);
695                                self.error();
696                                break 'attach_fraction;
697                            };
698                            match arith::xn_over_d(self.cur_val, num, denom) {
699                                Ok(r) => {
700                                    self.cur_val = r.quotient;
701                                    f = ((i64::from(num) * i64::from(f)
702                                        + 0o200000 * i64::from(r.remainder))
703                                        / i64::from(denom))
704                                        as i32;
705                                }
706                                Err(_) => arith_error = true,
707                            }
708                            self.cur_val = self.cur_val.wrapping_add(f / 0o200000);
709                            f %= 0o200000;
710                            break 'attach_fraction;
711                        }
712                    } else {
713                        if mu {
714                            self.scan_something_internal(ValLevel::Mu, false);
715                            if self.cur_val_level >= ValLevel::Glue {
716                                self.cur_val = self.cur_glue.width;
717                            }
718                            if self.cur_val_level != ValLevel::Mu {
719                                self.mu_error();
720                            }
721                        } else {
722                            self.scan_something_internal(ValLevel::Dimen, false);
723                        }
724                        v = self.cur_val;
725                    }
726                    // found:
727                    let frac = arith::xn_over_d(v, f, 0o200000)
728                        .map(|r| r.quotient)
729                        .unwrap_or_else(|_| {
730                            arith_error = true;
731                            0
732                        });
733                    self.cur_val = arith::nx_plus_y(save_cur_val, v, frac).unwrap_or_else(|_| {
734                        arith_error = true;
735                        0
736                    });
737                    break 'attach_sign;
738                }
739                // done: (sp units)
740                self.scan_optional_space();
741                break 'attach_sign;
742            }
743            // attach_fraction:
744            if self.cur_val >= 0o40000 {
745                arith_error = true;
746            } else {
747                self.cur_val = self.cur_val * UNITY + f;
748            }
749            self.scan_optional_space();
750        }
751        // attach_sign:
752        if arith_error || self.cur_val.wrapping_abs() >= 0o10000000000 || self.cur_val == i32::MIN {
753            self.print_err("Dimension too large");
754            self.help(&[
755                "I can't work with sizes bigger than about 19 feet.",
756                "Continue and I'll use the largest value I can.",
757            ]);
758            self.error();
759            self.cur_val = MAX_DIMEN;
760        }
761        if negative {
762            self.cur_val = -self.cur_val;
763        }
764        cur_order
765    }
766
767    /// `scan_int` that also reports whether the radix was 10 (needed by
768    /// `scan_dimen` to decide on a decimal fraction).
769    fn scan_int_radix(&mut self, radix10: &mut bool) {
770        // Peek at the first significant token to learn the radix.
771        self.get_nonblank_noncall();
772        let t = self.cur_tok;
773        *radix10 = !(t == Token::other('\'' as u32)
774            || t == Token::other('"' as u32)
775            || t == Token::other('`' as u32)
776            || self.cur_cmd.is_internal());
777        self.back_input();
778        self.scan_int();
779    }
780
781    /// Freezes the magnification (`prepare_mag`).
782    pub fn prepare_mag(&mut self) {
783        let mag = self.eqtb.int(pg::assign_int::MAG);
784        if self.mag_set > 0 && mag != self.mag_set {
785            self.print_err("Incompatible magnification (");
786            self.printer.print_int(mag.into());
787            self.printer.print_str(");");
788            self.printer
789                .print_nl(" the previous value will be retained");
790            self.help(&[
791                "I can handle only one magnification ratio per job. So I've",
792                "reverted to the magnification you used earlier on this run.",
793            ]);
794            self.int_error(self.mag_set);
795            self.eqtb.int_par[pg::assign_int::MAG as usize].val = self.mag_set;
796        }
797        let mag = self.eqtb.int(pg::assign_int::MAG);
798        if mag <= 0 || mag > 32768 {
799            self.print_err("Illegal magnification has been changed to 1000");
800            self.help(&["The magnification ratio must be between 1 and 32768."]);
801            self.int_error(mag);
802            self.eqtb.int_par[pg::assign_int::MAG as usize].val = 1000;
803        }
804        self.mag_set = self.eqtb.int(pg::assign_int::MAG);
805    }
806
807    /// Reports an error with an integer in parentheses (`int_error`).
808    pub fn int_error(&mut self, n: i32) {
809        self.printer.print_str(" (");
810        self.printer.print_int(n.into());
811        self.printer.print_char(')' as u32);
812        self.error();
813    }
814
815    // ----- glue --------------------------------------------------------------
816
817    /// Scans glue (`scan_glue`); `level` is [`ValLevel::Glue`] or
818    /// [`ValLevel::Mu`]. Sets `cur_glue`.
819    pub fn scan_glue(&mut self, level: ValLevel) {
820        let mu = level == ValLevel::Mu;
821        let mut negative = false;
822        loop {
823            self.get_nonblank_noncall();
824            if self.cur_tok == Token::other('-' as u32) {
825                negative = !negative;
826                self.cur_tok = Token::other('+' as u32);
827            }
828            if self.cur_tok != Token::other('+' as u32) {
829                break;
830            }
831        }
832        if self.cur_cmd.is_internal() {
833            self.scan_something_internal(level, negative);
834            if self.cur_val_level >= ValLevel::Glue {
835                if self.cur_val_level != level {
836                    self.mu_error();
837                }
838                return;
839            }
840            if self.cur_val_level == ValLevel::Int {
841                self.scan_dimen(mu, false, true);
842            } else if level == ValLevel::Mu {
843                self.mu_error();
844            }
845        } else {
846            self.back_input();
847            self.scan_dimen(mu, false, false);
848            if negative {
849                self.cur_val = -self.cur_val;
850            }
851        }
852        let mut q = Glue::fixed(self.cur_val);
853        if self.scan_keyword("plus") {
854            let o = self.scan_dimen(mu, true, false);
855            q.stretch = self.cur_val;
856            q.stretch_order = o;
857        }
858        if self.scan_keyword("minus") {
859            let o = self.scan_dimen(mu, true, false);
860            q.shrink = self.cur_val;
861            q.shrink_order = o;
862        }
863        self.cur_glue = q;
864        self.cur_val_level = level;
865    }
866
867    // ----- internal quantities -----------------------------------------------
868
869    /// Sets the scanned result.
870    fn scanned(&mut self, v: i32, level: ValLevel) {
871        self.cur_val = v;
872        self.cur_val_level = level;
873    }
874
875    fn scanned_glue(&mut self, g: Glue, level: ValLevel) {
876        self.cur_glue = g;
877        self.cur_val = g.width;
878        self.cur_val_level = level;
879    }
880
881    /// Fetches an internal quantity (`scan_something_internal`, §413).
882    pub fn scan_something_internal(&mut self, level: ValLevel, negative: bool) {
883        let m = self.cur_chr;
884        match self.cur_cmd {
885            Cmd::DefCharCode => {
886                self.scan_char_num();
887                let c = self.cur_val as u32;
888                use pg::def_char_code as dc;
889                let v = match m {
890                    dc::CATCODE => i64::from(self.eqtb.catcode(c)),
891                    dc::LCCODE => self.eqtb.lc.get(c),
892                    dc::UCCODE => self.eqtb.uc.get(c),
893                    dc::SFCODE => self.eqtb.sf.get(c),
894                    _ => i64::from(crate::codes::math_code_report(self.eqtb.math.get(c))),
895                };
896                self.scanned(v as i32, ValLevel::Int);
897            }
898            Cmd::DefDelCode | Cmd::ExtdefDelCode => {
899                self.scan_char_num();
900                let v = self.eqtb.del.get(self.cur_val as u32);
901                self.scanned(crate::codes::delcode_report(v, self.cur_cmd), ValLevel::Int);
902            }
903            Cmd::ExtdefMathCode => {
904                self.scan_char_num();
905                let v = self.eqtb.math.get(self.cur_val as u32);
906                self.scanned(crate::codes::math_code_report(v), ValLevel::Int);
907            }
908            Cmd::ToksRegister | Cmd::AssignToks | Cmd::DefFamily | Cmd::SetFont | Cmd::DefFont => {
909                if level != ValLevel::Tok {
910                    self.missing_number();
911                    self.scanned(0, ValLevel::Dimen);
912                } else if self.cur_cmd <= Cmd::AssignToks {
913                    let list = if self.cur_cmd == Cmd::ToksRegister {
914                        if m == 0 {
915                            self.scan_register_num();
916                            self.eqtb.toks[self.cur_val as usize].val.clone()
917                        } else {
918                            self.eqtb.toks[(m - REG_OFFSET) as usize].val.clone()
919                        }
920                    } else if m >= REG_OFFSET {
921                        self.eqtb.toks[(m - REG_OFFSET) as usize].val.clone()
922                    } else {
923                        self.eqtb.toks_param(m).clone()
924                    };
925                    self.cur_toks = Some(list);
926                    self.cur_val_level = ValLevel::Tok;
927                } else {
928                    self.back_input();
929                    self.scan_font_ident();
930                    self.cur_val_level = ValLevel::Ident;
931                }
932            }
933            Cmd::AssignInt => {
934                let v = if m >= REG_OFFSET {
935                    self.eqtb.count[(m - REG_OFFSET) as usize].val
936                } else {
937                    self.eqtb.int(m)
938                };
939                self.scanned(v, ValLevel::Int);
940            }
941            Cmd::AssignAttr => {
942                let v = self.eqtb.attr[(m - REG_OFFSET.min(m)) as usize].val;
943                self.scanned(v, ValLevel::Int);
944            }
945            Cmd::AssignDirection => self.scanned(self.dir_param(m), ValLevel::Int),
946            Cmd::AssignDir => {
947                // A direction, not a number: `\the` gives its name; where a
948                // number is wanted LuaTeX reports mixed glue units and
949                // reads 0 (observed).
950                let d = self.dir_param(m);
951                if level == ValLevel::Tok {
952                    let name = crate::showbox::DIR_NAMES[d.rem_euclid(4) as usize];
953                    self.cur_toks = Some(crate::the::str_toks(name.as_bytes()).into());
954                    self.scanned(0, ValLevel::Tok);
955                } else {
956                    self.mu_error();
957                    self.scanned(0, ValLevel::Int);
958                }
959            }
960            Cmd::AssignDimen => {
961                let v = if m >= REG_OFFSET {
962                    self.eqtb.dimen[(m - REG_OFFSET) as usize].val
963                } else {
964                    self.eqtb.dimen(m)
965                };
966                self.scanned(v, ValLevel::Dimen);
967            }
968            Cmd::AssignGlue => {
969                let g = if m >= REG_OFFSET {
970                    self.eqtb.skip[(m - REG_OFFSET) as usize].val
971                } else {
972                    self.eqtb.glue(m)
973                };
974                self.scanned_glue(g, ValLevel::Glue);
975            }
976            Cmd::AssignMuGlue => {
977                let g = if m >= REG_OFFSET {
978                    self.eqtb.muskip[(m - REG_OFFSET) as usize].val
979                } else {
980                    self.eqtb.glue(m + crate::eqtb::MU_GLUE_OFFSET)
981                };
982                self.scanned_glue(g, ValLevel::Mu);
983            }
984            Cmd::SetAux => {
985                let v = self.fetch_aux(m);
986                if let Some((v, l)) = v {
987                    self.scanned(v, l);
988                } else {
989                    self.scanned(
990                        0,
991                        if m == SET_AUX_PREV_DEPTH {
992                            ValLevel::Dimen
993                        } else {
994                            ValLevel::Int
995                        },
996                    );
997                }
998            }
999            Cmd::SetPrevGraf => {
1000                let v = self.fetch_prev_graf();
1001                self.scanned(v, ValLevel::Int);
1002            }
1003            Cmd::SetPageInt => {
1004                let v = match m {
1005                    0 => self.dead_cycles,
1006                    1 => self.insert_penalties,
1007                    _ => self.err.interaction as i32,
1008                };
1009                self.scanned(v, ValLevel::Int);
1010            }
1011            Cmd::SetPageDimen => {
1012                let v = self.page_so_far_value(m);
1013                self.scanned(v, ValLevel::Dimen);
1014            }
1015            Cmd::SetTexShape => {
1016                let v = self
1017                    .eqtb
1018                    .par_shape
1019                    .val
1020                    .as_ref()
1021                    .map_or(0, |s| (s.len() / 2) as i32);
1022                self.scanned(v, ValLevel::Int);
1023            }
1024            Cmd::SetEtexShape => {
1025                self.scan_int();
1026                let n = self.cur_val;
1027                let v = match &self.eqtb.etex_shape[m as usize].val {
1028                    None => 0,
1029                    Some(_) if n < 0 => 0,
1030                    Some(s) => {
1031                        let len = s.len() as i32;
1032                        if n == 0 {
1033                            len
1034                        } else {
1035                            s[(n.min(len) - 1) as usize]
1036                        }
1037                    }
1038                };
1039                self.scanned(v, ValLevel::Int);
1040            }
1041            Cmd::SetBoxDimen => {
1042                self.scan_register_num();
1043                let p = self.eqtb.boxes[self.cur_val as usize].val;
1044                let v = if p.is_null() {
1045                    0
1046                } else {
1047                    let b = self.nodes.box_data(p);
1048                    match (m, b) {
1049                        (2, Some(b)) => b.width,
1050                        (3, Some(b)) => b.depth,
1051                        (4, Some(b)) => b.height,
1052                        _ => 0,
1053                    }
1054                };
1055                self.scanned(v, ValLevel::Dimen);
1056            }
1057            Cmd::CharGiven | Cmd::XmathGiven => {
1058                self.scanned(self.cur_chr as i32, ValLevel::Int);
1059            }
1060            Cmd::MathGiven => {
1061                self.scanned(crate::codes::math_given_report(self.cur_chr), ValLevel::Int);
1062            }
1063            Cmd::AssignFontDimen => {
1064                let v = self.find_font_dimen(false);
1065                self.scanned(v, ValLevel::Dimen);
1066            }
1067            Cmd::AssignFontInt => {
1068                self.scan_font_ident();
1069                let f = self.cur_val as u32;
1070                let v = self.font_int_param(f, m);
1071                self.scanned(v, ValLevel::Int);
1072            }
1073            Cmd::Register => {
1074                self.scan_register_num();
1075                let n = self.cur_val as usize;
1076                match m {
1077                    reg::COUNT => self.scanned(self.eqtb.count[n].val, ValLevel::Int),
1078                    reg::ATTRIBUTE => self.scanned(self.eqtb.attr[n].val, ValLevel::Int),
1079                    reg::DIMEN => self.scanned(self.eqtb.dimen[n].val, ValLevel::Dimen),
1080                    reg::SKIP => {
1081                        let g = self.eqtb.skip[n].val;
1082                        self.scanned_glue(g, ValLevel::Glue);
1083                    }
1084                    _ => {
1085                        let g = self.eqtb.muskip[n].val;
1086                        self.scanned_glue(g, ValLevel::Mu);
1087                    }
1088                }
1089            }
1090            Cmd::SetMathParam => {
1091                // After a missing style LuaTeX reads an unset parameter.
1092                let found = self.scan_math_style();
1093                let style = self.cur_val as u32;
1094                let v = self
1095                    .eqtb
1096                    .math_param
1097                    .get(&(m * 8 + style))
1098                    .filter(|_| found)
1099                    .map_or(crate::codes::UNDEFINED_MATH_PARAM, |s| s.val);
1100                if crate::codes::math_param_is_glue(m) {
1101                    // An unset spacing shows TeX's default for the pair.
1102                    let g = if found && v == crate::codes::UNDEFINED_MATH_PARAM {
1103                        let (l, r) = ((m - 49) / 8, (m - 49) % 8);
1104                        self.math_spacing(l, r, style)
1105                            .map_or(crate::glue::Glue::ZERO, |(g, _)| g)
1106                    } else {
1107                        self.math_param_glue(v)
1108                    };
1109                    self.scanned_glue(g, ValLevel::Mu);
1110                } else if m == crate::mathparams::mp::RADICAL_DEGREE_RAISE {
1111                    // A percentage.
1112                    self.scanned(v, ValLevel::Int);
1113                } else {
1114                    self.scanned(v, ValLevel::Dimen);
1115                }
1116            }
1117            Cmd::LastItem => self.fetch_last_item(m, level),
1118            _ => {
1119                self.print_err("You can't use `");
1120                self.print_cmd_chr(self.cur_cmd, self.cur_chr);
1121                self.printer.print_str("' after ");
1122                self.print_esc_str("the");
1123                self.help(&["I'm forgetting what you said and using zero instead."]);
1124                self.error();
1125                if level != ValLevel::Tok {
1126                    self.scanned(0, ValLevel::Dimen);
1127                } else {
1128                    self.scanned(0, ValLevel::Int);
1129                }
1130            }
1131        }
1132        // Convert to a lower level.
1133        while self.cur_val_level > level {
1134            if self.cur_val_level == ValLevel::Glue {
1135                self.cur_val = self.cur_glue.width;
1136            } else if self.cur_val_level == ValLevel::Mu {
1137                self.mu_error();
1138            }
1139            self.cur_val_level = match self.cur_val_level {
1140                ValLevel::Tok => ValLevel::Ident,
1141                ValLevel::Ident => ValLevel::Mu,
1142                ValLevel::Mu => ValLevel::Glue,
1143                ValLevel::Glue => ValLevel::Dimen,
1144                _ => ValLevel::Int,
1145            };
1146            if self.cur_val_level == ValLevel::Glue || self.cur_val_level == ValLevel::Mu {
1147                // Coming from Ident/Tok (not possible in practice).
1148            }
1149        }
1150        if negative {
1151            if self.cur_val_level >= ValLevel::Glue {
1152                let g = &mut self.cur_glue;
1153                g.width = -g.width;
1154                g.stretch = -g.stretch;
1155                g.shrink = -g.shrink;
1156                self.cur_val = g.width;
1157            } else {
1158                self.cur_val = self.cur_val.wrapping_neg();
1159            }
1160        }
1161    }
1162
1163    /// Fetches a `last_item` quantity.
1164    fn fetch_last_item(&mut self, m: u32, level: ValLevel) {
1165        use last_item as li;
1166        match m {
1167            li::LAST_PENALTY | li::LAST_KERN | li::LAST_SKIP | li::LAST_NODE_TYPE => {
1168                let (v, g, l) = self.fetch_last_node_item(m);
1169                if l >= ValLevel::Glue {
1170                    self.scanned_glue(g, l);
1171                } else {
1172                    self.scanned(v, l);
1173                }
1174            }
1175            li::INPUT_LINE_NO => self.scanned(self.input.line(), ValLevel::Int),
1176            li::BADNESS => self.scanned(self.last_badness, ValLevel::Int),
1177            li::LUATEX_VERSION => self.scanned(LUATEX_VERSION, ValLevel::Int),
1178            li::ETEX_VERSION => self.scanned(ETEX_VERSION, ValLevel::Int),
1179            li::ETEX_MINOR_VERSION => self.scanned(ETEX_MINOR_VERSION, ValLevel::Int),
1180            li::CURRENT_GROUP_LEVEL => {
1181                self.scanned(i32::from(self.save.cur_level) - 1, ValLevel::Int)
1182            }
1183            li::CURRENT_GROUP_TYPE => self.scanned(self.save.cur_group as i32, ValLevel::Int),
1184            li::CURRENT_IF_LEVEL => self.scanned(self.cond.stack.len() as i32, ValLevel::Int),
1185            li::CURRENT_IF_TYPE => {
1186                let v = if self.cond.stack.is_empty() {
1187                    0
1188                } else if self.cond.cur_if < crate::cond::UNLESS_CODE {
1189                    self.cond.cur_if as i32 + 1
1190                } else {
1191                    -((self.cond.cur_if - crate::cond::UNLESS_CODE) as i32 + 1)
1192                };
1193                self.scanned(v, ValLevel::Int);
1194            }
1195            li::CURRENT_IF_BRANCH => {
1196                let v = match self.cond.if_limit {
1197                    crate::cond::OR_CODE | crate::cond::ELSE_CODE => 1,
1198                    crate::cond::FI_CODE => -1,
1199                    _ => 0,
1200                };
1201                self.scanned(v, ValLevel::Int);
1202            }
1203            li::RANDOM_SEED => self.scanned(self.random_seed, ValLevel::Int),
1204            li::LAST_X_POS => self.scanned(self.last_x_pos, ValLevel::Int),
1205            li::LAST_Y_POS => self.scanned(self.last_y_pos, ValLevel::Int),
1206            li::LAST_SAVED_BOX_RESOURCE_INDEX
1207            | li::LAST_SAVED_IMAGE_RESOURCE_INDEX
1208            | li::LAST_SAVED_IMAGE_RESOURCE_PAGES => {
1209                self.scanned(self.last_saved_resource(m), ValLevel::Int)
1210            }
1211            li::GLUE_STRETCH_ORDER
1212            | li::GLUE_SHRINK_ORDER
1213            | li::ETEX_GLUE_STRETCH_ORDER
1214            | li::ETEX_GLUE_SHRINK_ORDER => {
1215                self.scan_normal_glue();
1216                let g = self.cur_glue;
1217                let o = if m == li::GLUE_STRETCH_ORDER || m == li::ETEX_GLUE_STRETCH_ORDER {
1218                    g.stretch_order
1219                } else {
1220                    g.shrink_order
1221                };
1222                // e-TeX reports fil=1…filll=3, LuaTeX's own variant counts
1223                // the extra `fi` order.
1224                let v = if m == li::ETEX_GLUE_STRETCH_ORDER || m == li::ETEX_GLUE_SHRINK_ORDER {
1225                    i32::from(o)
1226                } else {
1227                    i32::from(o.saturating_sub(1)).max(0)
1228                };
1229                self.scanned(v, ValLevel::Int);
1230            }
1231            li::FONT_CHAR_WD | li::FONT_CHAR_HT | li::FONT_CHAR_DP | li::FONT_CHAR_IC => {
1232                self.scan_font_ident();
1233                let f = self.cur_val as u32;
1234                self.scan_char_num();
1235                let v = self.font_char_dimen(f, self.cur_val as u32, m - li::FONT_CHAR_WD);
1236                self.scanned(v, ValLevel::Dimen);
1237            }
1238            li::PAR_SHAPE_LENGTH | li::PAR_SHAPE_INDENT | li::PAR_SHAPE_DIMEN => {
1239                let q = m - li::PAR_SHAPE_LENGTH;
1240                self.scan_int();
1241                let v = match &self.eqtb.par_shape.val {
1242                    None => 0,
1243                    Some(s) if self.cur_val <= 0 => {
1244                        let _ = s;
1245                        0
1246                    }
1247                    Some(s) => {
1248                        let n = (s.len() / 2) as i32;
1249                        let mut cv = self.cur_val;
1250                        let mut q = q;
1251                        if q == 2 {
1252                            q = (cv % 2) as u32;
1253                            cv = (cv + q as i32) / 2;
1254                        }
1255                        if cv > n {
1256                            cv = n;
1257                        }
1258                        // indent is at 2*(k-1), length at 2*(k-1)+1
1259                        let idx = 2 * (cv - 1) as usize + if q == 0 { 1 } else { 0 };
1260                        s[idx]
1261                    }
1262                };
1263                self.scanned(v, ValLevel::Dimen);
1264            }
1265            li::GLUE_STRETCH | li::GLUE_SHRINK => {
1266                self.scan_normal_glue();
1267                let g = self.cur_glue;
1268                self.scanned(
1269                    if m == li::GLUE_STRETCH {
1270                        g.stretch
1271                    } else {
1272                        g.shrink
1273                    },
1274                    ValLevel::Dimen,
1275                );
1276            }
1277            li::MU_TO_GLUE => {
1278                self.scan_mu_glue();
1279                let g = self.cur_glue;
1280                self.scanned_glue(g, ValLevel::Glue);
1281            }
1282            li::GLUE_TO_MU => {
1283                self.scan_normal_glue();
1284                let g = self.cur_glue;
1285                self.scanned_glue(g, ValLevel::Mu);
1286            }
1287            li::NUM_EXPR | li::DIM_EXPR | li::GLUE_EXPR | li::MU_EXPR => {
1288                let l = match m {
1289                    li::NUM_EXPR => ValLevel::Int,
1290                    li::DIM_EXPR => ValLevel::Dimen,
1291                    li::GLUE_EXPR => ValLevel::Glue,
1292                    _ => ValLevel::Mu,
1293                };
1294                self.scan_expr(l);
1295            }
1296            _ => {
1297                let _ = level;
1298                self.scanned(0, ValLevel::Int);
1299            }
1300        }
1301    }
1302
1303    /// Scans glue (`scan_normal_glue`).
1304    pub fn scan_normal_glue(&mut self) {
1305        self.scan_glue(ValLevel::Glue);
1306    }
1307
1308    /// Scans muglue (`scan_mu_glue`).
1309    pub fn scan_mu_glue(&mut self) {
1310        self.scan_glue(ValLevel::Mu);
1311    }
1312
1313    /// Scans the math style of a `\Umath…` parameter: a style command
1314    /// (`\displaystyle`, `\crampedtextstyle`, …), whose number (0–7) is
1315    /// left in `cur_val`. Anything else is put back and reported, and
1316    /// `\displaystyle` is used (as LuaTeX 1.22 does; numbers are not
1317    /// accepted). Returns whether a style was found.
1318    pub fn scan_math_style(&mut self) -> bool {
1319        self.get_nonblank_noncall();
1320        if self.cur_cmd == Cmd::MathStyle {
1321            self.cur_val = self.cur_chr as i32;
1322            return true;
1323        }
1324        self.print_err("Missing math style, treated as ");
1325        self.print_esc_str("displaystyle");
1326        self.help(&["A style should have been here; I inserted `\\displaystyle'."]);
1327        self.back_error();
1328        self.cur_val = 0;
1329        false
1330    }
1331
1332    // ----- e-TeX expressions -------------------------------------------------
1333
1334    /// Evaluates an e-TeX expression (`scan_expr`). Sets `cur_val` (or
1335    /// `cur_glue`) and `cur_val_level`.
1336    pub fn scan_expr(&mut self, level: ValLevel) {
1337        let mut arith_err = false;
1338        let r = self.scan_expr_level(level, &mut arith_err, 0);
1339        if arith_err {
1340            self.print_err("Arithmetic overflow");
1341            self.help(&[
1342                "I can't evaluate this expression,",
1343                "since the result is out of range.",
1344            ]);
1345            self.error();
1346            match level {
1347                ValLevel::Glue | ValLevel::Mu => {
1348                    self.cur_glue = Glue::ZERO;
1349                    self.cur_val = 0;
1350                }
1351                _ => self.cur_val = 0,
1352            }
1353        } else {
1354            match level {
1355                ValLevel::Glue | ValLevel::Mu => {
1356                    self.cur_glue = r;
1357                    self.cur_val = r.width;
1358                }
1359                _ => self.cur_val = r.width,
1360            }
1361        }
1362        self.cur_val_level = level;
1363    }
1364
1365    /// Recursive-descent evaluator for e-TeX expressions. Values are
1366    /// carried as [`Glue`]; integers and dimensions use only `width`.
1367    fn scan_expr_level(&mut self, l: ValLevel, err: &mut bool, depth: u32) -> Glue {
1368        // expr ::= term { (+|-) term } ; term ::= factor { (*|/) number }
1369        let max = |l: ValLevel| {
1370            if l == ValLevel::Int {
1371                arith::INFINITY
1372            } else {
1373                MAX_DIMEN
1374            }
1375        };
1376        let mut e = Glue::ZERO;
1377        let mut first = true;
1378        let mut negative_next = false;
1379        loop {
1380            // A term. As in etex.ch, each operator token is read once: the
1381            // token after a term is the next operator (a back_input there
1382            // would change the error context of the expression's end).
1383            let mut t = self.scan_expr_factor(l, err, depth);
1384            self.get_nonblank_noncall();
1385            loop {
1386                if self.cur_tok == Token::other('*' as u32) {
1387                    self.scan_int_factor(err, depth);
1388                    let f = self.cur_val;
1389                    // A following '/' combines into scaling.
1390                    self.get_nonblank_noncall();
1391                    if self.cur_tok == Token::other('/' as u32) {
1392                        self.scan_int_factor(err, depth);
1393                        let d = self.cur_val;
1394                        t = self.expr_scale(t, f, d, l, err);
1395                        self.get_nonblank_noncall();
1396                    } else {
1397                        t = self.expr_mult(t, f, l, err);
1398                    }
1399                } else if self.cur_tok == Token::other('/' as u32) {
1400                    self.scan_int_factor(err, depth);
1401                    let d = self.cur_val;
1402                    t = self.expr_div(t, d, l, err);
1403                    self.get_nonblank_noncall();
1404                } else {
1405                    break;
1406                }
1407            }
1408            if first {
1409                e = t;
1410                first = false;
1411            } else {
1412                e = self.expr_add(e, t, l, negative_next, max(l), err);
1413            }
1414            if self.cur_tok == Token::other('+' as u32) {
1415                negative_next = false;
1416            } else if self.cur_tok == Token::other('-' as u32) {
1417                negative_next = true;
1418            } else {
1419                // End of expression: an optional \relax is absorbed, but
1420                // only at the outer level (etex.ch: inside parentheses it
1421                // is the token that is missing a `)`).
1422                if self.cur_cmd != Cmd::Relax || depth > 0 {
1423                    self.back_input();
1424                }
1425                return e;
1426            }
1427        }
1428    }
1429
1430    fn scan_expr_factor(&mut self, l: ValLevel, err: &mut bool, depth: u32) -> Glue {
1431        self.get_nonblank_noncall();
1432        if self.cur_tok == Token::other('(' as u32) {
1433            let v = self.scan_expr_paren(l, err, depth);
1434            return v;
1435        }
1436        self.back_input();
1437        match l {
1438            ValLevel::Int => {
1439                self.scan_int();
1440                Glue::fixed(self.cur_val)
1441            }
1442            ValLevel::Dimen => {
1443                self.scan_normal_dimen();
1444                Glue::fixed(self.cur_val)
1445            }
1446            ValLevel::Glue => {
1447                self.scan_normal_glue();
1448                self.cur_glue
1449            }
1450            _ => {
1451                self.scan_mu_glue();
1452                self.cur_glue
1453            }
1454        }
1455    }
1456
1457    fn scan_expr_paren(&mut self, l: ValLevel, err: &mut bool, depth: u32) -> Glue {
1458        let v = self.scan_expr_level(l, err, depth + 1);
1459        // scan_expr_level stops at a token it backed up; expect ')'.
1460        self.get_nonblank_noncall();
1461        if self.cur_tok != Token::other(')' as u32) {
1462            self.print_err("Missing ) inserted for expression");
1463            self.help(&["I was expecting to see `+', `-', `*', `/', or `)'. Didn't."]);
1464            self.back_error();
1465        }
1466        v
1467    }
1468
1469    fn scan_int_factor(&mut self, err: &mut bool, depth: u32) {
1470        self.get_nonblank_noncall();
1471        if self.cur_tok == Token::other('(' as u32) {
1472            let v = self.scan_expr_paren(ValLevel::Int, err, depth);
1473            self.cur_val = v.width;
1474        } else {
1475            self.back_input();
1476            self.scan_int();
1477        }
1478    }
1479
1480    fn expr_add(
1481        &mut self,
1482        a: Glue,
1483        b: Glue,
1484        l: ValLevel,
1485        neg: bool,
1486        max: i32,
1487        err: &mut bool,
1488    ) -> Glue {
1489        let mut add = |x: i32, y: i32| match arith::add_or_sub(x, y, max, neg) {
1490            Ok(v) => v,
1491            Err(_) => {
1492                *err = true;
1493                0
1494            }
1495        };
1496        if l <= ValLevel::Dimen {
1497            return Glue::fixed(add(a.width, b.width));
1498        }
1499        let mut e = a;
1500        e.width = add(a.width, b.width);
1501        if e.stretch_order == b.stretch_order {
1502            e.stretch = add(e.stretch, b.stretch);
1503        } else if e.stretch_order < b.stretch_order && b.stretch != 0 {
1504            e.stretch = if neg { -b.stretch } else { b.stretch };
1505            e.stretch_order = b.stretch_order;
1506        }
1507        if e.shrink_order == b.shrink_order {
1508            e.shrink = add(e.shrink, b.shrink);
1509        } else if e.shrink_order < b.shrink_order && b.shrink != 0 {
1510            e.shrink = if neg { -b.shrink } else { b.shrink };
1511            e.shrink_order = b.shrink_order;
1512        }
1513        normalize_glue(e)
1514    }
1515
1516    fn expr_mult(&mut self, t: Glue, f: i32, l: ValLevel, err: &mut bool) -> Glue {
1517        let mut m = |x: i32| {
1518            let r = if l == ValLevel::Int {
1519                arith::mult_integers(x, f)
1520            } else {
1521                arith::nx_plus_y(x, f, 0)
1522            };
1523            r.unwrap_or_else(|_| {
1524                *err = true;
1525                0
1526            })
1527        };
1528        if l <= ValLevel::Dimen {
1529            return Glue::fixed(m(t.width));
1530        }
1531        let mut g = t;
1532        g.width = m(g.width);
1533        g.stretch = m(g.stretch);
1534        g.shrink = m(g.shrink);
1535        normalize_glue(g)
1536    }
1537
1538    fn expr_div(&mut self, t: Glue, f: i32, l: ValLevel, err: &mut bool) -> Glue {
1539        let mut d = |x: i32| {
1540            arith::quotient(x, f).unwrap_or_else(|_| {
1541                *err = true;
1542                0
1543            })
1544        };
1545        if l <= ValLevel::Dimen {
1546            return Glue::fixed(d(t.width));
1547        }
1548        let mut g = t;
1549        g.width = d(g.width);
1550        g.stretch = d(g.stretch);
1551        g.shrink = d(g.shrink);
1552        normalize_glue(g)
1553    }
1554
1555    fn expr_scale(&mut self, t: Glue, n: i32, f: i32, l: ValLevel, err: &mut bool) -> Glue {
1556        let max = if l == ValLevel::Int {
1557            arith::INFINITY
1558        } else {
1559            MAX_DIMEN
1560        };
1561        let mut s = |x: i32| {
1562            arith::fract(x, n, f, max).unwrap_or_else(|_| {
1563                *err = true;
1564                0
1565            })
1566        };
1567        if l <= ValLevel::Dimen {
1568            return Glue::fixed(s(t.width));
1569        }
1570        let mut g = t;
1571        g.width = s(g.width);
1572        g.stretch = s(g.stretch);
1573        g.shrink = s(g.shrink);
1574        normalize_glue(g)
1575    }
1576
1577    // ----- token lists -------------------------------------------------------
1578
1579    /// Part of [`scan_toks`](Self::scan_toks): appends to `out` the tokens
1580    /// of the current token list that `scan_toks` would copy unchanged
1581    /// (characters other than `#`, `&` and parameters; braces, counted in
1582    /// `unbalance`; control sequences that are not expandable, or protected
1583    /// macros, when expanding; any but outer ones when not), and stops at
1584    /// the first other token, which the general path handles. Returns the
1585    /// closing brace if the list ended the body.
1586    fn copy_plain_tokens(
1587        &mut self,
1588        out: &mut Vec<Token>,
1589        unbalance: &mut i32,
1590        xpand: bool,
1591    ) -> Option<Token> {
1592        let Some(crate::input::Level::Tokens(t)) = self.input.stack.last_mut() else {
1593            return None;
1594        };
1595        while let Some(&tok) = t.list.get(t.loc) {
1596            if tok.is_cs() {
1597                let m = self.eqtb.meaning(tok.cs_id());
1598                let plain = if m.cmd <= Cmd::MAX_COMMAND {
1599                    // (A control sequence `\let` to `#`, or `\alignmark`, is a
1600                    // parameter character in a definition.)
1601                    !(Cmd::TabMark..=Cmd::MacParam).contains(&m.cmd)
1602                } else if m.cmd.is_outer_or_special() {
1603                    false
1604                } else if xpand {
1605                    m.cmd >= Cmd::Call && m.mac.as_ref().is_some_and(|m| m.protected)
1606                } else {
1607                    true
1608                };
1609                if !plain {
1610                    return None;
1611                }
1612            } else {
1613                match tok.cmd() {
1614                    Cmd::LeftBrace => {
1615                        self.input.align_state += 1;
1616                        *unbalance += 1;
1617                    }
1618                    Cmd::RightBrace => {
1619                        self.input.align_state -= 1;
1620                        *unbalance -= 1;
1621                        if *unbalance == 0 {
1622                            t.loc += 1;
1623                            return Some(tok);
1624                        }
1625                    }
1626                    Cmd::TabMark | Cmd::CarRet | Cmd::MacParam => return None,
1627                    _ => {}
1628                }
1629            }
1630            out.push(tok);
1631            t.loc += 1;
1632        }
1633        None
1634    }
1635
1636    /// Scans a balanced text (`scan_toks`). For macro definitions
1637    /// (`macro_def`), the parameter text is scanned first and the result
1638    /// contains match tokens, `END_MATCH` and the body. With `xpand`, the
1639    /// text is expanded as in `\edef`. Returns the list and whether the
1640    /// definition text used `#` correctly.
1641    pub fn scan_toks(&mut self, macro_def: bool, xpand: bool) -> Vec<Token> {
1642        if macro_def {
1643            self.input.scanner_status = ScannerStatus::Defining;
1644        } else {
1645            self.input.scanner_status = ScannerStatus::Absorbing;
1646        }
1647        self.input.warning_index = self.cur_cs;
1648        let mut out: Vec<Token> = Vec::new();
1649        // The tokens scanned so far are shown if the text runs away; they
1650        // stay in `runaway_list` while more input is read.
1651        let saved_runaway = std::mem::take(&mut self.runaway_list);
1652        macro_rules! runaway {
1653            ($e:expr) => {{
1654                self.runaway_list = std::mem::take(&mut out);
1655                let r = $e;
1656                out = std::mem::take(&mut self.runaway_list);
1657                r
1658            }};
1659        }
1660        let mut unbalance: i32;
1661        let mut t: u32 = 0x30; // zero_token: next parameter number
1662        let mut hash_brace = Token(0);
1663        'found: {
1664            if macro_def {
1665                // Scan and build the parameter part; `#{` ends it early
1666                // (§476) and the body follows.
1667                'params: loop {
1668                    runaway!(self.get_token());
1669                    if self.cur_tok.0 < Token::RIGHT_BRACE.0 & !0x1F_FFFF | 0x1F_FFFF
1670                        && !self.cur_tok.is_cs()
1671                        && self.cur_cmd <= Cmd::RightBrace
1672                    {
1673                        break; // left or right brace
1674                    }
1675                    // Any token whose command is mac_param: a `#` character,
1676                    // a control sequence `\let` to one, LuaTeX's
1677                    // `\alignmark` (§476 tests `cur_cmd`).
1678                    if self.cur_cmd == Cmd::MacParam {
1679                        // A parameter, or #{.
1680                        let s = Token::match_token(self.cur_chr);
1681                        runaway!(self.get_token());
1682                        if self.cur_cmd == Cmd::LeftBrace && !self.cur_tok.is_cs() {
1683                            hash_brace = self.cur_tok;
1684                            out.push(self.cur_tok);
1685                            break 'params;
1686                        }
1687                        if t == 0x39 {
1688                            self.print_err("You already have nine parameters");
1689                            self.help(&["I'm going to ignore the # sign you just used."]);
1690                            self.error();
1691                        } else {
1692                            t += 1;
1693                            if self.cur_tok != Token::other(t) {
1694                                self.print_err("Parameters must be numbered consecutively");
1695                                self.help(&[
1696                                    "I've inserted the digit you should have used after the #.",
1697                                    "Type `1' to delete what you did use.",
1698                                ]);
1699                                self.back_error();
1700                            }
1701                            self.cur_tok = s;
1702                        }
1703                    }
1704                    out.push(self.cur_tok);
1705                }
1706                out.push(Token::END_MATCH);
1707                if hash_brace.0 == 0 && self.cur_cmd == Cmd::RightBrace {
1708                    self.print_err("Missing { inserted");
1709                    self.input.align_state += 1;
1710                    self.help(&[
1711                        "Where was the left brace? You said something like `\\def\\a}',",
1712                        "which I'm going to interpret as `\\def\\a{}'.",
1713                    ]);
1714                    self.error();
1715                    break 'found;
1716                }
1717            } else {
1718                self.scan_left_brace();
1719            }
1720            // Scan and build the body.
1721            unbalance = 1;
1722            loop {
1723                // Fast path: copy the plain tokens of the current token list
1724                // directly (most of the body of an \edef that re-expands a
1725                // long macro).
1726                if let Some(end_brace) = self.copy_plain_tokens(&mut out, &mut unbalance, xpand) {
1727                    self.cur_tok = end_brace;
1728                    self.cur_cmd = Cmd::RightBrace;
1729                    self.cur_chr = end_brace.chr();
1730                    self.cur_cs = NO_CS;
1731                    break 'found;
1732                }
1733                if xpand {
1734                    // Expand the next part of the input.
1735                    loop {
1736                        runaway!(self.get_next());
1737                        if self.cur_cmd >= Cmd::Call
1738                            && self
1739                                .eqtb
1740                                .meaning(self.cur_cs)
1741                                .mac
1742                                .as_ref()
1743                                .is_some_and(|m| m.protected)
1744                        {
1745                            self.cur_cmd = Cmd::Relax;
1746                            self.cur_chr = crate::input::NO_EXPAND_FLAG;
1747                            break;
1748                        }
1749                        if self.cur_cmd <= Cmd::MAX_COMMAND {
1750                            break;
1751                        }
1752                        if self.cur_cmd != Cmd::The {
1753                            runaway!(self.expand());
1754                        } else {
1755                            let list = runaway!(self.the_toks());
1756                            out.extend_from_slice(&list);
1757                        }
1758                    }
1759                    runaway!(self.x_token());
1760                } else {
1761                    runaway!(self.get_token());
1762                }
1763                // Braces are explicit characters only (§477 compares
1764                // `cur_tok`), parameters any token with command mac_param.
1765                if self.cur_cmd == Cmd::MacParam && macro_def {
1766                    // Look for parameter number or ##.
1767                    let s = self.cur_tok;
1768                    if xpand {
1769                        runaway!(self.get_x_token());
1770                    } else {
1771                        runaway!(self.get_token());
1772                    }
1773                    if self.cur_cmd != Cmd::MacParam {
1774                        let c = self.cur_tok;
1775                        if c.is_cs() || c.cmd() != Cmd::OtherChar || c.chr() <= 0x30 || c.chr() > t
1776                        {
1777                            self.print_err("Illegal parameter number in definition of ");
1778                            self.sprint_cs(self.input.warning_index);
1779                            self.help(&[
1780                                "You meant to type ## instead of #, right?",
1781                                "Or maybe a } was forgotten somewhere earlier, and things",
1782                                "are all screwed up? I'm going to assume that you meant ##.",
1783                            ]);
1784                            self.back_error();
1785                            self.cur_tok = s;
1786                        } else {
1787                            self.cur_tok = Token::out_param(c.chr() - 0x30);
1788                        }
1789                    }
1790                } else if self.cur_tok.is_cs()
1791                    || self.cur_cmd > Cmd::MacParam
1792                    || self.cur_cmd < Cmd::LeftBrace
1793                {
1794                    // not a brace or parameter
1795                } else if self.cur_cmd < Cmd::RightBrace {
1796                    unbalance += 1;
1797                } else if self.cur_cmd == Cmd::RightBrace {
1798                    unbalance -= 1;
1799                    if unbalance == 0 {
1800                        break 'found;
1801                    }
1802                }
1803                out.push(self.cur_tok);
1804            }
1805        }
1806        self.input.scanner_status = ScannerStatus::Normal;
1807        self.runaway_list = saved_runaway;
1808        if hash_brace.0 != 0 {
1809            out.push(hash_brace);
1810        }
1811        out
1812    }
1813}
1814
1815/// `set_aux` modifier for `\prevdepth`.
1816pub const SET_AUX_PREV_DEPTH: u32 = 1;
1817/// `set_aux` modifier for `\spacefactor`.
1818pub const SET_AUX_SPACE_FACTOR: u32 = 134;
1819
1820/// Sets an infinite component's order to normal when the component is
1821/// zero (`normalize_glue`).
1822pub fn normalize_glue(mut g: Glue) -> Glue {
1823    if g.stretch == 0 {
1824        g.stretch_order = glue::NORMAL;
1825    }
1826    if g.shrink == 0 {
1827        g.shrink_order = glue::NORMAL;
1828    }
1829    g
1830}
1831
1832/// Avoids an unused-import warning when `Loc` is not needed here.
1833#[allow(dead_code)]
1834fn _loc(_: Loc) {}