kaish_kernel/validator/
scope_tracker.rs1use std::collections::HashSet;
7
8pub struct ScopeTracker {
13 frames: Vec<HashSet<String>>,
15}
16
17impl Default for ScopeTracker {
18 fn default() -> Self {
19 Self::new()
20 }
21}
22
23impl ScopeTracker {
24 pub fn new() -> Self {
26 let mut tracker = Self {
27 frames: vec![HashSet::new()],
28 };
29
30 tracker.bind_builtins();
32
33 tracker
34 }
35
36 fn bind_builtins(&mut self) {
38 let builtins = [
39 "?",
41 "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
43 "@", "#", "*",
45 "HOME", "PATH", "PWD", "OLDPWD", "USER", "SHELL", "TERM",
47 "LINENO", "FUNCNAME", "BASH_SOURCE",
49 "IFS",
51 "RANDOM",
53 "PID", "PPID", "UID", "EUID",
55 ];
56
57 for name in builtins {
58 self.bind(name);
59 }
60 }
61
62 pub fn push_frame(&mut self) {
67 self.frames.push(HashSet::new());
68 }
69
70 pub fn pop_frame(&mut self) {
75 if self.frames.len() > 1 {
76 self.frames.pop();
77 }
78 }
79
80 pub fn bind(&mut self, name: impl Into<String>) {
82 if let Some(frame) = self.frames.last_mut() {
83 frame.insert(name.into());
84 }
85 }
86
87 pub fn is_bound(&self, name: &str) -> bool {
91 self.frames.iter().rev().any(|frame| frame.contains(name))
92 }
93
94 pub fn should_skip_undefined_check(name: &str) -> bool {
99 name.starts_with('_')
100 }
101
102 pub fn depth(&self) -> usize {
104 self.frames.len()
105 }
106
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn new_has_builtins() {
115 let tracker = ScopeTracker::new();
116 assert!(tracker.is_bound("?"));
117 assert!(tracker.is_bound("HOME"));
118 assert!(tracker.is_bound("PATH"));
119 assert!(tracker.is_bound("0"));
120 assert!(tracker.is_bound("@"));
121 }
122
123 #[test]
124 fn bind_and_lookup() {
125 let mut tracker = ScopeTracker::new();
126 assert!(!tracker.is_bound("MY_VAR"));
127 tracker.bind("MY_VAR");
128 assert!(tracker.is_bound("MY_VAR"));
129 }
130
131 #[test]
132 fn nested_scopes() {
133 let mut tracker = ScopeTracker::new();
134
135 tracker.bind("OUTER");
136 assert!(tracker.is_bound("OUTER"));
137
138 tracker.push_frame();
139 tracker.bind("INNER");
140 assert!(tracker.is_bound("INNER"));
141 assert!(tracker.is_bound("OUTER")); tracker.pop_frame();
144 assert!(!tracker.is_bound("INNER")); assert!(tracker.is_bound("OUTER")); }
147
148 #[test]
149 fn underscore_convention() {
150 assert!(ScopeTracker::should_skip_undefined_check("_EXTERNAL"));
151 assert!(ScopeTracker::should_skip_undefined_check("__private"));
152 assert!(!ScopeTracker::should_skip_undefined_check("NORMAL"));
153 }
154
155 #[test]
156 fn depth_tracking() {
157 let mut tracker = ScopeTracker::new();
158 assert_eq!(tracker.depth(), 1);
159
160 tracker.push_frame();
161 assert_eq!(tracker.depth(), 2);
162
163 tracker.push_frame();
164 assert_eq!(tracker.depth(), 3);
165
166 tracker.pop_frame();
167 assert_eq!(tracker.depth(), 2);
168
169 tracker.pop_frame();
170 assert_eq!(tracker.depth(), 1);
171
172 tracker.pop_frame();
174 assert_eq!(tracker.depth(), 1);
175 }
176}