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.
332pub fn initex_catcode(c: u32) -> i64 {
333    match c {
334        0x5C => 0,                       // escape
335        0x25 => 14,                      // comment
336        0x41..=0x5A | 0x61..=0x7A => 11, // letter
337        0x0D => 5,                       // car_ret
338        0x20 => 10,                      // spacer
339        0x00 => 9,                       // ignore
340        0x7F => 15,                      // invalid
341        _ => 12,                         // other
342    }
343}
344
345/// The full table of equivalents.
346#[derive(Clone, Debug)]
347pub struct Eqtb {
348    /// Meanings of control sequences, indexed by [`CsId`].
349    pub cs: Vec<Slot<Meaning>>,
350    /// Integer parameters.
351    pub int_par: Vec<Slot<i32>>,
352    /// `\count` registers.
353    pub count: Vec<Slot<i32>>,
354    /// `\attribute` registers.
355    pub attr: Vec<Slot<i32>>,
356    /// Dimension parameters.
357    pub dimen_par: Vec<Slot<i32>>,
358    /// `\dimen` registers.
359    pub dimen: Vec<Slot<i32>>,
360    /// Glue and muglue parameters.
361    pub glue_par: Vec<Slot<Glue>>,
362    /// `\skip` registers.
363    pub skip: Vec<Slot<Glue>>,
364    /// `\muskip` registers.
365    pub muskip: Vec<Slot<Glue>>,
366    /// Token-list parameters.
367    pub toks_par: Vec<Slot<TokenList>>,
368    /// `\toks` registers.
369    pub toks: Vec<Slot<TokenList>>,
370    /// `\box` registers.
371    pub boxes: Vec<Slot<NodeRef>>,
372    /// The current font.
373    pub cur_font: Slot<u32>,
374    /// Math fonts, `size * NUM_FAMILIES + family`.
375    pub math_font: Vec<Slot<u32>>,
376    /// `\parshape`: pairs (indent, length) flattened.
377    pub par_shape: Slot<Option<Rc<[i32]>>>,
378    /// e-TeX penalty arrays (`\interlinepenalties`, …).
379    pub etex_shape: Vec<Slot<Option<Rc<[i32]>>>>,
380    /// Catcode tables, indexed by table number.
381    pub catcode_tables: HashMap<u32, CodeTable, crate::hash::FastHash>,
382    /// `\lccode`.
383    pub lc: CodeTable,
384    /// `\uccode`.
385    pub uc: CodeTable,
386    /// `\sfcode`.
387    pub sf: CodeTable,
388    /// `\mathcode`.
389    pub math: CodeTable,
390    /// `\delcode`.
391    pub del: CodeTable,
392    /// `\Umath…` parameters: `param * 8 + style`.
393    pub math_param: HashMap<u32, Slot<i32>>,
394    /// `\localleftbox`, `\localrightbox`.
395    pub local_box: [Slot<NodeRef>; 2],
396    /// Direction parameters (`\pagedir` …, as numbers 0–3).
397    pub dir_par: [Slot<i32>; pg::assign_dir::COUNT as usize],
398    /// `\nolocalwhatsits`, `\nolocaldirs`.
399    pub local_count: [Slot<i32>; 2],
400    /// Incremented whenever an attribute register changes.
401    pub attr_generation: u64,
402    /// Whether any attribute register was ever set.
403    pub attrs_in_use: bool,
404}
405
406impl Eqtb {
407    /// The initial table of IniTeX: everything undefined or zero, with
408    /// TeX's few non-zero defaults.
409    pub fn initex() -> Self {
410        use pg::assign_int as ip;
411        let regs_i = || vec![Slot::new(0); NUM_REGISTERS];
412        let mut int_par = vec![Slot::new(0i32); INT_PARS];
413        int_par[ip::TOLERANCE as usize].val = 10000;
414        int_par[ip::MAG as usize].val = 1000;
415        int_par[ip::MAXDEADCYCLES as usize].val = 25;
416        int_par[ip::ESCAPECHAR as usize].val = '\\' as i32;
417        int_par[crate::backend::MAJOR_VERSION as usize].val = 1;
418        int_par[ip::ENDLINECHAR as usize].val = 13;
419        let mut catcode_tables: HashMap<u32, CodeTable, crate::hash::FastHash> = HashMap::default();
420        catcode_tables.insert(0, CodeTable::new(initex_catcode));
421        Eqtb {
422            cs: Vec::new(),
423            int_par,
424            count: regs_i(),
425            attr: vec![Slot::new(UNUSED_ATTRIBUTE); NUM_REGISTERS],
426            dimen_par: vec![Slot::new(0); DIMEN_PARS],
427            dimen: regs_i(),
428            glue_par: vec![
429                Slot::new(Glue::ZERO);
430                (pg::assign_glue::COUNT + pg::assign_mu_glue::COUNT) as usize
431            ],
432            skip: vec![Slot::new(Glue::ZERO); NUM_REGISTERS],
433            muskip: vec![Slot::new(Glue::ZERO); NUM_REGISTERS],
434            toks_par: vec![Slot::new(empty_list()); TOKS_PARS],
435            toks: vec![Slot::new(empty_list()); NUM_REGISTERS],
436            boxes: vec![Slot::new(NodeRef::NULL); NUM_REGISTERS],
437            cur_font: Slot::new(0),
438            math_font: vec![Slot::new(0); 3 * NUM_FAMILIES],
439            par_shape: Slot::new(None),
440            etex_shape: vec![Slot::new(None); pg::set_etex_shape::COUNT as usize],
441            catcode_tables,
442            lc: CodeTable::new(crate::codes::initex_lccode),
443            uc: CodeTable::new(crate::codes::initex_uccode),
444            sf: CodeTable::new(crate::codes::initex_sfcode),
445            math: CodeTable::new(crate::codes::initex_mathcode),
446            del: CodeTable::new(crate::codes::initex_delcode),
447            math_param: HashMap::new(),
448            local_box: [Slot::new(NodeRef::NULL), Slot::new(NodeRef::NULL)],
449            dir_par: std::array::from_fn(|_| Slot::new(0)),
450            local_count: [Slot::new(0), Slot::new(0)],
451            attr_generation: 0,
452            attrs_in_use: false,
453        }
454    }
455
456    /// Makes sure `cs` has an entry.
457    #[inline]
458    pub fn ensure_cs(&mut self, cs: CsId) {
459        let n = cs.0 as usize + 1;
460        if self.cs.len() < n {
461            self.cs.resize(
462                n,
463                Slot {
464                    val: Meaning::UNDEFINED,
465                    level: LEVEL_ZERO,
466                },
467            );
468        }
469    }
470
471    /// The meaning of a control sequence.
472    #[inline]
473    pub fn meaning(&self, cs: CsId) -> &Meaning {
474        match self.cs.get(cs.0 as usize) {
475            Some(slot) => &slot.val,
476            None => &Meaning::UNDEFINED,
477        }
478    }
479
480    /// The integer parameter with index `i`.
481    #[inline]
482    pub fn int(&self, i: u32) -> i32 {
483        self.int_par[i as usize].val
484    }
485
486    /// The dimension parameter with index `i`.
487    #[inline]
488    pub fn dimen(&self, i: u32) -> i32 {
489        self.dimen_par[i as usize].val
490    }
491
492    /// The glue parameter with index `i`.
493    #[inline]
494    pub fn glue(&self, i: u32) -> Glue {
495        self.glue_par[i as usize].val
496    }
497
498    /// The token-list parameter with index `i`.
499    #[inline]
500    pub fn toks_param(&self, i: u32) -> &TokenList {
501        &self.toks_par[i as usize].val
502    }
503
504    /// The current catcode table number.
505    #[inline]
506    pub fn cur_cat_table(&self) -> u32 {
507        self.int(pg::assign_int::CATCODETABLE) as u32
508    }
509
510    /// The category code of `c` in the current catcode table.
511    #[inline]
512    pub fn catcode(&self, c: u32) -> u8 {
513        self.catcode_in(self.cur_cat_table(), c)
514    }
515
516    /// The category code of `c` in table `table`. Missing tables read as
517    /// IniTeX defaults.
518    pub fn catcode_in(&self, table: u32, c: u32) -> u8 {
519        match self.catcode_tables.get(&table) {
520            Some(t) => t.get(c) as u8,
521            None => initex_catcode(c) as u8,
522        }
523    }
524
525    /// Reads the value at `loc`.
526    pub fn get(&self, loc: Loc) -> Val {
527        match loc {
528            Loc::Cs(cs) => Val::Meaning(self.meaning(cs).clone()),
529            Loc::IntPar(i) => Val::Int(self.int_par[i as usize].val.into()),
530            Loc::Count(i) => Val::Int(self.count[i as usize].val.into()),
531            Loc::Attr(i) => Val::Int(self.attr[i as usize].val.into()),
532            Loc::DimenPar(i) => Val::Int(self.dimen_par[i as usize].val.into()),
533            Loc::Dimen(i) => Val::Int(self.dimen[i as usize].val.into()),
534            Loc::GluePar(i) => Val::Glue(self.glue_par[i as usize].val),
535            Loc::Skip(i) => Val::Glue(self.skip[i as usize].val),
536            Loc::MuSkip(i) => Val::Glue(self.muskip[i as usize].val),
537            Loc::ToksPar(i) => Val::Toks(self.toks_par[i as usize].val.clone()),
538            Loc::Toks(i) => Val::Toks(self.toks[i as usize].val.clone()),
539            Loc::Box(i) => Val::Node(self.boxes[i as usize].val),
540            Loc::CurFont => Val::Int(self.cur_font.val.into()),
541            Loc::MathFont(i) => Val::Int(self.math_font[i as usize].val.into()),
542            Loc::ParShape => Val::Shape(self.par_shape.val.clone()),
543            Loc::EtexShape(i) => Val::Shape(self.etex_shape[i as usize].val.clone()),
544            Loc::Cat { table, ch } => Val::Int(self.catcode_in(table, ch).into()),
545            Loc::Code(kind, ch) => Val::Int(self.code_table(kind).get(ch)),
546            Loc::MathParam(i) => Val::Int(
547                self.math_param
548                    .get(&i)
549                    .map_or(crate::codes::UNDEFINED_MATH_PARAM.into(), |s| s.val.into()),
550            ),
551            Loc::LocalBox(i) => Val::Node(self.local_box[i as usize].val),
552            Loc::DirPar(i) => Val::Int(self.dir_par[i as usize].val.into()),
553            Loc::LocalCount(i) => Val::Int(self.local_count[i as usize].val.into()),
554        }
555    }
556
557    /// Whether `loc` holds `val` already (TeX's test for "reassigning"),
558    /// without cloning the current value.
559    pub fn holds(&self, loc: Loc, val: &Val) -> bool {
560        match (loc, val) {
561            (Loc::Cs(cs), Val::Meaning(m)) => self.meaning(cs).same_as(m),
562            (Loc::IntPar(i), Val::Int(v)) => i64::from(self.int_par[i as usize].val) == *v,
563            (Loc::Count(i), Val::Int(v)) => i64::from(self.count[i as usize].val) == *v,
564            (Loc::Dimen(i), Val::Int(v)) => i64::from(self.dimen[i as usize].val) == *v,
565            (Loc::DimenPar(i), Val::Int(v)) => i64::from(self.dimen_par[i as usize].val) == *v,
566            (Loc::Code(kind, ch), Val::Int(v)) => self.code_table(kind).get(ch) == *v,
567            (Loc::ToksPar(i), Val::Toks(t)) => Rc::ptr_eq(&self.toks_par[i as usize].val, t),
568            (Loc::Toks(i), Val::Toks(t)) => Rc::ptr_eq(&self.toks[i as usize].val, t),
569            _ => same_val(&self.get(loc), val),
570        }
571    }
572
573    /// The level at which the value at `loc` was defined.
574    pub fn level(&self, loc: Loc) -> u16 {
575        match loc {
576            Loc::Cs(cs) => self.cs.get(cs.0 as usize).map_or(LEVEL_ZERO, |s| s.level),
577            Loc::IntPar(i) => self.int_par[i as usize].level,
578            Loc::Count(i) => self.count[i as usize].level,
579            Loc::Attr(i) => self.attr[i as usize].level,
580            Loc::DimenPar(i) => self.dimen_par[i as usize].level,
581            Loc::Dimen(i) => self.dimen[i as usize].level,
582            Loc::GluePar(i) => self.glue_par[i as usize].level,
583            Loc::Skip(i) => self.skip[i as usize].level,
584            Loc::MuSkip(i) => self.muskip[i as usize].level,
585            Loc::ToksPar(i) => self.toks_par[i as usize].level,
586            Loc::Toks(i) => self.toks[i as usize].level,
587            Loc::Box(i) => self.boxes[i as usize].level,
588            Loc::CurFont => self.cur_font.level,
589            Loc::MathFont(i) => self.math_font[i as usize].level,
590            Loc::ParShape => self.par_shape.level,
591            Loc::EtexShape(i) => self.etex_shape[i as usize].level,
592            Loc::Cat { table, ch } => self
593                .catcode_tables
594                .get(&table)
595                .map_or(LEVEL_ONE, |t| t.level(ch)),
596            Loc::Code(kind, ch) => self.code_table(kind).level(ch),
597            Loc::MathParam(i) => self.math_param.get(&i).map_or(LEVEL_ONE, |s| s.level),
598            Loc::LocalBox(i) => self.local_box[i as usize].level,
599            Loc::DirPar(i) => self.dir_par[i as usize].level,
600            Loc::LocalCount(i) => self.local_count[i as usize].level,
601        }
602    }
603
604    /// Stores `val` at `loc` with level `level`. Mismatched value kinds are
605    /// ignored (they indicate a bug and are caught in debug builds).
606    pub fn set(&mut self, loc: Loc, val: Val, level: u16) {
607        fn int(v: &Val) -> i64 {
608            match v {
609                Val::Int(i) => *i,
610                _ => {
611                    debug_assert!(false, "expected an integer value");
612                    0
613                }
614            }
615        }
616        match loc {
617            Loc::Cs(cs) => {
618                self.ensure_cs(cs);
619                if let Val::Meaning(m) = val {
620                    self.cs[cs.0 as usize] = Slot { val: m, level };
621                }
622            }
623            Loc::IntPar(i) => {
624                self.int_par[i as usize] = Slot {
625                    val: int(&val) as i32,
626                    level,
627                }
628            }
629            Loc::Count(i) => {
630                self.count[i as usize] = Slot {
631                    val: int(&val) as i32,
632                    level,
633                }
634            }
635            Loc::Attr(i) => {
636                self.attr[i as usize] = Slot {
637                    val: int(&val) as i32,
638                    level,
639                };
640                self.attr_generation += 1;
641                self.attrs_in_use = true;
642            }
643            Loc::DimenPar(i) => {
644                self.dimen_par[i as usize] = Slot {
645                    val: int(&val) as i32,
646                    level,
647                }
648            }
649            Loc::Dimen(i) => {
650                self.dimen[i as usize] = Slot {
651                    val: int(&val) as i32,
652                    level,
653                }
654            }
655            Loc::GluePar(i) | Loc::Skip(i) | Loc::MuSkip(i) => {
656                if let Val::Glue(g) = val {
657                    let slot = Slot { val: g, level };
658                    match loc {
659                        Loc::GluePar(_) => self.glue_par[i as usize] = slot,
660                        Loc::Skip(_) => self.skip[i as usize] = slot,
661                        _ => self.muskip[i as usize] = slot,
662                    }
663                }
664            }
665            Loc::ToksPar(i) | Loc::Toks(i) => {
666                if let Val::Toks(t) = val {
667                    let slot = Slot { val: t, level };
668                    if matches!(loc, Loc::ToksPar(_)) {
669                        self.toks_par[i as usize] = slot;
670                    } else {
671                        self.toks[i as usize] = slot;
672                    }
673                }
674            }
675            Loc::Box(i) => {
676                if let Val::Node(n) = val {
677                    self.boxes[i as usize] = Slot { val: n, level };
678                }
679            }
680            Loc::CurFont => {
681                self.cur_font = Slot {
682                    val: int(&val) as u32,
683                    level,
684                }
685            }
686            Loc::MathFont(i) => {
687                self.math_font[i as usize] = Slot {
688                    val: int(&val) as u32,
689                    level,
690                }
691            }
692            Loc::ParShape => {
693                if let Val::Shape(s) = val {
694                    self.par_shape = Slot { val: s, level };
695                }
696            }
697            Loc::EtexShape(i) => {
698                if let Val::Shape(s) = val {
699                    self.etex_shape[i as usize] = Slot { val: s, level };
700                }
701            }
702            Loc::Cat { table, ch } => {
703                self.catcode_tables
704                    .entry(table)
705                    .or_insert_with(|| CodeTable::new(initex_catcode))
706                    .set(ch, int(&val), level);
707            }
708            Loc::Code(kind, ch) => self.code_table_mut(kind).set(ch, int(&val), level),
709            Loc::MathParam(i) => {
710                self.math_param.insert(
711                    i,
712                    Slot {
713                        val: int(&val) as i32,
714                        level,
715                    },
716                );
717            }
718            Loc::LocalBox(i) => {
719                if let Val::Node(n) = val {
720                    self.local_box[i as usize] = Slot { val: n, level };
721                }
722            }
723            Loc::DirPar(i) => {
724                self.dir_par[i as usize] = Slot {
725                    val: int(&val) as i32,
726                    level,
727                }
728            }
729            Loc::LocalCount(i) => {
730                self.local_count[i as usize] = Slot {
731                    val: int(&val) as i32,
732                    level,
733                }
734            }
735        }
736    }
737
738    /// The code table of the given kind.
739    pub fn code_table(&self, kind: CodeKind) -> &CodeTable {
740        match kind {
741            CodeKind::Lc => &self.lc,
742            CodeKind::Uc => &self.uc,
743            CodeKind::Sf => &self.sf,
744            CodeKind::Math => &self.math,
745            CodeKind::Del => &self.del,
746        }
747    }
748
749    pub(crate) fn code_table_mut(&mut self, kind: CodeKind) -> &mut CodeTable {
750        match kind {
751            CodeKind::Lc => &mut self.lc,
752            CodeKind::Uc => &mut self.uc,
753            CodeKind::Sf => &mut self.sf,
754            CodeKind::Math => &mut self.math,
755            CodeKind::Del => &mut self.del,
756        }
757    }
758
759    /// Whether the location holds a "word" quantity (TeX regions 5–6:
760    /// integers and dimensions, which use `xeq_level` semantics) as opposed
761    /// to an "equivalent" (regions 1–4).
762    pub fn is_word(loc: Loc) -> bool {
763        matches!(
764            loc,
765            Loc::IntPar(_)
766                | Loc::Count(_)
767                | Loc::Attr(_)
768                | Loc::DimenPar(_)
769                | Loc::Dimen(_)
770                | Loc::MathParam(_)
771        )
772    }
773}
774
775/// Compares two saved values for TeX's "reassigning" test.
776pub fn same_val(a: &Val, b: &Val) -> bool {
777    match (a, b) {
778        (Val::Meaning(x), Val::Meaning(y)) => x.same_as(y),
779        (Val::Int(x), Val::Int(y)) => x == y,
780        (Val::Glue(x), Val::Glue(y)) => x == y,
781        (Val::Toks(x), Val::Toks(y)) => Rc::ptr_eq(x, y),
782        (Val::Node(x), Val::Node(y)) => x == y,
783        (Val::Shape(x), Val::Shape(y)) => match (x, y) {
784            (None, None) => true,
785            (Some(p), Some(q)) => Rc::ptr_eq(p, q),
786            _ => false,
787        },
788        _ => false,
789    }
790}