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::AssignDir | Cmd::AssignDirection => {
946                let v = self.eqtb.int(pg::assign_int::COUNT.min(m + DIR_PAR_BASE));
947                let _ = v;
948                self.scanned(self.dir_param(m), ValLevel::Int);
949            }
950            Cmd::AssignDimen => {
951                let v = if m >= REG_OFFSET {
952                    self.eqtb.dimen[(m - REG_OFFSET) as usize].val
953                } else {
954                    self.eqtb.dimen(m)
955                };
956                self.scanned(v, ValLevel::Dimen);
957            }
958            Cmd::AssignGlue => {
959                let g = if m >= REG_OFFSET {
960                    self.eqtb.skip[(m - REG_OFFSET) as usize].val
961                } else {
962                    self.eqtb.glue(m)
963                };
964                self.scanned_glue(g, ValLevel::Glue);
965            }
966            Cmd::AssignMuGlue => {
967                let g = if m >= REG_OFFSET {
968                    self.eqtb.muskip[(m - REG_OFFSET) as usize].val
969                } else {
970                    self.eqtb.glue(m + crate::eqtb::MU_GLUE_OFFSET)
971                };
972                self.scanned_glue(g, ValLevel::Mu);
973            }
974            Cmd::SetAux => {
975                let v = self.fetch_aux(m);
976                if let Some((v, l)) = v {
977                    self.scanned(v, l);
978                } else {
979                    self.scanned(
980                        0,
981                        if m == SET_AUX_PREV_DEPTH {
982                            ValLevel::Dimen
983                        } else {
984                            ValLevel::Int
985                        },
986                    );
987                }
988            }
989            Cmd::SetPrevGraf => {
990                let v = self.fetch_prev_graf();
991                self.scanned(v, ValLevel::Int);
992            }
993            Cmd::SetPageInt => {
994                let v = match m {
995                    0 => self.dead_cycles,
996                    1 => self.insert_penalties,
997                    _ => self.err.interaction as i32,
998                };
999                self.scanned(v, ValLevel::Int);
1000            }
1001            Cmd::SetPageDimen => {
1002                let v = self.page_so_far_value(m);
1003                self.scanned(v, ValLevel::Dimen);
1004            }
1005            Cmd::SetTexShape => {
1006                let v = self
1007                    .eqtb
1008                    .par_shape
1009                    .val
1010                    .as_ref()
1011                    .map_or(0, |s| (s.len() / 2) as i32);
1012                self.scanned(v, ValLevel::Int);
1013            }
1014            Cmd::SetEtexShape => {
1015                self.scan_int();
1016                let n = self.cur_val;
1017                let v = match &self.eqtb.etex_shape[m as usize].val {
1018                    None => 0,
1019                    Some(s) if n < 0 => 0,
1020                    Some(s) => {
1021                        let len = s.len() as i32;
1022                        if n == 0 {
1023                            len
1024                        } else {
1025                            s[(n.min(len) - 1) as usize]
1026                        }
1027                    }
1028                };
1029                self.scanned(v, ValLevel::Int);
1030            }
1031            Cmd::SetBoxDimen => {
1032                self.scan_register_num();
1033                let p = self.eqtb.boxes[self.cur_val as usize].val;
1034                let v = if p.is_null() {
1035                    0
1036                } else {
1037                    let b = self.nodes.box_data(p);
1038                    match (m, b) {
1039                        (2, Some(b)) => b.width,
1040                        (3, Some(b)) => b.depth,
1041                        (4, Some(b)) => b.height,
1042                        _ => 0,
1043                    }
1044                };
1045                self.scanned(v, ValLevel::Dimen);
1046            }
1047            Cmd::CharGiven | Cmd::XmathGiven => {
1048                self.scanned(self.cur_chr as i32, ValLevel::Int);
1049            }
1050            Cmd::MathGiven => {
1051                self.scanned(crate::codes::math_given_report(self.cur_chr), ValLevel::Int);
1052            }
1053            Cmd::AssignFontDimen => {
1054                let v = self.find_font_dimen(false);
1055                self.scanned(v, ValLevel::Dimen);
1056            }
1057            Cmd::AssignFontInt => {
1058                self.scan_font_ident();
1059                let f = self.cur_val as u32;
1060                let v = self.font_int_param(f, m);
1061                self.scanned(v, ValLevel::Int);
1062            }
1063            Cmd::Register => {
1064                self.scan_register_num();
1065                let n = self.cur_val as usize;
1066                match m {
1067                    reg::COUNT => self.scanned(self.eqtb.count[n].val, ValLevel::Int),
1068                    reg::ATTRIBUTE => self.scanned(self.eqtb.attr[n].val, ValLevel::Int),
1069                    reg::DIMEN => self.scanned(self.eqtb.dimen[n].val, ValLevel::Dimen),
1070                    reg::SKIP => {
1071                        let g = self.eqtb.skip[n].val;
1072                        self.scanned_glue(g, ValLevel::Glue);
1073                    }
1074                    _ => {
1075                        let g = self.eqtb.muskip[n].val;
1076                        self.scanned_glue(g, ValLevel::Mu);
1077                    }
1078                }
1079            }
1080            Cmd::SetMathParam => {
1081                // After a missing style LuaTeX reads an unset parameter.
1082                let found = self.scan_math_style();
1083                let style = self.cur_val as u32;
1084                let v = self
1085                    .eqtb
1086                    .math_param
1087                    .get(&(m * 8 + style))
1088                    .filter(|_| found)
1089                    .map_or(crate::codes::UNDEFINED_MATH_PARAM, |s| s.val);
1090                if crate::codes::math_param_is_glue(m) {
1091                    // An unset spacing shows TeX's default for the pair.
1092                    let g = if found && v == crate::codes::UNDEFINED_MATH_PARAM {
1093                        let (l, r) = ((m - 49) / 8, (m - 49) % 8);
1094                        self.math_spacing(l, r, style)
1095                            .map_or(crate::glue::Glue::ZERO, |(g, _)| g)
1096                    } else {
1097                        self.math_param_glue(v)
1098                    };
1099                    self.scanned_glue(g, ValLevel::Mu);
1100                } else if m == crate::mathparams::mp::RADICAL_DEGREE_RAISE {
1101                    // A percentage.
1102                    self.scanned(v, ValLevel::Int);
1103                } else {
1104                    self.scanned(v, ValLevel::Dimen);
1105                }
1106            }
1107            Cmd::LastItem => self.fetch_last_item(m, level),
1108            _ => {
1109                self.print_err("You can't use `");
1110                self.print_cmd_chr(self.cur_cmd, self.cur_chr);
1111                self.printer.print_str("' after ");
1112                self.print_esc_str("the");
1113                self.help(&["I'm forgetting what you said and using zero instead."]);
1114                self.error();
1115                if level != ValLevel::Tok {
1116                    self.scanned(0, ValLevel::Dimen);
1117                } else {
1118                    self.scanned(0, ValLevel::Int);
1119                }
1120            }
1121        }
1122        // Convert to a lower level.
1123        while self.cur_val_level > level {
1124            if self.cur_val_level == ValLevel::Glue {
1125                self.cur_val = self.cur_glue.width;
1126            } else if self.cur_val_level == ValLevel::Mu {
1127                self.mu_error();
1128            }
1129            self.cur_val_level = match self.cur_val_level {
1130                ValLevel::Tok => ValLevel::Ident,
1131                ValLevel::Ident => ValLevel::Mu,
1132                ValLevel::Mu => ValLevel::Glue,
1133                ValLevel::Glue => ValLevel::Dimen,
1134                _ => ValLevel::Int,
1135            };
1136            if self.cur_val_level == ValLevel::Glue || self.cur_val_level == ValLevel::Mu {
1137                // Coming from Ident/Tok (not possible in practice).
1138            }
1139        }
1140        if negative {
1141            if self.cur_val_level >= ValLevel::Glue {
1142                let g = &mut self.cur_glue;
1143                g.width = -g.width;
1144                g.stretch = -g.stretch;
1145                g.shrink = -g.shrink;
1146                self.cur_val = g.width;
1147            } else {
1148                self.cur_val = self.cur_val.wrapping_neg();
1149            }
1150        }
1151    }
1152
1153    /// Fetches a `last_item` quantity.
1154    fn fetch_last_item(&mut self, m: u32, level: ValLevel) {
1155        use last_item as li;
1156        match m {
1157            li::LAST_PENALTY | li::LAST_KERN | li::LAST_SKIP | li::LAST_NODE_TYPE => {
1158                let (v, g, l) = self.fetch_last_node_item(m);
1159                if l >= ValLevel::Glue {
1160                    self.scanned_glue(g, l);
1161                } else {
1162                    self.scanned(v, l);
1163                }
1164            }
1165            li::INPUT_LINE_NO => self.scanned(self.input.line(), ValLevel::Int),
1166            li::BADNESS => self.scanned(self.last_badness, ValLevel::Int),
1167            li::LUATEX_VERSION => self.scanned(LUATEX_VERSION, ValLevel::Int),
1168            li::ETEX_VERSION => self.scanned(ETEX_VERSION, ValLevel::Int),
1169            li::ETEX_MINOR_VERSION => self.scanned(ETEX_MINOR_VERSION, ValLevel::Int),
1170            li::CURRENT_GROUP_LEVEL => {
1171                self.scanned(i32::from(self.save.cur_level) - 1, ValLevel::Int)
1172            }
1173            li::CURRENT_GROUP_TYPE => self.scanned(self.save.cur_group as i32, ValLevel::Int),
1174            li::CURRENT_IF_LEVEL => self.scanned(self.cond.stack.len() as i32, ValLevel::Int),
1175            li::CURRENT_IF_TYPE => {
1176                let v = if self.cond.stack.is_empty() {
1177                    0
1178                } else if self.cond.cur_if < crate::cond::UNLESS_CODE {
1179                    self.cond.cur_if as i32 + 1
1180                } else {
1181                    -((self.cond.cur_if - crate::cond::UNLESS_CODE) as i32 + 1)
1182                };
1183                self.scanned(v, ValLevel::Int);
1184            }
1185            li::CURRENT_IF_BRANCH => {
1186                let v = match self.cond.if_limit {
1187                    crate::cond::OR_CODE | crate::cond::ELSE_CODE => 1,
1188                    crate::cond::FI_CODE => -1,
1189                    _ => 0,
1190                };
1191                self.scanned(v, ValLevel::Int);
1192            }
1193            li::RANDOM_SEED => self.scanned(self.random_seed, ValLevel::Int),
1194            li::LAST_X_POS => self.scanned(self.last_x_pos, ValLevel::Int),
1195            li::LAST_Y_POS => self.scanned(self.last_y_pos, ValLevel::Int),
1196            li::LAST_SAVED_BOX_RESOURCE_INDEX
1197            | li::LAST_SAVED_IMAGE_RESOURCE_INDEX
1198            | li::LAST_SAVED_IMAGE_RESOURCE_PAGES => {
1199                self.scanned(self.last_saved_resource(m), ValLevel::Int)
1200            }
1201            li::GLUE_STRETCH_ORDER
1202            | li::GLUE_SHRINK_ORDER
1203            | li::ETEX_GLUE_STRETCH_ORDER
1204            | li::ETEX_GLUE_SHRINK_ORDER => {
1205                self.scan_normal_glue();
1206                let g = self.cur_glue;
1207                let o = if m == li::GLUE_STRETCH_ORDER || m == li::ETEX_GLUE_STRETCH_ORDER {
1208                    g.stretch_order
1209                } else {
1210                    g.shrink_order
1211                };
1212                // e-TeX reports fil=1…filll=3, LuaTeX's own variant counts
1213                // the extra `fi` order.
1214                let v = if m == li::ETEX_GLUE_STRETCH_ORDER || m == li::ETEX_GLUE_SHRINK_ORDER {
1215                    i32::from(o)
1216                } else {
1217                    i32::from(o.saturating_sub(1)).max(0)
1218                };
1219                self.scanned(v, ValLevel::Int);
1220            }
1221            li::FONT_CHAR_WD | li::FONT_CHAR_HT | li::FONT_CHAR_DP | li::FONT_CHAR_IC => {
1222                self.scan_font_ident();
1223                let f = self.cur_val as u32;
1224                self.scan_char_num();
1225                let v = self.font_char_dimen(f, self.cur_val as u32, m - li::FONT_CHAR_WD);
1226                self.scanned(v, ValLevel::Dimen);
1227            }
1228            li::PAR_SHAPE_LENGTH | li::PAR_SHAPE_INDENT | li::PAR_SHAPE_DIMEN => {
1229                let q = m - li::PAR_SHAPE_LENGTH;
1230                self.scan_int();
1231                let v = match &self.eqtb.par_shape.val {
1232                    None => 0,
1233                    Some(s) if self.cur_val <= 0 => {
1234                        let _ = s;
1235                        0
1236                    }
1237                    Some(s) => {
1238                        let n = (s.len() / 2) as i32;
1239                        let mut cv = self.cur_val;
1240                        let mut q = q;
1241                        if q == 2 {
1242                            q = (cv % 2) as u32;
1243                            cv = (cv + q as i32) / 2;
1244                        }
1245                        if cv > n {
1246                            cv = n;
1247                        }
1248                        // indent is at 2*(k-1), length at 2*(k-1)+1
1249                        let idx = 2 * (cv - 1) as usize + if q == 0 { 1 } else { 0 };
1250                        s[idx]
1251                    }
1252                };
1253                self.scanned(v, ValLevel::Dimen);
1254            }
1255            li::GLUE_STRETCH | li::GLUE_SHRINK => {
1256                self.scan_normal_glue();
1257                let g = self.cur_glue;
1258                self.scanned(
1259                    if m == li::GLUE_STRETCH {
1260                        g.stretch
1261                    } else {
1262                        g.shrink
1263                    },
1264                    ValLevel::Dimen,
1265                );
1266            }
1267            li::MU_TO_GLUE => {
1268                self.scan_mu_glue();
1269                let g = self.cur_glue;
1270                self.scanned_glue(g, ValLevel::Glue);
1271            }
1272            li::GLUE_TO_MU => {
1273                self.scan_normal_glue();
1274                let g = self.cur_glue;
1275                self.scanned_glue(g, ValLevel::Mu);
1276            }
1277            li::NUM_EXPR | li::DIM_EXPR | li::GLUE_EXPR | li::MU_EXPR => {
1278                let l = match m {
1279                    li::NUM_EXPR => ValLevel::Int,
1280                    li::DIM_EXPR => ValLevel::Dimen,
1281                    li::GLUE_EXPR => ValLevel::Glue,
1282                    _ => ValLevel::Mu,
1283                };
1284                self.scan_expr(l);
1285            }
1286            _ => {
1287                let _ = level;
1288                self.scanned(0, ValLevel::Int);
1289            }
1290        }
1291    }
1292
1293    /// Scans glue (`scan_normal_glue`).
1294    pub fn scan_normal_glue(&mut self) {
1295        self.scan_glue(ValLevel::Glue);
1296    }
1297
1298    /// Scans muglue (`scan_mu_glue`).
1299    pub fn scan_mu_glue(&mut self) {
1300        self.scan_glue(ValLevel::Mu);
1301    }
1302
1303    /// Scans the math style of a `\Umath…` parameter: a style command
1304    /// (`\displaystyle`, `\crampedtextstyle`, …), whose number (0–7) is
1305    /// left in `cur_val`. Anything else is put back and reported, and
1306    /// `\displaystyle` is used (as LuaTeX 1.22 does; numbers are not
1307    /// accepted). Returns whether a style was found.
1308    pub fn scan_math_style(&mut self) -> bool {
1309        self.get_nonblank_noncall();
1310        if self.cur_cmd == Cmd::MathStyle {
1311            self.cur_val = self.cur_chr as i32;
1312            return true;
1313        }
1314        self.print_err("Missing math style, treated as ");
1315        self.print_esc_str("displaystyle");
1316        self.help(&["A style should have been here; I inserted `\\displaystyle'."]);
1317        self.back_error();
1318        self.cur_val = 0;
1319        false
1320    }
1321
1322    // ----- e-TeX expressions -------------------------------------------------
1323
1324    /// Evaluates an e-TeX expression (`scan_expr`). Sets `cur_val` (or
1325    /// `cur_glue`) and `cur_val_level`.
1326    pub fn scan_expr(&mut self, level: ValLevel) {
1327        let mut arith_err = false;
1328        let r = self.scan_expr_level(level, &mut arith_err, 0);
1329        if arith_err {
1330            self.print_err("Arithmetic overflow");
1331            self.help(&[
1332                "I can't evaluate this expression,",
1333                "since the result is out of range.",
1334            ]);
1335            self.error();
1336            match level {
1337                ValLevel::Glue | ValLevel::Mu => {
1338                    self.cur_glue = Glue::ZERO;
1339                    self.cur_val = 0;
1340                }
1341                _ => self.cur_val = 0,
1342            }
1343        } else {
1344            match level {
1345                ValLevel::Glue | ValLevel::Mu => {
1346                    self.cur_glue = r;
1347                    self.cur_val = r.width;
1348                }
1349                _ => self.cur_val = r.width,
1350            }
1351        }
1352        self.cur_val_level = level;
1353    }
1354
1355    /// Recursive-descent evaluator for e-TeX expressions. Values are
1356    /// carried as [`Glue`]; integers and dimensions use only `width`.
1357    fn scan_expr_level(&mut self, l: ValLevel, err: &mut bool, depth: u32) -> Glue {
1358        // expr ::= term { (+|-) term } ; term ::= factor { (*|/) number }
1359        let max = |l: ValLevel| {
1360            if l == ValLevel::Int {
1361                arith::INFINITY
1362            } else {
1363                MAX_DIMEN
1364            }
1365        };
1366        let mut e = Glue::ZERO;
1367        let mut first = true;
1368        let mut negative_next = false;
1369        loop {
1370            // A term. As in etex.ch, each operator token is read once: the
1371            // token after a term is the next operator (a back_input there
1372            // would change the error context of the expression's end).
1373            let mut t = self.scan_expr_factor(l, err, depth);
1374            self.get_nonblank_noncall();
1375            loop {
1376                if self.cur_tok == Token::other('*' as u32) {
1377                    self.scan_int_factor(err, depth);
1378                    let f = self.cur_val;
1379                    // A following '/' combines into scaling.
1380                    self.get_nonblank_noncall();
1381                    if self.cur_tok == Token::other('/' as u32) {
1382                        self.scan_int_factor(err, depth);
1383                        let d = self.cur_val;
1384                        t = self.expr_scale(t, f, d, l, err);
1385                        self.get_nonblank_noncall();
1386                    } else {
1387                        t = self.expr_mult(t, f, l, err);
1388                    }
1389                } else if self.cur_tok == Token::other('/' as u32) {
1390                    self.scan_int_factor(err, depth);
1391                    let d = self.cur_val;
1392                    t = self.expr_div(t, d, l, err);
1393                    self.get_nonblank_noncall();
1394                } else {
1395                    break;
1396                }
1397            }
1398            if first {
1399                e = t;
1400                first = false;
1401            } else {
1402                e = self.expr_add(e, t, l, negative_next, max(l), err);
1403            }
1404            if self.cur_tok == Token::other('+' as u32) {
1405                negative_next = false;
1406            } else if self.cur_tok == Token::other('-' as u32) {
1407                negative_next = true;
1408            } else {
1409                // End of expression: an optional \relax is absorbed, but
1410                // only at the outer level (etex.ch: inside parentheses it
1411                // is the token that is missing a `)`).
1412                if self.cur_cmd != Cmd::Relax || depth > 0 {
1413                    self.back_input();
1414                }
1415                return e;
1416            }
1417        }
1418    }
1419
1420    fn scan_expr_factor(&mut self, l: ValLevel, err: &mut bool, depth: u32) -> Glue {
1421        self.get_nonblank_noncall();
1422        if self.cur_tok == Token::other('(' as u32) {
1423            let v = self.scan_expr_paren(l, err, depth);
1424            return v;
1425        }
1426        self.back_input();
1427        match l {
1428            ValLevel::Int => {
1429                self.scan_int();
1430                Glue::fixed(self.cur_val)
1431            }
1432            ValLevel::Dimen => {
1433                self.scan_normal_dimen();
1434                Glue::fixed(self.cur_val)
1435            }
1436            ValLevel::Glue => {
1437                self.scan_normal_glue();
1438                self.cur_glue
1439            }
1440            _ => {
1441                self.scan_mu_glue();
1442                self.cur_glue
1443            }
1444        }
1445    }
1446
1447    fn scan_expr_paren(&mut self, l: ValLevel, err: &mut bool, depth: u32) -> Glue {
1448        let v = self.scan_expr_level(l, err, depth + 1);
1449        // scan_expr_level stops at a token it backed up; expect ')'.
1450        self.get_nonblank_noncall();
1451        if self.cur_tok != Token::other(')' as u32) {
1452            self.print_err("Missing ) inserted for expression");
1453            self.help(&["I was expecting to see `+', `-', `*', `/', or `)'. Didn't."]);
1454            self.back_error();
1455        }
1456        v
1457    }
1458
1459    fn scan_int_factor(&mut self, err: &mut bool, depth: u32) {
1460        self.get_nonblank_noncall();
1461        if self.cur_tok == Token::other('(' as u32) {
1462            let v = self.scan_expr_paren(ValLevel::Int, err, depth);
1463            self.cur_val = v.width;
1464        } else {
1465            self.back_input();
1466            self.scan_int();
1467        }
1468    }
1469
1470    fn expr_add(
1471        &mut self,
1472        a: Glue,
1473        b: Glue,
1474        l: ValLevel,
1475        neg: bool,
1476        max: i32,
1477        err: &mut bool,
1478    ) -> Glue {
1479        let mut add = |x: i32, y: i32| match arith::add_or_sub(x, y, max, neg) {
1480            Ok(v) => v,
1481            Err(_) => {
1482                *err = true;
1483                0
1484            }
1485        };
1486        if l <= ValLevel::Dimen {
1487            return Glue::fixed(add(a.width, b.width));
1488        }
1489        let mut e = a;
1490        e.width = add(a.width, b.width);
1491        if e.stretch_order == b.stretch_order {
1492            e.stretch = add(e.stretch, b.stretch);
1493        } else if e.stretch_order < b.stretch_order && b.stretch != 0 {
1494            e.stretch = if neg { -b.stretch } else { b.stretch };
1495            e.stretch_order = b.stretch_order;
1496        }
1497        if e.shrink_order == b.shrink_order {
1498            e.shrink = add(e.shrink, b.shrink);
1499        } else if e.shrink_order < b.shrink_order && b.shrink != 0 {
1500            e.shrink = if neg { -b.shrink } else { b.shrink };
1501            e.shrink_order = b.shrink_order;
1502        }
1503        normalize_glue(e)
1504    }
1505
1506    fn expr_mult(&mut self, t: Glue, f: i32, l: ValLevel, err: &mut bool) -> Glue {
1507        let mut m = |x: i32| {
1508            let r = if l == ValLevel::Int {
1509                arith::mult_integers(x, f)
1510            } else {
1511                arith::nx_plus_y(x, f, 0)
1512            };
1513            r.unwrap_or_else(|_| {
1514                *err = true;
1515                0
1516            })
1517        };
1518        if l <= ValLevel::Dimen {
1519            return Glue::fixed(m(t.width));
1520        }
1521        let mut g = t;
1522        g.width = m(g.width);
1523        g.stretch = m(g.stretch);
1524        g.shrink = m(g.shrink);
1525        normalize_glue(g)
1526    }
1527
1528    fn expr_div(&mut self, t: Glue, f: i32, l: ValLevel, err: &mut bool) -> Glue {
1529        let mut d = |x: i32| {
1530            arith::quotient(x, f).unwrap_or_else(|_| {
1531                *err = true;
1532                0
1533            })
1534        };
1535        if l <= ValLevel::Dimen {
1536            return Glue::fixed(d(t.width));
1537        }
1538        let mut g = t;
1539        g.width = d(g.width);
1540        g.stretch = d(g.stretch);
1541        g.shrink = d(g.shrink);
1542        normalize_glue(g)
1543    }
1544
1545    fn expr_scale(&mut self, t: Glue, n: i32, f: i32, l: ValLevel, err: &mut bool) -> Glue {
1546        let max = if l == ValLevel::Int {
1547            arith::INFINITY
1548        } else {
1549            MAX_DIMEN
1550        };
1551        let mut s = |x: i32| {
1552            arith::fract(x, n, f, max).unwrap_or_else(|_| {
1553                *err = true;
1554                0
1555            })
1556        };
1557        if l <= ValLevel::Dimen {
1558            return Glue::fixed(s(t.width));
1559        }
1560        let mut g = t;
1561        g.width = s(g.width);
1562        g.stretch = s(g.stretch);
1563        g.shrink = s(g.shrink);
1564        normalize_glue(g)
1565    }
1566
1567    // ----- token lists -------------------------------------------------------
1568
1569    /// Part of [`scan_toks`](Self::scan_toks): appends to `out` the tokens
1570    /// of the current token list that `scan_toks` would copy unchanged
1571    /// (characters other than `#`, `&` and parameters; braces, counted in
1572    /// `unbalance`; control sequences that are not expandable, or protected
1573    /// macros, when expanding; any but outer ones when not), and stops at
1574    /// the first other token, which the general path handles. Returns the
1575    /// closing brace if the list ended the body.
1576    fn copy_plain_tokens(
1577        &mut self,
1578        out: &mut Vec<Token>,
1579        unbalance: &mut i32,
1580        xpand: bool,
1581    ) -> Option<Token> {
1582        let Some(crate::input::Level::Tokens(t)) = self.input.stack.last_mut() else {
1583            return None;
1584        };
1585        while let Some(&tok) = t.list.get(t.loc) {
1586            if tok.is_cs() {
1587                let m = self.eqtb.meaning(tok.cs_id());
1588                let plain = if m.cmd <= Cmd::MAX_COMMAND {
1589                    !(Cmd::TabMark..=Cmd::CarRet).contains(&m.cmd)
1590                } else if m.cmd.is_outer_or_special() {
1591                    false
1592                } else if xpand {
1593                    m.cmd >= Cmd::Call && m.mac.as_ref().is_some_and(|m| m.protected)
1594                } else {
1595                    true
1596                };
1597                if !plain {
1598                    return None;
1599                }
1600            } else {
1601                match tok.cmd() {
1602                    Cmd::LeftBrace => {
1603                        self.input.align_state += 1;
1604                        *unbalance += 1;
1605                    }
1606                    Cmd::RightBrace => {
1607                        self.input.align_state -= 1;
1608                        *unbalance -= 1;
1609                        if *unbalance == 0 {
1610                            t.loc += 1;
1611                            return Some(tok);
1612                        }
1613                    }
1614                    Cmd::TabMark | Cmd::CarRet | Cmd::MacParam => return None,
1615                    _ => {}
1616                }
1617            }
1618            out.push(tok);
1619            t.loc += 1;
1620        }
1621        None
1622    }
1623
1624    /// Scans a balanced text (`scan_toks`). For macro definitions
1625    /// (`macro_def`), the parameter text is scanned first and the result
1626    /// contains match tokens, `END_MATCH` and the body. With `xpand`, the
1627    /// text is expanded as in `\edef`. Returns the list and whether the
1628    /// definition text used `#` correctly.
1629    pub fn scan_toks(&mut self, macro_def: bool, xpand: bool) -> Vec<Token> {
1630        if macro_def {
1631            self.input.scanner_status = ScannerStatus::Defining;
1632        } else {
1633            self.input.scanner_status = ScannerStatus::Absorbing;
1634        }
1635        self.input.warning_index = self.cur_cs;
1636        let mut out: Vec<Token> = Vec::new();
1637        // The tokens scanned so far are shown if the text runs away; they
1638        // stay in `runaway_list` while more input is read.
1639        let saved_runaway = std::mem::take(&mut self.runaway_list);
1640        macro_rules! runaway {
1641            ($e:expr) => {{
1642                self.runaway_list = std::mem::take(&mut out);
1643                let r = $e;
1644                out = std::mem::take(&mut self.runaway_list);
1645                r
1646            }};
1647        }
1648        let mut unbalance: i32;
1649        let mut t: u32 = 0x30; // zero_token: next parameter number
1650        let mut hash_brace = Token(0);
1651        'found: {
1652            if macro_def {
1653                // Scan and build the parameter part; `#{` ends it early
1654                // (§476) and the body follows.
1655                'params: loop {
1656                    runaway!(self.get_token());
1657                    if self.cur_tok.0 < Token::RIGHT_BRACE.0 & !0x1F_FFFF | 0x1F_FFFF
1658                        && !self.cur_tok.is_cs()
1659                        && self.cur_cmd <= Cmd::RightBrace
1660                    {
1661                        break; // left or right brace
1662                    }
1663                    if self.cur_cmd == Cmd::MacParam && !self.cur_tok.is_cs() {
1664                        // A parameter, or #{.
1665                        let s = Token::match_token(self.cur_chr);
1666                        runaway!(self.get_token());
1667                        if self.cur_cmd == Cmd::LeftBrace && !self.cur_tok.is_cs() {
1668                            hash_brace = self.cur_tok;
1669                            out.push(self.cur_tok);
1670                            break 'params;
1671                        }
1672                        if t == 0x39 {
1673                            self.print_err("You already have nine parameters");
1674                            self.help(&["I'm going to ignore the # sign you just used."]);
1675                            self.error();
1676                        } else {
1677                            t += 1;
1678                            if self.cur_tok != Token::other(t) {
1679                                self.print_err("Parameters must be numbered consecutively");
1680                                self.help(&[
1681                                    "I've inserted the digit you should have used after the #.",
1682                                    "Type `1' to delete what you did use.",
1683                                ]);
1684                                self.back_error();
1685                            }
1686                            self.cur_tok = s;
1687                        }
1688                    }
1689                    out.push(self.cur_tok);
1690                }
1691                out.push(Token::END_MATCH);
1692                if hash_brace.0 == 0 && self.cur_cmd == Cmd::RightBrace {
1693                    self.print_err("Missing { inserted");
1694                    self.input.align_state += 1;
1695                    self.help(&[
1696                        "Where was the left brace? You said something like `\\def\\a}',",
1697                        "which I'm going to interpret as `\\def\\a{}'.",
1698                    ]);
1699                    self.error();
1700                    break 'found;
1701                }
1702            } else {
1703                self.scan_left_brace();
1704            }
1705            // Scan and build the body.
1706            unbalance = 1;
1707            loop {
1708                // Fast path: copy the plain tokens of the current token list
1709                // directly (most of the body of an \edef that re-expands a
1710                // long macro).
1711                if let Some(end_brace) = self.copy_plain_tokens(&mut out, &mut unbalance, xpand) {
1712                    self.cur_tok = end_brace;
1713                    self.cur_cmd = Cmd::RightBrace;
1714                    self.cur_chr = end_brace.chr();
1715                    self.cur_cs = NO_CS;
1716                    break 'found;
1717                }
1718                if xpand {
1719                    // Expand the next part of the input.
1720                    loop {
1721                        runaway!(self.get_next());
1722                        if self.cur_cmd >= Cmd::Call
1723                            && self
1724                                .eqtb
1725                                .meaning(self.cur_cs)
1726                                .mac
1727                                .as_ref()
1728                                .is_some_and(|m| m.protected)
1729                        {
1730                            self.cur_cmd = Cmd::Relax;
1731                            self.cur_chr = crate::input::NO_EXPAND_FLAG;
1732                            break;
1733                        }
1734                        if self.cur_cmd <= Cmd::MAX_COMMAND {
1735                            break;
1736                        }
1737                        if self.cur_cmd != Cmd::The {
1738                            runaway!(self.expand());
1739                        } else {
1740                            let list = runaway!(self.the_toks());
1741                            out.extend_from_slice(&list);
1742                        }
1743                    }
1744                    runaway!(self.x_token());
1745                } else {
1746                    runaway!(self.get_token());
1747                }
1748                if self.cur_tok.is_cs()
1749                    || self.cur_cmd > Cmd::MacParam
1750                    || self.cur_cmd < Cmd::LeftBrace
1751                {
1752                    // not a brace or parameter
1753                } else if self.cur_cmd < Cmd::RightBrace {
1754                    unbalance += 1;
1755                } else if self.cur_cmd == Cmd::RightBrace {
1756                    unbalance -= 1;
1757                    if unbalance == 0 {
1758                        break 'found;
1759                    }
1760                } else if self.cur_cmd == Cmd::MacParam && macro_def {
1761                    // Look for parameter number or ##.
1762                    let s = self.cur_tok;
1763                    if xpand {
1764                        runaway!(self.get_x_token());
1765                    } else {
1766                        runaway!(self.get_token());
1767                    }
1768                    if self.cur_cmd != Cmd::MacParam || self.cur_tok.is_cs() {
1769                        let c = self.cur_tok;
1770                        if c.is_cs() || c.cmd() != Cmd::OtherChar || c.chr() <= 0x30 || c.chr() > t
1771                        {
1772                            self.print_err("Illegal parameter number in definition of ");
1773                            self.sprint_cs(self.input.warning_index);
1774                            self.help(&[
1775                                "You meant to type ## instead of #, right?",
1776                                "Or maybe a } was forgotten somewhere earlier, and things",
1777                                "are all screwed up? I'm going to assume that you meant ##.",
1778                            ]);
1779                            self.back_error();
1780                            self.cur_tok = s;
1781                        } else {
1782                            self.cur_tok = Token::out_param(c.chr() - 0x30);
1783                        }
1784                    }
1785                }
1786                out.push(self.cur_tok);
1787            }
1788        }
1789        self.input.scanner_status = ScannerStatus::Normal;
1790        self.runaway_list = saved_runaway;
1791        if hash_brace.0 != 0 {
1792            out.push(hash_brace);
1793        }
1794        out
1795    }
1796}
1797
1798/// Offset used to map direction parameter modifiers (unused placeholder).
1799const DIR_PAR_BASE: u32 = 0;
1800
1801/// `set_aux` modifier for `\prevdepth`.
1802pub const SET_AUX_PREV_DEPTH: u32 = 1;
1803/// `set_aux` modifier for `\spacefactor`.
1804pub const SET_AUX_SPACE_FACTOR: u32 = 134;
1805
1806/// Sets an infinite component's order to normal when the component is
1807/// zero (`normalize_glue`).
1808pub fn normalize_glue(mut g: Glue) -> Glue {
1809    if g.stretch == 0 {
1810        g.stretch_order = glue::NORMAL;
1811    }
1812    if g.shrink == 0 {
1813        g.shrink_order = glue::NORMAL;
1814    }
1815    g
1816}
1817
1818/// Avoids an unused-import warning when `Loc` is not needed here.
1819#[allow(dead_code)]
1820fn _loc(_: Loc) {}