use smallvec::SmallVec;
use thiserror::Error;
use tinyvec::ArrayVec;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ScriptItem {
Num(i64),
Bytes(SmallVec<[u8; 32]>),
}
impl Default for ScriptItem {
fn default() -> Self {
Self::Bytes(SmallVec::new())
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Stack {
items: ArrayVec<[ScriptItem; Self::MAX_DEPTH]>,
}
impl Stack {
pub const MAX_DEPTH: usize = 1000;
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, item: ScriptItem) -> Result<(), StackError> {
match self.items.try_push(item) {
Some(_) => Err(StackError::Overflow),
None => Ok(()),
}
}
pub fn pop(&mut self) -> Result<ScriptItem, StackError> {
self.items.pop().ok_or(StackError::Underflow)
}
pub fn peek(&self) -> Result<&ScriptItem, StackError> {
self.items.last().ok_or(StackError::Underflow)
}
#[must_use]
pub fn len(&self) -> usize {
self.items.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn clear(&mut self) {
self.items.clear();
}
}
#[derive(Copy, Clone, Debug, Error, PartialEq, Eq)]
pub enum StackError {
#[error("script stack overflow")]
Overflow,
#[error("script stack underflow")]
Underflow,
}
#[cfg(test)]
mod tests {
use super::{ScriptItem, Stack, StackError};
#[test]
fn stack_rejects_overflow_and_reports_underflow() {
let mut stack = Stack::new();
assert_eq!(stack.pop(), Err(StackError::Underflow));
for value in 0..Stack::MAX_DEPTH {
let num = i64::try_from(value)
.unwrap_or_else(|error| panic!("stack test index should fit in i64: {error}"));
assert_eq!(stack.push(ScriptItem::Num(num)), Ok(()));
}
assert_eq!(stack.len(), Stack::MAX_DEPTH);
assert_eq!(stack.push(ScriptItem::Num(1)), Err(StackError::Overflow));
}
}