use std::collections::HashSet;
pub struct ScopeTracker {
frames: Vec<HashSet<String>>,
}
impl Default for ScopeTracker {
fn default() -> Self {
Self::new()
}
}
impl ScopeTracker {
pub fn new() -> Self {
let mut tracker = Self {
frames: vec![HashSet::new()],
};
tracker.bind_builtins();
tracker
}
fn bind_builtins(&mut self) {
let builtins = [
"?",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"@", "#", "*",
"HOME", "PATH", "PWD", "OLDPWD", "USER", "SHELL", "TERM",
"LINENO", "FUNCNAME", "BASH_SOURCE",
"IFS",
"RANDOM",
"PID", "PPID", "UID", "EUID",
];
for name in builtins {
self.bind(name);
}
}
pub fn push_frame(&mut self) {
self.frames.push(HashSet::new());
}
pub fn pop_frame(&mut self) {
if self.frames.len() > 1 {
self.frames.pop();
}
}
pub fn bind(&mut self, name: impl Into<String>) {
if let Some(frame) = self.frames.last_mut() {
frame.insert(name.into());
}
}
pub fn is_bound(&self, name: &str) -> bool {
self.frames.iter().rev().any(|frame| frame.contains(name))
}
pub fn should_skip_undefined_check(name: &str) -> bool {
name.starts_with('_')
}
pub fn depth(&self) -> usize {
self.frames.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_has_builtins() {
let tracker = ScopeTracker::new();
assert!(tracker.is_bound("?"));
assert!(tracker.is_bound("HOME"));
assert!(tracker.is_bound("PATH"));
assert!(tracker.is_bound("0"));
assert!(tracker.is_bound("@"));
}
#[test]
fn bind_and_lookup() {
let mut tracker = ScopeTracker::new();
assert!(!tracker.is_bound("MY_VAR"));
tracker.bind("MY_VAR");
assert!(tracker.is_bound("MY_VAR"));
}
#[test]
fn nested_scopes() {
let mut tracker = ScopeTracker::new();
tracker.bind("OUTER");
assert!(tracker.is_bound("OUTER"));
tracker.push_frame();
tracker.bind("INNER");
assert!(tracker.is_bound("INNER"));
assert!(tracker.is_bound("OUTER"));
tracker.pop_frame();
assert!(!tracker.is_bound("INNER")); assert!(tracker.is_bound("OUTER")); }
#[test]
fn underscore_convention() {
assert!(ScopeTracker::should_skip_undefined_check("_EXTERNAL"));
assert!(ScopeTracker::should_skip_undefined_check("__private"));
assert!(!ScopeTracker::should_skip_undefined_check("NORMAL"));
}
#[test]
fn depth_tracking() {
let mut tracker = ScopeTracker::new();
assert_eq!(tracker.depth(), 1);
tracker.push_frame();
assert_eq!(tracker.depth(), 2);
tracker.push_frame();
assert_eq!(tracker.depth(), 3);
tracker.pop_frame();
assert_eq!(tracker.depth(), 2);
tracker.pop_frame();
assert_eq!(tracker.depth(), 1);
tracker.pop_frame();
assert_eq!(tracker.depth(), 1);
}
}