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        } else if i == pg::assign_int::SYNCTEX {
493            self.synctex_changed();
494        }
495    }
496
497    /// `\let`, `\glet`, `\futurelet`, `\letcharcode`.
498    fn let_command(&mut self, global: bool) {
499        let n = self.cur_chr;
500        let mut global = global;
501        if n == 3 {
502            // \letcharcode <char> = <cs>: defines the active character.
503            self.scan_int();
504            let c = self.cur_val;
505            if !(1..=0x10_FFFF).contains(&c) {
506                self.print_err("invalid number for \\letcharcode");
507                self.help(&[]);
508                self.error();
509                return;
510            }
511            let p = self.hash.active(c as u32);
512            self.scan_optional_equals();
513            self.get_token();
514            let m = self.token_meaning();
515            self.define_cs(global, p, m);
516            return;
517        }
518        if n == 0 {
519            global = true; // \glet
520        }
521        self.get_r_token();
522        let p = self.cur_cs;
523        if n <= 1 {
524            loop {
525                self.get_token();
526                if self.cur_cmd != Cmd::Spacer {
527                    break;
528                }
529            }
530            if self.cur_tok == Token::other('=' as u32) {
531                self.get_token();
532                if self.cur_cmd == Cmd::Spacer {
533                    self.get_token();
534                }
535            }
536        } else {
537            self.get_token();
538            let q = self.cur_tok;
539            self.get_token();
540            self.back_input();
541            self.cur_tok = q;
542            self.back_input(); // back_input does not change cur_cmd/cur_chr
543        }
544        let m = self.token_meaning();
545        self.define_cs(global, p, m);
546    }
547
548    /// The meaning of the current token, for `\let`.
549    fn token_meaning(&self) -> Meaning {
550        if self.cur_cs != NO_CS {
551            let mut m = self.eqtb.meaning(self.cur_cs).clone();
552            // `cur_cmd`/`cur_chr` may differ from the eqtb entry for
553            // \noexpand'ed tokens; follow TeX and use them.
554            if m.cmd != self.cur_cmd {
555                m = Meaning::prim(self.cur_cmd, self.cur_chr);
556            }
557            m
558        } else {
559            Meaning::prim(self.cur_cmd, self.cur_chr)
560        }
561    }
562
563    /// `\chardef` and friends.
564    fn shorthand_def(&mut self, global: bool) {
565        let n = self.cur_chr;
566        self.get_r_token();
567        let p = self.cur_cs;
568        self.define_cs(global, p, Meaning::prim(Cmd::Relax, RELAX_CHR));
569        self.scan_optional_equals();
570        let m = match n {
571            shorthand::CHAR_DEF => {
572                self.scan_char_num();
573                Meaning::prim(Cmd::CharGiven, self.cur_val as u32)
574            }
575            shorthand::MATH_CHAR_DEF => {
576                // Kept in TeX's 15-bit form, as LuaTeX shows and reads it.
577                let g = self.scan_math_given();
578                Meaning::prim(Cmd::MathGiven, g)
579            }
580            shorthand::UMATH_CHAR_DEF => {
581                let code = self.scan_umath_triple();
582                Meaning::prim(Cmd::XmathGiven, code as u32)
583            }
584            shorthand::UMATH_CHAR_NUM_DEF => {
585                self.scan_int();
586                Meaning::prim(Cmd::XmathGiven, self.cur_val as u32)
587            }
588            _ => {
589                self.scan_register_num();
590                let r = self.cur_val as u32 + REG_OFFSET;
591                match n {
592                    shorthand::COUNT_DEF => Meaning::prim(Cmd::AssignInt, r),
593                    shorthand::ATTRIBUTE_DEF => Meaning::prim(Cmd::AssignAttr, r),
594                    shorthand::DIMEN_DEF => Meaning::prim(Cmd::AssignDimen, r),
595                    shorthand::SKIP_DEF => Meaning::prim(Cmd::AssignGlue, r),
596                    shorthand::MU_SKIP_DEF => Meaning::prim(Cmd::AssignMuGlue, r),
597                    _ => Meaning::prim(Cmd::AssignToks, r),
598                }
599            }
600        };
601        self.define_cs(global, p, m);
602    }
603
604    /// Scans `class family char` for `\Umathcode` and `\Umathchardef`.
605    pub(crate) fn scan_umath_triple(&mut self) -> i64 {
606        self.scan_int();
607        let class = self.cur_val;
608        self.scan_math_family();
609        let fam = self.cur_val;
610        self.scan_char_num();
611        let chr = self.cur_val;
612        if !(0..=7).contains(&class) {
613            self.print_err("Invalid math code");
614            self.help(&["I'm going to use 0 instead of that illegal code value."]);
615            self.error();
616            return 0;
617        }
618        crate::codes::math_code(class as u32, fam as u32, chr as u32)
619    }
620
621    /// `\toks`, `\everypar` and other token-list assignments.
622    fn assign_toks(&mut self, global: bool) {
623        let q = self.cur_cs;
624        let loc = if self.cur_cmd == Cmd::ToksRegister {
625            self.scan_register_num();
626            Loc::Toks(self.cur_val as u32)
627        } else if self.cur_chr >= REG_OFFSET {
628            Loc::Toks(self.cur_chr - REG_OFFSET)
629        } else {
630            Loc::ToksPar(self.cur_chr)
631        };
632        self.scan_optional_equals();
633        self.get_nonblank_nonrelax_noncall();
634        if self.cur_cmd != Cmd::LeftBrace {
635            // The right-hand side may be a token parameter or register.
636            let src = if self.cur_cmd == Cmd::ToksRegister {
637                self.scan_register_num();
638                Some(self.eqtb.toks[self.cur_val as usize].val.clone())
639            } else if self.cur_cmd == Cmd::AssignToks {
640                Some(if self.cur_chr >= REG_OFFSET {
641                    self.eqtb.toks[(self.cur_chr - REG_OFFSET) as usize]
642                        .val
643                        .clone()
644                } else {
645                    self.eqtb.toks_param(self.cur_chr).clone()
646                })
647            } else {
648                None
649            };
650            if let Some(list) = src {
651                self.define(global, loc, Val::Toks(list));
652                return;
653            }
654        }
655        self.back_input();
656        self.cur_cs = q;
657        self.def_ref_cs = q;
658        let mut toks = self.scan_toks(false, false);
659        self.def_ref_cs = NO_CS;
660        if !toks.is_empty() && loc == Loc::ToksPar(pg::assign_toks::OUTPUT) {
661            toks.insert(0, Token::LEFT_BRACE);
662            toks.push(Token::RIGHT_BRACE);
663        }
664        let list: TokenList = if toks.is_empty() {
665            crate::token::empty_list()
666        } else {
667            Rc::from(toks)
668        };
669        self.define(global, loc, Val::Toks(list));
670    }
671
672    /// LuaTeX's `\toksapp`, `\etoksapp`, `\tokspre`, … (`combinetoks`).
673    fn combine_toks(&mut self) {
674        let code = self.cur_chr;
675        let global = code >= 4;
676        let expand = code % 2 == 1;
677        let append = code % 4 < 2;
678        // The target: a register number or a token register control
679        // sequence.
680        self.get_nonblank_nonrelax_noncall();
681        let loc = if self.cur_cmd == Cmd::AssignToks {
682            if self.cur_chr >= REG_OFFSET {
683                Loc::Toks(self.cur_chr - REG_OFFSET)
684            } else {
685                Loc::ToksPar(self.cur_chr)
686            }
687        } else if self.cur_cmd == Cmd::ToksRegister {
688            self.scan_register_num();
689            Loc::Toks(self.cur_val as u32)
690        } else {
691            self.back_input();
692            self.scan_register_num();
693            Loc::Toks(self.cur_val as u32)
694        };
695        let new = if expand {
696            self.scan_toks_expanded()
697        } else {
698            self.scan_general_text().to_vec()
699        };
700        let old = match self.eqtb.get(loc) {
701            Val::Toks(t) => t,
702            _ => crate::token::empty_list(),
703        };
704        let mut v: Vec<Token> = Vec::with_capacity(old.len() + new.len());
705        if append {
706            v.extend_from_slice(&old);
707            v.extend_from_slice(&new);
708        } else {
709            v.extend_from_slice(&new);
710            v.extend_from_slice(&old);
711        }
712        self.define(global, loc, Val::Toks(Rc::from(v)));
713    }
714
715    fn invalid_code(&mut self, v: i32, n: i32, range: bool) {
716        self.print_err("Invalid code (");
717        self.printer.print_int(v.into());
718        if range {
719            self.printer.print_str("), should be in the range 0..");
720        } else {
721            self.printer.print_str("), should be at most ");
722        }
723        self.printer.print_int(n.into());
724        self.help(&["I'm going to use 0 instead of that illegal code value."]);
725        self.error();
726        self.cur_val = 0;
727    }
728
729    /// `\catcode`, `\lccode`, `\uccode`, `\sfcode`, `\mathcode`.
730    fn def_char_code(&mut self, global: bool) {
731        use pg::def_char_code as dc;
732        let which = self.cur_chr;
733        let n: i32 = match which {
734            dc::CATCODE => 15,
735            dc::MATHCODE => 0x8000,
736            dc::SFCODE => 0o77777,
737            _ => 0x10_FFFF,
738        };
739        self.scan_char_num();
740        let c = self.cur_val as u32;
741        self.scan_optional_equals();
742        self.scan_int();
743        if self.cur_val < 0 || self.cur_val > n {
744            self.invalid_code(self.cur_val, n, true);
745        }
746        let v = self.cur_val;
747        match which {
748            dc::CATCODE => {
749                let table = self.eqtb.cur_cat_table();
750                self.define(global, Loc::Cat { table, ch: c }, Val::Int(i64::from(v)));
751            }
752            dc::LCCODE => self.define(global, Loc::Code(CodeKind::Lc, c), Val::Int(v.into())),
753            dc::UCCODE => self.define(global, Loc::Code(CodeKind::Uc, c), Val::Int(v.into())),
754            dc::SFCODE => self.define(global, Loc::Code(CodeKind::Sf, c), Val::Int(v.into())),
755            _ => {
756                let code = if v == 0x8000 {
757                    crate::codes::MATH_ACTIVE
758                } else {
759                    let v = v as u32;
760                    crate::codes::math_code(v >> 12, (v >> 8) & 0xF, v & 0xFF)
761                };
762                self.define(global, Loc::Code(CodeKind::Math, c), Val::Int(code));
763            }
764        }
765    }
766
767    /// `\count`, `\advance`, `\multiply`, `\divide` (`do_register_command`).
768    fn do_register_command(&mut self, global: bool) {
769        let q = self.cur_cmd;
770        // Compute the register location and its type.
771        let (loc, p): (Loc, ValLevel) = 'found: {
772            if q != Cmd::Register {
773                self.get_x_token();
774                let r = self.cur_chr;
775                let l = match self.cur_cmd {
776                    Cmd::AssignInt => Some((
777                        if r >= REG_OFFSET {
778                            Loc::Count(r - REG_OFFSET)
779                        } else {
780                            Loc::IntPar(r)
781                        },
782                        ValLevel::Int,
783                    )),
784                    Cmd::AssignAttr => Some((Loc::Attr(r - REG_OFFSET.min(r)), ValLevel::Int)),
785                    Cmd::AssignDimen => Some((
786                        if r >= REG_OFFSET {
787                            Loc::Dimen(r - REG_OFFSET)
788                        } else {
789                            Loc::DimenPar(r)
790                        },
791                        ValLevel::Dimen,
792                    )),
793                    Cmd::AssignGlue => Some((
794                        if r >= REG_OFFSET {
795                            Loc::Skip(r - REG_OFFSET)
796                        } else {
797                            Loc::GluePar(r)
798                        },
799                        ValLevel::Glue,
800                    )),
801                    Cmd::AssignMuGlue => Some((
802                        if r >= REG_OFFSET {
803                            Loc::MuSkip(r - REG_OFFSET)
804                        } else {
805                            Loc::GluePar(r + MU_GLUE_OFFSET)
806                        },
807                        ValLevel::Mu,
808                    )),
809                    _ => None,
810                };
811                if let Some(x) = l {
812                    break 'found x;
813                }
814                if self.cur_cmd != Cmd::Register {
815                    self.print_err("You can't use `");
816                    self.print_cmd_chr(self.cur_cmd, self.cur_chr);
817                    self.printer.print_str("' after ");
818                    self.print_cmd_chr(q, 0);
819                    self.help(&["I'm forgetting what you said and not changing anything."]);
820                    self.error();
821                    return;
822                }
823            }
824            let kind = self.cur_chr;
825            self.scan_register_num();
826            let n = self.cur_val as u32;
827            match kind {
828                reg::COUNT => (Loc::Count(n), ValLevel::Int),
829                reg::ATTRIBUTE => (Loc::Attr(n), ValLevel::Int),
830                reg::DIMEN => (Loc::Dimen(n), ValLevel::Dimen),
831                reg::SKIP => (Loc::Skip(n), ValLevel::Glue),
832                _ => (Loc::MuSkip(n), ValLevel::Mu),
833            }
834        };
835        if q == Cmd::Register {
836            self.scan_optional_equals();
837        } else {
838            let _ = self.scan_keyword("by");
839        }
840        let mut arith_error = false;
841        let old = self.eqtb.get(loc);
842        let result: Val = if q < Cmd::Multiply {
843            if p < ValLevel::Glue {
844                if p == ValLevel::Int {
845                    self.scan_int();
846                } else {
847                    self.scan_normal_dimen();
848                }
849                let mut v = self.cur_val;
850                if q == Cmd::Advance
851                    && let Val::Int(o) = old
852                {
853                    v = v.wrapping_add(o as i32);
854                }
855                Val::Int(i64::from(v))
856            } else {
857                self.scan_glue(p);
858                let mut g = self.cur_glue;
859                if q == Cmd::Advance
860                    && let Val::Glue(r) = old
861                {
862                    g = add_glue(g, r);
863                }
864                Val::Glue(g)
865            }
866        } else {
867            self.scan_int();
868            let f = self.cur_val;
869            let op = |x: i32, err: &mut bool| -> i32 {
870                let r = if q == Cmd::Multiply {
871                    if p == ValLevel::Int {
872                        arith::mult_integers(x, f)
873                    } else {
874                        arith::nx_plus_y(x, f, 0)
875                    }
876                } else {
877                    arith::x_over_n(x, f).map(|d| d.quotient)
878                };
879                r.unwrap_or_else(|_| {
880                    *err = true;
881                    0
882                })
883            };
884            match old {
885                Val::Int(x) => Val::Int(i64::from(op(x as i32, &mut arith_error))),
886                Val::Glue(g) => {
887                    let mut n = g;
888                    n.width = op(g.width, &mut arith_error);
889                    n.stretch = op(g.stretch, &mut arith_error);
890                    n.shrink = op(g.shrink, &mut arith_error);
891                    Val::Glue(n)
892                }
893                other => other,
894            }
895        };
896        if arith_error {
897            self.print_err("Arithmetic overflow");
898            self.help(&[
899                "I can't carry out that multiplication or division,",
900                "since the result is out of range.",
901            ]);
902            self.error();
903            return;
904        }
905        self.define(global, loc, result);
906    }
907
908    /// `\batchmode` etc. (`new_interaction`).
909    pub fn new_interaction(&mut self) {
910        self.printer.print_ln();
911        self.err.interaction = crate::error::Interaction::from_i32(self.cur_chr as i32);
912        self.printer.selector = match (self.err.interaction, self.printer.log_opened()) {
913            (crate::error::Interaction::Batch, true) => crate::print::Selector::LogOnly,
914            (crate::error::Interaction::Batch, false) => crate::print::Selector::NoPrint,
915            (_, true) => crate::print::Selector::TermAndLog,
916            (_, false) => crate::print::Selector::TermOnly,
917        };
918    }
919
920    /// `\deadcycles`, `\insertpenalties`, `\interactionmode`
921    /// (`alter_integer`).
922    fn alter_integer(&mut self) {
923        let c = self.cur_chr;
924        self.scan_optional_equals();
925        self.scan_int();
926        match c {
927            0 => self.dead_cycles = self.cur_val,
928            1 => self.insert_penalties = self.cur_val,
929            _ => {
930                if (0..=3).contains(&self.cur_val) {
931                    self.cur_chr = self.cur_val as u32;
932                    self.new_interaction();
933                } else {
934                    self.print_err("Bad interaction mode (");
935                    self.printer.print_int(self.cur_val.into());
936                    self.printer.print_char(')' as u32);
937                    self.help(&[
938                        "Modes are 0=batch, 1=nonstop, 2=scroll, and",
939                        "3=errorstop. Proceed, and I'll ignore this case.",
940                    ]);
941                    self.int_error(self.cur_val);
942                }
943            }
944        }
945    }
946}
947
948/// Adds glue `r` to `q` (TeX's "Compute the sum of two glue specs").
949pub fn add_glue(mut q: Glue, r: Glue) -> Glue {
950    q.width = q.width.wrapping_add(r.width);
951    if q.stretch == 0 {
952        q.stretch_order = crate::glue::NORMAL;
953    }
954    if q.stretch_order == r.stretch_order {
955        q.stretch = q.stretch.wrapping_add(r.stretch);
956    } else if q.stretch_order < r.stretch_order && r.stretch != 0 {
957        q.stretch = r.stretch;
958        q.stretch_order = r.stretch_order;
959    }
960    if q.shrink == 0 {
961        q.shrink_order = crate::glue::NORMAL;
962    }
963    if q.shrink_order == r.shrink_order {
964        q.shrink = q.shrink.wrapping_add(r.shrink);
965    } else if q.shrink_order < r.shrink_order && r.shrink != 0 {
966        q.shrink = r.shrink;
967        q.shrink_order = r.shrink_order;
968    }
969    q
970}