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: u64,
60}
61
62impl Scope {
63 pub fn new() -> Self {
68 Self {
69 frames: Arc::new(vec![HashMap::new()]),
70 exported: HashSet::new(),
71 last_result: ExecResult::default(),
72 script_name: String::new(),
73 positional: Vec::new(),
74 error_exit: false,
75 errexit_suppressed: 0,
76 show_ast: false,
77 latch_enabled: false,
78 trash_enabled: false,
79 trash_max_size: 10 * 1024 * 1024, glob_enabled: true,
81 pid: 0,
82 }
83 }
84
85 pub fn pid(&self) -> u64 {
87 self.pid
88 }
89
90 pub fn set_pid(&mut self, pid: u64) {
94 self.pid = pid;
95 }
96
97 pub fn push_frame(&mut self) {
99 Arc::make_mut(&mut self.frames).push(HashMap::new());
100 }
101
102 pub fn pop_frame(&mut self) {
106 if self.frames.len() > 1 {
107 Arc::make_mut(&mut self.frames).pop();
108 } else {
109 panic!("cannot pop the root scope frame");
110 }
111 }
112
113 pub fn set(&mut self, name: impl Into<String>, value: Value) {
117 if let Some(frame) = Arc::make_mut(&mut self.frames).last_mut() {
118 frame.insert(name.into(), value);
119 }
120 }
121
122 pub fn set_global(&mut self, name: impl Into<String>, value: Value) {
128 let name = name.into();
129
130 let frames = Arc::make_mut(&mut self.frames);
132 for frame in frames.iter_mut().rev() {
133 if let std::collections::hash_map::Entry::Occupied(mut e) = frame.entry(name.clone()) {
134 e.insert(value);
135 return;
136 }
137 }
138
139 if let Some(frame) = frames.first_mut() {
141 frame.insert(name, value);
142 }
143 }
144
145 pub fn get(&self, name: &str) -> Option<&Value> {
147 for frame in self.frames.iter().rev() {
148 if let Some(value) = frame.get(name) {
149 return Some(value);
150 }
151 }
152 None
153 }
154
155 pub fn remove(&mut self, name: &str) -> Option<Value> {
159 for frame in Arc::make_mut(&mut self.frames).iter_mut().rev() {
160 if let Some(value) = frame.remove(name) {
161 return Some(value);
162 }
163 }
164 None
165 }
166
167 pub fn set_last_result(&mut self, result: ExecResult) {
169 self.last_result = result;
170 }
171
172 pub fn last_result(&self) -> &ExecResult {
174 &self.last_result
175 }
176
177 pub fn set_positional(&mut self, script_name: impl Into<String>, args: Vec<String>) {
181 self.script_name = script_name.into();
182 self.positional = args;
183 }
184
185 pub fn save_positional(&self) -> (String, Vec<String>) {
189 (self.script_name.clone(), self.positional.clone())
190 }
191
192 pub fn get_positional(&self, n: usize) -> Option<&str> {
196 if n == 0 {
197 if self.script_name.is_empty() {
198 None
199 } else {
200 Some(&self.script_name)
201 }
202 } else {
203 self.positional.get(n - 1).map(|s| s.as_str())
204 }
205 }
206
207 pub fn all_args(&self) -> &[String] {
209 &self.positional
210 }
211
212 pub fn arg_count(&self) -> usize {
214 self.positional.len()
215 }
216
217 pub fn error_exit_enabled(&self) -> bool {
222 self.error_exit && self.errexit_suppressed == 0
223 }
224
225 pub fn set_error_exit(&mut self, enabled: bool) {
227 self.error_exit = enabled;
228 }
229
230 pub fn suppress_errexit(&mut self) {
232 self.errexit_suppressed += 1;
233 }
234
235 pub fn unsuppress_errexit(&mut self) {
237 self.errexit_suppressed = self.errexit_suppressed.saturating_sub(1);
238 }
239
240 pub fn show_ast(&self) -> bool {
242 self.show_ast
243 }
244
245 pub fn set_show_ast(&mut self, enabled: bool) {
247 self.show_ast = enabled;
248 }
249
250 pub fn latch_enabled(&self) -> bool {
252 self.latch_enabled
253 }
254
255 pub fn set_latch_enabled(&mut self, enabled: bool) {
257 self.latch_enabled = enabled;
258 }
259
260 pub fn trash_enabled(&self) -> bool {
262 self.trash_enabled
263 }
264
265 pub fn set_trash_enabled(&mut self, enabled: bool) {
267 self.trash_enabled = enabled;
268 }
269
270 pub fn trash_max_size(&self) -> u64 {
272 self.trash_max_size
273 }
274
275 pub fn set_trash_max_size(&mut self, size: u64) {
277 self.trash_max_size = size;
278 }
279
280 pub fn glob_enabled(&self) -> bool {
282 self.glob_enabled
283 }
284
285 pub fn set_glob_enabled(&mut self, enabled: bool) {
287 self.glob_enabled = enabled;
288 }
289
290 pub fn export(&mut self, name: impl Into<String>) {
294 self.exported.insert(name.into());
295 }
296
297 pub fn is_exported(&self, name: &str) -> bool {
299 self.exported.contains(name)
300 }
301
302 pub fn set_exported(&mut self, name: impl Into<String>, value: Value) {
309 let name = name.into();
310 self.set(&name, value);
311 self.export(name);
312 }
313
314 pub fn set_exported_global(&mut self, name: impl Into<String>, value: Value) {
320 let name = name.into();
321 self.set_global(&name, value);
322 self.export(name);
323 }
324
325 pub fn unexport(&mut self, name: &str) {
327 self.exported.remove(name);
328 }
329
330 pub fn exported_vars(&self) -> Vec<(String, Value)> {
334 let mut result = Vec::new();
335 for name in &self.exported {
336 if let Some(value) = self.get(name) {
337 result.push((name.clone(), value.clone()));
338 }
339 }
340 result.sort_by(|(a, _), (b, _)| a.cmp(b));
341 result
342 }
343
344 pub fn exported_names(&self) -> Vec<&str> {
346 let mut names: Vec<&str> = self.exported.iter().map(|s| s.as_str()).collect();
347 names.sort();
348 names
349 }
350
351 pub fn resolve_path(&self, path: &VarPath) -> Option<Value> {
357 if path.segments.is_empty() {
358 return None;
359 }
360
361 let VarSegment::Field(root_name) = &path.segments[0];
363
364 if root_name == "?" {
366 return self.resolve_result_path(&path.segments[1..]);
367 }
368
369 if path.segments.len() > 1 {
371 return None; }
373
374 self.get(root_name).cloned()
375 }
376
377 fn resolve_result_path(&self, segments: &[VarSegment]) -> Option<Value> {
384 if segments.is_empty() {
385 return Some(Value::Int(self.last_result.code));
386 }
387 None
388 }
389
390 pub fn contains(&self, name: &str) -> bool {
392 self.get(name).is_some()
393 }
394
395 pub fn all_names(&self) -> Vec<&str> {
397 let mut names: Vec<&str> = self
398 .frames
399 .iter()
400 .flat_map(|f| f.keys().map(|s| s.as_str()))
401 .collect();
402 names.sort();
403 names.dedup();
404 names
405 }
406
407 pub fn all(&self) -> Vec<(String, Value)> {
411 let mut result = std::collections::HashMap::new();
412 for frame in self.frames.iter() {
414 for (name, value) in frame {
415 result.insert(name.clone(), value.clone());
416 }
417 }
418 let mut pairs: Vec<_> = result.into_iter().collect();
419 pairs.sort_by(|(a, _), (b, _)| a.cmp(b));
420 pairs
421 }
422}
423
424impl Default for Scope {
425 fn default() -> Self {
426 Self::new()
427 }
428}
429
430#[cfg(test)]
431mod tests {
432 use super::*;
433
434 #[test]
435 fn new_scope_has_one_frame() {
436 let scope = Scope::new();
437 assert_eq!(scope.frames.len(), 1);
438 }
439
440 #[test]
441 fn set_and_get_variable() {
442 let mut scope = Scope::new();
443 scope.set("X", Value::Int(42));
444 assert_eq!(scope.get("X"), Some(&Value::Int(42)));
445 }
446
447 #[test]
448 fn get_nonexistent_returns_none() {
449 let scope = Scope::new();
450 assert_eq!(scope.get("MISSING"), None);
451 }
452
453 #[test]
454 fn inner_frame_shadows_outer() {
455 let mut scope = Scope::new();
456 scope.set("X", Value::Int(1));
457 scope.push_frame();
458 scope.set("X", Value::Int(2));
459 assert_eq!(scope.get("X"), Some(&Value::Int(2)));
460 scope.pop_frame();
461 assert_eq!(scope.get("X"), Some(&Value::Int(1)));
462 }
463
464 #[test]
465 fn inner_frame_can_see_outer_vars() {
466 let mut scope = Scope::new();
467 scope.set("OUTER", Value::String("visible".into()));
468 scope.push_frame();
469 assert_eq!(scope.get("OUTER"), Some(&Value::String("visible".into())));
470 }
471
472 #[test]
473 fn resolve_simple_path() {
474 let mut scope = Scope::new();
475 scope.set("NAME", Value::String("Alice".into()));
476
477 let path = VarPath::simple("NAME");
478 assert_eq!(
479 scope.resolve_path(&path),
480 Some(Value::String("Alice".into()))
481 );
482 }
483
484 #[test]
485 fn resolve_bare_last_result_returns_exit_code() {
486 let mut scope = Scope::new();
487 scope.set_last_result(ExecResult::failure(127, "not found"));
488
489 let path = VarPath {
490 segments: vec![VarSegment::Field("?".into())],
491 };
492 assert_eq!(scope.resolve_path(&path), Some(Value::Int(127)));
493 }
494
495 #[test]
496 fn resolve_last_result_field_access_is_rejected() {
497 let mut scope = Scope::new();
501 scope.set_last_result(ExecResult::success_with_data(
502 "1",
503 Value::Json(serde_json::json!({"count": 5})),
504 ));
505
506 let path = VarPath {
507 segments: vec![
508 VarSegment::Field("?".into()),
509 VarSegment::Field("data".into()),
510 ],
511 };
512 assert_eq!(scope.resolve_path(&path), None);
513 }
514
515 #[test]
516 fn resolve_invalid_path_returns_none() {
517 let mut scope = Scope::new();
518 scope.set("X", Value::Int(42));
519
520 let path = VarPath {
522 segments: vec![
523 VarSegment::Field("X".into()),
524 VarSegment::Field("invalid".into()),
525 ],
526 };
527 assert_eq!(scope.resolve_path(&path), None);
528 }
529
530 #[test]
531 fn contains_finds_variable() {
532 let mut scope = Scope::new();
533 scope.set("EXISTS", Value::Bool(true));
534 assert!(scope.contains("EXISTS"));
535 assert!(!scope.contains("MISSING"));
536 }
537
538 #[test]
539 fn all_names_lists_variables() {
540 let mut scope = Scope::new();
541 scope.set("A", Value::Int(1));
542 scope.set("B", Value::Int(2));
543 scope.push_frame();
544 scope.set("C", Value::Int(3));
545
546 let names = scope.all_names();
547 assert!(names.contains(&"A"));
548 assert!(names.contains(&"B"));
549 assert!(names.contains(&"C"));
550 }
551
552 #[test]
553 #[should_panic(expected = "cannot pop the root scope frame")]
554 fn pop_root_frame_panics() {
555 let mut scope = Scope::new();
556 scope.pop_frame();
557 }
558
559 #[test]
560 fn positional_params_basic() {
561 let mut scope = Scope::new();
562 scope.set_positional("my_tool", vec!["arg1".into(), "arg2".into(), "arg3".into()]);
563
564 assert_eq!(scope.get_positional(0), Some("my_tool"));
566 assert_eq!(scope.get_positional(1), Some("arg1"));
568 assert_eq!(scope.get_positional(2), Some("arg2"));
569 assert_eq!(scope.get_positional(3), Some("arg3"));
570 assert_eq!(scope.get_positional(4), None);
572 }
573
574 #[test]
575 fn positional_params_empty() {
576 let scope = Scope::new();
577 assert_eq!(scope.get_positional(0), None);
579 assert_eq!(scope.get_positional(1), None);
580 assert_eq!(scope.arg_count(), 0);
581 assert!(scope.all_args().is_empty());
582 }
583
584 #[test]
585 fn all_args_returns_slice() {
586 let mut scope = Scope::new();
587 scope.set_positional("test", vec!["a".into(), "b".into(), "c".into()]);
588
589 let args = scope.all_args();
590 assert_eq!(args, &["a", "b", "c"]);
591 }
592
593 #[test]
594 fn arg_count_returns_count() {
595 let mut scope = Scope::new();
596 scope.set_positional("test", vec!["one".into(), "two".into()]);
597
598 assert_eq!(scope.arg_count(), 2);
599 }
600
601 #[test]
602 fn export_marks_variable() {
603 let mut scope = Scope::new();
604 scope.set("X", Value::Int(42));
605
606 assert!(!scope.is_exported("X"));
607 scope.export("X");
608 assert!(scope.is_exported("X"));
609 }
610
611 #[test]
612 fn set_exported_sets_and_exports() {
613 let mut scope = Scope::new();
614 scope.set_exported("PATH", Value::String("/usr/bin".into()));
615
616 assert!(scope.is_exported("PATH"));
617 assert_eq!(scope.get("PATH"), Some(&Value::String("/usr/bin".into())));
618 }
619
620 #[test]
621 fn unexport_removes_export_marker() {
622 let mut scope = Scope::new();
623 scope.set_exported("VAR", Value::Int(1));
624 assert!(scope.is_exported("VAR"));
625
626 scope.unexport("VAR");
627 assert!(!scope.is_exported("VAR"));
628 assert!(scope.get("VAR").is_some());
630 }
631
632 #[test]
633 fn exported_vars_returns_only_exported_with_values() {
634 let mut scope = Scope::new();
635 scope.set_exported("A", Value::Int(1));
636 scope.set_exported("B", Value::Int(2));
637 scope.set("C", Value::Int(3)); scope.export("D"); let exported = scope.exported_vars();
641 assert_eq!(exported.len(), 2);
642 assert_eq!(exported[0], ("A".to_string(), Value::Int(1)));
643 assert_eq!(exported[1], ("B".to_string(), Value::Int(2)));
644 }
645
646 #[test]
647 fn exported_names_returns_sorted_names() {
648 let mut scope = Scope::new();
649 scope.export("Z");
650 scope.export("A");
651 scope.export("M");
652
653 let names = scope.exported_names();
654 assert_eq!(names, vec!["A", "M", "Z"]);
655 }
656}