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        if self.figures.recording() {
238            self.figures.global(loc);
239        }
240        self.eqtb.set(loc, val, LEVEL_ONE);
241        if tracing {
242            self.restore_trace(loc, "into");
243        }
244    }
245
246    /// Defines `loc` locally or globally.
247    pub fn define(&mut self, global: bool, loc: Loc, val: Val) {
248        if global {
249            self.geq_define(loc, val);
250        } else {
251            self.eq_define(loc, val);
252        }
253    }
254
255    /// Saves a token for insertion at the end of the group
256    /// (`save_for_after`).
257    pub fn save_for_after(&mut self, t: Token) {
258        if self.save.cur_level > LEVEL_ONE {
259            self.save.stack.push(SaveEntry::InsertToken(t));
260        }
261    }
262
263    /// Ends the current group (`unsave`).
264    pub fn unsave(&mut self) {
265        if self.save.cur_level <= LEVEL_ONE {
266            self.confusion("curlevel");
267        }
268        self.save.cur_level -= 1;
269        self.drop_text_dirs_above(self.save.cur_level);
270        let tracing = self.eqtb.int(ip::TRACINGRESTORES) > 0;
271        // \aftergroup tokens are collected and reinserted as one list, in
272        // the order they were saved (e-TeX).
273        let mut after: Vec<Token> = Vec::new();
274        loop {
275            let Some(entry) = self.save.stack.pop() else {
276                self.confusion("unsave");
277            };
278            match entry {
279                SaveEntry::Boundary {
280                    group,
281                    boundary,
282                    line,
283                } => {
284                    if self.eqtb.int(ip::TRACINGGROUPS) > 0 {
285                        // Trace with the ending group's state still current.
286                        self.save.stack.push(SaveEntry::Boundary {
287                            group,
288                            boundary,
289                            line,
290                        });
291                        self.group_trace(true);
292                        self.save.stack.pop();
293                    }
294                    self.group_warning();
295                    self.save.cur_group = group;
296                    self.save.cur_boundary = boundary;
297                    if self.figures.busy() {
298                        self.check_figure_end(!after.is_empty());
299                    }
300                    if !after.is_empty() {
301                        after.reverse();
302                        self.back_list(std::rc::Rc::from(after));
303                    }
304                    break;
305                }
306                SaveEntry::InsertToken(t) => after.push(t),
307                SaveEntry::Restore { loc, old, level } => {
308                    if self.eqtb.level(loc) == LEVEL_ONE {
309                        // The saved value is discarded (`eq_destroy`).
310                        if let (Loc::Box(n), Val::Node(b)) = (loc, &old)
311                            && !b.is_null()
312                            && *b != self.eqtb.boxes[n as usize].val
313                        {
314                            self.nodes.flush_list(*b);
315                        }
316                        if tracing {
317                            self.restore_trace(loc, "retaining");
318                        }
319                    } else {
320                        // The current value is discarded (`eq_destroy`).
321                        if let (Loc::Box(n), Val::Node(b)) = (loc, &old) {
322                            let cur = self.eqtb.boxes[n as usize].val;
323                            if !cur.is_null() && cur != *b {
324                                self.nodes.flush_list(cur);
325                            }
326                        }
327                        self.eqtb.set(loc, old, level);
328                        // Parameters with side effects (\newlinechar is
329                        // cached by the printer) follow restores too.
330                        if let Loc::IntPar(i) = loc {
331                            self.int_param_changed(i);
332                        }
333                        if tracing {
334                            self.restore_trace(loc, "restoring");
335                        }
336                    }
337                }
338                SaveEntry::Value(_) => {}
339                SaveEntry::RestoreCodes { kind, old, upto } => {
340                    self.eqtb.code_table_mut(kind).restore_identity(&old, upto);
341                }
342            }
343        }
344    }
345
346    /// The line number saved with the current group boundary.
347    pub fn group_line(&self) -> i32 {
348        match self.save.stack.get(self.save.cur_boundary) {
349            Some(SaveEntry::Boundary { line, .. }) => *line,
350            _ => 0,
351        }
352    }
353
354    /// Prints a description of the current group (`print_group`).
355    pub fn print_group(&mut self, e: bool) {
356        let p = &mut self.printer;
357        match self.save.cur_group {
358            GroupCode::BottomLevel => {
359                p.print_str("bottom level");
360                return;
361            }
362            GroupCode::Simple | GroupCode::SemiSimple => {
363                if self.save.cur_group == GroupCode::SemiSimple {
364                    p.print_str("semi ");
365                }
366                p.print_str("simple");
367            }
368            GroupCode::Hbox | GroupCode::AdjustedHbox => {
369                if self.save.cur_group == GroupCode::AdjustedHbox {
370                    p.print_str("adjusted ");
371                }
372                p.print_str("hbox");
373            }
374            GroupCode::Vbox => p.print_str("vbox"),
375            GroupCode::Vtop => p.print_str("vtop"),
376            GroupCode::Align | GroupCode::NoAlign => {
377                if self.save.cur_group == GroupCode::NoAlign {
378                    p.print_str("no ");
379                }
380                p.print_str("align");
381            }
382            GroupCode::Output => p.print_str("output"),
383            GroupCode::Disc => p.print_str("disc"),
384            GroupCode::Insert => p.print_str("insert"),
385            GroupCode::Vcenter => p.print_str("vcenter"),
386            GroupCode::Math
387            | GroupCode::MathChoice
388            | GroupCode::MathShift
389            | GroupCode::MathLeft => {
390                p.print_str("math");
391                match self.save.cur_group {
392                    GroupCode::MathChoice => p.print_str(" choice"),
393                    GroupCode::MathShift => p.print_str(" shift"),
394                    GroupCode::MathLeft => p.print_str(" left"),
395                    _ => {}
396                }
397            }
398            GroupCode::LocalBox => p.print_str("local box"),
399            GroupCode::SplitOff => p.print_str("split off"),
400            GroupCode::SplitKeep => p.print_str("split keep"),
401            GroupCode::Preamble => p.print_str("preamble"),
402            GroupCode::AlignSet => p.print_str("align set"),
403            GroupCode::FinRow => p.print_str("fin row"),
404        }
405        let level = self.save.cur_level;
406        let line = self.group_line();
407        let p = &mut self.printer;
408        p.print_str(" group (level ");
409        p.print_int(i64::from(level));
410        p.print_char(')' as u32);
411        if line != 0 {
412            p.print_str(if e { " entered at line " } else { " at line " });
413            p.print_int(line.into());
414        }
415    }
416
417    /// Traces entering or leaving a group (`group_trace`).
418    fn group_trace(&mut self, e: bool) {
419        self.begin_diagnostic();
420        self.printer.print_char('{' as u32);
421        self.printer
422            .print_str(if e { "leaving " } else { "entering " });
423        self.print_group(e);
424        self.printer.print_char('}' as u32);
425        self.end_diagnostic(false);
426    }
427
428    /// Traces an assignment or restoration (`restore_trace`). Observed:
429    /// LuaTeX does not trace its character code tables (\catcode,
430    /// \lccode, \uccode, \sfcode, \mathcode, \delcode).
431    pub fn restore_trace(&mut self, loc: Loc, s: &str) {
432        if matches!(loc, Loc::Cat { .. } | Loc::Code(..)) {
433            return;
434        }
435        self.begin_diagnostic();
436        self.printer.print_char('{' as u32);
437        self.printer.print_str(s);
438        self.printer.print_char(' ' as u32);
439        self.show_eqtb(loc);
440        self.printer.print_char('}' as u32);
441        self.end_diagnostic(false);
442    }
443
444    /// e-TeX's warning when a group ends in a different file than it began
445    /// (`group_warning`). Only active with `\tracingnesting > 0`.
446    fn group_warning(&mut self) {
447        // Implemented together with file-level nesting checks.
448    }
449}