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