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