use crate::{asm::Word, error::StackError, StackResult};
use essential_types::convert::bool_from_word;
#[derive(Clone, Debug, PartialEq, Default)]
pub struct Stack(Vec<Word>);
impl Stack {
pub const SIZE_LIMIT: usize = 4096;
pub fn push(&mut self, word: Word) -> StackResult<()> {
if self.len() >= Self::SIZE_LIMIT {
return Err(StackError::Overflow);
}
self.0.push(word);
Ok(())
}
pub fn extend(&mut self, words: impl IntoIterator<Item = Word>) -> StackResult<()> {
for word in words {
self.push(word)?;
}
Ok(())
}
pub(crate) fn dup_from(&mut self) -> StackResult<()> {
let rev_ix_w = self.pop()?;
let rev_ix = usize::try_from(rev_ix_w).map_err(|_| StackError::IndexOutOfBounds)?;
let ix = self
.len()
.checked_sub(rev_ix)
.and_then(|i| i.checked_sub(1))
.ok_or(StackError::IndexOutOfBounds)?;
let w = *self.get(ix).ok_or(StackError::IndexOutOfBounds)?;
self.push(w)?;
Ok(())
}
pub(crate) fn swap_index(&mut self) -> StackResult<()> {
let rev_ix_w = self.pop()?;
let top_ix = self
.len()
.checked_sub(1)
.ok_or(StackError::IndexOutOfBounds)?;
let rev_ix = usize::try_from(rev_ix_w).map_err(|_| StackError::IndexOutOfBounds)?;
let ix = top_ix
.checked_sub(rev_ix)
.ok_or(StackError::IndexOutOfBounds)?;
self.0.swap(ix, top_ix);
Ok(())
}
pub(crate) fn select(&mut self) -> StackResult<()> {
self.pop().and_then(|cond_w| {
self.pop2_push1(|w0, w1| {
Ok(
if bool_from_word(cond_w).ok_or(StackError::InvalidCondition(cond_w))? {
w1
} else {
w0
},
)
})
})?;
Ok(())
}
pub(crate) fn select_range(&mut self) -> StackResult<()> {
let cond_w = self.pop()?;
let cond = bool_from_word(cond_w).ok_or(StackError::InvalidCondition(cond_w))?;
let len = self.pop_len()?;
if len == 0 {
return Ok(());
}
self.len()
.checked_sub(len.checked_mul(2).ok_or(StackError::IndexOutOfBounds)?)
.ok_or(StackError::IndexOutOfBounds)?;
let arr_b_index = self.len() - len;
if cond {
let arr_a_index = arr_b_index - len;
self.0
.copy_within(arr_b_index..(arr_b_index + len), arr_a_index);
}
self.0.truncate(arr_b_index);
Ok(())
}
pub fn pop(&mut self) -> StackResult<Word> {
self.0.pop().ok_or(StackError::Empty)
}
pub fn pop2(&mut self) -> StackResult<[Word; 2]> {
let w1 = self.pop()?;
let w0 = self.pop()?;
Ok([w0, w1])
}
pub fn pop3(&mut self) -> StackResult<[Word; 3]> {
let w2 = self.pop()?;
let [w0, w1] = self.pop2()?;
Ok([w0, w1, w2])
}
pub fn pop4(&mut self) -> StackResult<[Word; 4]> {
let w3 = self.pop()?;
let [w0, w1, w2] = self.pop3()?;
Ok([w0, w1, w2, w3])
}
pub fn pop8(&mut self) -> StackResult<[Word; 8]> {
let [w4, w5, w6, w7] = self.pop4()?;
let [w0, w1, w2, w3] = self.pop4()?;
Ok([w0, w1, w2, w3, w4, w5, w6, w7])
}
pub fn pop1_push1<F, E>(&mut self, f: F) -> Result<(), E>
where
F: FnOnce(Word) -> Result<Word, E>,
E: From<StackError>,
{
let w = self.pop()?;
let x = f(w)?;
self.push(x)?;
Ok(())
}
pub fn pop2_push1<F, E>(&mut self, f: F) -> Result<(), E>
where
F: FnOnce(Word, Word) -> Result<Word, E>,
E: From<StackError>,
{
let [w0, w1] = self.pop2()?;
let x = f(w0, w1)?;
self.push(x)?;
Ok(())
}
pub fn pop8_push1<F, E>(&mut self, f: F) -> Result<(), E>
where
F: FnOnce([Word; 8]) -> Result<Word, E>,
E: From<StackError>,
{
let ws = self.pop8()?;
let x = f(ws)?;
self.push(x)?;
Ok(())
}
pub fn pop1_push2<F, E>(&mut self, f: F) -> Result<(), E>
where
F: FnOnce(Word) -> Result<[Word; 2], E>,
E: From<StackError>,
{
let w = self.pop()?;
let xs = f(w)?;
self.extend(xs)?;
Ok(())
}
pub fn pop2_push2<F, E>(&mut self, f: F) -> Result<(), E>
where
F: FnOnce(Word, Word) -> Result<[Word; 2], E>,
E: From<StackError>,
{
let [w0, w1] = self.pop2()?;
let xs = f(w0, w1)?;
self.extend(xs)?;
Ok(())
}
pub fn pop2_push4<F, E>(&mut self, f: F) -> Result<(), E>
where
F: FnOnce(Word, Word) -> Result<[Word; 4], E>,
E: From<StackError>,
{
let [w0, w1] = self.pop2()?;
let xs = f(w0, w1)?;
self.extend(xs)?;
Ok(())
}
pub fn pop_len(&mut self) -> StackResult<usize> {
let len_word = self.pop()?;
let len = usize::try_from(len_word).map_err(|_| StackError::IndexOutOfBounds)?;
Ok(len)
}
pub fn pop_len_words<F, O, E>(&mut self, f: F) -> Result<O, E>
where
F: FnOnce(&[Word]) -> Result<O, E>,
E: From<StackError>,
{
let len = self.pop_len()?;
let ix = self
.len()
.checked_sub(len)
.ok_or(StackError::IndexOutOfBounds)?;
let out = f(&self[ix..])?;
self.0.truncate(ix);
Ok(out)
}
}
impl From<Stack> for Vec<Word> {
fn from(stack: Stack) -> Self {
stack.0
}
}
impl From<Vec<Word>> for Stack {
fn from(vec: Vec<Word>) -> Self {
Self(vec)
}
}
impl core::ops::Deref for Stack {
type Target = Vec<Word>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(test)]
mod tests {
use crate::{
asm::Stack,
error::{ConstraintError, OpError, StackError},
eval_ops, exec_ops,
test_util::*,
};
#[test]
fn dup_from_1() {
let ops = &[
Stack::Push(42).into(),
Stack::Push(2).into(),
Stack::Push(1).into(),
Stack::Push(0).into(),
Stack::Push(3).into(), Stack::DupFrom.into(),
];
let stack = exec_ops(ops, *test_access()).unwrap();
assert_eq!(&stack[..], &[42, 2, 1, 0, 42]);
}
#[test]
fn dup_from_2() {
let ops = &[
Stack::Push(3).into(),
Stack::Push(2).into(),
Stack::Push(1).into(),
Stack::Push(42).into(),
Stack::Push(0).into(), Stack::DupFrom.into(),
];
let stack = exec_ops(ops, *test_access()).unwrap();
assert_eq!(&stack[..], &[3, 2, 1, 42, 42]);
}
#[test]
fn push1() {
let ops = &[Stack::Push(42).into()];
let stack = exec_ops(ops, *test_access()).unwrap();
assert_eq!(&stack[..], &[42]);
}
#[test]
fn push2_pop_push() {
let ops = &[
Stack::Push(1).into(),
Stack::Push(2).into(),
Stack::Pop.into(),
Stack::Push(3).into(),
];
let stack = exec_ops(ops, *test_access()).unwrap();
assert_eq!(&stack[..], &[1, 3]);
}
#[test]
fn pop_empty() {
let ops = &[Stack::Pop.into()];
match eval_ops(ops, *test_access()) {
Err(ConstraintError::Op(0, OpError::Stack(StackError::Empty))) => (),
_ => panic!("expected empty stack error"),
}
}
#[test]
fn index_oob() {
let ops = &[Stack::Push(0).into(), Stack::DupFrom.into()];
match eval_ops(ops, *test_access()) {
Err(ConstraintError::Op(1, OpError::Stack(StackError::IndexOutOfBounds))) => (),
_ => panic!("expected index out-of-bounds stack error"),
}
}
#[test]
fn swap_index() {
let ops = &[
Stack::Push(3).into(),
Stack::Push(4).into(),
Stack::Push(5).into(),
Stack::Push(42).into(),
Stack::Push(2).into(), Stack::SwapIndex.into(),
];
let stack = exec_ops(ops, *test_access()).unwrap();
assert_eq!(&stack[..], &[3, 42, 5, 4]);
}
#[test]
fn swap_index_oob() {
let ops = &[
Stack::Push(3).into(),
Stack::Push(4).into(),
Stack::Push(2).into(), Stack::SwapIndex.into(),
];
match eval_ops(ops, *test_access()) {
Err(ConstraintError::Op(3, OpError::Stack(StackError::IndexOutOfBounds))) => (),
_ => panic!("expected index out-of-bounds stack error"),
}
}
#[test]
fn select() {
let ops = &[
Stack::Push(3).into(),
Stack::Push(4).into(),
Stack::Push(1).into(),
Stack::Select.into(),
];
let stack = exec_ops(ops, *test_access()).unwrap();
assert_eq!(&stack[..], &[4]);
}
#[test]
fn select_range_cond_1() {
let ops = &[
Stack::Push(4).into(),
Stack::Push(4).into(),
Stack::Push(4).into(),
Stack::Push(5).into(),
Stack::Push(5).into(),
Stack::Push(5).into(),
Stack::Push(3).into(), Stack::Push(1).into(), Stack::SelectRange.into(),
];
let stack = exec_ops(ops, *test_access()).unwrap();
assert_eq!(&stack[..], &[5, 5, 5]);
}
#[test]
fn select_range_cond_0() {
let ops = &[
Stack::Push(4).into(),
Stack::Push(4).into(),
Stack::Push(4).into(),
Stack::Push(5).into(),
Stack::Push(5).into(),
Stack::Push(5).into(),
Stack::Push(3).into(), Stack::Push(0).into(), Stack::SelectRange.into(),
];
let stack = exec_ops(ops, *test_access()).unwrap();
assert_eq!(&stack[..], &[4, 4, 4]);
}
#[test]
fn select_range_cond_invalid() {
let ops = &[
Stack::Push(4).into(),
Stack::Push(5).into(),
Stack::Push(1).into(), Stack::Push(42).into(), Stack::SelectRange.into(),
];
match eval_ops(ops, *test_access()) {
Err(ConstraintError::Op(4, OpError::Stack(StackError::InvalidCondition(42)))) => (),
_ => panic!("expected invalid condition stack error"),
}
}
#[test]
fn select_range_len_0() {
let ops = &[
Stack::Push(4).into(),
Stack::Push(5).into(),
Stack::Push(0).into(), Stack::Push(0).into(), Stack::SelectRange.into(),
];
let stack = exec_ops(ops, *test_access()).unwrap();
assert_eq!(&stack[..], &[4, 5]);
}
#[test]
fn select_range_len_negative() {
let ops = &[
Stack::Push(-42).into(), Stack::Push(0).into(), Stack::SelectRange.into(),
];
match eval_ops(ops, *test_access()) {
Err(ConstraintError::Op(2, OpError::Stack(StackError::IndexOutOfBounds))) => (),
_ => panic!("expected index out of bounds stack error"),
}
}
#[test]
fn select_range_len_too_big() {
let ops = &[
Stack::Push(4).into(),
Stack::Push(4).into(),
Stack::Push(5).into(),
Stack::Push(2).into(), Stack::Push(0).into(), Stack::SelectRange.into(),
];
match eval_ops(ops, *test_access()) {
Err(ConstraintError::Op(5, OpError::Stack(StackError::IndexOutOfBounds))) => (),
_ => panic!("expected index out of bounds stack error"),
}
}
}