Skip to main content

stet_core/
save_stack.rs

1// stet - A PostScript Interpreter
2// Copyright (c) 2026 Scott Bowman
3// SPDX-License-Identifier: Apache-2.0 OR MIT
4
5//! Save/restore stack for PostScript VM persistence.
6//!
7//! Implements copy-on-write save/restore: `save` records a level, mutations
8//! create COW copies, and `restore` swaps offsets to revert changes.
9
10use crate::graphics_state::{GraphicsState, GstateEntry};
11use crate::object::EntityId;
12
13/// Which store type a save record refers to.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum StoreType {
16    String,
17    Array,
18    Dict,
19}
20
21/// Records one COW copy made during a save level.
22#[derive(Debug, Clone)]
23pub struct SaveRecord {
24    /// The original entity that was COW-copied.
25    pub src: EntityId,
26    /// The backup entity holding the pre-mutation data.
27    pub copy: EntityId,
28    /// Which store the entities belong to.
29    pub store_type: StoreType,
30}
31
32/// One save level's state.
33pub struct SaveLevel {
34    /// Numeric level (1-based, 0 = no save active).
35    pub level: u16,
36    /// Unique save id for invalidation tracking.
37    pub save_id: u32,
38    /// COW records accumulated during this save level.
39    pub records: Vec<SaveRecord>,
40    /// Whether this save level is still valid (becomes false on restore).
41    pub valid: bool,
42    /// Snapshot of d_stack length at save time (for restore validation).
43    pub d_stack_depth: usize,
44    /// Saved packing mode (`setpacking`/`currentpacking`).
45    pub packing_mode: bool,
46    /// Saved VM allocation mode (`setglobal`/`currentglobal`).
47    pub vm_alloc_mode: bool,
48    /// Saved binary object format (`setobjectformat`/`currentobjectformat`).
49    pub object_format: i32,
50    /// Saved graphics state and graphics state stack.
51    pub gstate: GraphicsState,
52    pub gstate_stack: Vec<GstateEntry>,
53}
54
55/// The save/restore stack.
56pub struct SaveStack {
57    levels: Vec<SaveLevel>,
58    next_save_id: u32,
59}
60
61impl SaveStack {
62    /// Create an empty save stack.
63    pub fn new() -> Self {
64        Self {
65            levels: Vec::new(),
66            next_save_id: 1,
67        }
68    }
69
70    /// Push a new save level. Returns `(level, save_id)`.
71    pub fn save(
72        &mut self,
73        d_stack_depth: usize,
74        packing_mode: bool,
75        vm_alloc_mode: bool,
76        object_format: i32,
77        gstate: GraphicsState,
78        gstate_stack: Vec<GstateEntry>,
79    ) -> (u16, u32) {
80        let level = (self.levels.len() + 1) as u16;
81        let save_id = self.next_save_id;
82        self.next_save_id += 1;
83        self.levels.push(SaveLevel {
84            level,
85            save_id,
86            records: Vec::new(),
87            valid: true,
88            d_stack_depth,
89            packing_mode,
90            vm_alloc_mode,
91            object_format,
92            gstate,
93            gstate_stack,
94        });
95        (level, save_id)
96    }
97
98    /// Add a COW record to the current save level.
99    pub fn add_record(&mut self, record: SaveRecord) {
100        if let Some(level) = self.levels.last_mut() {
101            level.records.push(record);
102        }
103    }
104
105    /// Pop the topmost save level, returning its records for restore processing.
106    /// Returns `None` if the stack is empty.
107    pub fn restore(&mut self) -> Option<SaveLevel> {
108        self.levels.pop()
109    }
110
111    /// Pop all save levels from `save_id` upward (inclusive), returning them
112    /// in stack order (target level first, newest level last).
113    /// Per PLRM, `restore` can target any valid save — not just the topmost.
114    /// All newer saves are invalidated and their COW records are also returned
115    /// so they can be undone.
116    pub fn restore_to(&mut self, save_id: u32) -> Option<Vec<SaveLevel>> {
117        let idx = self.levels.iter().position(|l| l.save_id == save_id)?;
118        let popped: Vec<SaveLevel> = self.levels.drain(idx..).collect();
119        Some(popped)
120    }
121
122    /// Current save level (0 if no save active).
123    pub fn current_level(&self) -> u16 {
124        self.levels.last().map(|l| l.level).unwrap_or(0)
125    }
126
127    /// Save ID of the most recent save (0 if no save active).
128    /// Used for entity creation tracking (invalidrestore).
129    pub fn last_save_id(&self) -> u32 {
130        self.levels.last().map(|l| l.save_id).unwrap_or(0)
131    }
132
133    /// Check if a save_id is valid (exists and not invalidated).
134    pub fn is_valid(&self, save_id: u32) -> bool {
135        self.levels.iter().any(|l| l.save_id == save_id && l.valid)
136    }
137
138    /// Number of active save levels.
139    pub fn depth(&self) -> usize {
140        self.levels.len()
141    }
142
143    /// Read-only access to the levels (for validation checks).
144    pub fn levels_ref(&self) -> &[SaveLevel] {
145        &self.levels
146    }
147
148    /// Invalidate all save levels newer than the given save_id.
149    pub fn invalidate_newer(&mut self, save_id: u32) {
150        let mut found = false;
151        for level in &mut self.levels {
152            if found {
153                level.valid = false;
154            }
155            if level.save_id == save_id {
156                found = true;
157            }
158        }
159    }
160}
161
162impl Default for SaveStack {
163    fn default() -> Self {
164        Self::new()
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    fn test_save_and_depth() {
174        let mut ss = SaveStack::new();
175        assert_eq!(ss.depth(), 0);
176        assert_eq!(ss.current_level(), 0);
177
178        let (level, id) = ss.save(3, false, false, 0, GraphicsState::new(), Vec::new());
179        assert_eq!(level, 1);
180        assert_eq!(id, 1);
181        assert_eq!(ss.depth(), 1);
182        assert_eq!(ss.current_level(), 1);
183    }
184
185    #[test]
186    fn test_nested_save() {
187        let mut ss = SaveStack::new();
188        let (l1, _) = ss.save(3, false, false, 0, GraphicsState::new(), Vec::new());
189        let (l2, _) = ss.save(3, false, false, 0, GraphicsState::new(), Vec::new());
190        assert_eq!(l1, 1);
191        assert_eq!(l2, 2);
192        assert_eq!(ss.depth(), 2);
193        assert_eq!(ss.current_level(), 2);
194    }
195
196    #[test]
197    fn test_restore() {
198        let mut ss = SaveStack::new();
199        let (_, id1) = ss.save(3, false, false, 0, GraphicsState::new(), Vec::new());
200        ss.add_record(SaveRecord {
201            src: EntityId(0),
202            copy: EntityId(1),
203            store_type: StoreType::String,
204        });
205
206        let level = ss.restore().unwrap();
207        assert_eq!(level.save_id, id1);
208        assert_eq!(level.records.len(), 1);
209        assert_eq!(ss.depth(), 0);
210    }
211
212    #[test]
213    fn test_is_valid() {
214        let mut ss = SaveStack::new();
215        let (_, id1) = ss.save(3, false, false, 0, GraphicsState::new(), Vec::new());
216        assert!(ss.is_valid(id1));
217        ss.restore();
218        assert!(!ss.is_valid(id1));
219    }
220
221    #[test]
222    fn test_invalidate_newer() {
223        let mut ss = SaveStack::new();
224        let (_, id1) = ss.save(3, false, false, 0, GraphicsState::new(), Vec::new());
225        let (_, id2) = ss.save(3, false, false, 0, GraphicsState::new(), Vec::new());
226        let (_, id3) = ss.save(3, false, false, 0, GraphicsState::new(), Vec::new());
227
228        ss.invalidate_newer(id1);
229        assert!(ss.is_valid(id1));
230        assert!(!ss.is_valid(id2));
231        assert!(!ss.is_valid(id3));
232    }
233
234    #[test]
235    fn test_add_record_to_current() {
236        let mut ss = SaveStack::new();
237        ss.save(3, false, false, 0, GraphicsState::new(), Vec::new());
238        ss.add_record(SaveRecord {
239            src: EntityId(0),
240            copy: EntityId(1),
241            store_type: StoreType::Array,
242        });
243        ss.add_record(SaveRecord {
244            src: EntityId(2),
245            copy: EntityId(3),
246            store_type: StoreType::Dict,
247        });
248
249        let level = ss.restore().unwrap();
250        assert_eq!(level.records.len(), 2);
251    }
252
253    #[test]
254    fn test_restore_empty() {
255        let mut ss = SaveStack::new();
256        assert!(ss.restore().is_none());
257    }
258
259    #[test]
260    fn test_d_stack_depth_snapshot() {
261        let mut ss = SaveStack::new();
262        ss.save(5, false, false, 0, GraphicsState::new(), Vec::new());
263        let level = ss.restore().unwrap();
264        assert_eq!(level.d_stack_depth, 5);
265    }
266
267    #[test]
268    fn test_unique_save_ids() {
269        let mut ss = SaveStack::new();
270        let (_, id1) = ss.save(3, false, false, 0, GraphicsState::new(), Vec::new());
271        let (_, id2) = ss.save(3, false, false, 0, GraphicsState::new(), Vec::new());
272        ss.restore();
273        let (_, id3) = ss.save(3, false, false, 0, GraphicsState::new(), Vec::new());
274        assert_ne!(id1, id2);
275        assert_ne!(id2, id3);
276        assert_ne!(id1, id3);
277    }
278
279    #[test]
280    fn test_save_level_numbers() {
281        let mut ss = SaveStack::new();
282        let (l1, _) = ss.save(3, false, false, 0, GraphicsState::new(), Vec::new());
283        let (l2, _) = ss.save(3, false, false, 0, GraphicsState::new(), Vec::new());
284        ss.restore();
285        // After restoring level 2, next save should be level 2 again
286        let (l3, _) = ss.save(3, false, false, 0, GraphicsState::new(), Vec::new());
287        assert_eq!(l1, 1);
288        assert_eq!(l2, 2);
289        assert_eq!(l3, 2);
290    }
291}