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