use std::collections::HashSet;
#[derive(Debug, Clone)]
pub struct ScopeTracker {
declared_vars: Vec<HashSet<String>>,
}
impl Default for ScopeTracker {
fn default() -> Self {
Self::new()
}
}
impl ScopeTracker {
pub fn new() -> Self {
Self {
declared_vars: vec![HashSet::new()],
}
}
pub fn enter_scope(&mut self) {
self.declared_vars.push(HashSet::new());
}
pub fn exit_scope(&mut self) -> bool {
if self.declared_vars.len() > 1 {
self.declared_vars.pop();
true
} else {
false
}
}
pub fn is_declared(&self, var_name: &str) -> bool {
self.declared_vars
.iter()
.any(|scope| scope.contains(var_name))
}
pub fn is_declared_in_current_scope(&self, var_name: &str) -> bool {
self.declared_vars
.last()
.is_some_and(|scope| scope.contains(var_name))
}
pub fn declare_var(&mut self, var_name: &str) -> bool {
if let Some(current_scope) = self.declared_vars.last_mut() {
current_scope.insert(var_name.to_string())
} else {
false
}
}
pub fn scope_depth(&self) -> usize {
self.declared_vars.len()
}
pub fn all_declared_vars(&self) -> HashSet<String> {
self.declared_vars
.iter()
.flat_map(|scope| scope.iter().cloned())
.collect()
}
pub fn current_scope_vars(&self) -> Option<&HashSet<String>> {
self.declared_vars.last()
}
pub fn declare_vars<I>(&mut self, var_names: I)
where
I: IntoIterator,
I::Item: AsRef<str>,
{
for var in var_names {
self.declare_var(var.as_ref());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_creates_single_scope() {
let tracker = ScopeTracker::new();
assert_eq!(tracker.scope_depth(), 1);
}
#[test]
fn test_default_creates_single_scope() {
let tracker = ScopeTracker::default();
assert_eq!(tracker.scope_depth(), 1);
}
#[test]
fn test_new_scope_is_empty() {
let tracker = ScopeTracker::new();
assert!(!tracker.is_declared("x"));
}
#[test]
fn test_declare_var_returns_true_for_new() {
let mut tracker = ScopeTracker::new();
assert!(tracker.declare_var("x"));
}
#[test]
fn test_declare_var_returns_false_for_existing() {
let mut tracker = ScopeTracker::new();
tracker.declare_var("x");
assert!(!tracker.declare_var("x"));
}
#[test]
fn test_declared_var_is_found() {
let mut tracker = ScopeTracker::new();
tracker.declare_var("x");
assert!(tracker.is_declared("x"));
}
#[test]
fn test_undeclared_var_is_not_found() {
let tracker = ScopeTracker::new();
assert!(!tracker.is_declared("y"));
}
#[test]
fn test_enter_scope_increases_depth() {
let mut tracker = ScopeTracker::new();
tracker.enter_scope();
assert_eq!(tracker.scope_depth(), 2);
}
#[test]
fn test_exit_scope_decreases_depth() {
let mut tracker = ScopeTracker::new();
tracker.enter_scope();
tracker.exit_scope();
assert_eq!(tracker.scope_depth(), 1);
}
#[test]
fn test_exit_scope_returns_true_when_nested() {
let mut tracker = ScopeTracker::new();
tracker.enter_scope();
assert!(tracker.exit_scope());
}
#[test]
fn test_exit_scope_returns_false_at_outermost() {
let mut tracker = ScopeTracker::new();
assert!(!tracker.exit_scope());
}
#[test]
fn test_exit_scope_removes_vars() {
let mut tracker = ScopeTracker::new();
tracker.enter_scope();
tracker.declare_var("inner");
assert!(tracker.is_declared("inner"));
tracker.exit_scope();
assert!(!tracker.is_declared("inner"));
}
#[test]
fn test_parent_scope_vars_visible_in_child() {
let mut tracker = ScopeTracker::new();
tracker.declare_var("outer");
tracker.enter_scope();
assert!(tracker.is_declared("outer"));
}
#[test]
fn test_parent_scope_vars_survive_exit() {
let mut tracker = ScopeTracker::new();
tracker.declare_var("outer");
tracker.enter_scope();
tracker.declare_var("inner");
tracker.exit_scope();
assert!(tracker.is_declared("outer"));
}
#[test]
fn test_is_declared_in_current_scope_true() {
let mut tracker = ScopeTracker::new();
tracker.declare_var("x");
assert!(tracker.is_declared_in_current_scope("x"));
}
#[test]
fn test_is_declared_in_current_scope_false_for_parent() {
let mut tracker = ScopeTracker::new();
tracker.declare_var("outer");
tracker.enter_scope();
assert!(!tracker.is_declared_in_current_scope("outer"));
assert!(tracker.is_declared("outer")); }
#[test]
fn test_is_declared_in_current_scope_false_for_undeclared() {
let tracker = ScopeTracker::new();
assert!(!tracker.is_declared_in_current_scope("x"));
}
#[test]
fn test_all_declared_vars_empty() {
let tracker = ScopeTracker::new();
assert!(tracker.all_declared_vars().is_empty());
}
#[test]
fn test_all_declared_vars_single_scope() {
let mut tracker = ScopeTracker::new();
tracker.declare_var("a");
tracker.declare_var("b");
let vars = tracker.all_declared_vars();
assert!(vars.contains("a"));
assert!(vars.contains("b"));
assert_eq!(vars.len(), 2);
}
#[test]
fn test_all_declared_vars_multiple_scopes() {
let mut tracker = ScopeTracker::new();
tracker.declare_var("outer");
tracker.enter_scope();
tracker.declare_var("inner");
let vars = tracker.all_declared_vars();
assert!(vars.contains("outer"));
assert!(vars.contains("inner"));
assert_eq!(vars.len(), 2);
}
#[test]
fn test_current_scope_vars_returns_some() {
let tracker = ScopeTracker::new();
assert!(tracker.current_scope_vars().is_some());
}
#[test]
fn test_current_scope_vars_contains_declared() {
let mut tracker = ScopeTracker::new();
tracker.declare_var("x");
let vars = tracker.current_scope_vars().unwrap();
assert!(vars.contains("x"));
}
#[test]
fn test_current_scope_vars_excludes_parent() {
let mut tracker = ScopeTracker::new();
tracker.declare_var("outer");
tracker.enter_scope();
tracker.declare_var("inner");
let vars = tracker.current_scope_vars().unwrap();
assert!(!vars.contains("outer"));
assert!(vars.contains("inner"));
}
#[test]
fn test_declare_vars_multiple() {
let mut tracker = ScopeTracker::new();
tracker.declare_vars(["a", "b", "c"]);
assert!(tracker.is_declared("a"));
assert!(tracker.is_declared("b"));
assert!(tracker.is_declared("c"));
}
#[test]
fn test_declare_vars_empty() {
let mut tracker = ScopeTracker::new();
tracker.declare_vars(Vec::<&str>::new());
assert!(tracker.all_declared_vars().is_empty());
}
#[test]
fn test_declare_vars_with_strings() {
let mut tracker = ScopeTracker::new();
let vars = vec!["x".to_string(), "y".to_string()];
tracker.declare_vars(vars);
assert!(tracker.is_declared("x"));
assert!(tracker.is_declared("y"));
}
#[test]
fn test_scope_depth_nested() {
let mut tracker = ScopeTracker::new();
assert_eq!(tracker.scope_depth(), 1);
tracker.enter_scope();
assert_eq!(tracker.scope_depth(), 2);
tracker.enter_scope();
assert_eq!(tracker.scope_depth(), 3);
tracker.exit_scope();
assert_eq!(tracker.scope_depth(), 2);
tracker.exit_scope();
assert_eq!(tracker.scope_depth(), 1);
}
#[test]
fn test_redeclare_in_nested_scope() {
let mut tracker = ScopeTracker::new();
tracker.declare_var("x");
tracker.enter_scope();
assert!(tracker.declare_var("x"));
assert!(tracker.is_declared_in_current_scope("x"));
}
#[test]
fn test_shadow_persists_only_in_scope() {
let mut tracker = ScopeTracker::new();
tracker.declare_var("x");
tracker.enter_scope();
tracker.declare_var("x"); tracker.exit_scope();
assert!(tracker.is_declared("x"));
}
#[test]
fn test_deeply_nested_scopes() {
let mut tracker = ScopeTracker::new();
tracker.declare_var("a");
tracker.enter_scope();
tracker.declare_var("b");
tracker.enter_scope();
tracker.declare_var("c");
tracker.enter_scope();
tracker.declare_var("d");
assert!(tracker.is_declared("a"));
assert!(tracker.is_declared("b"));
assert!(tracker.is_declared("c"));
assert!(tracker.is_declared("d"));
assert!(tracker.is_declared_in_current_scope("d"));
assert!(!tracker.is_declared_in_current_scope("a"));
tracker.exit_scope();
assert!(!tracker.is_declared("d"));
tracker.exit_scope();
assert!(!tracker.is_declared("c"));
tracker.exit_scope();
assert!(!tracker.is_declared("b"));
assert!(tracker.is_declared("a"));
}
#[test]
fn test_function_like_usage() {
let mut tracker = ScopeTracker::new();
tracker.declare_vars(["x", "y"]);
tracker.declare_var("z");
tracker.enter_scope();
tracker.declare_var("w");
assert!(tracker.is_declared("w"));
assert!(tracker.is_declared("x")); tracker.exit_scope();
assert!(!tracker.is_declared("w"));
assert!(tracker.is_declared("x")); assert!(tracker.is_declared("z")); }
}