kaish_kernel/interpreter/
scope.rs1use std::collections::{HashMap, HashSet};
9
10use crate::ast::{Value, VarPath, VarSegment};
11
12use super::result::ExecResult;
13
14#[derive(Debug, Clone)]
19pub struct Scope {
20 frames: Vec<HashMap<String, Value>>,
22 exported: HashSet<String>,
24 last_result: ExecResult,
26 script_name: String,
28 positional: Vec<String>,
30 error_exit: bool,
32 pid: u32,
34}
35
36impl Scope {
37 pub fn new() -> Self {
39 Self {
40 frames: vec![HashMap::new()],
41 exported: HashSet::new(),
42 last_result: ExecResult::default(),
43 script_name: String::new(),
44 positional: Vec::new(),
45 error_exit: false,
46 pid: std::process::id(),
47 }
48 }
49
50 pub fn pid(&self) -> u32 {
52 self.pid
53 }
54
55 pub fn push_frame(&mut self) {
57 self.frames.push(HashMap::new());
58 }
59
60 pub fn pop_frame(&mut self) {
64 if self.frames.len() > 1 {
65 self.frames.pop();
66 } else {
67 panic!("cannot pop the root scope frame");
68 }
69 }
70
71 pub fn set(&mut self, name: impl Into<String>, value: Value) {
75 if let Some(frame) = self.frames.last_mut() {
76 frame.insert(name.into(), value);
77 }
78 }
79
80 pub fn set_global(&mut self, name: impl Into<String>, value: Value) {
86 let name = name.into();
87
88 for frame in self.frames.iter_mut().rev() {
90 if let std::collections::hash_map::Entry::Occupied(mut e) = frame.entry(name.clone()) {
91 e.insert(value);
92 return;
93 }
94 }
95
96 if let Some(frame) = self.frames.first_mut() {
98 frame.insert(name, value);
99 }
100 }
101
102 pub fn get(&self, name: &str) -> Option<&Value> {
104 for frame in self.frames.iter().rev() {
105 if let Some(value) = frame.get(name) {
106 return Some(value);
107 }
108 }
109 None
110 }
111
112 pub fn remove(&mut self, name: &str) -> Option<Value> {
116 for frame in self.frames.iter_mut().rev() {
117 if let Some(value) = frame.remove(name) {
118 return Some(value);
119 }
120 }
121 None
122 }
123
124 pub fn set_last_result(&mut self, result: ExecResult) {
126 self.last_result = result;
127 }
128
129 pub fn last_result(&self) -> &ExecResult {
131 &self.last_result
132 }
133
134 pub fn set_positional(&mut self, script_name: impl Into<String>, args: Vec<String>) {
138 self.script_name = script_name.into();
139 self.positional = args;
140 }
141
142 pub fn save_positional(&self) -> (String, Vec<String>) {
146 (self.script_name.clone(), self.positional.clone())
147 }
148
149 pub fn get_positional(&self, n: usize) -> Option<&str> {
153 if n == 0 {
154 if self.script_name.is_empty() {
155 None
156 } else {
157 Some(&self.script_name)
158 }
159 } else {
160 self.positional.get(n - 1).map(|s| s.as_str())
161 }
162 }
163
164 pub fn all_args(&self) -> &[String] {
166 &self.positional
167 }
168
169 pub fn arg_count(&self) -> usize {
171 self.positional.len()
172 }
173
174 pub fn error_exit_enabled(&self) -> bool {
176 self.error_exit
177 }
178
179 pub fn set_error_exit(&mut self, enabled: bool) {
181 self.error_exit = enabled;
182 }
183
184 pub fn export(&mut self, name: impl Into<String>) {
188 self.exported.insert(name.into());
189 }
190
191 pub fn is_exported(&self, name: &str) -> bool {
193 self.exported.contains(name)
194 }
195
196 pub fn set_exported(&mut self, name: impl Into<String>, value: Value) {
198 let name = name.into();
199 self.set(&name, value);
200 self.export(name);
201 }
202
203 pub fn unexport(&mut self, name: &str) {
205 self.exported.remove(name);
206 }
207
208 pub fn exported_vars(&self) -> Vec<(String, Value)> {
212 let mut result = Vec::new();
213 for name in &self.exported {
214 if let Some(value) = self.get(name) {
215 result.push((name.clone(), value.clone()));
216 }
217 }
218 result.sort_by(|(a, _), (b, _)| a.cmp(b));
219 result
220 }
221
222 pub fn exported_names(&self) -> Vec<&str> {
224 let mut names: Vec<&str> = self.exported.iter().map(|s| s.as_str()).collect();
225 names.sort();
226 names
227 }
228
229 pub fn resolve_path(&self, path: &VarPath) -> Option<Value> {
234 if path.segments.is_empty() {
235 return None;
236 }
237
238 let VarSegment::Field(root_name) = &path.segments[0];
240
241 if root_name == "?" {
243 return self.resolve_result_path(&path.segments[1..]);
244 }
245
246 if path.segments.len() > 1 {
248 return None; }
250
251 self.get(root_name).cloned()
252 }
253
254 fn resolve_result_path(&self, segments: &[VarSegment]) -> Option<Value> {
259 if segments.is_empty() {
260 return Some(Value::Int(self.last_result.code));
262 }
263
264 let VarSegment::Field(field_name) = &segments[0];
266
267 if segments.len() > 1 {
269 return None;
270 }
271
272 self.last_result.get_field(field_name)
274 }
275
276 pub fn contains(&self, name: &str) -> bool {
278 self.get(name).is_some()
279 }
280
281 pub fn all_names(&self) -> Vec<&str> {
283 let mut names: Vec<&str> = self
284 .frames
285 .iter()
286 .flat_map(|f| f.keys().map(|s| s.as_str()))
287 .collect();
288 names.sort();
289 names.dedup();
290 names
291 }
292
293 pub fn all(&self) -> Vec<(String, Value)> {
297 let mut result = std::collections::HashMap::new();
298 for frame in &self.frames {
300 for (name, value) in frame {
301 result.insert(name.clone(), value.clone());
302 }
303 }
304 let mut pairs: Vec<_> = result.into_iter().collect();
305 pairs.sort_by(|(a, _), (b, _)| a.cmp(b));
306 pairs
307 }
308}
309
310impl Default for Scope {
311 fn default() -> Self {
312 Self::new()
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319
320 #[test]
321 fn new_scope_has_one_frame() {
322 let scope = Scope::new();
323 assert_eq!(scope.frames.len(), 1);
324 }
325
326 #[test]
327 fn set_and_get_variable() {
328 let mut scope = Scope::new();
329 scope.set("X", Value::Int(42));
330 assert_eq!(scope.get("X"), Some(&Value::Int(42)));
331 }
332
333 #[test]
334 fn get_nonexistent_returns_none() {
335 let scope = Scope::new();
336 assert_eq!(scope.get("MISSING"), None);
337 }
338
339 #[test]
340 fn inner_frame_shadows_outer() {
341 let mut scope = Scope::new();
342 scope.set("X", Value::Int(1));
343 scope.push_frame();
344 scope.set("X", Value::Int(2));
345 assert_eq!(scope.get("X"), Some(&Value::Int(2)));
346 scope.pop_frame();
347 assert_eq!(scope.get("X"), Some(&Value::Int(1)));
348 }
349
350 #[test]
351 fn inner_frame_can_see_outer_vars() {
352 let mut scope = Scope::new();
353 scope.set("OUTER", Value::String("visible".into()));
354 scope.push_frame();
355 assert_eq!(scope.get("OUTER"), Some(&Value::String("visible".into())));
356 }
357
358 #[test]
359 fn resolve_simple_path() {
360 let mut scope = Scope::new();
361 scope.set("NAME", Value::String("Alice".into()));
362
363 let path = VarPath::simple("NAME");
364 assert_eq!(
365 scope.resolve_path(&path),
366 Some(Value::String("Alice".into()))
367 );
368 }
369
370 #[test]
371 fn resolve_last_result_ok() {
372 let mut scope = Scope::new();
373 scope.set_last_result(ExecResult::success("output"));
374
375 let path = VarPath {
376 segments: vec![
377 VarSegment::Field("?".into()),
378 VarSegment::Field("ok".into()),
379 ],
380 };
381 assert_eq!(scope.resolve_path(&path), Some(Value::Bool(true)));
382 }
383
384 #[test]
385 fn resolve_last_result_code() {
386 let mut scope = Scope::new();
387 scope.set_last_result(ExecResult::failure(127, "not found"));
388
389 let path = VarPath {
390 segments: vec![
391 VarSegment::Field("?".into()),
392 VarSegment::Field("code".into()),
393 ],
394 };
395 assert_eq!(scope.resolve_path(&path), Some(Value::Int(127)));
396 }
397
398 #[test]
399 fn resolve_last_result_data_field() {
400 let mut scope = Scope::new();
401 scope.set_last_result(ExecResult::success(r#"{"count": 5}"#));
402
403 let path = VarPath {
405 segments: vec![
406 VarSegment::Field("?".into()),
407 VarSegment::Field("data".into()),
408 ],
409 };
410 let result = scope.resolve_path(&path);
412 assert!(result.is_some());
413 if let Some(Value::Json(json)) = result {
414 assert_eq!(json.get("count"), Some(&serde_json::json!(5)));
415 } else {
416 panic!("expected Value::Json, got {:?}", result);
417 }
418 }
419
420 #[test]
421 fn resolve_invalid_path_returns_none() {
422 let mut scope = Scope::new();
423 scope.set("X", Value::Int(42));
424
425 let path = VarPath {
427 segments: vec![
428 VarSegment::Field("X".into()),
429 VarSegment::Field("invalid".into()),
430 ],
431 };
432 assert_eq!(scope.resolve_path(&path), None);
433 }
434
435 #[test]
436 fn contains_finds_variable() {
437 let mut scope = Scope::new();
438 scope.set("EXISTS", Value::Bool(true));
439 assert!(scope.contains("EXISTS"));
440 assert!(!scope.contains("MISSING"));
441 }
442
443 #[test]
444 fn all_names_lists_variables() {
445 let mut scope = Scope::new();
446 scope.set("A", Value::Int(1));
447 scope.set("B", Value::Int(2));
448 scope.push_frame();
449 scope.set("C", Value::Int(3));
450
451 let names = scope.all_names();
452 assert!(names.contains(&"A"));
453 assert!(names.contains(&"B"));
454 assert!(names.contains(&"C"));
455 }
456
457 #[test]
458 #[should_panic(expected = "cannot pop the root scope frame")]
459 fn pop_root_frame_panics() {
460 let mut scope = Scope::new();
461 scope.pop_frame();
462 }
463
464 #[test]
465 fn positional_params_basic() {
466 let mut scope = Scope::new();
467 scope.set_positional("my_tool", vec!["arg1".into(), "arg2".into(), "arg3".into()]);
468
469 assert_eq!(scope.get_positional(0), Some("my_tool"));
471 assert_eq!(scope.get_positional(1), Some("arg1"));
473 assert_eq!(scope.get_positional(2), Some("arg2"));
474 assert_eq!(scope.get_positional(3), Some("arg3"));
475 assert_eq!(scope.get_positional(4), None);
477 }
478
479 #[test]
480 fn positional_params_empty() {
481 let scope = Scope::new();
482 assert_eq!(scope.get_positional(0), None);
484 assert_eq!(scope.get_positional(1), None);
485 assert_eq!(scope.arg_count(), 0);
486 assert!(scope.all_args().is_empty());
487 }
488
489 #[test]
490 fn all_args_returns_slice() {
491 let mut scope = Scope::new();
492 scope.set_positional("test", vec!["a".into(), "b".into(), "c".into()]);
493
494 let args = scope.all_args();
495 assert_eq!(args, &["a", "b", "c"]);
496 }
497
498 #[test]
499 fn arg_count_returns_count() {
500 let mut scope = Scope::new();
501 scope.set_positional("test", vec!["one".into(), "two".into()]);
502
503 assert_eq!(scope.arg_count(), 2);
504 }
505
506 #[test]
507 fn export_marks_variable() {
508 let mut scope = Scope::new();
509 scope.set("X", Value::Int(42));
510
511 assert!(!scope.is_exported("X"));
512 scope.export("X");
513 assert!(scope.is_exported("X"));
514 }
515
516 #[test]
517 fn set_exported_sets_and_exports() {
518 let mut scope = Scope::new();
519 scope.set_exported("PATH", Value::String("/usr/bin".into()));
520
521 assert!(scope.is_exported("PATH"));
522 assert_eq!(scope.get("PATH"), Some(&Value::String("/usr/bin".into())));
523 }
524
525 #[test]
526 fn unexport_removes_export_marker() {
527 let mut scope = Scope::new();
528 scope.set_exported("VAR", Value::Int(1));
529 assert!(scope.is_exported("VAR"));
530
531 scope.unexport("VAR");
532 assert!(!scope.is_exported("VAR"));
533 assert!(scope.get("VAR").is_some());
535 }
536
537 #[test]
538 fn exported_vars_returns_only_exported_with_values() {
539 let mut scope = Scope::new();
540 scope.set_exported("A", Value::Int(1));
541 scope.set_exported("B", Value::Int(2));
542 scope.set("C", Value::Int(3)); scope.export("D"); let exported = scope.exported_vars();
546 assert_eq!(exported.len(), 2);
547 assert_eq!(exported[0], ("A".to_string(), Value::Int(1)));
548 assert_eq!(exported[1], ("B".to_string(), Value::Int(2)));
549 }
550
551 #[test]
552 fn exported_names_returns_sorted_names() {
553 let mut scope = Scope::new();
554 scope.export("Z");
555 scope.export("A");
556 scope.export("M");
557
558 let names = scope.exported_names();
559 assert_eq!(names, vec!["A", "M", "Z"]);
560 }
561}