kaish_kernel/interpreter/
scope.rs1use std::collections::{HashMap, HashSet};
9use std::sync::Arc;
10
11use crate::ast::{Value, VarPath, VarSegment};
12
13use super::result::ExecResult;
14
15#[derive(Debug, Clone)]
26pub struct Scope {
27 frames: Arc<Vec<HashMap<String, Value>>>,
30 exported: HashSet<String>,
32 last_result: ExecResult,
34 script_name: String,
36 positional: Vec<String>,
38 error_exit: bool,
40 errexit_suppressed: usize,
43 show_ast: bool,
45 latch_enabled: bool,
47 trash_enabled: bool,
49 trash_max_size: u64,
52 glob_enabled: bool,
54 pid: u32,
56}
57
58impl Scope {
59 pub fn new() -> Self {
61 Self {
62 frames: Arc::new(vec![HashMap::new()]),
63 exported: HashSet::new(),
64 last_result: ExecResult::default(),
65 script_name: String::new(),
66 positional: Vec::new(),
67 error_exit: false,
68 errexit_suppressed: 0,
69 show_ast: false,
70 latch_enabled: false,
71 trash_enabled: false,
72 trash_max_size: 10 * 1024 * 1024, glob_enabled: true,
74 pid: std::process::id(),
75 }
76 }
77
78 pub fn pid(&self) -> u32 {
80 self.pid
81 }
82
83 pub fn push_frame(&mut self) {
85 Arc::make_mut(&mut self.frames).push(HashMap::new());
86 }
87
88 pub fn pop_frame(&mut self) {
92 if self.frames.len() > 1 {
93 Arc::make_mut(&mut self.frames).pop();
94 } else {
95 panic!("cannot pop the root scope frame");
96 }
97 }
98
99 pub fn set(&mut self, name: impl Into<String>, value: Value) {
103 if let Some(frame) = Arc::make_mut(&mut self.frames).last_mut() {
104 frame.insert(name.into(), value);
105 }
106 }
107
108 pub fn set_global(&mut self, name: impl Into<String>, value: Value) {
114 let name = name.into();
115
116 let frames = Arc::make_mut(&mut self.frames);
118 for frame in frames.iter_mut().rev() {
119 if let std::collections::hash_map::Entry::Occupied(mut e) = frame.entry(name.clone()) {
120 e.insert(value);
121 return;
122 }
123 }
124
125 if let Some(frame) = frames.first_mut() {
127 frame.insert(name, value);
128 }
129 }
130
131 pub fn get(&self, name: &str) -> Option<&Value> {
133 for frame in self.frames.iter().rev() {
134 if let Some(value) = frame.get(name) {
135 return Some(value);
136 }
137 }
138 None
139 }
140
141 pub fn remove(&mut self, name: &str) -> Option<Value> {
145 for frame in Arc::make_mut(&mut self.frames).iter_mut().rev() {
146 if let Some(value) = frame.remove(name) {
147 return Some(value);
148 }
149 }
150 None
151 }
152
153 pub fn set_last_result(&mut self, result: ExecResult) {
155 self.last_result = result;
156 }
157
158 pub fn last_result(&self) -> &ExecResult {
160 &self.last_result
161 }
162
163 pub fn set_positional(&mut self, script_name: impl Into<String>, args: Vec<String>) {
167 self.script_name = script_name.into();
168 self.positional = args;
169 }
170
171 pub fn save_positional(&self) -> (String, Vec<String>) {
175 (self.script_name.clone(), self.positional.clone())
176 }
177
178 pub fn get_positional(&self, n: usize) -> Option<&str> {
182 if n == 0 {
183 if self.script_name.is_empty() {
184 None
185 } else {
186 Some(&self.script_name)
187 }
188 } else {
189 self.positional.get(n - 1).map(|s| s.as_str())
190 }
191 }
192
193 pub fn all_args(&self) -> &[String] {
195 &self.positional
196 }
197
198 pub fn arg_count(&self) -> usize {
200 self.positional.len()
201 }
202
203 pub fn error_exit_enabled(&self) -> bool {
208 self.error_exit && self.errexit_suppressed == 0
209 }
210
211 pub fn set_error_exit(&mut self, enabled: bool) {
213 self.error_exit = enabled;
214 }
215
216 pub fn suppress_errexit(&mut self) {
218 self.errexit_suppressed += 1;
219 }
220
221 pub fn unsuppress_errexit(&mut self) {
223 self.errexit_suppressed = self.errexit_suppressed.saturating_sub(1);
224 }
225
226 pub fn show_ast(&self) -> bool {
228 self.show_ast
229 }
230
231 pub fn set_show_ast(&mut self, enabled: bool) {
233 self.show_ast = enabled;
234 }
235
236 pub fn latch_enabled(&self) -> bool {
238 self.latch_enabled
239 }
240
241 pub fn set_latch_enabled(&mut self, enabled: bool) {
243 self.latch_enabled = enabled;
244 }
245
246 pub fn trash_enabled(&self) -> bool {
248 self.trash_enabled
249 }
250
251 pub fn set_trash_enabled(&mut self, enabled: bool) {
253 self.trash_enabled = enabled;
254 }
255
256 pub fn trash_max_size(&self) -> u64 {
258 self.trash_max_size
259 }
260
261 pub fn set_trash_max_size(&mut self, size: u64) {
263 self.trash_max_size = size;
264 }
265
266 pub fn glob_enabled(&self) -> bool {
268 self.glob_enabled
269 }
270
271 pub fn set_glob_enabled(&mut self, enabled: bool) {
273 self.glob_enabled = enabled;
274 }
275
276 pub fn export(&mut self, name: impl Into<String>) {
280 self.exported.insert(name.into());
281 }
282
283 pub fn is_exported(&self, name: &str) -> bool {
285 self.exported.contains(name)
286 }
287
288 pub fn set_exported(&mut self, name: impl Into<String>, value: Value) {
290 let name = name.into();
291 self.set(&name, value);
292 self.export(name);
293 }
294
295 pub fn unexport(&mut self, name: &str) {
297 self.exported.remove(name);
298 }
299
300 pub fn exported_vars(&self) -> Vec<(String, Value)> {
304 let mut result = Vec::new();
305 for name in &self.exported {
306 if let Some(value) = self.get(name) {
307 result.push((name.clone(), value.clone()));
308 }
309 }
310 result.sort_by(|(a, _), (b, _)| a.cmp(b));
311 result
312 }
313
314 pub fn exported_names(&self) -> Vec<&str> {
316 let mut names: Vec<&str> = self.exported.iter().map(|s| s.as_str()).collect();
317 names.sort();
318 names
319 }
320
321 pub fn resolve_path(&self, path: &VarPath) -> Option<Value> {
326 if path.segments.is_empty() {
327 return None;
328 }
329
330 let VarSegment::Field(root_name) = &path.segments[0];
332
333 if root_name == "?" {
335 return self.resolve_result_path(&path.segments[1..]);
336 }
337
338 if path.segments.len() > 1 {
340 return None; }
342
343 self.get(root_name).cloned()
344 }
345
346 fn resolve_result_path(&self, segments: &[VarSegment]) -> Option<Value> {
351 if segments.is_empty() {
352 return Some(Value::Int(self.last_result.code));
354 }
355
356 let VarSegment::Field(field_name) = &segments[0];
358
359 if segments.len() > 1 {
361 return None;
362 }
363
364 self.last_result.get_field(field_name)
366 }
367
368 pub fn contains(&self, name: &str) -> bool {
370 self.get(name).is_some()
371 }
372
373 pub fn all_names(&self) -> Vec<&str> {
375 let mut names: Vec<&str> = self
376 .frames
377 .iter()
378 .flat_map(|f| f.keys().map(|s| s.as_str()))
379 .collect();
380 names.sort();
381 names.dedup();
382 names
383 }
384
385 pub fn all(&self) -> Vec<(String, Value)> {
389 let mut result = std::collections::HashMap::new();
390 for frame in self.frames.iter() {
392 for (name, value) in frame {
393 result.insert(name.clone(), value.clone());
394 }
395 }
396 let mut pairs: Vec<_> = result.into_iter().collect();
397 pairs.sort_by(|(a, _), (b, _)| a.cmp(b));
398 pairs
399 }
400}
401
402impl Default for Scope {
403 fn default() -> Self {
404 Self::new()
405 }
406}
407
408#[cfg(test)]
409mod tests {
410 use super::*;
411
412 #[test]
413 fn new_scope_has_one_frame() {
414 let scope = Scope::new();
415 assert_eq!(scope.frames.len(), 1);
416 }
417
418 #[test]
419 fn set_and_get_variable() {
420 let mut scope = Scope::new();
421 scope.set("X", Value::Int(42));
422 assert_eq!(scope.get("X"), Some(&Value::Int(42)));
423 }
424
425 #[test]
426 fn get_nonexistent_returns_none() {
427 let scope = Scope::new();
428 assert_eq!(scope.get("MISSING"), None);
429 }
430
431 #[test]
432 fn inner_frame_shadows_outer() {
433 let mut scope = Scope::new();
434 scope.set("X", Value::Int(1));
435 scope.push_frame();
436 scope.set("X", Value::Int(2));
437 assert_eq!(scope.get("X"), Some(&Value::Int(2)));
438 scope.pop_frame();
439 assert_eq!(scope.get("X"), Some(&Value::Int(1)));
440 }
441
442 #[test]
443 fn inner_frame_can_see_outer_vars() {
444 let mut scope = Scope::new();
445 scope.set("OUTER", Value::String("visible".into()));
446 scope.push_frame();
447 assert_eq!(scope.get("OUTER"), Some(&Value::String("visible".into())));
448 }
449
450 #[test]
451 fn resolve_simple_path() {
452 let mut scope = Scope::new();
453 scope.set("NAME", Value::String("Alice".into()));
454
455 let path = VarPath::simple("NAME");
456 assert_eq!(
457 scope.resolve_path(&path),
458 Some(Value::String("Alice".into()))
459 );
460 }
461
462 #[test]
463 fn resolve_last_result_ok() {
464 let mut scope = Scope::new();
465 scope.set_last_result(ExecResult::success("output"));
466
467 let path = VarPath {
468 segments: vec![
469 VarSegment::Field("?".into()),
470 VarSegment::Field("ok".into()),
471 ],
472 };
473 assert_eq!(scope.resolve_path(&path), Some(Value::Bool(true)));
474 }
475
476 #[test]
477 fn resolve_last_result_code() {
478 let mut scope = Scope::new();
479 scope.set_last_result(ExecResult::failure(127, "not found"));
480
481 let path = VarPath {
482 segments: vec![
483 VarSegment::Field("?".into()),
484 VarSegment::Field("code".into()),
485 ],
486 };
487 assert_eq!(scope.resolve_path(&path), Some(Value::Int(127)));
488 }
489
490 #[test]
491 fn resolve_last_result_data_field() {
492 let mut scope = Scope::new();
493 scope.set_last_result(ExecResult::success(r#"{"count": 5}"#));
494
495 let path = VarPath {
497 segments: vec![
498 VarSegment::Field("?".into()),
499 VarSegment::Field("data".into()),
500 ],
501 };
502 let result = scope.resolve_path(&path);
504 assert!(result.is_some());
505 if let Some(Value::Json(json)) = result {
506 assert_eq!(json.get("count"), Some(&serde_json::json!(5)));
507 } else {
508 panic!("expected Value::Json, got {:?}", result);
509 }
510 }
511
512 #[test]
513 fn resolve_invalid_path_returns_none() {
514 let mut scope = Scope::new();
515 scope.set("X", Value::Int(42));
516
517 let path = VarPath {
519 segments: vec![
520 VarSegment::Field("X".into()),
521 VarSegment::Field("invalid".into()),
522 ],
523 };
524 assert_eq!(scope.resolve_path(&path), None);
525 }
526
527 #[test]
528 fn contains_finds_variable() {
529 let mut scope = Scope::new();
530 scope.set("EXISTS", Value::Bool(true));
531 assert!(scope.contains("EXISTS"));
532 assert!(!scope.contains("MISSING"));
533 }
534
535 #[test]
536 fn all_names_lists_variables() {
537 let mut scope = Scope::new();
538 scope.set("A", Value::Int(1));
539 scope.set("B", Value::Int(2));
540 scope.push_frame();
541 scope.set("C", Value::Int(3));
542
543 let names = scope.all_names();
544 assert!(names.contains(&"A"));
545 assert!(names.contains(&"B"));
546 assert!(names.contains(&"C"));
547 }
548
549 #[test]
550 #[should_panic(expected = "cannot pop the root scope frame")]
551 fn pop_root_frame_panics() {
552 let mut scope = Scope::new();
553 scope.pop_frame();
554 }
555
556 #[test]
557 fn positional_params_basic() {
558 let mut scope = Scope::new();
559 scope.set_positional("my_tool", vec!["arg1".into(), "arg2".into(), "arg3".into()]);
560
561 assert_eq!(scope.get_positional(0), Some("my_tool"));
563 assert_eq!(scope.get_positional(1), Some("arg1"));
565 assert_eq!(scope.get_positional(2), Some("arg2"));
566 assert_eq!(scope.get_positional(3), Some("arg3"));
567 assert_eq!(scope.get_positional(4), None);
569 }
570
571 #[test]
572 fn positional_params_empty() {
573 let scope = Scope::new();
574 assert_eq!(scope.get_positional(0), None);
576 assert_eq!(scope.get_positional(1), None);
577 assert_eq!(scope.arg_count(), 0);
578 assert!(scope.all_args().is_empty());
579 }
580
581 #[test]
582 fn all_args_returns_slice() {
583 let mut scope = Scope::new();
584 scope.set_positional("test", vec!["a".into(), "b".into(), "c".into()]);
585
586 let args = scope.all_args();
587 assert_eq!(args, &["a", "b", "c"]);
588 }
589
590 #[test]
591 fn arg_count_returns_count() {
592 let mut scope = Scope::new();
593 scope.set_positional("test", vec!["one".into(), "two".into()]);
594
595 assert_eq!(scope.arg_count(), 2);
596 }
597
598 #[test]
599 fn export_marks_variable() {
600 let mut scope = Scope::new();
601 scope.set("X", Value::Int(42));
602
603 assert!(!scope.is_exported("X"));
604 scope.export("X");
605 assert!(scope.is_exported("X"));
606 }
607
608 #[test]
609 fn set_exported_sets_and_exports() {
610 let mut scope = Scope::new();
611 scope.set_exported("PATH", Value::String("/usr/bin".into()));
612
613 assert!(scope.is_exported("PATH"));
614 assert_eq!(scope.get("PATH"), Some(&Value::String("/usr/bin".into())));
615 }
616
617 #[test]
618 fn unexport_removes_export_marker() {
619 let mut scope = Scope::new();
620 scope.set_exported("VAR", Value::Int(1));
621 assert!(scope.is_exported("VAR"));
622
623 scope.unexport("VAR");
624 assert!(!scope.is_exported("VAR"));
625 assert!(scope.get("VAR").is_some());
627 }
628
629 #[test]
630 fn exported_vars_returns_only_exported_with_values() {
631 let mut scope = Scope::new();
632 scope.set_exported("A", Value::Int(1));
633 scope.set_exported("B", Value::Int(2));
634 scope.set("C", Value::Int(3)); scope.export("D"); let exported = scope.exported_vars();
638 assert_eq!(exported.len(), 2);
639 assert_eq!(exported[0], ("A".to_string(), Value::Int(1)));
640 assert_eq!(exported[1], ("B".to_string(), Value::Int(2)));
641 }
642
643 #[test]
644 fn exported_names_returns_sorted_names() {
645 let mut scope = Scope::new();
646 scope.export("Z");
647 scope.export("A");
648 scope.export("M");
649
650 let names = scope.exported_names();
651 assert_eq!(names, vec!["A", "M", "Z"]);
652 }
653}