use crate::error::ScriptError;
pub(crate) fn is_true(bytes: &[u8]) -> bool {
if bytes.is_empty() {
return false;
}
for byte in &bytes[..bytes.len() - 1] {
if *byte != 0x00 {
return true;
}
}
let last = bytes[bytes.len() - 1];
last != 0x00 && last != 0x80
}
pub(crate) struct Stack {
items: Vec<Vec<u8>>,
}
impl Stack {
pub(crate) fn new() -> Self {
Self { items: Vec::new() }
}
pub(crate) fn push(&mut self, item: Vec<u8>) {
self.items.push(item);
}
pub(crate) fn pop(&mut self) -> Result<Vec<u8>, ScriptError> {
self.items.pop().ok_or(ScriptError::StackUnderflow)
}
pub(crate) fn peek(&self) -> Result<&[u8], ScriptError> {
self.items
.last()
.map(|v| v.as_slice())
.ok_or(ScriptError::StackUnderflow)
}
pub(crate) fn len(&self) -> usize {
self.items.len()
}
pub(crate) fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub(crate) fn push_bool(&mut self, val: bool) {
if val {
self.items.push(vec![0x01]);
} else {
self.items.push(vec![]);
}
}
pub(crate) fn remove(&mut self, idx: usize) -> Result<Vec<u8>, ScriptError> {
if idx >= self.items.len() {
return Err(ScriptError::StackUnderflow);
}
Ok(self.items.remove(idx))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_true_empty() {
assert!(!is_true(&[]));
}
#[test]
fn is_true_zero() {
assert!(!is_true(&[0x00]));
}
#[test]
fn is_true_negative_zero() {
assert!(!is_true(&[0x80]));
}
#[test]
fn is_true_multi_byte_zero() {
assert!(!is_true(&[0x00, 0x00]));
}
#[test]
fn is_true_multi_byte_negative_zero() {
assert!(!is_true(&[0x00, 0x80]));
}
#[test]
fn is_true_three_byte_negative_zero() {
assert!(!is_true(&[0x00, 0x00, 0x80]));
}
#[test]
fn is_true_one() {
assert!(is_true(&[0x01]));
}
#[test]
fn is_true_negative_one() {
assert!(is_true(&[0x81]));
}
#[test]
fn is_true_nonzero_low_byte() {
assert!(is_true(&[0x00, 0x01]));
}
#[test]
fn is_true_0x80_not_last() {
assert!(is_true(&[0x80, 0x00]));
}
#[test]
fn push_and_pop() {
let mut stack = Stack::new();
stack.push(vec![0x01, 0x02]);
assert_eq!(stack.len(), 1);
let item = stack.pop().unwrap();
assert_eq!(item, vec![0x01, 0x02]);
assert!(stack.is_empty());
}
#[test]
fn pop_empty_stack() {
let mut stack = Stack::new();
let err = stack.pop().unwrap_err();
assert!(matches!(err, ScriptError::StackUnderflow));
}
#[test]
fn peek_returns_top() {
let mut stack = Stack::new();
stack.push(vec![0xaa]);
stack.push(vec![0xbb]);
assert_eq!(stack.peek().unwrap(), &[0xbb]);
assert_eq!(stack.len(), 2); }
#[test]
fn peek_empty_stack() {
let stack = Stack::new();
let err = stack.peek().unwrap_err();
assert!(matches!(err, ScriptError::StackUnderflow));
}
#[test]
fn push_bool_true() {
let mut stack = Stack::new();
stack.push_bool(true);
assert_eq!(stack.pop().unwrap(), vec![0x01]);
}
#[test]
fn push_bool_false() {
let mut stack = Stack::new();
stack.push_bool(false);
let val = stack.pop().unwrap();
assert!(val.is_empty());
}
#[test]
fn remove_second_from_top() {
let mut stack = Stack::new();
stack.push(vec![0x01]); stack.push(vec![0x02]); let removed = stack.remove(0).unwrap();
assert_eq!(removed, vec![0x01]);
assert_eq!(stack.len(), 1);
assert_eq!(stack.peek().unwrap(), &[0x02]);
}
#[test]
fn remove_out_of_bounds() {
let mut stack = Stack::new();
stack.push(vec![0x01]);
let err = stack.remove(5).unwrap_err();
assert!(matches!(err, ScriptError::StackUnderflow));
}
#[test]
fn lifo_order() {
let mut stack = Stack::new();
stack.push(vec![0x01]);
stack.push(vec![0x02]);
stack.push(vec![0x03]);
assert_eq!(stack.pop().unwrap(), vec![0x03]);
assert_eq!(stack.pop().unwrap(), vec![0x02]);
assert_eq!(stack.pop().unwrap(), vec![0x01]);
}
}