feat: generalize Stack

This commit is contained in:
2025-05-15 11:20:59 +08:00
parent 2293b9e2de
commit 3e7a8a1c05
3 changed files with 17 additions and 18 deletions

85
src/stack.rs Normal file
View File

@@ -0,0 +1,85 @@
use std::mem::{MaybeUninit, replace, transmute};
use std::ops::Deref;
use crate::error::*;
pub struct Stack<T, const CAP: usize> {
// items: Box<[MaybeUninit<Value<'vm>>; CAP]>,
items: [MaybeUninit<T>; CAP],
top: usize,
}
macro_rules! into {
($e:expr) => {
// SAFETY: This macro is used to transmute `MaybeUninit<Value<'vm>>` to `Value<'vm>`
// or `&MaybeUninit<Value<'vm>>` to `&Value<'vm>`.
// This is safe because the `Stack` ensures that only initialized values are accessed
// within the `0..top` range.
unsafe { transmute($e) }
};
}
impl<T, const CAP: usize> Stack<T, CAP> {
pub fn new() -> Self {
Stack {
items: [const { MaybeUninit::uninit() }; CAP],
top: 0,
}
}
pub fn push(&mut self, item: T) -> Result<()> {
self.items
.get_mut(self.top)
.map_or_else(
|| Err(Error::EvalError("stack overflow".to_string())),
|ok| Ok(ok),
)?
.write(item);
self.top += 1;
Ok(())
}
pub fn pop(&mut self) -> T {
self.top -= 1;
let item = self.items.get_mut(self.top).unwrap();
// SAFETY: `item` at `self.top` was previously written and is initialized.
// We replace it with `MaybeUninit::uninit()` and then `assume_init`
// on the original value, which is safe as it was initialized.
unsafe { replace(item, MaybeUninit::uninit()).assume_init() }
}
pub fn tos(&self) -> Result<&T> {
if self.top == 0 {
panic!("stack empty")
} else {
Ok(into!(&self.items[self.top - 1]))
}
}
pub fn tos_mut(&mut self) -> Result<&mut T> {
if self.top == 0 {
panic!("stack empty")
} else {
Ok(into!(&mut self.items[self.top - 1]))
}
}
}
impl<T, const CAP: usize> Deref for Stack<T, CAP> {
type Target = [T];
fn deref(&self) -> &Self::Target {
into!(&self.items[0..self.top])
}
}
impl<T, const CAP: usize> Drop for Stack<T, CAP> {
fn drop(&mut self) {
self.items.as_mut_slice()[0..self.top]
.iter_mut()
// SAFETY: Items in the range `0..self.top` are guaranteed to be initialized.
// `assume_init_drop` is called to correctly drop these initialized `Value`s.
.map(|item| unsafe { item.assume_init_drop() })
.for_each(drop)
}
}