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 #[allow(dead_code)]
109 pub fn all_bound(&self) -> Vec<&str> {
110 self.frames
111 .iter()
112 .flat_map(|f| f.iter().map(|s| s.as_str()))
113 .collect()
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn new_has_builtins() {
123 let tracker = ScopeTracker::new();
124 assert!(tracker.is_bound("?"));
125 assert!(tracker.is_bound("HOME"));
126 assert!(tracker.is_bound("PATH"));
127 assert!(tracker.is_bound("0"));
128 assert!(tracker.is_bound("@"));
129 }
130
131 #[test]
132 fn bind_and_lookup() {
133 let mut tracker = ScopeTracker::new();
134 assert!(!tracker.is_bound("MY_VAR"));
135 tracker.bind("MY_VAR");
136 assert!(tracker.is_bound("MY_VAR"));
137 }
138
139 #[test]
140 fn nested_scopes() {
141 let mut tracker = ScopeTracker::new();
142
143 tracker.bind("OUTER");
144 assert!(tracker.is_bound("OUTER"));
145
146 tracker.push_frame();
147 tracker.bind("INNER");
148 assert!(tracker.is_bound("INNER"));
149 assert!(tracker.is_bound("OUTER")); tracker.pop_frame();
152 assert!(!tracker.is_bound("INNER")); assert!(tracker.is_bound("OUTER")); }
155
156 #[test]
157 fn underscore_convention() {
158 assert!(ScopeTracker::should_skip_undefined_check("_EXTERNAL"));
159 assert!(ScopeTracker::should_skip_undefined_check("__private"));
160 assert!(!ScopeTracker::should_skip_undefined_check("NORMAL"));
161 }
162
163 #[test]
164 fn depth_tracking() {
165 let mut tracker = ScopeTracker::new();
166 assert_eq!(tracker.depth(), 1);
167
168 tracker.push_frame();
169 assert_eq!(tracker.depth(), 2);
170
171 tracker.push_frame();
172 assert_eq!(tracker.depth(), 3);
173
174 tracker.pop_frame();
175 assert_eq!(tracker.depth(), 2);
176
177 tracker.pop_frame();
178 assert_eq!(tracker.depth(), 1);
179
180 tracker.pop_frame();
182 assert_eq!(tracker.depth(), 1);
183 }
184}