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 pid: u32,
42}
43
44impl Scope {
45 pub fn new() -> Self {
47 Self {
48 frames: Arc::new(vec![HashMap::new()]),
49 exported: HashSet::new(),
50 last_result: ExecResult::default(),
51 script_name: String::new(),
52 positional: Vec::new(),
53 error_exit: false,
54 pid: std::process::id(),
55 }
56 }
57
58 pub fn pid(&self) -> u32 {
60 self.pid
61 }
62
63 pub fn push_frame(&mut self) {
65 Arc::make_mut(&mut self.frames).push(HashMap::new());
66 }
67
68 pub fn pop_frame(&mut self) {
72 if self.frames.len() > 1 {
73 Arc::make_mut(&mut self.frames).pop();
74 } else {
75 panic!("cannot pop the root scope frame");
76 }
77 }
78
79 pub fn set(&mut self, name: impl Into<String>, value: Value) {
83 if let Some(frame) = Arc::make_mut(&mut self.frames).last_mut() {
84 frame.insert(name.into(), value);
85 }
86 }
87
88 pub fn set_global(&mut self, name: impl Into<String>, value: Value) {
94 let name = name.into();
95
96 let frames = Arc::make_mut(&mut self.frames);
98 for frame in frames.iter_mut().rev() {
99 if let std::collections::hash_map::Entry::Occupied(mut e) = frame.entry(name.clone()) {
100 e.insert(value);
101 return;
102 }
103 }
104
105 if let Some(frame) = frames.first_mut() {
107 frame.insert(name, value);
108 }
109 }
110
111 pub fn get(&self, name: &str) -> Option<&Value> {
113 for frame in self.frames.iter().rev() {
114 if let Some(value) = frame.get(name) {
115 return Some(value);
116 }
117 }
118 None
119 }
120
121 pub fn remove(&mut self, name: &str) -> Option<Value> {
125 for frame in Arc::make_mut(&mut self.frames).iter_mut().rev() {
126 if let Some(value) = frame.remove(name) {
127 return Some(value);
128 }
129 }
130 None
131 }
132
133 pub fn set_last_result(&mut self, result: ExecResult) {
135 self.last_result = result;
136 }
137
138 pub fn last_result(&self) -> &ExecResult {
140 &self.last_result
141 }
142
143 pub fn set_positional(&mut self, script_name: impl Into<String>, args: Vec<String>) {
147 self.script_name = script_name.into();
148 self.positional = args;
149 }
150
151 pub fn save_positional(&self) -> (String, Vec<String>) {
155 (self.script_name.clone(), self.positional.clone())
156 }
157
158 pub fn get_positional(&self, n: usize) -> Option<&str> {
162 if n == 0 {
163 if self.script_name.is_empty() {
164 None
165 } else {
166 Some(&self.script_name)
167 }
168 } else {
169 self.positional.get(n - 1).map(|s| s.as_str())
170 }
171 }
172
173 pub fn all_args(&self) -> &[String] {
175 &self.positional
176 }
177
178 pub fn arg_count(&self) -> usize {
180 self.positional.len()
181 }
182
183 pub fn error_exit_enabled(&self) -> bool {
185 self.error_exit
186 }
187
188 pub fn set_error_exit(&mut self, enabled: bool) {
190 self.error_exit = enabled;
191 }
192
193 pub fn export(&mut self, name: impl Into<String>) {
197 self.exported.insert(name.into());
198 }
199
200 pub fn is_exported(&self, name: &str) -> bool {
202 self.exported.contains(name)
203 }
204
205 pub fn set_exported(&mut self, name: impl Into<String>, value: Value) {
207 let name = name.into();
208 self.set(&name, value);
209 self.export(name);
210 }
211
212 pub fn unexport(&mut self, name: &str) {
214 self.exported.remove(name);
215 }
216
217 pub fn exported_vars(&self) -> Vec<(String, Value)> {
221 let mut result = Vec::new();
222 for name in &self.exported {
223 if let Some(value) = self.get(name) {
224 result.push((name.clone(), value.clone()));
225 }
226 }
227 result.sort_by(|(a, _), (b, _)| a.cmp(b));
228 result
229 }
230
231 pub fn exported_names(&self) -> Vec<&str> {
233 let mut names: Vec<&str> = self.exported.iter().map(|s| s.as_str()).collect();
234 names.sort();
235 names
236 }
237
238 pub fn resolve_path(&self, path: &VarPath) -> Option<Value> {
243 if path.segments.is_empty() {
244 return None;
245 }
246
247 let VarSegment::Field(root_name) = &path.segments[0];
249
250 if root_name == "?" {
252 return self.resolve_result_path(&path.segments[1..]);
253 }
254
255 if path.segments.len() > 1 {
257 return None; }
259
260 self.get(root_name).cloned()
261 }
262
263 fn resolve_result_path(&self, segments: &[VarSegment]) -> Option<Value> {
268 if segments.is_empty() {
269 return Some(Value::Int(self.last_result.code));
271 }
272
273 let VarSegment::Field(field_name) = &segments[0];
275
276 if segments.len() > 1 {
278 return None;
279 }
280
281 self.last_result.get_field(field_name)
283 }
284
285 pub fn contains(&self, name: &str) -> bool {
287 self.get(name).is_some()
288 }
289
290 pub fn all_names(&self) -> Vec<&str> {
292 let mut names: Vec<&str> = self
293 .frames
294 .iter()
295 .flat_map(|f| f.keys().map(|s| s.as_str()))
296 .collect();
297 names.sort();
298 names.dedup();
299 names
300 }
301
302 pub fn all(&self) -> Vec<(String, Value)> {
306 let mut result = std::collections::HashMap::new();
307 for frame in self.frames.iter() {
309 for (name, value) in frame {
310 result.insert(name.clone(), value.clone());
311 }
312 }
313 let mut pairs: Vec<_> = result.into_iter().collect();
314 pairs.sort_by(|(a, _), (b, _)| a.cmp(b));
315 pairs
316 }
317}
318
319impl Default for Scope {
320 fn default() -> Self {
321 Self::new()
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328
329 #[test]
330 fn new_scope_has_one_frame() {
331 let scope = Scope::new();
332 assert_eq!(scope.frames.len(), 1);
333 }
334
335 #[test]
336 fn set_and_get_variable() {
337 let mut scope = Scope::new();
338 scope.set("X", Value::Int(42));
339 assert_eq!(scope.get("X"), Some(&Value::Int(42)));
340 }
341
342 #[test]
343 fn get_nonexistent_returns_none() {
344 let scope = Scope::new();
345 assert_eq!(scope.get("MISSING"), None);
346 }
347
348 #[test]
349 fn inner_frame_shadows_outer() {
350 let mut scope = Scope::new();
351 scope.set("X", Value::Int(1));
352 scope.push_frame();
353 scope.set("X", Value::Int(2));
354 assert_eq!(scope.get("X"), Some(&Value::Int(2)));
355 scope.pop_frame();
356 assert_eq!(scope.get("X"), Some(&Value::Int(1)));
357 }
358
359 #[test]
360 fn inner_frame_can_see_outer_vars() {
361 let mut scope = Scope::new();
362 scope.set("OUTER", Value::String("visible".into()));
363 scope.push_frame();
364 assert_eq!(scope.get("OUTER"), Some(&Value::String("visible".into())));
365 }
366
367 #[test]
368 fn resolve_simple_path() {
369 let mut scope = Scope::new();
370 scope.set("NAME", Value::String("Alice".into()));
371
372 let path = VarPath::simple("NAME");
373 assert_eq!(
374 scope.resolve_path(&path),
375 Some(Value::String("Alice".into()))
376 );
377 }
378
379 #[test]
380 fn resolve_last_result_ok() {
381 let mut scope = Scope::new();
382 scope.set_last_result(ExecResult::success("output"));
383
384 let path = VarPath {
385 segments: vec![
386 VarSegment::Field("?".into()),
387 VarSegment::Field("ok".into()),
388 ],
389 };
390 assert_eq!(scope.resolve_path(&path), Some(Value::Bool(true)));
391 }
392
393 #[test]
394 fn resolve_last_result_code() {
395 let mut scope = Scope::new();
396 scope.set_last_result(ExecResult::failure(127, "not found"));
397
398 let path = VarPath {
399 segments: vec![
400 VarSegment::Field("?".into()),
401 VarSegment::Field("code".into()),
402 ],
403 };
404 assert_eq!(scope.resolve_path(&path), Some(Value::Int(127)));
405 }
406
407 #[test]
408 fn resolve_last_result_data_field() {
409 let mut scope = Scope::new();
410 scope.set_last_result(ExecResult::success(r#"{"count": 5}"#));
411
412 let path = VarPath {
414 segments: vec![
415 VarSegment::Field("?".into()),
416 VarSegment::Field("data".into()),
417 ],
418 };
419 let result = scope.resolve_path(&path);
421 assert!(result.is_some());
422 if let Some(Value::Json(json)) = result {
423 assert_eq!(json.get("count"), Some(&serde_json::json!(5)));
424 } else {
425 panic!("expected Value::Json, got {:?}", result);
426 }
427 }
428
429 #[test]
430 fn resolve_invalid_path_returns_none() {
431 let mut scope = Scope::new();
432 scope.set("X", Value::Int(42));
433
434 let path = VarPath {
436 segments: vec![
437 VarSegment::Field("X".into()),
438 VarSegment::Field("invalid".into()),
439 ],
440 };
441 assert_eq!(scope.resolve_path(&path), None);
442 }
443
444 #[test]
445 fn contains_finds_variable() {
446 let mut scope = Scope::new();
447 scope.set("EXISTS", Value::Bool(true));
448 assert!(scope.contains("EXISTS"));
449 assert!(!scope.contains("MISSING"));
450 }
451
452 #[test]
453 fn all_names_lists_variables() {
454 let mut scope = Scope::new();
455 scope.set("A", Value::Int(1));
456 scope.set("B", Value::Int(2));
457 scope.push_frame();
458 scope.set("C", Value::Int(3));
459
460 let names = scope.all_names();
461 assert!(names.contains(&"A"));
462 assert!(names.contains(&"B"));
463 assert!(names.contains(&"C"));
464 }
465
466 #[test]
467 #[should_panic(expected = "cannot pop the root scope frame")]
468 fn pop_root_frame_panics() {
469 let mut scope = Scope::new();
470 scope.pop_frame();
471 }
472
473 #[test]
474 fn positional_params_basic() {
475 let mut scope = Scope::new();
476 scope.set_positional("my_tool", vec!["arg1".into(), "arg2".into(), "arg3".into()]);
477
478 assert_eq!(scope.get_positional(0), Some("my_tool"));
480 assert_eq!(scope.get_positional(1), Some("arg1"));
482 assert_eq!(scope.get_positional(2), Some("arg2"));
483 assert_eq!(scope.get_positional(3), Some("arg3"));
484 assert_eq!(scope.get_positional(4), None);
486 }
487
488 #[test]
489 fn positional_params_empty() {
490 let scope = Scope::new();
491 assert_eq!(scope.get_positional(0), None);
493 assert_eq!(scope.get_positional(1), None);
494 assert_eq!(scope.arg_count(), 0);
495 assert!(scope.all_args().is_empty());
496 }
497
498 #[test]
499 fn all_args_returns_slice() {
500 let mut scope = Scope::new();
501 scope.set_positional("test", vec!["a".into(), "b".into(), "c".into()]);
502
503 let args = scope.all_args();
504 assert_eq!(args, &["a", "b", "c"]);
505 }
506
507 #[test]
508 fn arg_count_returns_count() {
509 let mut scope = Scope::new();
510 scope.set_positional("test", vec!["one".into(), "two".into()]);
511
512 assert_eq!(scope.arg_count(), 2);
513 }
514
515 #[test]
516 fn export_marks_variable() {
517 let mut scope = Scope::new();
518 scope.set("X", Value::Int(42));
519
520 assert!(!scope.is_exported("X"));
521 scope.export("X");
522 assert!(scope.is_exported("X"));
523 }
524
525 #[test]
526 fn set_exported_sets_and_exports() {
527 let mut scope = Scope::new();
528 scope.set_exported("PATH", Value::String("/usr/bin".into()));
529
530 assert!(scope.is_exported("PATH"));
531 assert_eq!(scope.get("PATH"), Some(&Value::String("/usr/bin".into())));
532 }
533
534 #[test]
535 fn unexport_removes_export_marker() {
536 let mut scope = Scope::new();
537 scope.set_exported("VAR", Value::Int(1));
538 assert!(scope.is_exported("VAR"));
539
540 scope.unexport("VAR");
541 assert!(!scope.is_exported("VAR"));
542 assert!(scope.get("VAR").is_some());
544 }
545
546 #[test]
547 fn exported_vars_returns_only_exported_with_values() {
548 let mut scope = Scope::new();
549 scope.set_exported("A", Value::Int(1));
550 scope.set_exported("B", Value::Int(2));
551 scope.set("C", Value::Int(3)); scope.export("D"); let exported = scope.exported_vars();
555 assert_eq!(exported.len(), 2);
556 assert_eq!(exported[0], ("A".to_string(), Value::Int(1)));
557 assert_eq!(exported[1], ("B".to_string(), Value::Int(2)));
558 }
559
560 #[test]
561 fn exported_names_returns_sorted_names() {
562 let mut scope = Scope::new();
563 scope.export("Z");
564 scope.export("A");
565 scope.export("M");
566
567 let names = scope.exported_names();
568 assert_eq!(names, vec!["A", "M", "Z"]);
569 }
570}