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