Skip to main content

stet_core/
stack.rs

1// stet - A PostScript Interpreter
2// Copyright (c) 2026 Scott Bowman
3// SPDX-License-Identifier: Apache-2.0 OR MIT
4
5//! PostScript operand and execution stacks.
6
7use crate::error::PsError;
8use crate::object::PsObject;
9
10/// A bounded stack of `PsObject` values.
11pub struct Stack {
12    data: Vec<PsObject>,
13    max_size: usize,
14}
15
16impl Stack {
17    pub fn new(max_size: usize) -> Self {
18        Self {
19            data: Vec::with_capacity(max_size.min(256)),
20            max_size,
21        }
22    }
23
24    /// Push an object, returning `StackOverflow` if full.
25    #[inline]
26    pub fn push(&mut self, obj: PsObject) -> Result<(), PsError> {
27        if self.data.len() >= self.max_size {
28            return Err(PsError::StackOverflow);
29        }
30        self.data.push(obj);
31        Ok(())
32    }
33
34    /// Insert an object at a specific index from the bottom.
35    ///
36    /// Used by the ExecArray inner loop to insert a continuation cursor
37    /// below items that an operator just pushed to e_stack.
38    pub fn insert_at(&mut self, index: usize, obj: PsObject) -> Result<(), PsError> {
39        if self.data.len() >= self.max_size {
40            return Err(PsError::StackOverflow);
41        }
42        self.data.insert(index, obj);
43        Ok(())
44    }
45
46    /// Pop the top object, returning `StackUnderflow` if empty.
47    #[inline]
48    pub fn pop(&mut self) -> Result<PsObject, PsError> {
49        self.data.pop().ok_or(PsError::StackUnderflow)
50    }
51
52    /// Try to pop — returns `None` if empty (no error).
53    #[inline]
54    pub fn try_pop(&mut self) -> Option<PsObject> {
55        self.data.pop()
56    }
57
58    /// Peek at an object relative to top. 0 = top, 1 = second from top, etc.
59    #[inline]
60    pub fn peek(&self, from_top: usize) -> Result<PsObject, PsError> {
61        if from_top >= self.data.len() {
62            return Err(PsError::StackUnderflow);
63        }
64        Ok(self.data[self.data.len() - 1 - from_top])
65    }
66
67    /// Mutable peek at an object relative to top.
68    pub fn peek_mut(&mut self, from_top: usize) -> Result<&mut PsObject, PsError> {
69        let len = self.data.len();
70        if from_top >= len {
71            return Err(PsError::StackUnderflow);
72        }
73        Ok(&mut self.data[len - 1 - from_top])
74    }
75
76    #[inline]
77    pub fn len(&self) -> usize {
78        self.data.len()
79    }
80
81    #[inline]
82    pub fn is_empty(&self) -> bool {
83        self.data.is_empty()
84    }
85
86    pub fn clear(&mut self) {
87        self.data.clear();
88    }
89
90    /// Update the maximum stack size.
91    pub fn set_max_size(&mut self, max_size: usize) {
92        self.max_size = max_size;
93    }
94
95    pub fn as_slice(&self) -> &[PsObject] {
96        &self.data
97    }
98
99    pub fn as_mut_slice(&mut self) -> &mut [PsObject] {
100        &mut self.data
101    }
102
103    pub fn truncate(&mut self, len: usize) {
104        self.data.truncate(len);
105    }
106
107    /// Swap the top two elements (for `exch`).
108    pub fn swap_top_two(&mut self) -> Result<(), PsError> {
109        let len = self.data.len();
110        if len < 2 {
111            return Err(PsError::StackUnderflow);
112        }
113        self.data.swap(len - 1, len - 2);
114        Ok(())
115    }
116
117    /// Max capacity of this stack.
118    pub fn max_size(&self) -> usize {
119        self.max_size
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn test_push_pop() {
129        let mut s = Stack::new(10);
130        s.push(PsObject::int(1)).unwrap();
131        s.push(PsObject::int(2)).unwrap();
132        assert_eq!(s.len(), 2);
133        assert_eq!(s.pop().unwrap().as_i32(), Some(2));
134        assert_eq!(s.pop().unwrap().as_i32(), Some(1));
135        assert!(s.is_empty());
136    }
137
138    #[test]
139    fn test_overflow() {
140        let mut s = Stack::new(2);
141        s.push(PsObject::int(1)).unwrap();
142        s.push(PsObject::int(2)).unwrap();
143        assert_eq!(s.push(PsObject::int(3)), Err(PsError::StackOverflow));
144    }
145
146    #[test]
147    fn test_underflow() {
148        let mut s = Stack::new(10);
149        assert_eq!(s.pop(), Err(PsError::StackUnderflow));
150    }
151
152    #[test]
153    fn test_peek() {
154        let mut s = Stack::new(10);
155        s.push(PsObject::int(10)).unwrap();
156        s.push(PsObject::int(20)).unwrap();
157        s.push(PsObject::int(30)).unwrap();
158        assert_eq!(s.peek(0).unwrap().as_i32(), Some(30));
159        assert_eq!(s.peek(1).unwrap().as_i32(), Some(20));
160        assert_eq!(s.peek(2).unwrap().as_i32(), Some(10));
161        assert_eq!(s.peek(3), Err(PsError::StackUnderflow));
162    }
163
164    #[test]
165    fn test_swap_top_two() {
166        let mut s = Stack::new(10);
167        s.push(PsObject::int(1)).unwrap();
168        s.push(PsObject::int(2)).unwrap();
169        s.swap_top_two().unwrap();
170        assert_eq!(s.peek(0).unwrap().as_i32(), Some(1));
171        assert_eq!(s.peek(1).unwrap().as_i32(), Some(2));
172    }
173}