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::{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}
87
88/// The save stack and grouping state.
89#[derive(Debug, Clone)]
90pub struct SaveStack {
91    /// The stack entries.
92    pub stack: Vec<SaveEntry>,
93    /// Current group level (`cur_level`).
94    pub cur_level: u16,
95    /// Current group code (`cur_group`).
96    pub cur_group: GroupCode,
97    /// Index of the current group's boundary entry (`cur_boundary`).
98    pub cur_boundary: usize,
99}
100
101impl Default for SaveStack {
102    fn default() -> Self {
103        SaveStack {
104            stack: Vec::new(),
105            cur_level: LEVEL_ONE,
106            cur_group: GroupCode::BottomLevel,
107            cur_boundary: 0,
108        }
109    }
110}
111
112impl Engine {
113    /// Begins a new group (`new_save_level`).
114    pub fn new_save_level(&mut self, c: GroupCode) {
115        let line = self.input.line();
116        let entry = SaveEntry::Boundary {
117            group: self.save.cur_group,
118            boundary: self.save.cur_boundary,
119            line,
120        };
121        if self.save.cur_level == u16::MAX {
122            self.overflow("grouping levels", i64::from(u16::MAX - 1));
123        }
124        self.save.cur_boundary = self.save.stack.len();
125        self.save.stack.push(entry);
126        self.save.cur_group = c;
127        if self.eqtb.int(ip::TRACINGGROUPS) > 0 {
128            self.group_trace(false);
129        }
130        self.save.cur_level += 1;
131    }
132
133    /// Saves the value at `loc` before it changes (`eq_save`).
134    fn eq_save(&mut self, loc: Loc, level: u16) {
135        let old = if level == LEVEL_ZERO {
136            Val::Meaning(Meaning::UNDEFINED)
137        } else {
138            self.eqtb.get(loc)
139        };
140        self.save.stack.push(SaveEntry::Restore { loc, old, level });
141    }
142
143    /// Defines `loc` locally (`eq_define` / `eq_word_define`).
144    pub fn eq_define(&mut self, loc: Loc, val: Val) {
145        let tracing = self.eqtb.int(ip::TRACINGASSIGNS) > 0;
146        // Fast paths for the most common locations, when no save-stack
147        // entry is needed.
148        if !tracing {
149            let cur = self.save.cur_level;
150            match (loc, &val) {
151                (Loc::Code(kind, ch), Val::Int(v)) => {
152                    if self.eqtb.code_table_mut(kind).define_in_place(ch, *v, cur) {
153                        return;
154                    }
155                }
156                (Loc::Count(i), Val::Int(v)) => {
157                    let s = &mut self.eqtb.count[i as usize];
158                    if i64::from(s.val) == *v {
159                        return;
160                    }
161                    if s.level == cur || cur == LEVEL_ONE {
162                        s.val = *v as i32;
163                        s.level = cur;
164                        return;
165                    }
166                }
167                (Loc::Cs(cs), Val::Meaning(m)) if (cs.0 as usize) < self.eqtb.cs.len() => {
168                    let s = &self.eqtb.cs[cs.0 as usize];
169                    if s.val.same_as(m) {
170                        return;
171                    }
172                    if s.level == cur || cur == LEVEL_ONE {
173                        let Val::Meaning(m) = val else { unreachable!() };
174                        self.eqtb.cs[cs.0 as usize] = crate::eqtb::Slot { val: m, level: cur };
175                        return;
176                    }
177                }
178                _ => {}
179            }
180        }
181        if self.eqtb.holds(loc, &val) {
182            if tracing {
183                self.restore_trace(loc, "reassigning");
184            }
185            return;
186        }
187        if tracing {
188            self.restore_trace(loc, "changing");
189        }
190        let cur = self.save.cur_level;
191        let level = self.eqtb.level(loc);
192        if level != cur && cur > LEVEL_ONE {
193            self.eq_save(loc, level);
194        } else if Eqtb::is_word(loc) && level != cur {
195            // Word regions save whenever the level differs (cur_level is
196            // level_one here, so no save entry is needed).
197        }
198        self.eqtb.set(loc, val, cur);
199        if tracing {
200            self.restore_trace(loc, "into");
201        }
202    }
203
204    /// Defines `loc` globally (`geq_define` / `geq_word_define`).
205    pub fn geq_define(&mut self, loc: Loc, val: Val) {
206        let tracing = self.eqtb.int(ip::TRACINGASSIGNS) > 0;
207        if tracing {
208            self.restore_trace(loc, "globally changing");
209        }
210        self.eqtb.set(loc, val, LEVEL_ONE);
211        if tracing {
212            self.restore_trace(loc, "into");
213        }
214    }
215
216    /// Defines `loc` locally or globally.
217    pub fn define(&mut self, global: bool, loc: Loc, val: Val) {
218        if global {
219            self.geq_define(loc, val);
220        } else {
221            self.eq_define(loc, val);
222        }
223    }
224
225    /// Saves a token for insertion at the end of the group
226    /// (`save_for_after`).
227    pub fn save_for_after(&mut self, t: Token) {
228        if self.save.cur_level > LEVEL_ONE {
229            self.save.stack.push(SaveEntry::InsertToken(t));
230        }
231    }
232
233    /// Ends the current group (`unsave`).
234    pub fn unsave(&mut self) {
235        if self.save.cur_level <= LEVEL_ONE {
236            self.confusion("curlevel");
237        }
238        self.save.cur_level -= 1;
239        self.drop_text_dirs_above(self.save.cur_level);
240        let tracing = self.eqtb.int(ip::TRACINGRESTORES) > 0;
241        // \aftergroup tokens are collected and reinserted as one list, in
242        // the order they were saved (e-TeX).
243        let mut after: Vec<Token> = Vec::new();
244        loop {
245            let Some(entry) = self.save.stack.pop() else {
246                self.confusion("unsave");
247            };
248            match entry {
249                SaveEntry::Boundary {
250                    group,
251                    boundary,
252                    line,
253                } => {
254                    if self.eqtb.int(ip::TRACINGGROUPS) > 0 {
255                        // Trace with the ending group's state still current.
256                        self.save.stack.push(SaveEntry::Boundary {
257                            group,
258                            boundary,
259                            line,
260                        });
261                        self.group_trace(true);
262                        self.save.stack.pop();
263                    }
264                    self.group_warning();
265                    self.save.cur_group = group;
266                    self.save.cur_boundary = boundary;
267                    if !after.is_empty() {
268                        after.reverse();
269                        self.back_list(std::rc::Rc::from(after));
270                    }
271                    break;
272                }
273                SaveEntry::InsertToken(t) => after.push(t),
274                SaveEntry::Restore { loc, old, level } => {
275                    if self.eqtb.level(loc) == LEVEL_ONE {
276                        // The saved value is discarded (`eq_destroy`).
277                        if let (Loc::Box(n), Val::Node(b)) = (loc, &old)
278                            && !b.is_null()
279                            && *b != self.eqtb.boxes[n as usize].val
280                        {
281                            self.nodes.flush_list(*b);
282                        }
283                        if tracing {
284                            self.restore_trace(loc, "retaining");
285                        }
286                    } else {
287                        // The current value is discarded (`eq_destroy`).
288                        if let (Loc::Box(n), Val::Node(b)) = (loc, &old) {
289                            let cur = self.eqtb.boxes[n as usize].val;
290                            if !cur.is_null() && cur != *b {
291                                self.nodes.flush_list(cur);
292                            }
293                        }
294                        self.eqtb.set(loc, old, level);
295                        if tracing {
296                            self.restore_trace(loc, "restoring");
297                        }
298                    }
299                }
300                SaveEntry::Value(_) => {}
301            }
302        }
303    }
304
305    /// The line number saved with the current group boundary.
306    pub fn group_line(&self) -> i32 {
307        match self.save.stack.get(self.save.cur_boundary) {
308            Some(SaveEntry::Boundary { line, .. }) => *line,
309            _ => 0,
310        }
311    }
312
313    /// Prints a description of the current group (`print_group`).
314    pub fn print_group(&mut self, e: bool) {
315        let p = &mut self.printer;
316        match self.save.cur_group {
317            GroupCode::BottomLevel => {
318                p.print_str("bottom level");
319                return;
320            }
321            GroupCode::Simple | GroupCode::SemiSimple => {
322                if self.save.cur_group == GroupCode::SemiSimple {
323                    p.print_str("semi ");
324                }
325                p.print_str("simple");
326            }
327            GroupCode::Hbox | GroupCode::AdjustedHbox => {
328                if self.save.cur_group == GroupCode::AdjustedHbox {
329                    p.print_str("adjusted ");
330                }
331                p.print_str("hbox");
332            }
333            GroupCode::Vbox => p.print_str("vbox"),
334            GroupCode::Vtop => p.print_str("vtop"),
335            GroupCode::Align | GroupCode::NoAlign => {
336                if self.save.cur_group == GroupCode::NoAlign {
337                    p.print_str("no ");
338                }
339                p.print_str("align");
340            }
341            GroupCode::Output => p.print_str("output"),
342            GroupCode::Disc => p.print_str("disc"),
343            GroupCode::Insert => p.print_str("insert"),
344            GroupCode::Vcenter => p.print_str("vcenter"),
345            GroupCode::Math
346            | GroupCode::MathChoice
347            | GroupCode::MathShift
348            | GroupCode::MathLeft => {
349                p.print_str("math");
350                match self.save.cur_group {
351                    GroupCode::MathChoice => p.print_str(" choice"),
352                    GroupCode::MathShift => p.print_str(" shift"),
353                    GroupCode::MathLeft => p.print_str(" left"),
354                    _ => {}
355                }
356            }
357            GroupCode::LocalBox => p.print_str("local box"),
358            GroupCode::SplitOff => p.print_str("split off"),
359            GroupCode::SplitKeep => p.print_str("split keep"),
360            GroupCode::Preamble => p.print_str("preamble"),
361            GroupCode::AlignSet => p.print_str("align set"),
362            GroupCode::FinRow => p.print_str("fin row"),
363        }
364        let level = self.save.cur_level;
365        let line = self.group_line();
366        let p = &mut self.printer;
367        p.print_str(" group (level ");
368        p.print_int(i64::from(level));
369        p.print_char(')' as u32);
370        if line != 0 {
371            p.print_str(if e { " entered at line " } else { " at line " });
372            p.print_int(line.into());
373        }
374    }
375
376    /// Traces entering or leaving a group (`group_trace`).
377    fn group_trace(&mut self, e: bool) {
378        self.begin_diagnostic();
379        self.printer.print_char('{' as u32);
380        self.printer
381            .print_str(if e { "leaving " } else { "entering " });
382        self.print_group(e);
383        self.printer.print_char('}' as u32);
384        self.end_diagnostic(false);
385    }
386
387    /// Traces an assignment or restoration (`restore_trace`).
388    pub fn restore_trace(&mut self, loc: Loc, s: &str) {
389        self.begin_diagnostic();
390        self.printer.print_char('{' as u32);
391        self.printer.print_str(s);
392        self.printer.print_char(' ' as u32);
393        self.show_eqtb(loc);
394        self.printer.print_char('}' as u32);
395        self.end_diagnostic(false);
396    }
397
398    /// e-TeX's warning when a group ends in a different file than it began
399    /// (`group_warning`). Only active with `\tracingnesting > 0`.
400    fn group_warning(&mut self) {
401        // Implemented together with file-level nesting checks.
402    }
403}