cao_lang/collections/
value_stack.rs

1//! Stack containing only cao-lang Values
2//! Because Values can express `nil` values we use them instead of optionals
3//!
4use crate::value::Value;
5use thiserror::Error;
6
7#[derive(Debug)]
8pub struct ValueStack {
9    count: usize,
10    data: Box<[Value]>,
11}
12
13#[derive(Debug, Error)]
14pub enum StackError {
15    #[error("Stack is full")]
16    Full,
17    #[error("Index out of bounds: capacity: {capacity} index: {index}")]
18    OutOfBounds { capacity: usize, index: usize },
19}
20impl std::fmt::Display for ValueStack {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        if self.count == 0 {
23            return write!(f, "[]");
24        }
25        writeln!(f, "[")?;
26        unsafe {
27            let data = self.data.as_ptr();
28            for i in (0..self.count).rev() {
29                writeln!(f, "\t{:?}: {:?}", data.add(i), &self.data[i])?;
30            }
31        }
32        write!(f, " ]")
33    }
34}
35
36impl ValueStack {
37    pub fn new(size: usize) -> Self {
38        assert!(size > 0);
39        Self {
40            count: 0,
41            data: vec![Value::Nil; size].into_boxed_slice(),
42        }
43    }
44
45    #[inline]
46    pub fn as_slice(&self) -> &[Value] {
47        &self.data[0..self.count]
48    }
49
50    #[inline]
51    pub fn push<T: Into<Value>>(&mut self, value: T) -> Result<(), StackError> {
52        if self.count + 1 < self.data.len() {
53            self.data[self.count] = value.into();
54            self.count += 1;
55            Ok(())
56        } else {
57            Err(StackError::Full)
58        }
59    }
60
61    #[inline]
62    pub fn clear(&mut self) {
63        self.count = 0;
64        self.data[0] = Value::Nil; // in case the stack is popped when empty
65    }
66
67    #[inline]
68    pub fn len(&self) -> usize {
69        self.count
70    }
71
72    /// Returns Nil if the stack is empty
73    #[inline]
74    pub fn pop(&mut self) -> Value {
75        let count = self.count.saturating_sub(1);
76        let value = self.data[count];
77        self.count = count;
78        self.data[self.count] = Value::Nil;
79        value
80    }
81
82    #[inline]
83    pub fn pop_n<const N: usize>(&mut self) -> [Value; N] {
84        let mut result = [Value::Nil; N];
85        let n = self.count.min(N);
86        #[allow(clippy::needless_range_loop)]
87        for i in 0..n {
88            result[i] = self.data[self.count - i - 1];
89        }
90        self.count -= n;
91        result
92    }
93
94    /// Pop value, treating offset as the 0 position
95    ///
96    /// ```
97    /// use cao_lang::collections::value_stack::ValueStack;
98    /// use cao_lang::prelude::Value;
99    ///
100    /// let mut stack = ValueStack::new(4);
101    /// stack.push(Value::Integer(42));
102    /// let res = stack.pop_w_offset(1);
103    /// assert_eq!(res, Value::Nil);
104    /// let res = stack.pop();
105    /// assert_eq!(res, Value::Integer(42));
106    /// ```
107    ///
108    pub fn pop_w_offset(&mut self, offset: usize) -> Value {
109        if self.count <= offset {
110            return Value::Nil;
111        }
112        self.pop()
113    }
114
115    /// Sets a value
116    /// Only previous values may be set
117    ///
118    /// Returns the old value
119    pub fn set(&mut self, index: usize, value: Value) -> Result<Value, StackError> {
120        if index > self.count {
121            return Err(StackError::OutOfBounds {
122                capacity: self.count,
123                index,
124            });
125        }
126        if index == self.count {
127            self.push(value)?;
128            Ok(Value::Nil)
129        } else {
130            let old = std::mem::replace(&mut self.data[index], value);
131            Ok(old)
132        }
133    }
134
135    pub fn get(&mut self, index: usize) -> Value {
136        if index >= self.count {
137            return Value::Nil;
138        }
139        self.data[index]
140    }
141
142    /// Returns the very first item
143    pub fn clear_until(&mut self, index: usize) -> Value {
144        let res = self.last();
145        self.count = index;
146        res
147    }
148
149    #[inline]
150    pub fn is_empty(&self) -> bool {
151        self.count == 0
152    }
153
154    /// Returns Null if the stack is empty
155    #[inline]
156    pub fn last(&self) -> Value {
157        if self.count > 0 {
158            self.data[self.count - 1]
159        } else {
160            Value::Nil
161        }
162    }
163
164    /// Returns Null if the index is out of bounds
165    #[inline]
166    pub fn peek_last(&self, n: usize) -> Value {
167        if self.count > n {
168            self.data[self.count - n - 1]
169        } else {
170            Value::Nil
171        }
172    }
173
174    pub fn iter(&self) -> impl Iterator<Item = Value> {
175        let ptr = self.data.as_ptr();
176        (0..self.count).map(move |i| unsafe { *ptr.add(i) })
177    }
178
179    pub fn top_location(&self) -> *const Value {
180        if self.count == 0 {
181            return std::ptr::null();
182        }
183        unsafe { self.data.as_ptr().add(self.count - 1) }
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190
191    #[test]
192    fn pop_n_not_enough_in_stack_test() {
193        let mut stack = ValueStack::new(4);
194        stack.push(Value::Integer(42)).unwrap();
195
196        let result = stack.pop_n::<8>();
197
198        assert_eq!(result.len(), 8);
199        assert_eq!(result[0], Value::Integer(42));
200        for i in 1..8 {
201            assert!(result[i].is_null());
202        }
203    }
204}