From a12d15b2cdc6ec441223e939a4b991f72e379913 Mon Sep 17 00:00:00 2001 From: Ava Affine Date: Wed, 28 May 2025 11:54:40 -0700 Subject: [PATCH] StackStack and CI Updates This commit adds a new data type to the codebase. It organizes data into first in last out linked lists of first in last out linked lists. Hence the name: StackStack. This data type was made to represent frames of execution, where there there is a stack for each function call containing local variables and arguments. The following tertiary goals were also met: - no relocating of adjacent data on push or pop - no copying or cloning of contained data on modification or mutation - index access to all elements of all contained stacks starting with most recent insertions. There are operations to allocate a new stack on the stackstack and to deallocate the top stack on the stackstack. Additionally there are operations for pushing and popping from the top stack. Unit tests are added and CI is updated to include them. Signed-off-by: Ava Affine --- .gitlab-ci.yml | 17 ++- Cargo.lock | 8 -- mycelium/src/lib.rs | 1 + mycelium/src/stackstack.rs | 234 +++++++++++++++++++++++++++++++++++++ 4 files changed, 248 insertions(+), 12 deletions(-) create mode 100644 mycelium/src/stackstack.rs diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a170259..4fa8b7a 100755 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,8 @@ default: stages: - build - - test + - test-frontend + - test-backend compile-library: stage: build @@ -15,14 +16,22 @@ compile-decomposer: script: - cargo build --bin decomposer -unit-test-language-frontend: - stage: test +unit-test-lexer: + stage: test-frontend script: - cargo test lexer + +unit-test-parser: + stage: test-frontend + script: - cargo test parser unit-test-number-package: - stage: test + stage: test-backend script: - cargo test number +unit-test-stackstack: + stage: test-backend + script: + - cargo test stackstack diff --git a/Cargo.lock b/Cargo.lock index 75350e7..e800b30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,10 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "amanita" -version = "0.1.0" - [[package]] name = "anstream" version = "0.6.18" @@ -116,10 +112,6 @@ dependencies = [ "mycelium", ] -[[package]] -name = "enoki" -version = "0.1.0" - [[package]] name = "heck" version = "0.5.0" diff --git a/mycelium/src/lib.rs b/mycelium/src/lib.rs index e404294..bb8a157 100644 --- a/mycelium/src/lib.rs +++ b/mycelium/src/lib.rs @@ -25,5 +25,6 @@ pub mod sexpr; pub mod lexer; pub mod parser; pub mod number; +pub mod stackstack; extern crate alloc; diff --git a/mycelium/src/stackstack.rs b/mycelium/src/stackstack.rs new file mode 100644 index 0000000..c772f99 --- /dev/null +++ b/mycelium/src/stackstack.rs @@ -0,0 +1,234 @@ +/* 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 . + */ + +use core::fmt::{self, Debug, Formatter}; +use core::ops::Index; +use alloc::rc::Rc; + +struct StackInner { + pub next: Stack, + pub data: T +} + +struct Stack (Rc>>); + +struct StackStackInner { + next: StackStack, + count: usize, + stack: Stack, +} + +pub struct StackStack (Rc>>); + +impl From for StackInner { + fn from(t: T) -> StackInner { + StackInner { + next: Stack(Rc::from(None)), + data: t, + } + } +} + +impl From> for Stack { + fn from(t: StackInner) -> Stack { + Stack(Rc::from(Some(t))) + } +} + +impl Index for StackStack { + 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 Debug for StackStack { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut ss_idx = 1; + let mut ss_cur = &*self.0; + while let Some(ref inner) = ss_cur { + write!(f, "Frame {ss_idx}:")?; + let mut s_cur = &*inner.stack.0; + while let Some(ref 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 Stack { + 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 StackStack { + 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 { + 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::::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::::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); + } +}