Skip to main content

retex_engine/
prefixed.rs

1//! Assignments (`prefixed_command`, *TeX: The Program* §1211–§1280, with
2//! e-TeX and LuaTeX additions).
3
4use std::rc::Rc;
5
6use retex_arith as arith;
7
8use crate::Engine;
9use crate::cmd::Cmd;
10use crate::eqtb::{CodeKind, Loc, MU_GLUE_OFFSET, Meaning, Val};
11use crate::expand::RELAX_CHR;
12use crate::glue::Glue;
13use crate::hash::{CsId, NO_CS};
14use crate::primitives_gen as pg;
15use crate::scan::{REG_OFFSET, ValLevel, reg};
16use crate::token::{Macro, Token, TokenList};
17
18/// `prefix` modifiers.
19pub mod prefix {
20    /// `\long`.
21    pub const LONG: u32 = 1;
22    /// `\outer`.
23    pub const OUTER: u32 = 2;
24    /// `\global`.
25    pub const GLOBAL: u32 = 4;
26    /// `\protected`.
27    pub const PROTECTED: u32 = 8;
28}
29
30/// `shorthand_def` modifiers (LuaTeX numbering).
31#[allow(missing_docs)]
32pub mod shorthand {
33    pub const CHAR_DEF: u32 = 0;
34    pub const MATH_CHAR_DEF: u32 = 1;
35    pub const UMATH_CHAR_DEF: u32 = 2;
36    pub const COUNT_DEF: u32 = 3;
37    pub const ATTRIBUTE_DEF: u32 = 4;
38    pub const DIMEN_DEF: u32 = 5;
39    pub const SKIP_DEF: u32 = 6;
40    pub const MU_SKIP_DEF: u32 = 7;
41    pub const TOKS_DEF: u32 = 8;
42    pub const UMATH_CHAR_NUM_DEF: u32 = 9;
43}
44
45impl Engine {
46    /// Gets a redefinable control sequence (`get_r_token`).
47    pub fn get_r_token(&mut self) {
48        loop {
49            loop {
50                self.get_token();
51                if self.cur_tok != Token::SPACE {
52                    break;
53                }
54            }
55            let frozen = self.cur_cs != NO_CS
56                && matches!(self.hash.kind(self.cur_cs), crate::hash::CsKind::Frozen);
57            if self.cur_cs == NO_CS || frozen {
58                self.print_err("Missing control sequence inserted");
59                self.help(&[
60                    "Please don't say `\\def cs{...}', say `\\def\\cs{...}'.",
61                    "I've inserted an inaccessible control sequence so that your",
62                    "definition will be completed without mixing me up too badly.",
63                    "You can recover graciously from this error, if you're",
64                    "careful; see exercise 27.2 in The TeXbook.",
65                ]);
66                if self.cur_cs == NO_CS {
67                    self.back_input();
68                }
69                self.cur_tok = Token::cs(self.cs.frozen_protection);
70                self.ins_error();
71                continue;
72            }
73            return;
74        }
75    }
76
77    /// Defines control sequence `p` with `meaning`.
78    fn define_cs(&mut self, global: bool, p: CsId, meaning: Meaning) {
79        self.define(global, Loc::Cs(p), Val::Meaning(meaning));
80    }
81
82    /// Performs an assignment, possibly prefixed (`prefixed_command`).
83    pub fn prefixed_command(&mut self) {
84        // Fast path: the most common assignments, without prefixes and
85        // with `\globaldefs` zero, skip the general machinery.
86        if self.cur_cmd != Cmd::Prefix && self.eqtb.int(pg::assign_int::GLOBALDEFS) == 0 {
87            match self.cur_cmd {
88                Cmd::Let => self.let_command(false),
89                Cmd::DefCharCode => self.def_char_code(false),
90                Cmd::Register | Cmd::Advance | Cmd::Multiply | Cmd::Divide => {
91                    self.do_register_command(false)
92                }
93                Cmd::AssignInt => self.assign_int_command(false),
94                _ => return self.prefixed_command_general(),
95            }
96            if self.after_token.0 != 0 {
97                self.cur_tok = self.after_token;
98                self.back_input();
99                self.after_token = Token(0);
100            }
101            return;
102        }
103        self.prefixed_command_general();
104    }
105
106    /// `\count`-like and integer parameter assignments.
107    fn assign_int_command(&mut self, global: bool) {
108        let p = self.cur_chr;
109        self.scan_optional_equals();
110        self.scan_int();
111        let loc = if p >= REG_OFFSET {
112            Loc::Count(p - REG_OFFSET)
113        } else {
114            Loc::IntPar(p)
115        };
116        self.assign_int_param(global, loc, self.cur_val);
117    }
118
119    /// [`prefixed_command`](Self::prefixed_command) in general.
120    #[inline(never)]
121    fn prefixed_command_general(&mut self) {
122        let mut a: u32 = 0;
123        while self.cur_cmd == Cmd::Prefix {
124            if a & self.cur_chr == 0 {
125                a += self.cur_chr;
126            }
127            self.get_nonblank_nonrelax_noncall();
128            if self.cur_cmd <= Cmd::MAX_NON_PREFIXED {
129                self.print_err("You can't use a prefix with `");
130                self.print_cmd_chr(self.cur_cmd, self.cur_chr);
131                self.printer.print_char('\'' as u32);
132                self.help(&[
133                    "I'll pretend you didn't say \\long or \\outer or \\global or",
134                    "\\protected.",
135                ]);
136                self.back_error();
137                return;
138            }
139            if self.eqtb.int(pg::assign_int::TRACINGCOMMANDS) > 2 {
140                self.show_cur_cmd_chr();
141            }
142        }
143        // Discard \long, \outer and \protected if irrelevant.
144        if self.cur_cmd != Cmd::Def
145            && self.cur_cmd != Cmd::DefLuaCall
146            && (a & (prefix::LONG | prefix::OUTER | prefix::PROTECTED)) != 0
147            && self.eqtb.int(pg::assign_int::SUPPRESSLONGERROR) <= 0
148        {
149            self.print_err("You can't use `");
150            self.print_esc_str("long");
151            self.printer.print_str("' or `");
152            self.print_esc_str("outer");
153            self.help(&["I'll pretend you didn't say \\long or \\outer or \\protected here."]);
154            self.printer.print_str("' or `");
155            self.print_esc_str("protected");
156            self.printer.print_str("' with `");
157            self.print_cmd_chr(self.cur_cmd, self.cur_chr);
158            self.printer.print_char('\'' as u32);
159            self.error();
160        }
161        // Adjust for \globaldefs.
162        let global_defs = self.eqtb.int(pg::assign_int::GLOBALDEFS);
163        if global_defs != 0 {
164            if global_defs < 0 {
165                a &= !prefix::GLOBAL;
166            } else {
167                a |= prefix::GLOBAL;
168            }
169        }
170        let global = a & prefix::GLOBAL != 0;
171        match self.cur_cmd {
172            Cmd::SetFont => {
173                let f = self.cur_chr;
174                self.define(global, Loc::CurFont, Val::Int(i64::from(f)));
175            }
176            Cmd::Def => {
177                let mut a = a;
178                if self.cur_chr & 1 != 0 && !global && global_defs >= 0 {
179                    a |= prefix::GLOBAL;
180                }
181                let e = self.cur_chr >= 2;
182                self.get_r_token();
183                let p = self.cur_cs;
184                self.def_ref_cs = p;
185                let toks = self.scan_toks(true, e);
186                self.def_ref_cs = NO_CS;
187                let cmd = Cmd::from_u8(Cmd::Call as u8 + (a & 3) as u8);
188                let mac = Macro {
189                    protected: a & prefix::PROTECTED != 0,
190                    tokens: Rc::from(toks),
191                };
192                self.define_cs(
193                    a & prefix::GLOBAL != 0,
194                    p,
195                    Meaning {
196                        cmd,
197                        chr: 0,
198                        mac: Some(Rc::new(mac)),
199                    },
200                );
201            }
202            Cmd::Let => self.let_command(global),
203            Cmd::ShorthandDef => self.shorthand_def(global),
204            Cmd::ReadToCs => {
205                let j = self.cur_chr;
206                self.scan_int();
207                let n = self.cur_val;
208                if !self.scan_keyword("to") {
209                    self.print_err("Missing `to' inserted");
210                    self.help(&[
211                        "You should have said `\\read<number> to \\cs'.",
212                        "I'm going to look for the \\cs now.",
213                    ]);
214                    self.error();
215                }
216                self.get_r_token();
217                let p = self.cur_cs;
218                let toks = self.read_toks(n, p, j);
219                self.define_cs(
220                    global,
221                    p,
222                    Meaning {
223                        cmd: Cmd::Call,
224                        chr: 0,
225                        mac: Some(Rc::new(Macro {
226                            protected: false,
227                            tokens: Rc::from(toks),
228                        })),
229                    },
230                );
231            }
232            Cmd::ToksRegister | Cmd::AssignToks => self.assign_toks(global),
233            Cmd::Combinetoks => self.combine_toks(),
234            Cmd::AssignInt => self.assign_int_command(global),
235            Cmd::AssignAttr => {
236                let p = self.cur_chr - REG_OFFSET.min(self.cur_chr);
237                self.scan_optional_equals();
238                self.scan_int();
239                self.define(global, Loc::Attr(p), Val::Int(i64::from(self.cur_val)));
240            }
241            Cmd::AssignDir | Cmd::AssignDirection => {
242                let p = self.cur_chr;
243                let by_name = self.cur_cmd == Cmd::AssignDir;
244                self.scan_optional_equals();
245                let d = if by_name {
246                    self.scan_direction_spec()
247                } else {
248                    self.scan_int();
249                    self.cur_val
250                };
251                self.assign_dir_param(global, p, d);
252                if p == crate::primitives_gen::assign_dir::TEXTDIR {
253                    self.text_dir_assigned(d);
254                }
255            }
256            Cmd::AssignDimen => {
257                let p = self.cur_chr;
258                self.scan_optional_equals();
259                self.scan_normal_dimen();
260                let loc = if p >= REG_OFFSET {
261                    Loc::Dimen(p - REG_OFFSET)
262                } else {
263                    Loc::DimenPar(p)
264                };
265                self.define(global, loc, Val::Int(i64::from(self.cur_val)));
266            }
267            Cmd::AssignGlue | Cmd::AssignMuGlue => {
268                let p = self.cur_chr;
269                let mu = self.cur_cmd == Cmd::AssignMuGlue;
270                self.scan_optional_equals();
271                self.scan_glue(if mu { ValLevel::Mu } else { ValLevel::Glue });
272                let g = self.cur_glue;
273                let loc = match (mu, p >= REG_OFFSET) {
274                    (false, true) => Loc::Skip(p - REG_OFFSET),
275                    (true, true) => Loc::MuSkip(p - REG_OFFSET),
276                    (false, false) => Loc::GluePar(p),
277                    (true, false) => Loc::GluePar(p + MU_GLUE_OFFSET),
278                };
279                self.define(global, loc, Val::Glue(g));
280            }
281            Cmd::DefCharCode => self.def_char_code(global),
282            Cmd::DefDelCode => {
283                self.scan_char_num();
284                let c = self.cur_val as u32;
285                self.scan_optional_equals();
286                self.scan_int();
287                let v = self.cur_val;
288                if v > 0o77777777 {
289                    self.invalid_code(v, 0o77777777, false);
290                }
291                let code = crate::codes::delcode_from_classic(self.cur_val);
292                self.define(global, Loc::Code(CodeKind::Del, c), Val::Int(code));
293            }
294            Cmd::ExtdefMathCode => {
295                let num = self.cur_chr != pg::extdef_math_code::UMATHCODE;
296                self.scan_char_num();
297                let c = self.cur_val as u32;
298                self.scan_optional_equals();
299                let code = if num {
300                    self.scan_int();
301                    i64::from(self.cur_val)
302                } else {
303                    self.scan_umath_triple()
304                };
305                self.define(global, Loc::Code(CodeKind::Math, c), Val::Int(code));
306            }
307            Cmd::ExtdefDelCode => {
308                let num = self.cur_chr != pg::extdef_del_code::UDELCODE;
309                self.scan_char_num();
310                let c = self.cur_val as u32;
311                self.scan_optional_equals();
312                let code = if num {
313                    self.scan_int();
314                    i64::from(self.cur_val)
315                } else {
316                    self.scan_math_family();
317                    let fam = self.cur_val as i64;
318                    self.scan_char_num();
319                    (fam << 21) + i64::from(self.cur_val)
320                };
321                self.define(global, Loc::Code(CodeKind::Del, c), Val::Int(code));
322            }
323            Cmd::DefFamily => {
324                let size = self.cur_chr;
325                self.scan_math_family();
326                let fam = self.cur_val as u32;
327                self.scan_optional_equals();
328                self.scan_font_ident();
329                let f = self.cur_val;
330                self.define(
331                    global,
332                    Loc::MathFont(size * crate::eqtb::NUM_FAMILIES as u32 + fam),
333                    Val::Int(i64::from(f)),
334                );
335                self.set_font_math_params(global, size, fam, f as u32);
336            }
337            Cmd::SetMathParam => {
338                let p = self.cur_chr;
339                let _ = self.scan_math_style();
340                let style = self.cur_val as u32;
341                self.scan_optional_equals();
342                let v = if crate::codes::math_param_is_glue(p) {
343                    self.scan_glue(ValLevel::Mu);
344                    self.store_math_param_glue(self.cur_glue)
345                } else if p == crate::mathparams::mp::RADICAL_DEGREE_RAISE {
346                    self.scan_int();
347                    self.cur_val
348                } else {
349                    self.scan_normal_dimen();
350                    self.cur_val
351                };
352                self.define(
353                    global,
354                    Loc::MathParam(p * 8 + style),
355                    Val::Int(i64::from(v)),
356                );
357            }
358            Cmd::Register | Cmd::Advance | Cmd::Multiply | Cmd::Divide => {
359                self.do_register_command(global)
360            }
361            Cmd::SetBox => {
362                self.scan_register_num();
363                let n = self.cur_val as u32;
364                let ctx = if global {
365                    crate::maincontrol::BoxContext::GlobalSetBox(n)
366                } else {
367                    crate::maincontrol::BoxContext::SetBox(n)
368                };
369                self.scan_optional_equals();
370                if self.err.set_box_allowed {
371                    self.scan_box(ctx);
372                } else {
373                    self.print_err("Improper ");
374                    self.print_esc_str("setbox");
375                    self.help(&[
376                        "Sorry, \\setbox is not allowed after \\halign in a display,",
377                        "or between \\accent and an accented character.",
378                    ]);
379                    self.error();
380                }
381            }
382            Cmd::SetAux => self.alter_aux(),
383            Cmd::SetPrevGraf => self.alter_prev_graf(),
384            Cmd::SetPageDimen => self.alter_page_so_far(),
385            Cmd::SetPageInt => self.alter_integer(),
386            Cmd::SetBoxDimen => self.alter_box_dimen(),
387            Cmd::SetTexShape => {
388                self.scan_optional_equals();
389                self.scan_int();
390                let n = self.cur_val;
391                let shape = if n <= 0 {
392                    None
393                } else {
394                    let mut v = Vec::with_capacity(2 * n as usize);
395                    for _ in 0..n {
396                        self.scan_normal_dimen();
397                        v.push(self.cur_val);
398                        self.scan_normal_dimen();
399                        v.push(self.cur_val);
400                    }
401                    Some(Rc::from(v))
402                };
403                self.define(global, Loc::ParShape, Val::Shape(shape));
404            }
405            Cmd::SetEtexShape => {
406                let q = self.cur_chr;
407                self.scan_optional_equals();
408                self.scan_int();
409                let n = self.cur_val;
410                let shape = if n <= 0 {
411                    None
412                } else {
413                    let mut v = Vec::with_capacity(n as usize);
414                    for _ in 0..n {
415                        self.scan_int();
416                        v.push(self.cur_val);
417                    }
418                    Some(Rc::from(v))
419                };
420                self.define(global, Loc::EtexShape(q), Val::Shape(shape));
421            }
422            Cmd::HyphData => self.hyph_data(global),
423            Cmd::AssignFontDimen => {
424                self.find_font_dimen(true);
425                let loc = self.font_dimen_loc;
426                self.scan_optional_equals();
427                self.scan_normal_dimen();
428                self.set_font_dimen(loc, self.cur_val);
429            }
430            Cmd::AssignFontInt => {
431                let n = self.cur_chr;
432                self.scan_font_ident();
433                let f = self.cur_val as u32;
434                if (2..=5).contains(&n) {
435                    // \lpcode, \rpcode, \efcode, \tagcode take a character.
436                    self.scan_char_num();
437                    let c = self.cur_val as u32;
438                    self.scan_optional_equals();
439                    self.scan_int();
440                    self.set_font_char_code(f, n, c, self.cur_val);
441                } else {
442                    self.scan_optional_equals();
443                    self.scan_int();
444                    self.set_font_int_param(f, n, self.cur_val);
445                }
446            }
447            Cmd::DefFont => self.new_font(global),
448            Cmd::LetterspaceFont => self.letterspace_font(global),
449            Cmd::CopyFont => self.copy_font(global),
450            Cmd::SetFontId => {
451                self.scan_int();
452                let f = self.cur_val;
453                if self.font_exists(f) {
454                    self.define(global, Loc::CurFont, Val::Int(i64::from(f)));
455                }
456            }
457            Cmd::SetInteraction => self.new_interaction(),
458            Cmd::DefLuaCall => self.def_lua_call(global, a & prefix::PROTECTED != 0),
459            Cmd::AssignBoxDir | Cmd::AssignBoxDirection => {
460                self.scan_register_num();
461                let n = self.cur_val as u32;
462                self.scan_optional_equals();
463                let d = if self.cur_cmd == Cmd::AssignBoxDir {
464                    self.scan_direction_spec()
465                } else {
466                    self.scan_int();
467                    self.cur_val
468                };
469                self.set_box_dir(n, d);
470            }
471            _ => self.confusion("prefix"),
472        }
473        // Insert a token saved by \afterassignment, if any.
474        if self.after_token.0 != 0 {
475            self.cur_tok = self.after_token;
476            self.back_input();
477            self.after_token = Token(0);
478        }
479    }
480
481    fn assign_int_param(&mut self, global: bool, loc: Loc, v: i32) {
482        self.define(global, loc, Val::Int(i64::from(v)));
483        if let Loc::IntPar(i) = loc {
484            self.int_param_changed(i);
485        }
486    }
487
488    /// Hooks for integer parameters with side effects (`\newlinechar`, …).
489    pub fn int_param_changed(&mut self, i: u32) {
490        if i == pg::assign_int::NEWLINECHAR {
491            self.printer.new_line_char = self.eqtb.int(i);
492        }
493    }
494
495    /// `\let`, `\glet`, `\futurelet`, `\letcharcode`.
496    fn let_command(&mut self, global: bool) {
497        let n = self.cur_chr;
498        let mut global = global;
499        if n == 3 {
500            // \letcharcode <char> = <cs>: defines the active character.
501            self.scan_int();
502            let c = self.cur_val;
503            if !(1..=0x10_FFFF).contains(&c) {
504                self.print_err("invalid number for \\letcharcode");
505                self.help(&[]);
506                self.error();
507                return;
508            }
509            let p = self.hash.active(c as u32);
510            self.scan_optional_equals();
511            self.get_token();
512            let m = self.token_meaning();
513            self.define_cs(global, p, m);
514            return;
515        }
516        if n == 0 {
517            global = true; // \glet
518        }
519        self.get_r_token();
520        let p = self.cur_cs;
521        if n <= 1 {
522            loop {
523                self.get_token();
524                if self.cur_cmd != Cmd::Spacer {
525                    break;
526                }
527            }
528            if self.cur_tok == Token::other('=' as u32) {
529                self.get_token();
530                if self.cur_cmd == Cmd::Spacer {
531                    self.get_token();
532                }
533            }
534        } else {
535            self.get_token();
536            let q = self.cur_tok;
537            self.get_token();
538            self.back_input();
539            self.cur_tok = q;
540            self.back_input(); // back_input does not change cur_cmd/cur_chr
541        }
542        let m = self.token_meaning();
543        self.define_cs(global, p, m);
544    }
545
546    /// The meaning of the current token, for `\let`.
547    fn token_meaning(&self) -> Meaning {
548        if self.cur_cs != NO_CS {
549            let mut m = self.eqtb.meaning(self.cur_cs).clone();
550            // `cur_cmd`/`cur_chr` may differ from the eqtb entry for
551            // \noexpand'ed tokens; follow TeX and use them.
552            if m.cmd != self.cur_cmd {
553                m = Meaning::prim(self.cur_cmd, self.cur_chr);
554            }
555            m
556        } else {
557            Meaning::prim(self.cur_cmd, self.cur_chr)
558        }
559    }
560
561    /// `\chardef` and friends.
562    fn shorthand_def(&mut self, global: bool) {
563        let n = self.cur_chr;
564        self.get_r_token();
565        let p = self.cur_cs;
566        self.define_cs(global, p, Meaning::prim(Cmd::Relax, RELAX_CHR));
567        self.scan_optional_equals();
568        let m = match n {
569            shorthand::CHAR_DEF => {
570                self.scan_char_num();
571                Meaning::prim(Cmd::CharGiven, self.cur_val as u32)
572            }
573            shorthand::MATH_CHAR_DEF => {
574                // Kept in TeX's 15-bit form, as LuaTeX shows and reads it.
575                let g = self.scan_math_given();
576                Meaning::prim(Cmd::MathGiven, g)
577            }
578            shorthand::UMATH_CHAR_DEF => {
579                let code = self.scan_umath_triple();
580                Meaning::prim(Cmd::XmathGiven, code as u32)
581            }
582            shorthand::UMATH_CHAR_NUM_DEF => {
583                self.scan_int();
584                Meaning::prim(Cmd::XmathGiven, self.cur_val as u32)
585            }
586            _ => {
587                self.scan_register_num();
588                let r = self.cur_val as u32 + REG_OFFSET;
589                match n {
590                    shorthand::COUNT_DEF => Meaning::prim(Cmd::AssignInt, r),
591                    shorthand::ATTRIBUTE_DEF => Meaning::prim(Cmd::AssignAttr, r),
592                    shorthand::DIMEN_DEF => Meaning::prim(Cmd::AssignDimen, r),
593                    shorthand::SKIP_DEF => Meaning::prim(Cmd::AssignGlue, r),
594                    shorthand::MU_SKIP_DEF => Meaning::prim(Cmd::AssignMuGlue, r),
595                    _ => Meaning::prim(Cmd::AssignToks, r),
596                }
597            }
598        };
599        self.define_cs(global, p, m);
600    }
601
602    /// Scans `class family char` for `\Umathcode` and `\Umathchardef`.
603    pub(crate) fn scan_umath_triple(&mut self) -> i64 {
604        self.scan_int();
605        let class = self.cur_val;
606        self.scan_math_family();
607        let fam = self.cur_val;
608        self.scan_char_num();
609        let chr = self.cur_val;
610        if !(0..=7).contains(&class) {
611            self.print_err("Invalid math code");
612            self.help(&["I'm going to use 0 instead of that illegal code value."]);
613            self.error();
614            return 0;
615        }
616        crate::codes::math_code(class as u32, fam as u32, chr as u32)
617    }
618
619    /// `\toks`, `\everypar` and other token-list assignments.
620    fn assign_toks(&mut self, global: bool) {
621        let q = self.cur_cs;
622        let loc = if self.cur_cmd == Cmd::ToksRegister {
623            self.scan_register_num();
624            Loc::Toks(self.cur_val as u32)
625        } else if self.cur_chr >= REG_OFFSET {
626            Loc::Toks(self.cur_chr - REG_OFFSET)
627        } else {
628            Loc::ToksPar(self.cur_chr)
629        };
630        self.scan_optional_equals();
631        self.get_nonblank_nonrelax_noncall();
632        if self.cur_cmd != Cmd::LeftBrace {
633            // The right-hand side may be a token parameter or register.
634            let src = if self.cur_cmd == Cmd::ToksRegister {
635                self.scan_register_num();
636                Some(self.eqtb.toks[self.cur_val as usize].val.clone())
637            } else if self.cur_cmd == Cmd::AssignToks {
638                Some(if self.cur_chr >= REG_OFFSET {
639                    self.eqtb.toks[(self.cur_chr - REG_OFFSET) as usize]
640                        .val
641                        .clone()
642                } else {
643                    self.eqtb.toks_param(self.cur_chr).clone()
644                })
645            } else {
646                None
647            };
648            if let Some(list) = src {
649                self.define(global, loc, Val::Toks(list));
650                return;
651            }
652        }
653        self.back_input();
654        self.cur_cs = q;
655        self.def_ref_cs = q;
656        let mut toks = self.scan_toks(false, false);
657        self.def_ref_cs = NO_CS;
658        if !toks.is_empty() && loc == Loc::ToksPar(pg::assign_toks::OUTPUT) {
659            toks.insert(0, Token::LEFT_BRACE);
660            toks.push(Token::RIGHT_BRACE);
661        }
662        let list: TokenList = if toks.is_empty() {
663            crate::token::empty_list()
664        } else {
665            Rc::from(toks)
666        };
667        self.define(global, loc, Val::Toks(list));
668    }
669
670    /// LuaTeX's `\toksapp`, `\etoksapp`, `\tokspre`, … (`combinetoks`).
671    fn combine_toks(&mut self) {
672        let code = self.cur_chr;
673        let global = code >= 4;
674        let expand = code % 2 == 1;
675        let append = code % 4 < 2;
676        // The target: a register number or a token register control
677        // sequence.
678        self.get_nonblank_nonrelax_noncall();
679        let loc = if self.cur_cmd == Cmd::AssignToks {
680            if self.cur_chr >= REG_OFFSET {
681                Loc::Toks(self.cur_chr - REG_OFFSET)
682            } else {
683                Loc::ToksPar(self.cur_chr)
684            }
685        } else if self.cur_cmd == Cmd::ToksRegister {
686            self.scan_register_num();
687            Loc::Toks(self.cur_val as u32)
688        } else {
689            self.back_input();
690            self.scan_register_num();
691            Loc::Toks(self.cur_val as u32)
692        };
693        let new = if expand {
694            self.scan_toks_expanded()
695        } else {
696            self.scan_general_text().to_vec()
697        };
698        let old = match self.eqtb.get(loc) {
699            Val::Toks(t) => t,
700            _ => crate::token::empty_list(),
701        };
702        let mut v: Vec<Token> = Vec::with_capacity(old.len() + new.len());
703        if append {
704            v.extend_from_slice(&old);
705            v.extend_from_slice(&new);
706        } else {
707            v.extend_from_slice(&new);
708            v.extend_from_slice(&old);
709        }
710        self.define(global, loc, Val::Toks(Rc::from(v)));
711    }
712
713    fn invalid_code(&mut self, v: i32, n: i32, range: bool) {
714        self.print_err("Invalid code (");
715        self.printer.print_int(v.into());
716        if range {
717            self.printer.print_str("), should be in the range 0..");
718        } else {
719            self.printer.print_str("), should be at most ");
720        }
721        self.printer.print_int(n.into());
722        self.help(&["I'm going to use 0 instead of that illegal code value."]);
723        self.error();
724        self.cur_val = 0;
725    }
726
727    /// `\catcode`, `\lccode`, `\uccode`, `\sfcode`, `\mathcode`.
728    fn def_char_code(&mut self, global: bool) {
729        use pg::def_char_code as dc;
730        let which = self.cur_chr;
731        let n: i32 = match which {
732            dc::CATCODE => 15,
733            dc::MATHCODE => 0x8000,
734            dc::SFCODE => 0o77777,
735            _ => 0x10_FFFF,
736        };
737        self.scan_char_num();
738        let c = self.cur_val as u32;
739        self.scan_optional_equals();
740        self.scan_int();
741        if self.cur_val < 0 || self.cur_val > n {
742            self.invalid_code(self.cur_val, n, true);
743        }
744        let v = self.cur_val;
745        match which {
746            dc::CATCODE => {
747                let table = self.eqtb.cur_cat_table();
748                self.define(global, Loc::Cat { table, ch: c }, Val::Int(i64::from(v)));
749            }
750            dc::LCCODE => self.define(global, Loc::Code(CodeKind::Lc, c), Val::Int(v.into())),
751            dc::UCCODE => self.define(global, Loc::Code(CodeKind::Uc, c), Val::Int(v.into())),
752            dc::SFCODE => self.define(global, Loc::Code(CodeKind::Sf, c), Val::Int(v.into())),
753            _ => {
754                let code = if v == 0x8000 {
755                    crate::codes::MATH_ACTIVE
756                } else {
757                    let v = v as u32;
758                    crate::codes::math_code(v >> 12, (v >> 8) & 0xF, v & 0xFF)
759                };
760                self.define(global, Loc::Code(CodeKind::Math, c), Val::Int(code));
761            }
762        }
763    }
764
765    /// `\count`, `\advance`, `\multiply`, `\divide` (`do_register_command`).
766    fn do_register_command(&mut self, global: bool) {
767        let q = self.cur_cmd;
768        // Compute the register location and its type.
769        let (loc, p): (Loc, ValLevel) = 'found: {
770            if q != Cmd::Register {
771                self.get_x_token();
772                let r = self.cur_chr;
773                let l = match self.cur_cmd {
774                    Cmd::AssignInt => Some((
775                        if r >= REG_OFFSET {
776                            Loc::Count(r - REG_OFFSET)
777                        } else {
778                            Loc::IntPar(r)
779                        },
780                        ValLevel::Int,
781                    )),
782                    Cmd::AssignAttr => Some((Loc::Attr(r - REG_OFFSET.min(r)), ValLevel::Int)),
783                    Cmd::AssignDimen => Some((
784                        if r >= REG_OFFSET {
785                            Loc::Dimen(r - REG_OFFSET)
786                        } else {
787                            Loc::DimenPar(r)
788                        },
789                        ValLevel::Dimen,
790                    )),
791                    Cmd::AssignGlue => Some((
792                        if r >= REG_OFFSET {
793                            Loc::Skip(r - REG_OFFSET)
794                        } else {
795                            Loc::GluePar(r)
796                        },
797                        ValLevel::Glue,
798                    )),
799                    Cmd::AssignMuGlue => Some((
800                        if r >= REG_OFFSET {
801                            Loc::MuSkip(r - REG_OFFSET)
802                        } else {
803                            Loc::GluePar(r + MU_GLUE_OFFSET)
804                        },
805                        ValLevel::Mu,
806                    )),
807                    _ => None,
808                };
809                if let Some(x) = l {
810                    break 'found x;
811                }
812                if self.cur_cmd != Cmd::Register {
813                    self.print_err("You can't use `");
814                    self.print_cmd_chr(self.cur_cmd, self.cur_chr);
815                    self.printer.print_str("' after ");
816                    self.print_cmd_chr(q, 0);
817                    self.help(&["I'm forgetting what you said and not changing anything."]);
818                    self.error();
819                    return;
820                }
821            }
822            let kind = self.cur_chr;
823            self.scan_register_num();
824            let n = self.cur_val as u32;
825            match kind {
826                reg::COUNT => (Loc::Count(n), ValLevel::Int),
827                reg::ATTRIBUTE => (Loc::Attr(n), ValLevel::Int),
828                reg::DIMEN => (Loc::Dimen(n), ValLevel::Dimen),
829                reg::SKIP => (Loc::Skip(n), ValLevel::Glue),
830                _ => (Loc::MuSkip(n), ValLevel::Mu),
831            }
832        };
833        if q == Cmd::Register {
834            self.scan_optional_equals();
835        } else {
836            let _ = self.scan_keyword("by");
837        }
838        let mut arith_error = false;
839        let old = self.eqtb.get(loc);
840        let result: Val = if q < Cmd::Multiply {
841            if p < ValLevel::Glue {
842                if p == ValLevel::Int {
843                    self.scan_int();
844                } else {
845                    self.scan_normal_dimen();
846                }
847                let mut v = self.cur_val;
848                if q == Cmd::Advance
849                    && let Val::Int(o) = old
850                {
851                    v = v.wrapping_add(o as i32);
852                }
853                Val::Int(i64::from(v))
854            } else {
855                self.scan_glue(p);
856                let mut g = self.cur_glue;
857                if q == Cmd::Advance
858                    && let Val::Glue(r) = old
859                {
860                    g = add_glue(g, r);
861                }
862                Val::Glue(g)
863            }
864        } else {
865            self.scan_int();
866            let f = self.cur_val;
867            let op = |x: i32, err: &mut bool| -> i32 {
868                let r = if q == Cmd::Multiply {
869                    if p == ValLevel::Int {
870                        arith::mult_integers(x, f)
871                    } else {
872                        arith::nx_plus_y(x, f, 0)
873                    }
874                } else {
875                    arith::x_over_n(x, f).map(|d| d.quotient)
876                };
877                r.unwrap_or_else(|_| {
878                    *err = true;
879                    0
880                })
881            };
882            match old {
883                Val::Int(x) => Val::Int(i64::from(op(x as i32, &mut arith_error))),
884                Val::Glue(g) => {
885                    let mut n = g;
886                    n.width = op(g.width, &mut arith_error);
887                    n.stretch = op(g.stretch, &mut arith_error);
888                    n.shrink = op(g.shrink, &mut arith_error);
889                    Val::Glue(n)
890                }
891                other => other,
892            }
893        };
894        if arith_error {
895            self.print_err("Arithmetic overflow");
896            self.help(&[
897                "I can't carry out that multiplication or division,",
898                "since the result is out of range.",
899            ]);
900            self.error();
901            return;
902        }
903        self.define(global, loc, result);
904    }
905
906    /// `\batchmode` etc. (`new_interaction`).
907    pub fn new_interaction(&mut self) {
908        self.printer.print_ln();
909        self.err.interaction = crate::error::Interaction::from_i32(self.cur_chr as i32);
910        self.printer.selector = match (self.err.interaction, self.printer.log_opened()) {
911            (crate::error::Interaction::Batch, true) => crate::print::Selector::LogOnly,
912            (crate::error::Interaction::Batch, false) => crate::print::Selector::NoPrint,
913            (_, true) => crate::print::Selector::TermAndLog,
914            (_, false) => crate::print::Selector::TermOnly,
915        };
916    }
917
918    /// `\deadcycles`, `\insertpenalties`, `\interactionmode`
919    /// (`alter_integer`).
920    fn alter_integer(&mut self) {
921        let c = self.cur_chr;
922        self.scan_optional_equals();
923        self.scan_int();
924        match c {
925            0 => self.dead_cycles = self.cur_val,
926            1 => self.insert_penalties = self.cur_val,
927            _ => {
928                if (0..=3).contains(&self.cur_val) {
929                    self.cur_chr = self.cur_val as u32;
930                    self.new_interaction();
931                } else {
932                    self.print_err("Bad interaction mode (");
933                    self.printer.print_int(self.cur_val.into());
934                    self.printer.print_char(')' as u32);
935                    self.help(&[
936                        "Modes are 0=batch, 1=nonstop, 2=scroll, and",
937                        "3=errorstop. Proceed, and I'll ignore this case.",
938                    ]);
939                    self.int_error(self.cur_val);
940                }
941            }
942        }
943    }
944}
945
946/// Adds glue `r` to `q` (TeX's "Compute the sum of two glue specs").
947pub fn add_glue(mut q: Glue, r: Glue) -> Glue {
948    q.width = q.width.wrapping_add(r.width);
949    if q.stretch == 0 {
950        q.stretch_order = crate::glue::NORMAL;
951    }
952    if q.stretch_order == r.stretch_order {
953        q.stretch = q.stretch.wrapping_add(r.stretch);
954    } else if q.stretch_order < r.stretch_order && r.stretch != 0 {
955        q.stretch = r.stretch;
956        q.stretch_order = r.stretch_order;
957    }
958    if q.shrink == 0 {
959        q.shrink_order = crate::glue::NORMAL;
960    }
961    if q.shrink_order == r.shrink_order {
962        q.shrink = q.shrink.wrapping_add(r.shrink);
963    } else if q.shrink_order < r.shrink_order && r.shrink != 0 {
964        q.shrink = r.shrink;
965        q.shrink_order = r.shrink_order;
966    }
967    q
968}