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) {
304 let name = name.into();
305 self.set(&name, value);
306 self.export(name);
307 }
308
309 pub fn unexport(&mut self, name: &str) {
311 self.exported.remove(name);
312 }
313
314 pub fn exported_vars(&self) -> Vec<(String, Value)> {
318 let mut result = Vec::new();
319 for name in &self.exported {
320 if let Some(value) = self.get(name) {
321 result.push((name.clone(), value.clone()));
322 }
323 }
324 result.sort_by(|(a, _), (b, _)| a.cmp(b));
325 result
326 }
327
328 pub fn exported_names(&self) -> Vec<&str> {
330 let mut names: Vec<&str> = self.exported.iter().map(|s| s.as_str()).collect();
331 names.sort();
332 names
333 }
334
335 pub fn resolve_path(&self, path: &VarPath) -> Option<Value> {
341 if path.segments.is_empty() {
342 return None;
343 }
344
345 let VarSegment::Field(root_name) = &path.segments[0];
347
348 if root_name == "?" {
350 return self.resolve_result_path(&path.segments[1..]);
351 }
352
353 if path.segments.len() > 1 {
355 return None; }
357
358 self.get(root_name).cloned()
359 }
360
361 fn resolve_result_path(&self, segments: &[VarSegment]) -> Option<Value> {
368 if segments.is_empty() {
369 return Some(Value::Int(self.last_result.code));
370 }
371 None
372 }
373
374 pub fn contains(&self, name: &str) -> bool {
376 self.get(name).is_some()
377 }
378
379 pub fn all_names(&self) -> Vec<&str> {
381 let mut names: Vec<&str> = self
382 .frames
383 .iter()
384 .flat_map(|f| f.keys().map(|s| s.as_str()))
385 .collect();
386 names.sort();
387 names.dedup();
388 names
389 }
390
391 pub fn all(&self) -> Vec<(String, Value)> {
395 let mut result = std::collections::HashMap::new();
396 for frame in self.frames.iter() {
398 for (name, value) in frame {
399 result.insert(name.clone(), value.clone());
400 }
401 }
402 let mut pairs: Vec<_> = result.into_iter().collect();
403 pairs.sort_by(|(a, _), (b, _)| a.cmp(b));
404 pairs
405 }
406}
407
408impl Default for Scope {
409 fn default() -> Self {
410 Self::new()
411 }
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417
418 #[test]
419 fn new_scope_has_one_frame() {
420 let scope = Scope::new();
421 assert_eq!(scope.frames.len(), 1);
422 }
423
424 #[test]
425 fn set_and_get_variable() {
426 let mut scope = Scope::new();
427 scope.set("X", Value::Int(42));
428 assert_eq!(scope.get("X"), Some(&Value::Int(42)));
429 }
430
431 #[test]
432 fn get_nonexistent_returns_none() {
433 let scope = Scope::new();
434 assert_eq!(scope.get("MISSING"), None);
435 }
436
437 #[test]
438 fn inner_frame_shadows_outer() {
439 let mut scope = Scope::new();
440 scope.set("X", Value::Int(1));
441 scope.push_frame();
442 scope.set("X", Value::Int(2));
443 assert_eq!(scope.get("X"), Some(&Value::Int(2)));
444 scope.pop_frame();
445 assert_eq!(scope.get("X"), Some(&Value::Int(1)));
446 }
447
448 #[test]
449 fn inner_frame_can_see_outer_vars() {
450 let mut scope = Scope::new();
451 scope.set("OUTER", Value::String("visible".into()));
452 scope.push_frame();
453 assert_eq!(scope.get("OUTER"), Some(&Value::String("visible".into())));
454 }
455
456 #[test]
457 fn resolve_simple_path() {
458 let mut scope = Scope::new();
459 scope.set("NAME", Value::String("Alice".into()));
460
461 let path = VarPath::simple("NAME");
462 assert_eq!(
463 scope.resolve_path(&path),
464 Some(Value::String("Alice".into()))
465 );
466 }
467
468 #[test]
469 fn resolve_bare_last_result_returns_exit_code() {
470 let mut scope = Scope::new();
471 scope.set_last_result(ExecResult::failure(127, "not found"));
472
473 let path = VarPath {
474 segments: vec![VarSegment::Field("?".into())],
475 };
476 assert_eq!(scope.resolve_path(&path), Some(Value::Int(127)));
477 }
478
479 #[test]
480 fn resolve_last_result_field_access_is_rejected() {
481 let mut scope = Scope::new();
485 scope.set_last_result(ExecResult::success_with_data(
486 "1",
487 Value::Json(serde_json::json!({"count": 5})),
488 ));
489
490 let path = VarPath {
491 segments: vec![
492 VarSegment::Field("?".into()),
493 VarSegment::Field("data".into()),
494 ],
495 };
496 assert_eq!(scope.resolve_path(&path), None);
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}