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 show_ast: bool,
42 pid: u32,
44}
45
46impl Scope {
47 pub fn new() -> Self {
49 Self {
50 frames: Arc::new(vec![HashMap::new()]),
51 exported: HashSet::new(),
52 last_result: ExecResult::default(),
53 script_name: String::new(),
54 positional: Vec::new(),
55 error_exit: false,
56 show_ast: false,
57 pid: std::process::id(),
58 }
59 }
60
61 pub fn pid(&self) -> u32 {
63 self.pid
64 }
65
66 pub fn push_frame(&mut self) {
68 Arc::make_mut(&mut self.frames).push(HashMap::new());
69 }
70
71 pub fn pop_frame(&mut self) {
75 if self.frames.len() > 1 {
76 Arc::make_mut(&mut self.frames).pop();
77 } else {
78 panic!("cannot pop the root scope frame");
79 }
80 }
81
82 pub fn set(&mut self, name: impl Into<String>, value: Value) {
86 if let Some(frame) = Arc::make_mut(&mut self.frames).last_mut() {
87 frame.insert(name.into(), value);
88 }
89 }
90
91 pub fn set_global(&mut self, name: impl Into<String>, value: Value) {
97 let name = name.into();
98
99 let frames = Arc::make_mut(&mut self.frames);
101 for frame in frames.iter_mut().rev() {
102 if let std::collections::hash_map::Entry::Occupied(mut e) = frame.entry(name.clone()) {
103 e.insert(value);
104 return;
105 }
106 }
107
108 if let Some(frame) = frames.first_mut() {
110 frame.insert(name, value);
111 }
112 }
113
114 pub fn get(&self, name: &str) -> Option<&Value> {
116 for frame in self.frames.iter().rev() {
117 if let Some(value) = frame.get(name) {
118 return Some(value);
119 }
120 }
121 None
122 }
123
124 pub fn remove(&mut self, name: &str) -> Option<Value> {
128 for frame in Arc::make_mut(&mut self.frames).iter_mut().rev() {
129 if let Some(value) = frame.remove(name) {
130 return Some(value);
131 }
132 }
133 None
134 }
135
136 pub fn set_last_result(&mut self, result: ExecResult) {
138 self.last_result = result;
139 }
140
141 pub fn last_result(&self) -> &ExecResult {
143 &self.last_result
144 }
145
146 pub fn set_positional(&mut self, script_name: impl Into<String>, args: Vec<String>) {
150 self.script_name = script_name.into();
151 self.positional = args;
152 }
153
154 pub fn save_positional(&self) -> (String, Vec<String>) {
158 (self.script_name.clone(), self.positional.clone())
159 }
160
161 pub fn get_positional(&self, n: usize) -> Option<&str> {
165 if n == 0 {
166 if self.script_name.is_empty() {
167 None
168 } else {
169 Some(&self.script_name)
170 }
171 } else {
172 self.positional.get(n - 1).map(|s| s.as_str())
173 }
174 }
175
176 pub fn all_args(&self) -> &[String] {
178 &self.positional
179 }
180
181 pub fn arg_count(&self) -> usize {
183 self.positional.len()
184 }
185
186 pub fn error_exit_enabled(&self) -> bool {
188 self.error_exit
189 }
190
191 pub fn set_error_exit(&mut self, enabled: bool) {
193 self.error_exit = enabled;
194 }
195
196 pub fn show_ast(&self) -> bool {
198 self.show_ast
199 }
200
201 pub fn set_show_ast(&mut self, enabled: bool) {
203 self.show_ast = enabled;
204 }
205
206 pub fn export(&mut self, name: impl Into<String>) {
210 self.exported.insert(name.into());
211 }
212
213 pub fn is_exported(&self, name: &str) -> bool {
215 self.exported.contains(name)
216 }
217
218 pub fn set_exported(&mut self, name: impl Into<String>, value: Value) {
220 let name = name.into();
221 self.set(&name, value);
222 self.export(name);
223 }
224
225 pub fn unexport(&mut self, name: &str) {
227 self.exported.remove(name);
228 }
229
230 pub fn exported_vars(&self) -> Vec<(String, Value)> {
234 let mut result = Vec::new();
235 for name in &self.exported {
236 if let Some(value) = self.get(name) {
237 result.push((name.clone(), value.clone()));
238 }
239 }
240 result.sort_by(|(a, _), (b, _)| a.cmp(b));
241 result
242 }
243
244 pub fn exported_names(&self) -> Vec<&str> {
246 let mut names: Vec<&str> = self.exported.iter().map(|s| s.as_str()).collect();
247 names.sort();
248 names
249 }
250
251 pub fn resolve_path(&self, path: &VarPath) -> Option<Value> {
256 if path.segments.is_empty() {
257 return None;
258 }
259
260 let VarSegment::Field(root_name) = &path.segments[0];
262
263 if root_name == "?" {
265 return self.resolve_result_path(&path.segments[1..]);
266 }
267
268 if path.segments.len() > 1 {
270 return None; }
272
273 self.get(root_name).cloned()
274 }
275
276 fn resolve_result_path(&self, segments: &[VarSegment]) -> Option<Value> {
281 if segments.is_empty() {
282 return Some(Value::Int(self.last_result.code));
284 }
285
286 let VarSegment::Field(field_name) = &segments[0];
288
289 if segments.len() > 1 {
291 return None;
292 }
293
294 self.last_result.get_field(field_name)
296 }
297
298 pub fn contains(&self, name: &str) -> bool {
300 self.get(name).is_some()
301 }
302
303 pub fn all_names(&self) -> Vec<&str> {
305 let mut names: Vec<&str> = self
306 .frames
307 .iter()
308 .flat_map(|f| f.keys().map(|s| s.as_str()))
309 .collect();
310 names.sort();
311 names.dedup();
312 names
313 }
314
315 pub fn all(&self) -> Vec<(String, Value)> {
319 let mut result = std::collections::HashMap::new();
320 for frame in self.frames.iter() {
322 for (name, value) in frame {
323 result.insert(name.clone(), value.clone());
324 }
325 }
326 let mut pairs: Vec<_> = result.into_iter().collect();
327 pairs.sort_by(|(a, _), (b, _)| a.cmp(b));
328 pairs
329 }
330}
331
332impl Default for Scope {
333 fn default() -> Self {
334 Self::new()
335 }
336}
337
338#[cfg(test)]
339mod tests {
340 use super::*;
341
342 #[test]
343 fn new_scope_has_one_frame() {
344 let scope = Scope::new();
345 assert_eq!(scope.frames.len(), 1);
346 }
347
348 #[test]
349 fn set_and_get_variable() {
350 let mut scope = Scope::new();
351 scope.set("X", Value::Int(42));
352 assert_eq!(scope.get("X"), Some(&Value::Int(42)));
353 }
354
355 #[test]
356 fn get_nonexistent_returns_none() {
357 let scope = Scope::new();
358 assert_eq!(scope.get("MISSING"), None);
359 }
360
361 #[test]
362 fn inner_frame_shadows_outer() {
363 let mut scope = Scope::new();
364 scope.set("X", Value::Int(1));
365 scope.push_frame();
366 scope.set("X", Value::Int(2));
367 assert_eq!(scope.get("X"), Some(&Value::Int(2)));
368 scope.pop_frame();
369 assert_eq!(scope.get("X"), Some(&Value::Int(1)));
370 }
371
372 #[test]
373 fn inner_frame_can_see_outer_vars() {
374 let mut scope = Scope::new();
375 scope.set("OUTER", Value::String("visible".into()));
376 scope.push_frame();
377 assert_eq!(scope.get("OUTER"), Some(&Value::String("visible".into())));
378 }
379
380 #[test]
381 fn resolve_simple_path() {
382 let mut scope = Scope::new();
383 scope.set("NAME", Value::String("Alice".into()));
384
385 let path = VarPath::simple("NAME");
386 assert_eq!(
387 scope.resolve_path(&path),
388 Some(Value::String("Alice".into()))
389 );
390 }
391
392 #[test]
393 fn resolve_last_result_ok() {
394 let mut scope = Scope::new();
395 scope.set_last_result(ExecResult::success("output"));
396
397 let path = VarPath {
398 segments: vec![
399 VarSegment::Field("?".into()),
400 VarSegment::Field("ok".into()),
401 ],
402 };
403 assert_eq!(scope.resolve_path(&path), Some(Value::Bool(true)));
404 }
405
406 #[test]
407 fn resolve_last_result_code() {
408 let mut scope = Scope::new();
409 scope.set_last_result(ExecResult::failure(127, "not found"));
410
411 let path = VarPath {
412 segments: vec![
413 VarSegment::Field("?".into()),
414 VarSegment::Field("code".into()),
415 ],
416 };
417 assert_eq!(scope.resolve_path(&path), Some(Value::Int(127)));
418 }
419
420 #[test]
421 fn resolve_last_result_data_field() {
422 let mut scope = Scope::new();
423 scope.set_last_result(ExecResult::success(r#"{"count": 5}"#));
424
425 let path = VarPath {
427 segments: vec![
428 VarSegment::Field("?".into()),
429 VarSegment::Field("data".into()),
430 ],
431 };
432 let result = scope.resolve_path(&path);
434 assert!(result.is_some());
435 if let Some(Value::Json(json)) = result {
436 assert_eq!(json.get("count"), Some(&serde_json::json!(5)));
437 } else {
438 panic!("expected Value::Json, got {:?}", result);
439 }
440 }
441
442 #[test]
443 fn resolve_invalid_path_returns_none() {
444 let mut scope = Scope::new();
445 scope.set("X", Value::Int(42));
446
447 let path = VarPath {
449 segments: vec![
450 VarSegment::Field("X".into()),
451 VarSegment::Field("invalid".into()),
452 ],
453 };
454 assert_eq!(scope.resolve_path(&path), None);
455 }
456
457 #[test]
458 fn contains_finds_variable() {
459 let mut scope = Scope::new();
460 scope.set("EXISTS", Value::Bool(true));
461 assert!(scope.contains("EXISTS"));
462 assert!(!scope.contains("MISSING"));
463 }
464
465 #[test]
466 fn all_names_lists_variables() {
467 let mut scope = Scope::new();
468 scope.set("A", Value::Int(1));
469 scope.set("B", Value::Int(2));
470 scope.push_frame();
471 scope.set("C", Value::Int(3));
472
473 let names = scope.all_names();
474 assert!(names.contains(&"A"));
475 assert!(names.contains(&"B"));
476 assert!(names.contains(&"C"));
477 }
478
479 #[test]
480 #[should_panic(expected = "cannot pop the root scope frame")]
481 fn pop_root_frame_panics() {
482 let mut scope = Scope::new();
483 scope.pop_frame();
484 }
485
486 #[test]
487 fn positional_params_basic() {
488 let mut scope = Scope::new();
489 scope.set_positional("my_tool", vec!["arg1".into(), "arg2".into(), "arg3".into()]);
490
491 assert_eq!(scope.get_positional(0), Some("my_tool"));
493 assert_eq!(scope.get_positional(1), Some("arg1"));
495 assert_eq!(scope.get_positional(2), Some("arg2"));
496 assert_eq!(scope.get_positional(3), Some("arg3"));
497 assert_eq!(scope.get_positional(4), None);
499 }
500
501 #[test]
502 fn positional_params_empty() {
503 let scope = Scope::new();
504 assert_eq!(scope.get_positional(0), None);
506 assert_eq!(scope.get_positional(1), None);
507 assert_eq!(scope.arg_count(), 0);
508 assert!(scope.all_args().is_empty());
509 }
510
511 #[test]
512 fn all_args_returns_slice() {
513 let mut scope = Scope::new();
514 scope.set_positional("test", vec!["a".into(), "b".into(), "c".into()]);
515
516 let args = scope.all_args();
517 assert_eq!(args, &["a", "b", "c"]);
518 }
519
520 #[test]
521 fn arg_count_returns_count() {
522 let mut scope = Scope::new();
523 scope.set_positional("test", vec!["one".into(), "two".into()]);
524
525 assert_eq!(scope.arg_count(), 2);
526 }
527
528 #[test]
529 fn export_marks_variable() {
530 let mut scope = Scope::new();
531 scope.set("X", Value::Int(42));
532
533 assert!(!scope.is_exported("X"));
534 scope.export("X");
535 assert!(scope.is_exported("X"));
536 }
537
538 #[test]
539 fn set_exported_sets_and_exports() {
540 let mut scope = Scope::new();
541 scope.set_exported("PATH", Value::String("/usr/bin".into()));
542
543 assert!(scope.is_exported("PATH"));
544 assert_eq!(scope.get("PATH"), Some(&Value::String("/usr/bin".into())));
545 }
546
547 #[test]
548 fn unexport_removes_export_marker() {
549 let mut scope = Scope::new();
550 scope.set_exported("VAR", Value::Int(1));
551 assert!(scope.is_exported("VAR"));
552
553 scope.unexport("VAR");
554 assert!(!scope.is_exported("VAR"));
555 assert!(scope.get("VAR").is_some());
557 }
558
559 #[test]
560 fn exported_vars_returns_only_exported_with_values() {
561 let mut scope = Scope::new();
562 scope.set_exported("A", Value::Int(1));
563 scope.set_exported("B", Value::Int(2));
564 scope.set("C", Value::Int(3)); scope.export("D"); let exported = scope.exported_vars();
568 assert_eq!(exported.len(), 2);
569 assert_eq!(exported[0], ("A".to_string(), Value::Int(1)));
570 assert_eq!(exported[1], ("B".to_string(), Value::Int(2)));
571 }
572
573 #[test]
574 fn exported_names_returns_sorted_names() {
575 let mut scope = Scope::new();
576 scope.export("Z");
577 scope.export("A");
578 scope.export("M");
579
580 let names = scope.exported_names();
581 assert_eq!(names, vec!["A", "M", "Z"]);
582 }
583}