Skip to main content

retex_engine/
eqtb.rs

1//! The table of equivalents (TeX's `eqtb`) and its storage.
2//!
3//! TeX keeps every quantity that obeys grouping — meanings of control
4//! sequences, registers, parameters, character codes, fonts — in one big
5//! array. Each entry carries the group *level* at which it was last defined.
6//! retex keeps the quantities in typed vectors instead. [`Loc`] addresses any
7//! one of them, so that the save stack ([`crate::save`]) can restore values
8//! generically.
9
10use std::collections::HashMap;
11use std::rc::Rc;
12
13use crate::cmd::Cmd;
14use crate::glue::Glue;
15use crate::hash::CsId;
16use crate::nodes::NodeRef;
17use crate::primitives_gen as pg;
18use crate::token::{Macro, TokenList, empty_list};
19
20/// The level of the outermost group (TeX's `level_one`).
21pub const LEVEL_ONE: u16 = 1;
22/// The level of undefined entries (TeX's `level_zero`).
23pub const LEVEL_ZERO: u16 = 0;
24
25/// Number of registers of each kind (LuaTeX: 65536).
26pub const NUM_REGISTERS: usize = 65536;
27/// Number of math families (LuaTeX: 256).
28pub const NUM_FAMILIES: usize = 256;
29/// The value of an unset attribute.
30pub const UNUSED_ATTRIBUTE: i32 = -0x7FFF_FFFF;
31
32/// The meaning of a control sequence: command code, modifier and, for
33/// macros, the definition.
34#[derive(Clone, Debug)]
35pub struct Meaning {
36    /// The command code (`eq_type`).
37    pub cmd: Cmd,
38    /// The modifier (`equiv` for non-macro commands).
39    pub chr: u32,
40    /// The definition, for `call` commands.
41    pub mac: Option<Rc<Macro>>,
42}
43
44impl Meaning {
45    /// The meaning of an undefined control sequence.
46    pub const UNDEFINED: Meaning = Meaning {
47        cmd: Cmd::UndefinedCs,
48        chr: 0,
49        mac: None,
50    };
51
52    /// A primitive or other non-macro meaning.
53    pub const fn prim(cmd: Cmd, chr: u32) -> Meaning {
54        Meaning {
55            cmd,
56            chr,
57            mac: None,
58        }
59    }
60
61    /// Identity of meanings as TeX's `eq_define` tests it (same command
62    /// and same equivalent; macros compare by reference).
63    pub fn same_as(&self, other: &Meaning) -> bool {
64        self.cmd == other.cmd
65            && self.chr == other.chr
66            && match (&self.mac, &other.mac) {
67                (None, None) => true,
68                (Some(a), Some(b)) => Rc::ptr_eq(a, b),
69                _ => false,
70            }
71    }
72}
73
74/// A value together with the level at which it was defined.
75#[derive(Clone, Debug)]
76pub struct Slot<T> {
77    /// The value.
78    pub val: T,
79    /// The group level of its definition.
80    pub level: u16,
81}
82
83impl<T> Slot<T> {
84    fn new(val: T) -> Self {
85        Slot {
86            val,
87            level: LEVEL_ONE,
88        }
89    }
90}
91
92/// Kinds of per-character code tables, other than category codes.
93#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
94pub enum CodeKind {
95    /// `\lccode`.
96    Lc,
97    /// `\uccode`.
98    Uc,
99    /// `\sfcode`.
100    Sf,
101    /// `\mathcode` / `\Umathcode` (packed, see [`crate::codes`]).
102    Math,
103    /// `\delcode` / `\Udelcode` (packed, see [`crate::codes`]).
104    Del,
105}
106
107/// A location in the table of equivalents.
108#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
109pub enum Loc {
110    /// The meaning of a control sequence.
111    Cs(CsId),
112    /// An integer parameter (`assign_int` index).
113    IntPar(u32),
114    /// A `\count` register.
115    Count(u32),
116    /// An `\attribute` register.
117    Attr(u32),
118    /// A dimension parameter (`assign_dimen` index).
119    DimenPar(u32),
120    /// A `\dimen` register.
121    Dimen(u32),
122    /// A glue parameter (`assign_glue` index) or, offset by
123    /// [`MU_GLUE_OFFSET`], a muglue parameter.
124    GluePar(u32),
125    /// A `\skip` register.
126    Skip(u32),
127    /// A `\muskip` register.
128    MuSkip(u32),
129    /// A token-list parameter (`assign_toks` index).
130    ToksPar(u32),
131    /// A `\toks` register.
132    Toks(u32),
133    /// A `\box` register.
134    Box(u32),
135    /// The current font.
136    CurFont,
137    /// A math font: `size * NUM_FAMILIES + family` (size 0 text, 1 script,
138    /// 2 scriptscript).
139    MathFont(u32),
140    /// `\parshape`.
141    ParShape,
142    /// An e-TeX penalty array (`set_etex_shape` index).
143    EtexShape(u32),
144    /// A category code in catcode table `table`.
145    Cat {
146        /// The catcode table number.
147        table: u32,
148        /// The character.
149        ch: u32,
150    },
151    /// Another character code.
152    Code(CodeKind, u32),
153    /// A math parameter (`\Umath…`): `param * 8 + style`.
154    MathParam(u32),
155    /// `\localleftbox` (0) or `\localrightbox` (1).
156    LocalBox(u32),
157    /// A direction parameter (`assign_dir` index: `\pagedir` …
158    /// `\linedir`).
159    DirPar(u32),
160    /// LuaTeX's group-local counters `\nolocalwhatsits` (0) and
161    /// `\nolocaldirs` (1), seen only in tracing (see `dirs`).
162    LocalCount(u32),
163}
164
165/// Number of integer parameters, including LuaTeX's internal backend
166/// integers (`\pdfvariable compresslevel` is index 137 … 164).
167pub const INT_PARS: usize = 165;
168/// Number of dimension parameters, including backend dimensions (28–33).
169pub const DIMEN_PARS: usize = 34;
170/// Number of token-list parameters, including backend token lists (11–17).
171pub const TOKS_PARS: usize = 18;
172
173/// Offset added to `assign_mu_glue` indices so that glue and muglue
174/// parameters share one vector.
175pub const MU_GLUE_OFFSET: u32 = pg::assign_glue::COUNT;
176
177/// A saved value, as stored on the save stack.
178#[derive(Clone, Debug)]
179pub enum Val {
180    /// A meaning.
181    Meaning(Meaning),
182    /// An integer, dimension or packed code.
183    Int(i64),
184    /// Glue.
185    Glue(Glue),
186    /// A token list.
187    Toks(TokenList),
188    /// A box (node list) or null.
189    Node(NodeRef),
190    /// A shape array (`\parshape` or penalties), or none.
191    Shape(Option<Rc<[i32]>>),
192}
193
194/// A table of per-character codes with a default function.
195///
196/// Storage is a two-level page table: 256-character pages, allocated on
197/// first assignment and filled with the defaults. Lookups are two array
198/// indexings, with no hashing, which matters because the scanner consults
199/// these tables for every character.
200#[derive(Clone, Debug)]
201pub struct CodeTable {
202    /// Pages are shared between tables (catcode tables are mostly copies
203    /// of one another) and copied on first write.
204    pages: Vec<Option<Rc<Page>>>,
205    default: fn(u32) -> i64,
206}
207
208/// Characters per page.
209const PAGE: usize = 256;
210/// One page of a code table.
211pub type Page = [Slot<i64>; PAGE];
212
213impl CodeTable {
214    /// A table where every character has value `default(c)`.
215    pub fn new(default: fn(u32) -> i64) -> Self {
216        let mut t = CodeTable {
217            pages: Vec::new(),
218            default,
219        };
220        // The ASCII/Latin-1 page is always present.
221        t.page_mut(0);
222        t
223    }
224
225    fn page_mut(&mut self, c: u32) -> &mut Page {
226        let k = c as usize / PAGE;
227        if self.pages.len() <= k {
228            self.pages.resize_with(k + 1, || None);
229        }
230        let default = self.default;
231        let page = self.pages[k].get_or_insert_with(|| {
232            let base = (k * PAGE) as u32;
233            Rc::new(std::array::from_fn(|i| Slot::new(default(base + i as u32))))
234        });
235        Rc::make_mut(page)
236    }
237
238    #[inline]
239    fn slot(&self, c: u32) -> Option<&Slot<i64>> {
240        match self.pages.get(c as usize / PAGE) {
241            Some(Some(p)) => Some(&p[c as usize % PAGE]),
242            _ => None,
243        }
244    }
245
246    /// The value for `c`.
247    #[inline]
248    pub fn get(&self, c: u32) -> i64 {
249        self.slot(c).map_or_else(|| (self.default)(c), |s| s.val)
250    }
251
252    /// The level of the definition for `c`.
253    pub fn level(&self, c: u32) -> u16 {
254        self.slot(c).map_or(LEVEL_ONE, |s| s.level)
255    }
256
257    /// Assigns `val` to `c` at level `cur` when that needs no save-stack
258    /// entry (the value is unchanged, or was defined at level `cur`, or
259    /// `cur` is level one). Returns `false`, changing nothing, otherwise.
260    #[inline]
261    pub fn define_in_place(&mut self, c: u32, val: i64, cur: u16) -> bool {
262        let (old, level) = match self.slot(c) {
263            Some(s) => (s.val, s.level),
264            None => ((self.default)(c), LEVEL_ONE),
265        };
266        if old == val {
267            return true;
268        }
269        if level != cur && cur > LEVEL_ONE {
270            return false;
271        }
272        let s = &mut self.page_mut(c)[c as usize % PAGE];
273        s.val = val;
274        s.level = cur;
275        true
276    }
277
278    /// Sets value and level for `c`.
279    pub fn set(&mut self, c: u32, val: i64, level: u16) {
280        self.page_mut(c)[c as usize % PAGE] = Slot { val, level };
281    }
282
283    /// The default function of this table.
284    pub fn default_fn(&self) -> fn(u32) -> i64 {
285        self.default
286    }
287
288    /// The values of every allocated page that differs from the defaults,
289    /// as (page number, values), for format dumping.
290    pub fn dump_pages(&self) -> Vec<(usize, [i64; PAGE])> {
291        let mut out = Vec::new();
292        for (k, p) in self.pages.iter().enumerate() {
293            let Some(p) = p else { continue };
294            let base = (k * PAGE) as u32;
295            let vals: [i64; PAGE] = std::array::from_fn(|i| p[i].val);
296            if vals
297                .iter()
298                .enumerate()
299                .any(|(i, v)| *v != (self.default)(base + i as u32))
300            {
301                out.push((k, vals));
302            }
303        }
304        out
305    }
306
307    /// Installs a page (format loading); the page may be shared.
308    pub fn load_page(&mut self, k: usize, page: Rc<Page>) {
309        if self.pages.len() <= k {
310            self.pages.resize_with(k + 1, || None);
311        }
312        self.pages[k] = Some(page);
313    }
314
315    /// Iterates, in code-point order, over the characters of all allocated
316    /// pages: the first 256 characters and every page containing an
317    /// assignment.
318    pub fn explicit(&self) -> impl Iterator<Item = (u32, i64, u16)> + '_ {
319        self.pages.iter().enumerate().flat_map(|(k, p)| {
320            p.iter().flat_map(move |p| {
321                p.iter()
322                    .enumerate()
323                    .map(move |(i, s)| ((k * PAGE + i) as u32, s.val, s.level))
324            })
325        })
326    }
327}
328
329/// Default category codes of IniTeX: escape for `\`, letters for ASCII
330/// letters, `%` comment, end of line for ^^M, space, ignored ^^@, invalid
331/// ^^?, everything else other. LuaTeX also ignores U+FEFF, the byte order
332/// mark some editors put at the start of UTF-8 files.
333pub fn initex_catcode(c: u32) -> i64 {
334    match c {
335        0x5C => 0,                       // escape
336        0x25 => 14,                      // comment
337        0x41..=0x5A | 0x61..=0x7A => 11, // letter
338        0x0D => 5,                       // car_ret
339        0x20 => 10,                      // spacer
340        0x00 | 0xFEFF => 9,              // ignore
341        0x7F => 15,                      // invalid
342        _ => 12,                         // other
343    }
344}
345
346/// The full table of equivalents.
347#[derive(Clone, Debug)]
348pub struct Eqtb {
349    /// Meanings of control sequences, indexed by [`CsId`].
350    pub cs: Vec<Slot<Meaning>>,
351    /// Integer parameters.
352    pub int_par: Vec<Slot<i32>>,
353    /// `\count` registers.
354    pub count: Vec<Slot<i32>>,
355    /// `\attribute` registers.
356    pub attr: Vec<Slot<i32>>,
357    /// Dimension parameters.
358    pub dimen_par: Vec<Slot<i32>>,
359    /// `\dimen` registers.
360    pub dimen: Vec<Slot<i32>>,
361    /// Glue and muglue parameters.
362    pub glue_par: Vec<Slot<Glue>>,
363    /// `\skip` registers.
364    pub skip: Vec<Slot<Glue>>,
365    /// `\muskip` registers.
366    pub muskip: Vec<Slot<Glue>>,
367    /// Token-list parameters.
368    pub toks_par: Vec<Slot<TokenList>>,
369    /// `\toks` registers.
370    pub toks: Vec<Slot<TokenList>>,
371    /// `\box` registers.
372    pub boxes: Vec<Slot<NodeRef>>,
373    /// The current font.
374    pub cur_font: Slot<u32>,
375    /// Math fonts, `size * NUM_FAMILIES + family`.
376    pub math_font: Vec<Slot<u32>>,
377    /// `\parshape`: pairs (indent, length) flattened.
378    pub par_shape: Slot<Option<Rc<[i32]>>>,
379    /// e-TeX penalty arrays (`\interlinepenalties`, …).
380    pub etex_shape: Vec<Slot<Option<Rc<[i32]>>>>,
381    /// Catcode tables, indexed by table number.
382    pub catcode_tables: HashMap<u32, CodeTable, crate::hash::FastHash>,
383    /// `\lccode`.
384    pub lc: CodeTable,
385    /// `\uccode`.
386    pub uc: CodeTable,
387    /// `\sfcode`.
388    pub sf: CodeTable,
389    /// `\mathcode`.
390    pub math: CodeTable,
391    /// `\delcode`.
392    pub del: CodeTable,
393    /// `\Umath…` parameters: `param * 8 + style`.
394    pub math_param: HashMap<u32, Slot<i32>>,
395    /// `\localleftbox`, `\localrightbox`.
396    pub local_box: [Slot<NodeRef>; 2],
397    /// Direction parameters (`\pagedir` …, as numbers 0–3).
398    pub dir_par: [Slot<i32>; pg::assign_dir::COUNT as usize],
399    /// `\nolocalwhatsits`, `\nolocaldirs`.
400    pub local_count: [Slot<i32>; 2],
401    /// Incremented whenever an attribute register changes.
402    pub attr_generation: u64,
403    /// Whether any attribute register was ever set.
404    pub attrs_in_use: bool,
405}
406
407impl Eqtb {
408    /// The initial table of IniTeX: everything undefined or zero, with
409    /// TeX's few non-zero defaults.
410    pub fn initex() -> Self {
411        use pg::assign_int as ip;
412        let regs_i = || vec![Slot::new(0); NUM_REGISTERS];
413        let mut int_par = vec![Slot::new(0i32); INT_PARS];
414        int_par[ip::TOLERANCE as usize].val = 10000;
415        int_par[ip::MAG as usize].val = 1000;
416        int_par[ip::MAXDEADCYCLES as usize].val = 25;
417        int_par[ip::ESCAPECHAR as usize].val = '\\' as i32;
418        int_par[crate::backend::MAJOR_VERSION as usize].val = 1;
419        int_par[ip::ENDLINECHAR as usize].val = 13;
420        let mut catcode_tables: HashMap<u32, CodeTable, crate::hash::FastHash> = HashMap::default();
421        catcode_tables.insert(0, CodeTable::new(initex_catcode));
422        Eqtb {
423            cs: Vec::new(),
424            int_par,
425            count: regs_i(),
426            attr: vec![Slot::new(UNUSED_ATTRIBUTE); NUM_REGISTERS],
427            dimen_par: vec![Slot::new(0); DIMEN_PARS],
428            dimen: regs_i(),
429            glue_par: vec![
430                Slot::new(Glue::ZERO);
431                (pg::assign_glue::COUNT + pg::assign_mu_glue::COUNT) as usize
432            ],
433            skip: vec![Slot::new(Glue::ZERO); NUM_REGISTERS],
434            muskip: vec![Slot::new(Glue::ZERO); NUM_REGISTERS],
435            toks_par: vec![Slot::new(empty_list()); TOKS_PARS],
436            toks: vec![Slot::new(empty_list()); NUM_REGISTERS],
437            boxes: vec![Slot::new(NodeRef::NULL); NUM_REGISTERS],
438            cur_font: Slot::new(0),
439            math_font: vec![Slot::new(0); 3 * NUM_FAMILIES],
440            par_shape: Slot::new(None),
441            etex_shape: vec![Slot::new(None); pg::set_etex_shape::COUNT as usize],
442            catcode_tables,
443            lc: CodeTable::new(crate::codes::initex_lccode),
444            uc: CodeTable::new(crate::codes::initex_uccode),
445            sf: CodeTable::new(crate::codes::initex_sfcode),
446            math: CodeTable::new(crate::codes::initex_mathcode),
447            del: CodeTable::new(crate::codes::initex_delcode),
448            math_param: HashMap::new(),
449            local_box: [Slot::new(NodeRef::NULL), Slot::new(NodeRef::NULL)],
450            dir_par: std::array::from_fn(|_| Slot::new(0)),
451            local_count: [Slot::new(0), Slot::new(0)],
452            attr_generation: 0,
453            attrs_in_use: false,
454        }
455    }
456
457    /// Makes sure `cs` has an entry.
458    #[inline]
459    pub fn ensure_cs(&mut self, cs: CsId) {
460        let n = cs.0 as usize + 1;
461        if self.cs.len() < n {
462            self.cs.resize(
463                n,
464                Slot {
465                    val: Meaning::UNDEFINED,
466                    level: LEVEL_ZERO,
467                },
468            );
469        }
470    }
471
472    /// The meaning of a control sequence.
473    #[inline]
474    pub fn meaning(&self, cs: CsId) -> &Meaning {
475        match self.cs.get(cs.0 as usize) {
476            Some(slot) => &slot.val,
477            None => &Meaning::UNDEFINED,
478        }
479    }
480
481    /// The integer parameter with index `i`.
482    #[inline]
483    pub fn int(&self, i: u32) -> i32 {
484        self.int_par[i as usize].val
485    }
486
487    /// The dimension parameter with index `i`.
488    #[inline]
489    pub fn dimen(&self, i: u32) -> i32 {
490        self.dimen_par[i as usize].val
491    }
492
493    /// The glue parameter with index `i`.
494    #[inline]
495    pub fn glue(&self, i: u32) -> Glue {
496        self.glue_par[i as usize].val
497    }
498
499    /// The token-list parameter with index `i`.
500    #[inline]
501    pub fn toks_param(&self, i: u32) -> &TokenList {
502        &self.toks_par[i as usize].val
503    }
504
505    /// The current catcode table number.
506    #[inline]
507    pub fn cur_cat_table(&self) -> u32 {
508        self.int(pg::assign_int::CATCODETABLE) as u32
509    }
510
511    /// The category code of `c` in the current catcode table.
512    #[inline]
513    pub fn catcode(&self, c: u32) -> u8 {
514        self.catcode_in(self.cur_cat_table(), c)
515    }
516
517    /// The category code of `c` in table `table`. Missing tables read as
518    /// IniTeX defaults.
519    pub fn catcode_in(&self, table: u32, c: u32) -> u8 {
520        match self.catcode_tables.get(&table) {
521            Some(t) => t.get(c) as u8,
522            None => initex_catcode(c) as u8,
523        }
524    }
525
526    /// Reads the value at `loc`.
527    pub fn get(&self, loc: Loc) -> Val {
528        match loc {
529            Loc::Cs(cs) => Val::Meaning(self.meaning(cs).clone()),
530            Loc::IntPar(i) => Val::Int(self.int_par[i as usize].val.into()),
531            Loc::Count(i) => Val::Int(self.count[i as usize].val.into()),
532            Loc::Attr(i) => Val::Int(self.attr[i as usize].val.into()),
533            Loc::DimenPar(i) => Val::Int(self.dimen_par[i as usize].val.into()),
534            Loc::Dimen(i) => Val::Int(self.dimen[i as usize].val.into()),
535            Loc::GluePar(i) => Val::Glue(self.glue_par[i as usize].val),
536            Loc::Skip(i) => Val::Glue(self.skip[i as usize].val),
537            Loc::MuSkip(i) => Val::Glue(self.muskip[i as usize].val),
538            Loc::ToksPar(i) => Val::Toks(self.toks_par[i as usize].val.clone()),
539            Loc::Toks(i) => Val::Toks(self.toks[i as usize].val.clone()),
540            Loc::Box(i) => Val::Node(self.boxes[i as usize].val),
541            Loc::CurFont => Val::Int(self.cur_font.val.into()),
542            Loc::MathFont(i) => Val::Int(self.math_font[i as usize].val.into()),
543            Loc::ParShape => Val::Shape(self.par_shape.val.clone()),
544            Loc::EtexShape(i) => Val::Shape(self.etex_shape[i as usize].val.clone()),
545            Loc::Cat { table, ch } => Val::Int(self.catcode_in(table, ch).into()),
546            Loc::Code(kind, ch) => Val::Int(self.code_table(kind).get(ch)),
547            Loc::MathParam(i) => Val::Int(
548                self.math_param
549                    .get(&i)
550                    .map_or(crate::codes::UNDEFINED_MATH_PARAM.into(), |s| s.val.into()),
551            ),
552            Loc::LocalBox(i) => Val::Node(self.local_box[i as usize].val),
553            Loc::DirPar(i) => Val::Int(self.dir_par[i as usize].val.into()),
554            Loc::LocalCount(i) => Val::Int(self.local_count[i as usize].val.into()),
555        }
556    }
557
558    /// Whether `loc` holds `val` already (TeX's test for "reassigning"),
559    /// without cloning the current value.
560    pub fn holds(&self, loc: Loc, val: &Val) -> bool {
561        match (loc, val) {
562            (Loc::Cs(cs), Val::Meaning(m)) => self.meaning(cs).same_as(m),
563            (Loc::IntPar(i), Val::Int(v)) => i64::from(self.int_par[i as usize].val) == *v,
564            (Loc::Count(i), Val::Int(v)) => i64::from(self.count[i as usize].val) == *v,
565            (Loc::Dimen(i), Val::Int(v)) => i64::from(self.dimen[i as usize].val) == *v,
566            (Loc::DimenPar(i), Val::Int(v)) => i64::from(self.dimen_par[i as usize].val) == *v,
567            (Loc::Code(kind, ch), Val::Int(v)) => self.code_table(kind).get(ch) == *v,
568            (Loc::ToksPar(i), Val::Toks(t)) => Rc::ptr_eq(&self.toks_par[i as usize].val, t),
569            (Loc::Toks(i), Val::Toks(t)) => Rc::ptr_eq(&self.toks[i as usize].val, t),
570            _ => same_val(&self.get(loc), val),
571        }
572    }
573
574    /// The level at which the value at `loc` was defined.
575    pub fn level(&self, loc: Loc) -> u16 {
576        match loc {
577            Loc::Cs(cs) => self.cs.get(cs.0 as usize).map_or(LEVEL_ZERO, |s| s.level),
578            Loc::IntPar(i) => self.int_par[i as usize].level,
579            Loc::Count(i) => self.count[i as usize].level,
580            Loc::Attr(i) => self.attr[i as usize].level,
581            Loc::DimenPar(i) => self.dimen_par[i as usize].level,
582            Loc::Dimen(i) => self.dimen[i as usize].level,
583            Loc::GluePar(i) => self.glue_par[i as usize].level,
584            Loc::Skip(i) => self.skip[i as usize].level,
585            Loc::MuSkip(i) => self.muskip[i as usize].level,
586            Loc::ToksPar(i) => self.toks_par[i as usize].level,
587            Loc::Toks(i) => self.toks[i as usize].level,
588            Loc::Box(i) => self.boxes[i as usize].level,
589            Loc::CurFont => self.cur_font.level,
590            Loc::MathFont(i) => self.math_font[i as usize].level,
591            Loc::ParShape => self.par_shape.level,
592            Loc::EtexShape(i) => self.etex_shape[i as usize].level,
593            Loc::Cat { table, ch } => self
594                .catcode_tables
595                .get(&table)
596                .map_or(LEVEL_ONE, |t| t.level(ch)),
597            Loc::Code(kind, ch) => self.code_table(kind).level(ch),
598            Loc::MathParam(i) => self.math_param.get(&i).map_or(LEVEL_ONE, |s| s.level),
599            Loc::LocalBox(i) => self.local_box[i as usize].level,
600            Loc::DirPar(i) => self.dir_par[i as usize].level,
601            Loc::LocalCount(i) => self.local_count[i as usize].level,
602        }
603    }
604
605    /// Stores `val` at `loc` with level `level`. Mismatched value kinds are
606    /// ignored (they indicate a bug and are caught in debug builds).
607    pub fn set(&mut self, loc: Loc, val: Val, level: u16) {
608        fn int(v: &Val) -> i64 {
609            match v {
610                Val::Int(i) => *i,
611                _ => {
612                    debug_assert!(false, "expected an integer value");
613                    0
614                }
615            }
616        }
617        match loc {
618            Loc::Cs(cs) => {
619                self.ensure_cs(cs);
620                if let Val::Meaning(m) = val {
621                    self.cs[cs.0 as usize] = Slot { val: m, level };
622                }
623            }
624            Loc::IntPar(i) => {
625                self.int_par[i as usize] = Slot {
626                    val: int(&val) as i32,
627                    level,
628                }
629            }
630            Loc::Count(i) => {
631                self.count[i as usize] = Slot {
632                    val: int(&val) as i32,
633                    level,
634                }
635            }
636            Loc::Attr(i) => {
637                self.attr[i as usize] = Slot {
638                    val: int(&val) as i32,
639                    level,
640                };
641                self.attr_generation += 1;
642                self.attrs_in_use = true;
643            }
644            Loc::DimenPar(i) => {
645                self.dimen_par[i as usize] = Slot {
646                    val: int(&val) as i32,
647                    level,
648                }
649            }
650            Loc::Dimen(i) => {
651                self.dimen[i as usize] = Slot {
652                    val: int(&val) as i32,
653                    level,
654                }
655            }
656            Loc::GluePar(i) | Loc::Skip(i) | Loc::MuSkip(i) => {
657                if let Val::Glue(g) = val {
658                    let slot = Slot { val: g, level };
659                    match loc {
660                        Loc::GluePar(_) => self.glue_par[i as usize] = slot,
661                        Loc::Skip(_) => self.skip[i as usize] = slot,
662                        _ => self.muskip[i as usize] = slot,
663                    }
664                }
665            }
666            Loc::ToksPar(i) | Loc::Toks(i) => {
667                if let Val::Toks(t) = val {
668                    let slot = Slot { val: t, level };
669                    if matches!(loc, Loc::ToksPar(_)) {
670                        self.toks_par[i as usize] = slot;
671                    } else {
672                        self.toks[i as usize] = slot;
673                    }
674                }
675            }
676            Loc::Box(i) => {
677                if let Val::Node(n) = val {
678                    self.boxes[i as usize] = Slot { val: n, level };
679                }
680            }
681            Loc::CurFont => {
682                self.cur_font = Slot {
683                    val: int(&val) as u32,
684                    level,
685                }
686            }
687            Loc::MathFont(i) => {
688                self.math_font[i as usize] = Slot {
689                    val: int(&val) as u32,
690                    level,
691                }
692            }
693            Loc::ParShape => {
694                if let Val::Shape(s) = val {
695                    self.par_shape = Slot { val: s, level };
696                }
697            }
698            Loc::EtexShape(i) => {
699                if let Val::Shape(s) = val {
700                    self.etex_shape[i as usize] = Slot { val: s, level };
701                }
702            }
703            Loc::Cat { table, ch } => {
704                self.catcode_tables
705                    .entry(table)
706                    .or_insert_with(|| CodeTable::new(initex_catcode))
707                    .set(ch, int(&val), level);
708            }
709            Loc::Code(kind, ch) => self.code_table_mut(kind).set(ch, int(&val), level),
710            Loc::MathParam(i) => {
711                self.math_param.insert(
712                    i,
713                    Slot {
714                        val: int(&val) as i32,
715                        level,
716                    },
717                );
718            }
719            Loc::LocalBox(i) => {
720                if let Val::Node(n) = val {
721                    self.local_box[i as usize] = Slot { val: n, level };
722                }
723            }
724            Loc::DirPar(i) => {
725                self.dir_par[i as usize] = Slot {
726                    val: int(&val) as i32,
727                    level,
728                }
729            }
730            Loc::LocalCount(i) => {
731                self.local_count[i as usize] = Slot {
732                    val: int(&val) as i32,
733                    level,
734                }
735            }
736        }
737    }
738
739    /// The code table of the given kind.
740    pub fn code_table(&self, kind: CodeKind) -> &CodeTable {
741        match kind {
742            CodeKind::Lc => &self.lc,
743            CodeKind::Uc => &self.uc,
744            CodeKind::Sf => &self.sf,
745            CodeKind::Math => &self.math,
746            CodeKind::Del => &self.del,
747        }
748    }
749
750    pub(crate) fn code_table_mut(&mut self, kind: CodeKind) -> &mut CodeTable {
751        match kind {
752            CodeKind::Lc => &mut self.lc,
753            CodeKind::Uc => &mut self.uc,
754            CodeKind::Sf => &mut self.sf,
755            CodeKind::Math => &mut self.math,
756            CodeKind::Del => &mut self.del,
757        }
758    }
759
760    /// Whether the location holds a "word" quantity (TeX regions 5–6:
761    /// integers and dimensions, which use `xeq_level` semantics) as opposed
762    /// to an "equivalent" (regions 1–4).
763    pub fn is_word(loc: Loc) -> bool {
764        matches!(
765            loc,
766            Loc::IntPar(_)
767                | Loc::Count(_)
768                | Loc::Attr(_)
769                | Loc::DimenPar(_)
770                | Loc::Dimen(_)
771                | Loc::MathParam(_)
772        )
773    }
774}
775
776/// Compares two saved values for TeX's "reassigning" test.
777pub fn same_val(a: &Val, b: &Val) -> bool {
778    match (a, b) {
779        (Val::Meaning(x), Val::Meaning(y)) => x.same_as(y),
780        (Val::Int(x), Val::Int(y)) => x == y,
781        (Val::Glue(x), Val::Glue(y)) => x == y,
782        (Val::Toks(x), Val::Toks(y)) => Rc::ptr_eq(x, y),
783        (Val::Node(x), Val::Node(y)) => x == y,
784        (Val::Shape(x), Val::Shape(y)) => match (x, y) {
785            (None, None) => true,
786            (Some(p), Some(q)) => Rc::ptr_eq(p, q),
787            _ => false,
788        },
789        _ => false,
790    }
791}