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