Skip to main content

retex_engine/
save.rs

1//! Grouping and the save stack (*TeX: The Program* §268–§284, with the
2//! e-TeX tracing extensions).
3
4use crate::Engine;
5use crate::eqtb::{CodeKind, Eqtb, LEVEL_ONE, LEVEL_ZERO, Loc, Meaning, Val};
6use crate::primitives_gen::assign_int as ip;
7use crate::token::Token;
8
9/// A group code (`cur_group`), numbered as `\currentgrouptype` reports.
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11#[repr(u8)]
12pub enum GroupCode {
13    /// The outermost level.
14    BottomLevel = 0,
15    /// `{…}`.
16    Simple = 1,
17    /// `\hbox{…}`.
18    Hbox = 2,
19    /// `\hbox{…}` in vertical mode (adjusted).
20    AdjustedHbox = 3,
21    /// `\vbox{…}`.
22    Vbox = 4,
23    /// `\vtop{…}`.
24    Vtop = 5,
25    /// `\halign{…}`, `\valign{…}`.
26    Align = 6,
27    /// `\noalign{…}`.
28    NoAlign = 7,
29    /// The output routine.
30    Output = 8,
31    /// Math subformula.
32    Math = 9,
33    /// `\discretionary{…}{…}{…}`.
34    Disc = 10,
35    /// `\insert`, `\vadjust`.
36    Insert = 11,
37    /// `\vcenter{…}`.
38    Vcenter = 12,
39    /// `\mathchoice{…}…`.
40    MathChoice = 13,
41    /// `\begingroup…\endgroup`.
42    SemiSimple = 14,
43    /// `$…$`.
44    MathShift = 15,
45    /// `\left…\right`.
46    MathLeft = 16,
47    /// `\localleftbox{…}`, `\localrightbox{…}` (LuaTeX).
48    LocalBox = 17,
49    /// `\vsplit … to` (LuaTeX internal).
50    SplitOff = 18,
51    /// LuaTeX internal.
52    SplitKeep = 19,
53    /// LuaTeX internal: alignment preamble.
54    Preamble = 20,
55    /// LuaTeX internal.
56    AlignSet = 21,
57    /// LuaTeX internal.
58    FinRow = 22,
59}
60
61/// An entry of the save stack.
62#[derive(Clone, Debug)]
63pub enum SaveEntry {
64    /// Restore `loc` to `old`, defined at `level`.
65    Restore {
66        /// The location.
67        loc: Loc,
68        /// The saved value.
69        old: Val,
70        /// The level of the saved value.
71        level: u16,
72    },
73    /// Insert a token when the group ends (`\aftergroup`).
74    InsertToken(Token),
75    /// A group boundary.
76    Boundary {
77        /// The group code of the enclosing group.
78        group: GroupCode,
79        /// The enclosing group's boundary index.
80        boundary: usize,
81        /// The line where the group started (e-TeX `saved(-1)`).
82        line: i32,
83    },
84    /// An auxiliary saved value (TeX's `saved(k)`).
85    Value(i64),
86    /// The bulk form of the `Restore` entries of `\lccode c = c` (or
87    /// another code table) for every `c < upto`: see
88    /// [`Engine::define_codes_identity`].
89    RestoreCodes {
90        /// The table.
91        kind: CodeKind,
92        /// The table before the assignments.
93        old: Box<crate::eqtb::CodeTable>,
94        /// The characters assigned are `0..upto`.
95        upto: u32,
96    },
97}
98
99/// The save stack and grouping state.
100#[derive(Debug, Clone)]
101pub struct SaveStack {
102    /// The stack entries.
103    pub stack: Vec<SaveEntry>,
104    /// Current group level (`cur_level`).
105    pub cur_level: u16,
106    /// Current group code (`cur_group`).
107    pub cur_group: GroupCode,
108    /// Index of the current group's boundary entry (`cur_boundary`).
109    pub cur_boundary: usize,
110}
111
112impl Default for SaveStack {
113    fn default() -> Self {
114        SaveStack {
115            stack: Vec::new(),
116            cur_level: LEVEL_ONE,
117            cur_group: GroupCode::BottomLevel,
118            cur_boundary: 0,
119        }
120    }
121}
122
123impl Engine {
124    /// Begins a new group (`new_save_level`).
125    pub fn new_save_level(&mut self, c: GroupCode) {
126        let line = self.input.line();
127        let entry = SaveEntry::Boundary {
128            group: self.save.cur_group,
129            boundary: self.save.cur_boundary,
130            line,
131        };
132        if self.save.cur_level == u16::MAX {
133            self.overflow("grouping levels", i64::from(u16::MAX - 1));
134        }
135        self.save.cur_boundary = self.save.stack.len();
136        self.save.stack.push(entry);
137        self.save.cur_group = c;
138        if self.eqtb.int(ip::TRACINGGROUPS) > 0 {
139            self.group_trace(false);
140        }
141        self.save.cur_level += 1;
142    }
143
144    /// Saves the value at `loc` before it changes (`eq_save`).
145    fn eq_save(&mut self, loc: Loc, level: u16) {
146        let old = if level == LEVEL_ZERO {
147            Val::Meaning(Meaning::UNDEFINED)
148        } else {
149            self.eqtb.get(loc)
150        };
151        self.save.stack.push(SaveEntry::Restore { loc, old, level });
152    }
153
154    /// Defines `loc` locally (`eq_define` / `eq_word_define`).
155    pub fn eq_define(&mut self, loc: Loc, val: Val) {
156        let tracing = self.eqtb.int(ip::TRACINGASSIGNS) > 0;
157        // Fast paths for the most common locations, when no save-stack
158        // entry is needed.
159        if !tracing {
160            let cur = self.save.cur_level;
161            match (loc, &val) {
162                (Loc::Code(kind, ch), Val::Int(v)) => {
163                    if self.eqtb.code_table_mut(kind).define_in_place(ch, *v, cur) {
164                        return;
165                    }
166                }
167                (Loc::Count(i), Val::Int(v)) => {
168                    let s = &mut self.eqtb.count[i as usize];
169                    if i64::from(s.val) == *v {
170                        return;
171                    }
172                    if s.level == cur || cur == LEVEL_ONE {
173                        s.val = *v as i32;
174                        s.level = cur;
175                        return;
176                    }
177                }
178                (Loc::Cs(cs), Val::Meaning(m)) if (cs.0 as usize) < self.eqtb.cs.len() => {
179                    let s = &self.eqtb.cs[cs.0 as usize];
180                    if s.val.same_as(m) {
181                        return;
182                    }
183                    if s.level == cur || cur == LEVEL_ONE {
184                        let Val::Meaning(m) = val else { unreachable!() };
185                        self.eqtb.cs[cs.0 as usize] = crate::eqtb::Slot { val: m, level: cur };
186                        return;
187                    }
188                }
189                _ => {}
190            }
191        }
192        if self.eqtb.holds(loc, &val) {
193            if tracing {
194                self.restore_trace(loc, "reassigning");
195            }
196            return;
197        }
198        if tracing {
199            self.restore_trace(loc, "changing");
200        }
201        let cur = self.save.cur_level;
202        let level = self.eqtb.level(loc);
203        if level != cur && cur > LEVEL_ONE {
204            self.eq_save(loc, level);
205        } else if Eqtb::is_word(loc) && level != cur {
206            // Word regions save whenever the level differs (cur_level is
207            // level_one here, so no save entry is needed).
208        }
209        self.eqtb.set(loc, val, cur);
210        if tracing {
211            self.restore_trace(loc, "into");
212        }
213    }
214
215    /// Local `\lccode c = c` (or the like for `kind`) for every `c <
216    /// upto`, with the save-stack effect of the individual assignments:
217    /// for natives. Only inside a group (above level one), with no
218    /// `\globaldefs` and no tracing, before any other assignment to the
219    /// table in this group.
220    pub fn define_codes_identity(&mut self, kind: CodeKind, upto: u32) {
221        let cur = self.save.cur_level;
222        debug_assert!(cur > LEVEL_ONE);
223        let old = self.eqtb.code_table_mut(kind).define_identity(upto, cur);
224        self.save.stack.push(SaveEntry::RestoreCodes {
225            kind,
226            old: Box::new(old),
227            upto,
228        });
229    }
230
231    /// Defines `loc` globally (`geq_define` / `geq_word_define`).
232    pub fn geq_define(&mut self, loc: Loc, val: Val) {
233        let tracing = self.eqtb.int(ip::TRACINGASSIGNS) > 0;
234        if tracing {
235            self.restore_trace(loc, "globally changing");
236        }
237        self.eqtb.set(loc, val, LEVEL_ONE);
238        if tracing {
239            self.restore_trace(loc, "into");
240        }
241    }
242
243    /// Defines `loc` locally or globally.
244    pub fn define(&mut self, global: bool, loc: Loc, val: Val) {
245        if global {
246            self.geq_define(loc, val);
247        } else {
248            self.eq_define(loc, val);
249        }
250    }
251
252    /// Saves a token for insertion at the end of the group
253    /// (`save_for_after`).
254    pub fn save_for_after(&mut self, t: Token) {
255        if self.save.cur_level > LEVEL_ONE {
256            self.save.stack.push(SaveEntry::InsertToken(t));
257        }
258    }
259
260    /// Ends the current group (`unsave`).
261    pub fn unsave(&mut self) {
262        if self.save.cur_level <= LEVEL_ONE {
263            self.confusion("curlevel");
264        }
265        self.save.cur_level -= 1;
266        self.drop_text_dirs_above(self.save.cur_level);
267        let tracing = self.eqtb.int(ip::TRACINGRESTORES) > 0;
268        // \aftergroup tokens are collected and reinserted as one list, in
269        // the order they were saved (e-TeX).
270        let mut after: Vec<Token> = Vec::new();
271        loop {
272            let Some(entry) = self.save.stack.pop() else {
273                self.confusion("unsave");
274            };
275            match entry {
276                SaveEntry::Boundary {
277                    group,
278                    boundary,
279                    line,
280                } => {
281                    if self.eqtb.int(ip::TRACINGGROUPS) > 0 {
282                        // Trace with the ending group's state still current.
283                        self.save.stack.push(SaveEntry::Boundary {
284                            group,
285                            boundary,
286                            line,
287                        });
288                        self.group_trace(true);
289                        self.save.stack.pop();
290                    }
291                    self.group_warning();
292                    self.save.cur_group = group;
293                    self.save.cur_boundary = boundary;
294                    if !after.is_empty() {
295                        after.reverse();
296                        self.back_list(std::rc::Rc::from(after));
297                    }
298                    break;
299                }
300                SaveEntry::InsertToken(t) => after.push(t),
301                SaveEntry::Restore { loc, old, level } => {
302                    if self.eqtb.level(loc) == LEVEL_ONE {
303                        // The saved value is discarded (`eq_destroy`).
304                        if let (Loc::Box(n), Val::Node(b)) = (loc, &old)
305                            && !b.is_null()
306                            && *b != self.eqtb.boxes[n as usize].val
307                        {
308                            self.nodes.flush_list(*b);
309                        }
310                        if tracing {
311                            self.restore_trace(loc, "retaining");
312                        }
313                    } else {
314                        // The current value is discarded (`eq_destroy`).
315                        if let (Loc::Box(n), Val::Node(b)) = (loc, &old) {
316                            let cur = self.eqtb.boxes[n as usize].val;
317                            if !cur.is_null() && cur != *b {
318                                self.nodes.flush_list(cur);
319                            }
320                        }
321                        self.eqtb.set(loc, old, level);
322                        // Parameters with side effects (\newlinechar is
323                        // cached by the printer) follow restores too.
324                        if let Loc::IntPar(i) = loc {
325                            self.int_param_changed(i);
326                        }
327                        if tracing {
328                            self.restore_trace(loc, "restoring");
329                        }
330                    }
331                }
332                SaveEntry::Value(_) => {}
333                SaveEntry::RestoreCodes { kind, old, upto } => {
334                    self.eqtb.code_table_mut(kind).restore_identity(&old, upto);
335                }
336            }
337        }
338    }
339
340    /// The line number saved with the current group boundary.
341    pub fn group_line(&self) -> i32 {
342        match self.save.stack.get(self.save.cur_boundary) {
343            Some(SaveEntry::Boundary { line, .. }) => *line,
344            _ => 0,
345        }
346    }
347
348    /// Prints a description of the current group (`print_group`).
349    pub fn print_group(&mut self, e: bool) {
350        let p = &mut self.printer;
351        match self.save.cur_group {
352            GroupCode::BottomLevel => {
353                p.print_str("bottom level");
354                return;
355            }
356            GroupCode::Simple | GroupCode::SemiSimple => {
357                if self.save.cur_group == GroupCode::SemiSimple {
358                    p.print_str("semi ");
359                }
360                p.print_str("simple");
361            }
362            GroupCode::Hbox | GroupCode::AdjustedHbox => {
363                if self.save.cur_group == GroupCode::AdjustedHbox {
364                    p.print_str("adjusted ");
365                }
366                p.print_str("hbox");
367            }
368            GroupCode::Vbox => p.print_str("vbox"),
369            GroupCode::Vtop => p.print_str("vtop"),
370            GroupCode::Align | GroupCode::NoAlign => {
371                if self.save.cur_group == GroupCode::NoAlign {
372                    p.print_str("no ");
373                }
374                p.print_str("align");
375            }
376            GroupCode::Output => p.print_str("output"),
377            GroupCode::Disc => p.print_str("disc"),
378            GroupCode::Insert => p.print_str("insert"),
379            GroupCode::Vcenter => p.print_str("vcenter"),
380            GroupCode::Math
381            | GroupCode::MathChoice
382            | GroupCode::MathShift
383            | GroupCode::MathLeft => {
384                p.print_str("math");
385                match self.save.cur_group {
386                    GroupCode::MathChoice => p.print_str(" choice"),
387                    GroupCode::MathShift => p.print_str(" shift"),
388                    GroupCode::MathLeft => p.print_str(" left"),
389                    _ => {}
390                }
391            }
392            GroupCode::LocalBox => p.print_str("local box"),
393            GroupCode::SplitOff => p.print_str("split off"),
394            GroupCode::SplitKeep => p.print_str("split keep"),
395            GroupCode::Preamble => p.print_str("preamble"),
396            GroupCode::AlignSet => p.print_str("align set"),
397            GroupCode::FinRow => p.print_str("fin row"),
398        }
399        let level = self.save.cur_level;
400        let line = self.group_line();
401        let p = &mut self.printer;
402        p.print_str(" group (level ");
403        p.print_int(i64::from(level));
404        p.print_char(')' as u32);
405        if line != 0 {
406            p.print_str(if e { " entered at line " } else { " at line " });
407            p.print_int(line.into());
408        }
409    }
410
411    /// Traces entering or leaving a group (`group_trace`).
412    fn group_trace(&mut self, e: bool) {
413        self.begin_diagnostic();
414        self.printer.print_char('{' as u32);
415        self.printer
416            .print_str(if e { "leaving " } else { "entering " });
417        self.print_group(e);
418        self.printer.print_char('}' as u32);
419        self.end_diagnostic(false);
420    }
421
422    /// Traces an assignment or restoration (`restore_trace`). Observed:
423    /// LuaTeX does not trace its character code tables (\catcode,
424    /// \lccode, \uccode, \sfcode, \mathcode, \delcode).
425    pub fn restore_trace(&mut self, loc: Loc, s: &str) {
426        if matches!(loc, Loc::Cat { .. } | Loc::Code(..)) {
427            return;
428        }
429        self.begin_diagnostic();
430        self.printer.print_char('{' as u32);
431        self.printer.print_str(s);
432        self.printer.print_char(' ' as u32);
433        self.show_eqtb(loc);
434        self.printer.print_char('}' as u32);
435        self.end_diagnostic(false);
436    }
437
438    /// e-TeX's warning when a group ends in a different file than it began
439    /// (`group_warning`). Only active with `\tracingnesting > 0`.
440    fn group_warning(&mut self) {
441        // Implemented together with file-level nesting checks.
442    }
443}