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