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 pid: u32,
54}
55
56impl Scope {
57 pub fn new() -> Self {
59 Self {
60 frames: Arc::new(vec![HashMap::new()]),
61 exported: HashSet::new(),
62 last_result: ExecResult::default(),
63 script_name: String::new(),
64 positional: Vec::new(),
65 error_exit: false,
66 errexit_suppressed: 0,
67 show_ast: false,
68 latch_enabled: false,
69 trash_enabled: false,
70 trash_max_size: 10 * 1024 * 1024, pid: std::process::id(),
72 }
73 }
74
75 pub fn pid(&self) -> u32 {
77 self.pid
78 }
79
80 pub fn push_frame(&mut self) {
82 Arc::make_mut(&mut self.frames).push(HashMap::new());
83 }
84
85 pub fn pop_frame(&mut self) {
89 if self.frames.len() > 1 {
90 Arc::make_mut(&mut self.frames).pop();
91 } else {
92 panic!("cannot pop the root scope frame");
93 }
94 }
95
96 pub fn set(&mut self, name: impl Into<String>, value: Value) {
100 if let Some(frame) = Arc::make_mut(&mut self.frames).last_mut() {
101 frame.insert(name.into(), value);
102 }
103 }
104
105 pub fn set_global(&mut self, name: impl Into<String>, value: Value) {
111 let name = name.into();
112
113 let frames = Arc::make_mut(&mut self.frames);
115 for frame in frames.iter_mut().rev() {
116 if let std::collections::hash_map::Entry::Occupied(mut e) = frame.entry(name.clone()) {
117 e.insert(value);
118 return;
119 }
120 }
121
122 if let Some(frame) = frames.first_mut() {
124 frame.insert(name, value);
125 }
126 }
127
128 pub fn get(&self, name: &str) -> Option<&Value> {
130 for frame in self.frames.iter().rev() {
131 if let Some(value) = frame.get(name) {
132 return Some(value);
133 }
134 }
135 None
136 }
137
138 pub fn remove(&mut self, name: &str) -> Option<Value> {
142 for frame in Arc::make_mut(&mut self.frames).iter_mut().rev() {
143 if let Some(value) = frame.remove(name) {
144 return Some(value);
145 }
146 }
147 None
148 }
149
150 pub fn set_last_result(&mut self, result: ExecResult) {
152 self.last_result = result;
153 }
154
155 pub fn last_result(&self) -> &ExecResult {
157 &self.last_result
158 }
159
160 pub fn set_positional(&mut self, script_name: impl Into<String>, args: Vec<String>) {
164 self.script_name = script_name.into();
165 self.positional = args;
166 }
167
168 pub fn save_positional(&self) -> (String, Vec<String>) {
172 (self.script_name.clone(), self.positional.clone())
173 }
174
175 pub fn get_positional(&self, n: usize) -> Option<&str> {
179 if n == 0 {
180 if self.script_name.is_empty() {
181 None
182 } else {
183 Some(&self.script_name)
184 }
185 } else {
186 self.positional.get(n - 1).map(|s| s.as_str())
187 }
188 }
189
190 pub fn all_args(&self) -> &[String] {
192 &self.positional
193 }
194
195 pub fn arg_count(&self) -> usize {
197 self.positional.len()
198 }
199
200 pub fn error_exit_enabled(&self) -> bool {
205 self.error_exit && self.errexit_suppressed == 0
206 }
207
208 pub fn set_error_exit(&mut self, enabled: bool) {
210 self.error_exit = enabled;
211 }
212
213 pub fn suppress_errexit(&mut self) {
215 self.errexit_suppressed += 1;
216 }
217
218 pub fn unsuppress_errexit(&mut self) {
220 self.errexit_suppressed = self.errexit_suppressed.saturating_sub(1);
221 }
222
223 pub fn show_ast(&self) -> bool {
225 self.show_ast
226 }
227
228 pub fn set_show_ast(&mut self, enabled: bool) {
230 self.show_ast = enabled;
231 }
232
233 pub fn latch_enabled(&self) -> bool {
235 self.latch_enabled
236 }
237
238 pub fn set_latch_enabled(&mut self, enabled: bool) {
240 self.latch_enabled = enabled;
241 }
242
243 pub fn trash_enabled(&self) -> bool {
245 self.trash_enabled
246 }
247
248 pub fn set_trash_enabled(&mut self, enabled: bool) {
250 self.trash_enabled = enabled;
251 }
252
253 pub fn trash_max_size(&self) -> u64 {
255 self.trash_max_size
256 }
257
258 pub fn set_trash_max_size(&mut self, size: u64) {
260 self.trash_max_size = size;
261 }
262
263 pub fn export(&mut self, name: impl Into<String>) {
267 self.exported.insert(name.into());
268 }
269
270 pub fn is_exported(&self, name: &str) -> bool {
272 self.exported.contains(name)
273 }
274
275 pub fn set_exported(&mut self, name: impl Into<String>, value: Value) {
277 let name = name.into();
278 self.set(&name, value);
279 self.export(name);
280 }
281
282 pub fn unexport(&mut self, name: &str) {
284 self.exported.remove(name);
285 }
286
287 pub fn exported_vars(&self) -> Vec<(String, Value)> {
291 let mut result = Vec::new();
292 for name in &self.exported {
293 if let Some(value) = self.get(name) {
294 result.push((name.clone(), value.clone()));
295 }
296 }
297 result.sort_by(|(a, _), (b, _)| a.cmp(b));
298 result
299 }
300
301 pub fn exported_names(&self) -> Vec<&str> {
303 let mut names: Vec<&str> = self.exported.iter().map(|s| s.as_str()).collect();
304 names.sort();
305 names
306 }
307
308 pub fn resolve_path(&self, path: &VarPath) -> Option<Value> {
313 if path.segments.is_empty() {
314 return None;
315 }
316
317 let VarSegment::Field(root_name) = &path.segments[0];
319
320 if root_name == "?" {
322 return self.resolve_result_path(&path.segments[1..]);
323 }
324
325 if path.segments.len() > 1 {
327 return None; }
329
330 self.get(root_name).cloned()
331 }
332
333 fn resolve_result_path(&self, segments: &[VarSegment]) -> Option<Value> {
338 if segments.is_empty() {
339 return Some(Value::Int(self.last_result.code));
341 }
342
343 let VarSegment::Field(field_name) = &segments[0];
345
346 if segments.len() > 1 {
348 return None;
349 }
350
351 self.last_result.get_field(field_name)
353 }
354
355 pub fn contains(&self, name: &str) -> bool {
357 self.get(name).is_some()
358 }
359
360 pub fn all_names(&self) -> Vec<&str> {
362 let mut names: Vec<&str> = self
363 .frames
364 .iter()
365 .flat_map(|f| f.keys().map(|s| s.as_str()))
366 .collect();
367 names.sort();
368 names.dedup();
369 names
370 }
371
372 pub fn all(&self) -> Vec<(String, Value)> {
376 let mut result = std::collections::HashMap::new();
377 for frame in self.frames.iter() {
379 for (name, value) in frame {
380 result.insert(name.clone(), value.clone());
381 }
382 }
383 let mut pairs: Vec<_> = result.into_iter().collect();
384 pairs.sort_by(|(a, _), (b, _)| a.cmp(b));
385 pairs
386 }
387}
388
389impl Default for Scope {
390 fn default() -> Self {
391 Self::new()
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398
399 #[test]
400 fn new_scope_has_one_frame() {
401 let scope = Scope::new();
402 assert_eq!(scope.frames.len(), 1);
403 }
404
405 #[test]
406 fn set_and_get_variable() {
407 let mut scope = Scope::new();
408 scope.set("X", Value::Int(42));
409 assert_eq!(scope.get("X"), Some(&Value::Int(42)));
410 }
411
412 #[test]
413 fn get_nonexistent_returns_none() {
414 let scope = Scope::new();
415 assert_eq!(scope.get("MISSING"), None);
416 }
417
418 #[test]
419 fn inner_frame_shadows_outer() {
420 let mut scope = Scope::new();
421 scope.set("X", Value::Int(1));
422 scope.push_frame();
423 scope.set("X", Value::Int(2));
424 assert_eq!(scope.get("X"), Some(&Value::Int(2)));
425 scope.pop_frame();
426 assert_eq!(scope.get("X"), Some(&Value::Int(1)));
427 }
428
429 #[test]
430 fn inner_frame_can_see_outer_vars() {
431 let mut scope = Scope::new();
432 scope.set("OUTER", Value::String("visible".into()));
433 scope.push_frame();
434 assert_eq!(scope.get("OUTER"), Some(&Value::String("visible".into())));
435 }
436
437 #[test]
438 fn resolve_simple_path() {
439 let mut scope = Scope::new();
440 scope.set("NAME", Value::String("Alice".into()));
441
442 let path = VarPath::simple("NAME");
443 assert_eq!(
444 scope.resolve_path(&path),
445 Some(Value::String("Alice".into()))
446 );
447 }
448
449 #[test]
450 fn resolve_last_result_ok() {
451 let mut scope = Scope::new();
452 scope.set_last_result(ExecResult::success("output"));
453
454 let path = VarPath {
455 segments: vec![
456 VarSegment::Field("?".into()),
457 VarSegment::Field("ok".into()),
458 ],
459 };
460 assert_eq!(scope.resolve_path(&path), Some(Value::Bool(true)));
461 }
462
463 #[test]
464 fn resolve_last_result_code() {
465 let mut scope = Scope::new();
466 scope.set_last_result(ExecResult::failure(127, "not found"));
467
468 let path = VarPath {
469 segments: vec![
470 VarSegment::Field("?".into()),
471 VarSegment::Field("code".into()),
472 ],
473 };
474 assert_eq!(scope.resolve_path(&path), Some(Value::Int(127)));
475 }
476
477 #[test]
478 fn resolve_last_result_data_field() {
479 let mut scope = Scope::new();
480 scope.set_last_result(ExecResult::success(r#"{"count": 5}"#));
481
482 let path = VarPath {
484 segments: vec![
485 VarSegment::Field("?".into()),
486 VarSegment::Field("data".into()),
487 ],
488 };
489 let result = scope.resolve_path(&path);
491 assert!(result.is_some());
492 if let Some(Value::Json(json)) = result {
493 assert_eq!(json.get("count"), Some(&serde_json::json!(5)));
494 } else {
495 panic!("expected Value::Json, got {:?}", result);
496 }
497 }
498
499 #[test]
500 fn resolve_invalid_path_returns_none() {
501 let mut scope = Scope::new();
502 scope.set("X", Value::Int(42));
503
504 let path = VarPath {
506 segments: vec![
507 VarSegment::Field("X".into()),
508 VarSegment::Field("invalid".into()),
509 ],
510 };
511 assert_eq!(scope.resolve_path(&path), None);
512 }
513
514 #[test]
515 fn contains_finds_variable() {
516 let mut scope = Scope::new();
517 scope.set("EXISTS", Value::Bool(true));
518 assert!(scope.contains("EXISTS"));
519 assert!(!scope.contains("MISSING"));
520 }
521
522 #[test]
523 fn all_names_lists_variables() {
524 let mut scope = Scope::new();
525 scope.set("A", Value::Int(1));
526 scope.set("B", Value::Int(2));
527 scope.push_frame();
528 scope.set("C", Value::Int(3));
529
530 let names = scope.all_names();
531 assert!(names.contains(&"A"));
532 assert!(names.contains(&"B"));
533 assert!(names.contains(&"C"));
534 }
535
536 #[test]
537 #[should_panic(expected = "cannot pop the root scope frame")]
538 fn pop_root_frame_panics() {
539 let mut scope = Scope::new();
540 scope.pop_frame();
541 }
542
543 #[test]
544 fn positional_params_basic() {
545 let mut scope = Scope::new();
546 scope.set_positional("my_tool", vec!["arg1".into(), "arg2".into(), "arg3".into()]);
547
548 assert_eq!(scope.get_positional(0), Some("my_tool"));
550 assert_eq!(scope.get_positional(1), Some("arg1"));
552 assert_eq!(scope.get_positional(2), Some("arg2"));
553 assert_eq!(scope.get_positional(3), Some("arg3"));
554 assert_eq!(scope.get_positional(4), None);
556 }
557
558 #[test]
559 fn positional_params_empty() {
560 let scope = Scope::new();
561 assert_eq!(scope.get_positional(0), None);
563 assert_eq!(scope.get_positional(1), None);
564 assert_eq!(scope.arg_count(), 0);
565 assert!(scope.all_args().is_empty());
566 }
567
568 #[test]
569 fn all_args_returns_slice() {
570 let mut scope = Scope::new();
571 scope.set_positional("test", vec!["a".into(), "b".into(), "c".into()]);
572
573 let args = scope.all_args();
574 assert_eq!(args, &["a", "b", "c"]);
575 }
576
577 #[test]
578 fn arg_count_returns_count() {
579 let mut scope = Scope::new();
580 scope.set_positional("test", vec!["one".into(), "two".into()]);
581
582 assert_eq!(scope.arg_count(), 2);
583 }
584
585 #[test]
586 fn export_marks_variable() {
587 let mut scope = Scope::new();
588 scope.set("X", Value::Int(42));
589
590 assert!(!scope.is_exported("X"));
591 scope.export("X");
592 assert!(scope.is_exported("X"));
593 }
594
595 #[test]
596 fn set_exported_sets_and_exports() {
597 let mut scope = Scope::new();
598 scope.set_exported("PATH", Value::String("/usr/bin".into()));
599
600 assert!(scope.is_exported("PATH"));
601 assert_eq!(scope.get("PATH"), Some(&Value::String("/usr/bin".into())));
602 }
603
604 #[test]
605 fn unexport_removes_export_marker() {
606 let mut scope = Scope::new();
607 scope.set_exported("VAR", Value::Int(1));
608 assert!(scope.is_exported("VAR"));
609
610 scope.unexport("VAR");
611 assert!(!scope.is_exported("VAR"));
612 assert!(scope.get("VAR").is_some());
614 }
615
616 #[test]
617 fn exported_vars_returns_only_exported_with_values() {
618 let mut scope = Scope::new();
619 scope.set_exported("A", Value::Int(1));
620 scope.set_exported("B", Value::Int(2));
621 scope.set("C", Value::Int(3)); scope.export("D"); let exported = scope.exported_vars();
625 assert_eq!(exported.len(), 2);
626 assert_eq!(exported[0], ("A".to_string(), Value::Int(1)));
627 assert_eq!(exported[1], ("B".to_string(), Value::Int(2)));
628 }
629
630 #[test]
631 fn exported_names_returns_sorted_names() {
632 let mut scope = Scope::new();
633 scope.export("Z");
634 scope.export("A");
635 scope.export("M");
636
637 let names = scope.exported_names();
638 assert_eq!(names, vec!["A", "M", "Z"]);
639 }
640}