All checks were successful
per-push tests / build (push) Successful in 49s
per-push tests / test-frontend (push) Successful in 53s
per-push tests / test-utility (push) Successful in 1m2s
per-push tests / test-backend (push) Successful in 54s
per-push tests / timed-decomposer-parse (push) Successful in 56s
This commit adds a testing framework for HyphaeVM which enables testing various aspects of the VM state after running input programs against a VM with possibly preinitialized state. This includes a builder pattern initializer for the VM, and bespoke logic for a test case tester. Signed-off-by: Ava Affine <ava@sunnypup.io>
281 lines
7.2 KiB
Rust
281 lines
7.2 KiB
Rust
/* Mycelium Scheme
|
|
* Copyright (C) 2025 Ava Affine
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
use core::fmt::{self, Debug, Formatter};
|
|
use core::ops::Index;
|
|
use alloc::rc::Rc;
|
|
|
|
struct StackInner<T: Sized> {
|
|
pub next: Stack<T>,
|
|
pub data: T
|
|
}
|
|
|
|
impl<T: Clone> Clone for StackInner<T> {
|
|
fn clone(&self) -> Self {
|
|
StackInner{
|
|
next: self.next.clone(),
|
|
data: self.data.clone()
|
|
}
|
|
}
|
|
|
|
fn clone_from(&mut self, source: &Self) {
|
|
*self = source.clone()
|
|
}
|
|
}
|
|
|
|
struct Stack<T: Sized> (Rc<Option<StackInner<T>>>);
|
|
|
|
impl<T: Clone> Clone for Stack<T> {
|
|
fn clone(&self) -> Self {
|
|
Stack(Rc::from((*self.0).clone()))
|
|
}
|
|
|
|
fn clone_from(&mut self, source: &Self) {
|
|
*self = source.clone()
|
|
}
|
|
}
|
|
|
|
struct StackStackInner<T: Sized> {
|
|
next: StackStack<T>,
|
|
count: usize,
|
|
stack: Stack<T>,
|
|
}
|
|
|
|
impl<T: Clone> Clone for StackStackInner<T> {
|
|
fn clone(&self) -> Self {
|
|
StackStackInner{
|
|
next: self.next.clone(),
|
|
count: self.count,
|
|
stack: self.stack.clone()
|
|
}
|
|
}
|
|
|
|
fn clone_from(&mut self, source: &Self) {
|
|
*self = source.clone()
|
|
}
|
|
}
|
|
|
|
pub struct StackStack<T: Sized> (Rc<Option<StackStackInner<T>>>);
|
|
|
|
impl<T: Clone> Clone for StackStack<T> {
|
|
fn clone(&self) -> Self {
|
|
StackStack(Rc::from((*self.0).clone()))
|
|
}
|
|
|
|
fn clone_from(&mut self, source: &Self) {
|
|
*self = source.clone()
|
|
}
|
|
}
|
|
|
|
impl<T> From<T> for StackInner<T> {
|
|
fn from(t: T) -> StackInner<T> {
|
|
StackInner {
|
|
next: Stack(Rc::from(None)),
|
|
data: t,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> From<StackInner<T>> for Stack<T> {
|
|
fn from(t: StackInner<T>) -> Stack<T> {
|
|
Stack(Rc::from(Some(t)))
|
|
}
|
|
}
|
|
|
|
impl<T> Index<usize> for StackStack<T> {
|
|
type Output = T;
|
|
fn index(&self, index: usize) -> &T {
|
|
if let Some(ref inner) = *self.0 {
|
|
// pass on to next
|
|
if inner.count <= index {
|
|
&inner.next[index - inner.count]
|
|
|
|
// fetch from our stack
|
|
} else {
|
|
let mut idx = index;
|
|
let mut cursor = &inner.stack;
|
|
while let Some(ref node) = *cursor.0 {
|
|
if idx == 0 {
|
|
return &node.data
|
|
}
|
|
idx -= 1;
|
|
cursor = &node.next;
|
|
}
|
|
// should never hit this case
|
|
panic!("encountered inconsistent lengths in stackstack")
|
|
}
|
|
|
|
// guaranteed out of bounds
|
|
} else {
|
|
panic!("index out of bounds on stackstack access")
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: Debug> Debug for StackStack<T> {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
let mut ss_idx = 1;
|
|
let mut ss_cur = &*self.0;
|
|
while let Some(inner) = ss_cur {
|
|
write!(f, "Frame {ss_idx}:")?;
|
|
let mut s_cur = &*inner.stack.0;
|
|
while let Some(node) = s_cur {
|
|
write!(f, " {:#?}", node.data)?;
|
|
s_cur = &*node.next.0;
|
|
}
|
|
write!(f, "\n")?;
|
|
ss_cur = &*inner.next.0;
|
|
ss_idx += 1;
|
|
}
|
|
|
|
write!(f, "\n")
|
|
}
|
|
}
|
|
|
|
impl<T> Stack<T> {
|
|
fn push(&mut self, item: T) {
|
|
self.0 = Rc::from(Some(StackInner{
|
|
data: item,
|
|
next: Stack(self.0.clone()),
|
|
}))
|
|
}
|
|
|
|
fn pop(&mut self) -> T {
|
|
// clone self.0 and then drop first ref, decreasing strong count back to 1
|
|
let d = self.0.clone();
|
|
self.0 = Rc::new(None);
|
|
|
|
// deconstruct the rc that formerly held self.0
|
|
let b = Rc::into_inner(d).unwrap();
|
|
if let Some(inner) = b {
|
|
let data = inner.data;
|
|
self.0 = inner.next.0;
|
|
data
|
|
} else {
|
|
panic!("pop from 0 length stack")
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> StackStack<T> {
|
|
pub fn push_current_stack(&mut self, item: T) {
|
|
if let Some(inner) = Rc::get_mut(&mut self.0).unwrap() {
|
|
inner.stack.push(item);
|
|
inner.count += 1;
|
|
} else {
|
|
panic!("push to uninitialized stackstack")
|
|
}
|
|
}
|
|
|
|
pub fn pop_current_stack(&mut self) -> T {
|
|
if let Some(inner) = Rc::get_mut(&mut self.0).unwrap() {
|
|
inner.count -= 1;
|
|
inner.stack.pop()
|
|
} else {
|
|
panic!("pop from uninitialized stackstack")
|
|
}
|
|
}
|
|
|
|
pub fn add_stack(&mut self) {
|
|
self.0 = Rc::from(Some(StackStackInner{
|
|
next: StackStack(self.0.clone()),
|
|
count: 0,
|
|
stack: Stack(Rc::from(None)),
|
|
}))
|
|
}
|
|
|
|
pub fn destroy_top_stack(&mut self) {
|
|
let s = Rc::get_mut(&mut self.0).unwrap();
|
|
if let Some(inner) = s {
|
|
self.0 = inner.next.0.clone()
|
|
} else {
|
|
panic!("del from empty stackstack")
|
|
}
|
|
}
|
|
|
|
pub fn new() -> StackStack<T> {
|
|
StackStack(Rc::from(Some(StackStackInner{
|
|
count: 0,
|
|
next: StackStack(Rc::from(None)),
|
|
stack: Stack(Rc::from(None)),
|
|
})))
|
|
}
|
|
|
|
pub fn len(&self) -> usize {
|
|
if let Some(ref inner) = *self.0 {
|
|
if let Some(_) = *inner.next.0 {
|
|
inner.next.len() + inner.count
|
|
} else {
|
|
inner.count
|
|
}
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_alloc_new_stack_and_push_many() {
|
|
let mut g = StackStack::<i8>::new();
|
|
g.add_stack();
|
|
g.push_current_stack(0);
|
|
g.push_current_stack(1);
|
|
g.push_current_stack(2);
|
|
assert_eq!(g.len(), 3);
|
|
g.add_stack();
|
|
g.push_current_stack(3);
|
|
g.push_current_stack(4);
|
|
assert_eq!(g.len(), 5);
|
|
|
|
assert_eq!(g.pop_current_stack(), 4);
|
|
assert_eq!(g.pop_current_stack(), 3);
|
|
g.destroy_top_stack();
|
|
assert_eq!(g.pop_current_stack(), 2);
|
|
assert_eq!(g.pop_current_stack(), 1);
|
|
assert_eq!(g.pop_current_stack(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_stack_index_bounds() {
|
|
let mut g = StackStack::<i8>::new();
|
|
g.add_stack();
|
|
g.push_current_stack(0);
|
|
g.push_current_stack(1);
|
|
g.push_current_stack(2);
|
|
assert_eq!(g.len(), 3);
|
|
g.add_stack();
|
|
g.push_current_stack(3);
|
|
g.push_current_stack(4);
|
|
assert_eq!(g.len(), 5);
|
|
|
|
assert_eq!(g[0], 4);
|
|
assert_eq!(g[1], 3);
|
|
assert_eq!(g[2], 2);
|
|
assert_eq!(g[3], 1);
|
|
assert_eq!(g[4], 0);
|
|
|
|
g.destroy_top_stack();
|
|
assert_eq!(g.len(), 3);
|
|
assert_eq!(g[0], 2);
|
|
assert_eq!(g[1], 1);
|
|
assert_eq!(g[2], 0);
|
|
}
|
|
}
|