Skip to main content

pnc_engine/
stack.rs

1use crate::value::Value;
2
3pub const DEFAULT_HISTORY_DEPTH: usize = 10;
4pub const DEFAULT_HISTORY_CAPACITY: usize = DEFAULT_HISTORY_DEPTH + 1;
5
6#[derive(Debug, Clone)]
7pub struct History {
8    snapshots: Vec<Vec<Value>>,
9    position: usize,
10    capacity: usize,
11}
12
13impl Default for History {
14    fn default() -> Self {
15        Self::new()
16    }
17}
18
19impl History {
20    pub fn new() -> Self {
21        Self::with_capacity(DEFAULT_HISTORY_CAPACITY)
22    }
23
24    pub fn with_capacity(capacity: usize) -> Self {
25        Self {
26            snapshots: Vec::new(),
27            position: 0,
28            capacity,
29        }
30    }
31
32    pub fn capacity(&self) -> usize {
33        self.capacity
34    }
35
36    pub fn is_enabled(&self) -> bool {
37        self.capacity >= 2
38    }
39
40    pub fn position(&self) -> usize {
41        self.position
42    }
43
44    pub fn len(&self) -> usize {
45        self.snapshots.len()
46    }
47
48    pub fn is_empty(&self) -> bool {
49        self.snapshots.is_empty()
50    }
51
52    pub fn can_undo(&self) -> bool {
53        self.position > 0
54    }
55
56    pub fn can_redo(&self) -> bool {
57        self.position + 1 < self.snapshots.len()
58    }
59
60    pub fn undo_count(&self) -> usize {
61        self.position
62    }
63
64    pub fn redo_count(&self) -> usize {
65        if self.snapshots.is_empty() {
66            0
67        } else {
68            self.snapshots.len() - 1 - self.position
69        }
70    }
71
72    pub fn undo(&mut self) -> Option<Vec<Value>> {
73        if !self.is_enabled() || self.position == 0 {
74            return None;
75        }
76        self.position -= 1;
77        Some(self.snapshots[self.position].clone())
78    }
79
80    pub fn redo(&mut self) -> Option<Vec<Value>> {
81        if !self.is_enabled() || self.position + 1 >= self.snapshots.len() {
82            return None;
83        }
84        self.position += 1;
85        Some(self.snapshots[self.position].clone())
86    }
87
88    pub fn record(&mut self, snapshot: Vec<Value>) {
89        if !self.is_enabled() {
90            return;
91        }
92
93        // Truncate forward/redo history.
94        self.snapshots.truncate(self.position + 1);
95
96        // Skip append if equal to last snapshot.
97        if !self.snapshots.is_empty() && snapshot == *self.snapshots.last().unwrap() {
98            return;
99        }
100
101        // Evict oldest if at capacity.
102        if self.snapshots.len() == self.capacity && self.capacity > 0 {
103            self.snapshots.remove(0);
104            if self.position > 0 {
105                self.position -= 1;
106            }
107        }
108
109        self.snapshots.push(snapshot);
110        self.position = self.snapshots.len() - 1;
111    }
112
113    pub fn seed(&mut self, snapshot: Vec<Value>) {
114        if !self.is_enabled() {
115            return;
116        }
117        self.snapshots = vec![snapshot];
118        self.position = 0;
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use crate::value::Value;
126
127    #[test]
128    fn undo_disabled_at_zero_capacity() {
129        let mut h = History::with_capacity(0);
130        assert!(!h.is_enabled());
131        h.record(vec![Value::Int(1.into())]);
132        assert_eq!(h.len(), 0);
133        assert!(h.undo().is_none());
134        assert!(h.redo().is_none());
135    }
136
137    #[test]
138    fn capacity_one_disables() {
139        let h = History::with_capacity(1);
140        assert!(!h.is_enabled());
141    }
142
143    #[test]
144    fn capacity_three_transition() {
145        let mut h = History::with_capacity(3);
146        h.seed(vec![Value::Int(0.into())]); // S0
147        h.record(vec![Value::Int(1.into())]); // S1
148        h.record(vec![Value::Int(2.into())]); // S2
149        assert_eq!(h.len(), 3);
150        assert_eq!(h.position(), 2);
151        h.record(vec![Value::Int(3.into())]); // S3 -> evict S0
152        assert_eq!(h.len(), 3);
153        assert_eq!(h.position(), 2);
154        // undo -> S2
155        let v = h.undo().unwrap();
156        assert_eq!(v, vec![Value::Int(2.into())]);
157        assert_eq!(h.position(), 1);
158        // redo -> S3
159        let v = h.redo().unwrap();
160        assert_eq!(v, vec![Value::Int(3.into())]);
161        assert_eq!(h.position(), 2);
162    }
163
164    #[test]
165    fn mutation_after_undo_drops_redo() {
166        let mut h = History::with_capacity(5);
167        h.seed(vec![Value::Int(0.into())]);
168        h.record(vec![Value::Int(1.into())]);
169        h.record(vec![Value::Int(2.into())]);
170        h.undo(); // position 1
171        h.record(vec![Value::Int(99.into())]); // truncate + append
172        assert_eq!(h.redo_count(), 0);
173    }
174
175    #[test]
176    fn no_op_result_truncates_redo_no_append() {
177        let mut h = History::with_capacity(5);
178        h.seed(vec![Value::Int(1.into())]);
179        h.record(vec![Value::Int(2.into())]);
180        h.undo(); // position 0
181        // record same as last before undo... actually record S1 again
182        // Simpler: record equal snapshot
183        h.record(vec![Value::Int(1.into())]);
184        // Should truncate redo (none here) and skip append since equal to S0
185        // Actually after undo position=0, snapshots=[S0], record S0 -> equal -> skip
186        assert_eq!(h.len(), 1);
187    }
188
189    #[test]
190    fn redo_count_empty_buffer() {
191        let h = History::with_capacity(5);
192        assert_eq!(h.redo_count(), 0);
193    }
194
195    #[test]
196    fn seed_empty_stack() {
197        let mut h = History::with_capacity(5);
198        h.seed(vec![]);
199        assert_eq!(h.len(), 1);
200        assert_eq!(h.position(), 0);
201        let v = h.undo();
202        assert!(v.is_none()); // position 0, can't undo
203    }
204
205    #[test]
206    fn new_delegates() {
207        let h = History::new();
208        assert_eq!(h.capacity(), DEFAULT_HISTORY_CAPACITY);
209    }
210}