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