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); + } +}