use bumpalo::collections::String as BumpString;
use bumpalo::collections::Vec as BumpVec;
use bumpalo::Bump;
use super::value::Value;
pub struct ScopeStack<'a> {
entries: Vec<(&'a str, &'a Value<'a>, u32)>,
depth: u32,
boundaries: Vec<usize>,
}
impl<'a> ScopeStack<'a> {
pub fn new() -> Self {
Self {
entries: Vec::with_capacity(64),
depth: 0,
boundaries: Vec::with_capacity(16),
}
}
pub fn push_scope(&mut self) {
self.boundaries.push(self.entries.len());
self.depth += 1;
}
pub fn pop_scope(&mut self) {
if let Some(boundary) = self.boundaries.pop() {
self.entries.truncate(boundary);
self.depth -= 1;
}
}
pub fn bind(&mut self, name: &'a str, value: &'a Value<'a>) {
self.entries.push((name, value, self.depth));
}
pub fn lookup(&self, name: &str) -> Option<&'a Value<'a>> {
for &(n, v, _) in self.entries.iter().rev() {
if n == name {
return Some(v);
}
}
None
}
pub fn depth(&self) -> u32 {
self.depth
}
pub fn capture(&self, arena: &'a Bump) -> BumpVec<'a, (BumpString<'a>, &'a Value<'a>)> {
let mut seen = Vec::new();
let mut captures = BumpVec::new_in(arena);
for &(name, value, _) in self.entries.iter().rev() {
if !seen.contains(&name) {
seen.push(name);
captures.push((BumpString::from_str_in(name, arena), value));
}
}
captures
}
pub fn restore_captures<'c>(
&mut self,
captures: &'c BumpVec<'a, (BumpString<'a>, &'a Value<'a>)>,
) where
'c: 'a,
{
for (name, value) in captures.iter() {
self.entries.push((name.as_str(), *value, self.depth));
}
}
}
impl Default for ScopeStack<'_> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_bind_and_lookup() {
let arena = Bump::new();
let mut scope = ScopeStack::new();
let val = Value::number(&arena, 42.0);
scope.bind("x", val);
assert_eq!(scope.lookup("x").unwrap().as_f64(), 42.0);
assert!(scope.lookup("y").is_none());
}
#[test]
fn scope_push_pop() {
let arena = Bump::new();
let mut scope = ScopeStack::new();
scope.bind("a", Value::number(&arena, 1.0));
scope.push_scope();
scope.bind("b", Value::number(&arena, 2.0));
assert_eq!(scope.lookup("a").unwrap().as_f64(), 1.0);
assert_eq!(scope.lookup("b").unwrap().as_f64(), 2.0);
scope.pop_scope();
assert_eq!(scope.lookup("a").unwrap().as_f64(), 1.0);
assert!(scope.lookup("b").is_none());
}
#[test]
fn shadowing() {
let arena = Bump::new();
let mut scope = ScopeStack::new();
scope.bind("x", Value::number(&arena, 1.0));
scope.push_scope();
scope.bind("x", Value::number(&arena, 2.0));
assert_eq!(scope.lookup("x").unwrap().as_f64(), 2.0);
scope.pop_scope();
assert_eq!(scope.lookup("x").unwrap().as_f64(), 1.0);
}
#[test]
fn capture_snapshot() {
let arena = Bump::new();
let mut scope = ScopeStack::new();
scope.bind("a", Value::number(&arena, 1.0));
scope.bind("b", Value::number(&arena, 2.0));
let captures = scope.capture(&arena);
assert_eq!(captures.len(), 2);
let a = captures.iter().find(|(n, _)| n.as_str() == "a");
assert!(a.is_some());
assert_eq!(a.unwrap().1.as_f64(), 1.0);
let b = captures.iter().find(|(n, _)| n.as_str() == "b");
assert!(b.is_some());
assert_eq!(b.unwrap().1.as_f64(), 2.0);
}
#[test]
fn capture_deduplicates() {
let arena = Bump::new();
let mut scope = ScopeStack::new();
scope.bind("x", Value::number(&arena, 1.0));
scope.push_scope();
scope.bind("x", Value::number(&arena, 2.0));
let captures = scope.capture(&arena);
let x_count = captures.iter().filter(|(n, _)| n.as_str() == "x").count();
assert_eq!(x_count, 1);
let (_, val) = captures.iter().find(|(n, _)| n.as_str() == "x").unwrap();
assert_eq!(val.as_f64(), 2.0);
}
#[test]
fn depth_tracking() {
let mut scope = ScopeStack::<'_>::new();
assert_eq!(scope.depth(), 0);
scope.push_scope();
assert_eq!(scope.depth(), 1);
scope.push_scope();
assert_eq!(scope.depth(), 2);
scope.pop_scope();
assert_eq!(scope.depth(), 1);
scope.pop_scope();
assert_eq!(scope.depth(), 0);
}
}