1use std::collections::{HashMap, HashSet};
2use std::sync::Arc;
3
4use indexmap::IndexMap;
5use parking_lot::{Mutex, RwLock};
6
7use crate::ast::PerlTypeName;
8use crate::error::StrykeError;
9use crate::value::StrykeValue;
10
11#[derive(Debug, Clone)]
13pub struct AtomicArray(pub Arc<Mutex<Vec<StrykeValue>>>);
14
15#[derive(Debug, Clone)]
17pub struct AtomicHash(pub Arc<Mutex<IndexMap<String, StrykeValue>>>);
18
19type ScopeCaptureWithAtomics = (
20 Vec<(String, StrykeValue)>,
21 Vec<(String, AtomicArray)>,
22 Vec<(String, AtomicHash)>,
23);
24
25type SharedHashEntry = (
28 String,
29 Arc<parking_lot::RwLock<IndexMap<String, StrykeValue>>>,
30);
31
32#[inline]
40pub(crate) fn strip_main_prefix(name: &str) -> Option<&str> {
41 let rest = name.strip_prefix("main::")?;
42 if rest.contains("::") {
43 return None;
44 }
45 Some(rest)
46}
47
48macro_rules! canon_main {
52 ($name:ident) => {
53 let $name: &str = $crate::scope::strip_main_prefix($name).unwrap_or($name);
54 };
55}
56
57#[inline]
61fn capture_skip_bootstrap_array(name: &str) -> bool {
62 matches!(
63 name,
64 "INC" | "ARGV" | "_" | "-" | "+" | "^CAPTURE" | "^CAPTURE_ALL"
65 )
66}
67
68#[inline]
70fn capture_skip_bootstrap_hash(name: &str) -> bool {
71 matches!(name, "INC" | "ENV" | "SIG" | "^HOOK")
72}
73
74#[inline]
79fn topic_alias(name: &str) -> Option<String> {
83 if name == "_" {
84 return Some("_0".to_string());
85 }
86 if name == "_0" {
87 return Some("_".to_string());
88 }
89 if let Some(rest) = name.strip_prefix("_0") {
91 if rest.chars().all(|c| c == '<') && !rest.is_empty() {
92 return Some(format!("_{rest}"));
93 }
94 }
95 if let Some(rest) = name.strip_prefix('_') {
96 if rest.chars().all(|c| c == '<') && !rest.is_empty() {
97 return Some(format!("_0{rest}"));
98 }
99 }
100 None
101}
102
103fn parse_positional_topic_slot(name: &str) -> Option<usize> {
104 let bytes = name.as_bytes();
105 if bytes.len() < 2 || bytes[0] != b'_' || !bytes[1].is_ascii_digit() {
106 return None;
107 }
108 let mut i = 1;
109 while i < bytes.len() && bytes[i].is_ascii_digit() {
110 i += 1;
111 }
112 let digits = &name[1..i];
113 while i < bytes.len() && bytes[i] == b'<' {
114 i += 1;
115 }
116 if i != bytes.len() {
117 return None;
118 }
119 digits.parse().ok().filter(|&n: &usize| n >= 1)
120}
121
122#[derive(Clone, Debug)]
124enum LocalRestore {
125 Scalar(String, StrykeValue),
126 Array(String, Vec<StrykeValue>),
127 Hash(String, IndexMap<String, StrykeValue>),
128 HashElement(String, String, Option<StrykeValue>),
130 ArrayElement(String, i64, StrykeValue),
132}
133
134#[derive(Debug, Clone)]
139struct Frame {
140 scalars: Vec<(String, StrykeValue)>,
141 arrays: Vec<(String, Vec<StrykeValue>)>,
142 sub_underscore: Option<Vec<StrykeValue>>,
145 hashes: Vec<(String, IndexMap<String, StrykeValue>)>,
146 scalar_slots: Vec<StrykeValue>,
150 scalar_slot_names: Vec<Option<String>>,
153 local_restores: Vec<LocalRestore>,
155 frozen_scalars: HashSet<String>,
157 frozen_arrays: HashSet<String>,
158 frozen_hashes: HashSet<String>,
159 typed_scalars: HashMap<String, PerlTypeName>,
161 shared_arrays: Vec<(String, Arc<parking_lot::RwLock<Vec<StrykeValue>>>)>,
165 shared_hashes: Vec<SharedHashEntry>,
167 atomic_arrays: Vec<(String, AtomicArray)>,
169 atomic_hashes: Vec<(String, AtomicHash)>,
171 defers: Vec<StrykeValue>,
173 set_topic_called: bool,
180}
181
182impl Frame {
183 #[inline]
186 fn clear_all_bindings(&mut self) {
187 self.scalars.clear();
188 self.arrays.clear();
189 self.sub_underscore = None;
190 self.hashes.clear();
191 self.scalar_slots.clear();
192 self.scalar_slot_names.clear();
193 self.local_restores.clear();
194 self.frozen_scalars.clear();
195 self.frozen_arrays.clear();
196 self.frozen_hashes.clear();
197 self.typed_scalars.clear();
198 self.shared_arrays.clear();
199 self.shared_hashes.clear();
200 self.atomic_arrays.clear();
201 self.defers.clear();
202 self.atomic_hashes.clear();
203 self.set_topic_called = false;
204 }
205
206 #[inline]
210 fn owns_scalar_slot_index(&self, idx: usize) -> bool {
211 self.scalar_slot_names.get(idx).is_some_and(|n| n.is_some())
212 }
213
214 #[inline]
215 fn new() -> Self {
216 Self {
217 scalars: Vec::new(),
218 arrays: Vec::new(),
219 sub_underscore: None,
220 hashes: Vec::new(),
221 scalar_slots: Vec::new(),
222 scalar_slot_names: Vec::new(),
223 frozen_scalars: HashSet::new(),
224 frozen_arrays: HashSet::new(),
225 frozen_hashes: HashSet::new(),
226 shared_arrays: Vec::new(),
227 shared_hashes: Vec::new(),
228 typed_scalars: HashMap::new(),
229 atomic_arrays: Vec::new(),
230 atomic_hashes: Vec::new(),
231 local_restores: Vec::new(),
232 defers: Vec::new(),
233 set_topic_called: false,
234 }
235 }
236
237 #[inline]
238 fn get_scalar(&self, name: &str) -> Option<&StrykeValue> {
239 let name = strip_main_prefix(name).unwrap_or(name);
240 if let Some(v) = self.get_scalar_from_slot(name) {
241 return Some(v);
242 }
243 self.scalars.iter().find(|(k, _)| k == name).map(|(_, v)| v)
244 }
245
246 #[inline]
249 fn get_scalar_from_slot(&self, name: &str) -> Option<&StrykeValue> {
250 let name = strip_main_prefix(name).unwrap_or(name);
251 for (i, sn) in self.scalar_slot_names.iter().enumerate() {
252 if let Some(ref n) = sn {
253 if n == name {
254 return self.scalar_slots.get(i);
255 }
256 }
257 }
258 None
259 }
260
261 #[inline]
262 fn has_scalar(&self, name: &str) -> bool {
263 let name = strip_main_prefix(name).unwrap_or(name);
264 if self
265 .scalar_slot_names
266 .iter()
267 .any(|sn| sn.as_deref() == Some(name))
268 {
269 return true;
270 }
271 self.scalars.iter().any(|(k, _)| k == name)
272 }
273
274 #[inline]
275 fn set_scalar(&mut self, name: &str, val: StrykeValue) {
276 let name = strip_main_prefix(name).unwrap_or(name);
277 for (i, sn) in self.scalar_slot_names.iter().enumerate() {
278 if let Some(ref n) = sn {
279 if n == name {
280 if i < self.scalar_slots.len() {
281 if let Some(r) = self.scalar_slots[i].as_capture_cell() {
283 *r.write() = val;
284 } else {
285 self.scalar_slots[i] = val;
286 }
287 }
288 return;
289 }
290 }
291 }
292 if let Some(entry) = self.scalars.iter_mut().find(|(k, _)| k == name) {
293 if let Some(r) = entry.1.as_capture_cell() {
295 *r.write() = val;
296 } else {
297 entry.1 = val;
298 }
299 } else {
300 self.scalars.push((name.to_string(), val));
301 }
302 }
303
304 #[inline]
313 fn set_scalar_raw(&mut self, name: &str, val: StrykeValue) {
314 let name = strip_main_prefix(name).unwrap_or(name);
315 for (i, sn) in self.scalar_slot_names.iter().enumerate() {
316 if let Some(ref n) = sn {
317 if n == name {
318 if i < self.scalar_slots.len() {
319 self.scalar_slots[i] = val;
320 }
321 return;
322 }
323 }
324 }
325 if let Some(entry) = self.scalars.iter_mut().find(|(k, _)| k == name) {
326 entry.1 = val;
327 } else {
328 self.scalars.push((name.to_string(), val));
329 }
330 }
331
332 #[inline]
333 fn get_array(&self, name: &str) -> Option<&Vec<StrykeValue>> {
334 let name = strip_main_prefix(name).unwrap_or(name);
335 if name == "_" {
336 if let Some(ref v) = self.sub_underscore {
337 return Some(v);
338 }
339 }
340 self.arrays.iter().find(|(k, _)| k == name).map(|(_, v)| v)
341 }
342
343 #[inline]
344 fn has_array(&self, name: &str) -> bool {
345 let name = strip_main_prefix(name).unwrap_or(name);
346 if name == "_" && self.sub_underscore.is_some() {
347 return true;
348 }
349 self.arrays.iter().any(|(k, _)| k == name)
350 || self.shared_arrays.iter().any(|(k, _)| k == name)
351 }
352
353 #[inline]
354 fn get_array_mut(&mut self, name: &str) -> Option<&mut Vec<StrykeValue>> {
355 let name = strip_main_prefix(name).unwrap_or(name);
356 if name == "_" {
357 return self.sub_underscore.as_mut();
358 }
359 self.arrays
360 .iter_mut()
361 .find(|(k, _)| k == name)
362 .map(|(_, v)| v)
363 }
364
365 #[inline]
366 fn set_array(&mut self, name: &str, val: Vec<StrykeValue>) {
367 let name = strip_main_prefix(name).unwrap_or(name);
368 if name == "_" {
369 if let Some(pos) = self.arrays.iter().position(|(k, _)| k == name) {
370 self.arrays.swap_remove(pos);
371 }
372 self.sub_underscore = Some(val);
373 return;
374 }
375 if let Some(entry) = self.arrays.iter_mut().find(|(k, _)| k == name) {
376 entry.1 = val;
377 } else {
378 self.arrays.push((name.to_string(), val));
379 }
380 }
381
382 #[inline]
383 fn get_hash(&self, name: &str) -> Option<&IndexMap<String, StrykeValue>> {
384 let name = strip_main_prefix(name).unwrap_or(name);
385 self.hashes.iter().find(|(k, _)| k == name).map(|(_, v)| v)
386 }
387
388 #[inline]
389 fn has_hash(&self, name: &str) -> bool {
390 let name = strip_main_prefix(name).unwrap_or(name);
391 self.hashes.iter().any(|(k, _)| k == name)
392 || self.shared_hashes.iter().any(|(k, _)| k == name)
393 }
394
395 #[inline]
396 fn get_hash_mut(&mut self, name: &str) -> Option<&mut IndexMap<String, StrykeValue>> {
397 let name = strip_main_prefix(name).unwrap_or(name);
398 self.hashes
399 .iter_mut()
400 .find(|(k, _)| k == name)
401 .map(|(_, v)| v)
402 }
403
404 #[inline]
405 fn set_hash(&mut self, name: &str, val: IndexMap<String, StrykeValue>) {
406 let name = strip_main_prefix(name).unwrap_or(name);
407 if let Some(entry) = self.hashes.iter_mut().find(|(k, _)| k == name) {
408 entry.1 = val;
409 } else {
410 self.hashes.push((name.to_string(), val));
411 }
412 }
413}
414
415#[derive(Debug, Clone)]
418pub struct Scope {
419 frames: Vec<Frame>,
421 frame_pool: Vec<Frame>,
423 parallel_guard: bool,
428 parallel_guard_baseline: usize,
437 max_active_slot: usize,
444}
445
446impl Default for Scope {
447 fn default() -> Self {
448 Self::new()
449 }
450}
451
452impl Scope {
453 pub fn new() -> Self {
455 let mut s = Self {
456 frames: Vec::with_capacity(32),
457 frame_pool: Vec::with_capacity(32),
458 parallel_guard: false,
459 parallel_guard_baseline: 0,
460 max_active_slot: 0,
461 };
462 s.frames.push(Frame::new());
463 s
464 }
465
466 #[inline]
471 pub fn set_parallel_guard(&mut self, enabled: bool) {
472 self.parallel_guard = enabled;
473 self.parallel_guard_baseline = if enabled { self.frames.len() } else { 0 };
474 }
475 #[inline]
477 pub fn parallel_guard(&self) -> bool {
478 self.parallel_guard
479 }
480
481 #[inline]
488 fn parallel_skip_special_name(_name: &str) -> bool {
489 false
490 }
491
492 #[inline]
494 fn parallel_allowed_topic_scalar(name: &str) -> bool {
495 matches!(name, "_" | "a" | "b")
496 }
497
498 #[inline]
500 fn parallel_allowed_internal_array(name: &str) -> bool {
501 matches!(name, "-" | "+" | "^CAPTURE" | "^CAPTURE_ALL")
502 }
503
504 #[inline]
506 fn parallel_allowed_internal_hash(name: &str) -> bool {
507 matches!(name, "+" | "-" | "ENV" | "INC")
508 }
509
510 fn check_parallel_scalar_write(&self, name: &str) -> Result<(), StrykeError> {
511 if !self.parallel_guard || Self::parallel_skip_special_name(name) {
512 return Ok(());
513 }
514 if Self::parallel_allowed_topic_scalar(name) {
515 return Ok(());
516 }
517 if crate::special_vars::is_regex_match_scalar_name(name) {
518 return Ok(());
519 }
520 let baseline = self.parallel_guard_baseline;
524 for (i, frame) in self.frames.iter().enumerate().rev() {
525 if frame.has_scalar(name) {
526 if let Some(v) = frame.get_scalar(name) {
527 if v.as_atomic_arc().is_some() {
528 return Ok(());
529 }
530 }
531 if i < baseline {
532 let directive = if name.contains("::") {
536 "declare `oursync` for shared package-global state"
537 } else {
538 "declare `mysync` for shared lexical state"
539 };
540 return Err(StrykeError::runtime(
541 format!(
542 "cannot assign to captured non-atomic variable `${}` in a parallel block — {}",
543 name, directive
544 ),
545 0,
546 ));
547 }
548 return Ok(());
549 }
550 }
551 Err(StrykeError::runtime(
552 format!(
553 "cannot assign to undeclared variable `${}` in a parallel block",
554 name
555 ),
556 0,
557 ))
558 }
559 #[inline]
561 pub fn depth(&self) -> usize {
562 self.frames.len()
563 }
564
565 #[inline]
568 pub fn pop_to_depth(&mut self, target_depth: usize) {
569 while self.frames.len() > target_depth && self.frames.len() > 1 {
570 self.pop_frame();
571 }
572 }
573 #[inline]
575 pub fn push_frame(&mut self) {
576 if let Some(mut frame) = self.frame_pool.pop() {
577 frame.clear_all_bindings();
578 self.frames.push(frame);
579 } else {
580 self.frames.push(Frame::new());
581 }
582 }
583
584 #[inline]
589 pub fn get_scalar_slot(&self, slot: u8) -> StrykeValue {
590 let idx = slot as usize;
591 for frame in self.frames.iter().rev() {
592 if idx < frame.scalar_slots.len() && frame.owns_scalar_slot_index(idx) {
593 let val = &frame.scalar_slots[idx];
594 if let Some(arc) = val.as_capture_cell() {
597 return arc.read().clone();
598 }
599 return val.clone();
600 }
601 }
602 StrykeValue::UNDEF
603 }
604
605 #[inline]
607 pub fn set_scalar_slot(&mut self, slot: u8, val: StrykeValue) {
608 let idx = slot as usize;
609 let len = self.frames.len();
610 for i in (0..len).rev() {
611 if idx < self.frames[i].scalar_slots.len() && self.frames[i].owns_scalar_slot_index(idx)
612 {
613 if let Some(r) = self.frames[i].scalar_slots[idx].as_capture_cell() {
615 *r.write() = val;
616 } else {
617 self.frames[i].scalar_slots[idx] = val;
618 }
619 return;
620 }
621 }
622 let top = self.frames.last_mut().unwrap();
623 top.scalar_slots.resize(idx + 1, StrykeValue::UNDEF);
624 if idx >= top.scalar_slot_names.len() {
625 top.scalar_slot_names.resize(idx + 1, None);
626 }
627 top.scalar_slot_names[idx] = Some(String::new());
628 top.scalar_slots[idx] = val;
629 }
630
631 #[inline]
635 pub fn set_scalar_slot_checked(
636 &mut self,
637 slot: u8,
638 val: StrykeValue,
639 slot_name: Option<&str>,
640 ) -> Result<(), StrykeError> {
641 if self.parallel_guard {
642 let idx = slot as usize;
643 let len = self.frames.len();
644 let top_has = idx < self.frames[len - 1].scalar_slots.len()
645 && self.frames[len - 1].owns_scalar_slot_index(idx);
646 if !top_has {
647 let name_owned: String = {
648 let mut found = String::new();
649 for i in (0..len).rev() {
650 if let Some(Some(n)) = self.frames[i].scalar_slot_names.get(idx) {
651 found = n.clone();
652 break;
653 }
654 }
655 if found.is_empty() {
656 if let Some(sn) = slot_name {
657 found = sn.to_string();
658 }
659 }
660 found
661 };
662 let name = name_owned.as_str();
663 if !name.is_empty() && !Self::parallel_allowed_topic_scalar(name) {
664 let baseline = self.parallel_guard_baseline;
665 for (fi, frame) in self.frames.iter().enumerate().rev() {
666 if frame.has_scalar(name)
667 || (idx < frame.scalar_slots.len() && frame.owns_scalar_slot_index(idx))
668 {
669 if fi < baseline {
670 return Err(StrykeError::runtime(
671 format!(
672 "cannot assign to captured outer lexical `${}` inside a parallel block (use `mysync`)",
673 name
674 ),
675 0,
676 ));
677 }
678 break;
679 }
680 }
681 }
682 }
683 }
684 self.set_scalar_slot(slot, val);
685 Ok(())
686 }
687
688 #[inline]
692 pub fn declare_scalar_slot(&mut self, slot: u8, val: StrykeValue, name: Option<&str>) {
693 let idx = slot as usize;
694 let frame = self.frames.last_mut().unwrap();
695 if idx >= frame.scalar_slots.len() {
696 frame.scalar_slots.resize(idx + 1, StrykeValue::UNDEF);
697 }
698 frame.scalar_slots[idx] = val;
699 if idx >= frame.scalar_slot_names.len() {
700 frame.scalar_slot_names.resize(idx + 1, None);
701 }
702 match name {
703 Some(n) => frame.scalar_slot_names[idx] = Some(n.to_string()),
704 None => frame.scalar_slot_names[idx] = Some(String::new()),
706 }
707 }
708
709 #[inline]
720 pub fn scalar_slot_concat_repeat_inplace(&mut self, slot: u8, rhs: &str, n: usize) -> bool {
721 let idx = slot as usize;
722 let len = self.frames.len();
723 let fi = {
724 let mut found = len - 1;
725 if idx >= self.frames[found].scalar_slots.len()
726 || !self.frames[found].owns_scalar_slot_index(idx)
727 {
728 for i in (0..len - 1).rev() {
729 if idx < self.frames[i].scalar_slots.len()
730 && self.frames[i].owns_scalar_slot_index(idx)
731 {
732 found = i;
733 break;
734 }
735 }
736 }
737 found
738 };
739 let frame = &mut self.frames[fi];
740 if idx >= frame.scalar_slots.len() {
741 frame.scalar_slots.resize(idx + 1, StrykeValue::UNDEF);
742 }
743 frame.scalar_slots[idx].try_concat_repeat_inplace(rhs, n)
744 }
745
746 #[inline]
751 pub fn scalar_slot_concat_repeat_slow(&mut self, slot: u8, rhs: &str, n: usize) {
752 let pv = StrykeValue::string(rhs.to_owned());
753 for _ in 0..n {
754 let _ = self.scalar_slot_concat_inplace(slot, &pv);
755 }
756 }
757 #[inline]
759 pub fn scalar_slot_concat_inplace(&mut self, slot: u8, rhs: &StrykeValue) -> StrykeValue {
760 let idx = slot as usize;
761 let len = self.frames.len();
762 let fi = {
763 let mut found = len - 1;
764 if idx >= self.frames[found].scalar_slots.len()
765 || !self.frames[found].owns_scalar_slot_index(idx)
766 {
767 for i in (0..len - 1).rev() {
768 if idx < self.frames[i].scalar_slots.len()
769 && self.frames[i].owns_scalar_slot_index(idx)
770 {
771 found = i;
772 break;
773 }
774 }
775 }
776 found
777 };
778 let frame = &mut self.frames[fi];
779 if idx >= frame.scalar_slots.len() {
780 frame.scalar_slots.resize(idx + 1, StrykeValue::UNDEF);
781 }
782 if frame.scalar_slots[idx].try_concat_append_inplace(rhs) {
790 return frame.scalar_slots[idx].shallow_clone();
791 }
792 let new_val = std::mem::replace(&mut frame.scalar_slots[idx], StrykeValue::UNDEF)
793 .concat_append_owned(rhs);
794 let handle = new_val.shallow_clone();
795 frame.scalar_slots[idx] = new_val;
796 handle
797 }
798
799 #[inline]
800 pub(crate) fn can_pop_frame(&self) -> bool {
801 self.frames.len() > 1
802 }
803 #[inline]
805 pub fn pop_frame(&mut self) {
806 if self.frames.len() > 1 {
807 let mut frame = self.frames.pop().expect("pop_frame");
808 let saved_guard = self.parallel_guard;
811 self.parallel_guard = false;
812 for entry in frame.local_restores.drain(..).rev() {
813 match entry {
814 LocalRestore::Scalar(name, old) => {
815 let _ = self.set_scalar(&name, old);
816 }
817 LocalRestore::Array(name, old) => {
818 let _ = self.set_array(&name, old);
819 }
820 LocalRestore::Hash(name, old) => {
821 let _ = self.set_hash(&name, old);
822 }
823 LocalRestore::HashElement(name, key, old) => match old {
824 Some(v) => {
825 let _ = self.set_hash_element(&name, &key, v);
826 }
827 None => {
828 let _ = self.delete_hash_element(&name, &key);
829 }
830 },
831 LocalRestore::ArrayElement(name, index, old) => {
832 let _ = self.set_array_element(&name, index, old);
833 }
834 }
835 }
836 self.parallel_guard = saved_guard;
837 frame.clear_all_bindings();
838 if self.frame_pool.len() < 64 {
840 self.frame_pool.push(frame);
841 }
842 }
843 }
844
845 pub fn local_set_scalar(&mut self, name: &str, val: StrykeValue) -> Result<(), StrykeError> {
847 let old = self.get_scalar(name);
848 if let Some(frame) = self.frames.last_mut() {
849 frame
850 .local_restores
851 .push(LocalRestore::Scalar(name.to_string(), old));
852 }
853 self.set_scalar(name, val)
854 }
855
856 pub fn local_set_array(
858 &mut self,
859 name: &str,
860 val: Vec<StrykeValue>,
861 ) -> Result<(), StrykeError> {
862 if self.find_atomic_array(name).is_some() {
863 return Err(StrykeError::runtime(
864 "local cannot be used on mysync arrays",
865 0,
866 ));
867 }
868 let old = self.get_array(name);
869 if let Some(frame) = self.frames.last_mut() {
870 frame
871 .local_restores
872 .push(LocalRestore::Array(name.to_string(), old));
873 }
874 self.set_array(name, val)?;
875 Ok(())
876 }
877
878 pub fn local_set_hash(
880 &mut self,
881 name: &str,
882 val: IndexMap<String, StrykeValue>,
883 ) -> Result<(), StrykeError> {
884 if self.find_atomic_hash(name).is_some() {
885 return Err(StrykeError::runtime(
886 "local cannot be used on mysync hashes",
887 0,
888 ));
889 }
890 let old = self.get_hash(name);
891 if let Some(frame) = self.frames.last_mut() {
892 frame
893 .local_restores
894 .push(LocalRestore::Hash(name.to_string(), old));
895 }
896 self.set_hash(name, val)?;
897 Ok(())
898 }
899
900 pub fn local_set_hash_element(
902 &mut self,
903 name: &str,
904 key: &str,
905 val: StrykeValue,
906 ) -> Result<(), StrykeError> {
907 if self.find_atomic_hash(name).is_some() {
908 return Err(StrykeError::runtime(
909 "local cannot be used on mysync hash elements",
910 0,
911 ));
912 }
913 let old = if self.exists_hash_element(name, key) {
914 Some(self.get_hash_element(name, key))
915 } else {
916 None
917 };
918 if let Some(frame) = self.frames.last_mut() {
919 frame.local_restores.push(LocalRestore::HashElement(
920 name.to_string(),
921 key.to_string(),
922 old,
923 ));
924 }
925 self.set_hash_element(name, key, val)?;
926 Ok(())
927 }
928
929 pub fn local_set_array_element(
932 &mut self,
933 name: &str,
934 index: i64,
935 val: StrykeValue,
936 ) -> Result<(), StrykeError> {
937 if self.find_atomic_array(name).is_some() {
938 return Err(StrykeError::runtime(
939 "local cannot be used on mysync array elements",
940 0,
941 ));
942 }
943 let old = self.get_array_element(name, index);
944 if let Some(frame) = self.frames.last_mut() {
945 frame
946 .local_restores
947 .push(LocalRestore::ArrayElement(name.to_string(), index, old));
948 }
949 self.set_array_element(name, index, val)?;
950 Ok(())
951 }
952
953 #[inline]
956 pub fn declare_scalar(&mut self, name: &str, val: StrykeValue) {
957 let _ = self.declare_scalar_frozen(name, val, false, None);
958 }
959
960 pub fn declare_scalar_frozen(
963 &mut self,
964 name: &str,
965 val: StrykeValue,
966 frozen: bool,
967 ty: Option<PerlTypeName>,
968 ) -> Result<(), StrykeError> {
969 canon_main!(name);
970 if let Some(ref t) = ty {
971 t.check_value(&val)
972 .map_err(|msg| StrykeError::type_error(format!("`${}`: {}", name, msg), 0))?;
973 }
974 if let Some(frame) = self.frames.last_mut() {
975 frame.set_scalar(name, val);
976 if frozen {
977 frame.frozen_scalars.insert(name.to_string());
978 }
979 if let Some(t) = ty {
980 frame.typed_scalars.insert(name.to_string(), t);
981 }
982 }
983 Ok(())
984 }
985
986 pub fn is_scalar_frozen(&self, name: &str) -> bool {
988 for frame in self.frames.iter().rev() {
989 if frame.has_scalar(name) {
990 return frame.frozen_scalars.contains(name);
991 }
992 }
993 false
994 }
995
996 pub fn is_array_frozen(&self, name: &str) -> bool {
998 for frame in self.frames.iter().rev() {
999 if frame.has_array(name) {
1000 return frame.frozen_arrays.contains(name);
1001 }
1002 }
1003 false
1004 }
1005
1006 pub fn is_hash_frozen(&self, name: &str) -> bool {
1008 for frame in self.frames.iter().rev() {
1009 if frame.has_hash(name) {
1010 return frame.frozen_hashes.contains(name);
1011 }
1012 }
1013 false
1014 }
1015
1016 pub fn check_frozen(&self, sigil: &str, name: &str) -> Option<&'static str> {
1018 match sigil {
1019 "$" => {
1020 if self.is_scalar_frozen(name) {
1021 Some("scalar")
1022 } else {
1023 None
1024 }
1025 }
1026 "@" => {
1027 if self.is_array_frozen(name) {
1028 Some("array")
1029 } else {
1030 None
1031 }
1032 }
1033 "%" => {
1034 if self.is_hash_frozen(name) {
1035 Some("hash")
1036 } else {
1037 None
1038 }
1039 }
1040 _ => None,
1041 }
1042 }
1043 #[inline]
1045 pub fn get_scalar(&self, name: &str) -> StrykeValue {
1046 if let Some(rest) = strip_main_prefix(name) {
1048 return self.get_scalar(rest);
1049 }
1050 for frame in self.frames.iter().rev() {
1051 if let Some(val) = frame.get_scalar(name) {
1052 if let Some(arc) = val.as_atomic_arc() {
1054 return arc.lock().clone();
1055 }
1056 if let Some(arc) = val.as_capture_cell() {
1059 return arc.read().clone();
1060 }
1061 return val.clone();
1068 }
1069 }
1070 StrykeValue::UNDEF
1071 }
1072
1073 #[inline]
1082 pub(crate) fn is_topic_variant_name(name: &str) -> bool {
1083 let bytes = name.as_bytes();
1084 if bytes.is_empty() || bytes[0] != b'_' {
1085 return false;
1086 }
1087 let mut i = 1;
1088 while i < bytes.len() && bytes[i].is_ascii_digit() {
1089 i += 1;
1090 }
1091 while i < bytes.len() && bytes[i] == b'<' {
1092 i += 1;
1093 }
1094 i == bytes.len()
1095 }
1096
1097 #[inline]
1099 pub fn scalar_binding_exists(&self, name: &str) -> bool {
1100 canon_main!(name);
1101 for frame in self.frames.iter().rev() {
1102 if frame.has_scalar(name) {
1103 return true;
1104 }
1105 }
1106 false
1107 }
1108
1109 pub fn all_scalar_names(&self) -> Vec<String> {
1111 let mut names = Vec::new();
1112 for frame in &self.frames {
1113 for (name, _) in &frame.scalars {
1114 if !names.contains(name) {
1115 names.push(name.clone());
1116 }
1117 }
1118 for name in frame.scalar_slot_names.iter().flatten() {
1119 if !names.contains(name) {
1120 names.push(name.clone());
1121 }
1122 }
1123 }
1124 names
1125 }
1126
1127 pub fn all_array_names(&self) -> Vec<String> {
1129 let mut names = Vec::new();
1130 for frame in &self.frames {
1131 for (name, _) in &frame.arrays {
1132 if !names.contains(name) {
1133 names.push(name.clone());
1134 }
1135 }
1136 for (name, _) in &frame.shared_arrays {
1137 if !names.contains(name) {
1138 names.push(name.clone());
1139 }
1140 }
1141 for (name, _) in &frame.atomic_arrays {
1142 if !names.contains(name) {
1143 names.push(name.clone());
1144 }
1145 }
1146 }
1147 names
1148 }
1149
1150 pub fn all_hash_names(&self) -> Vec<String> {
1152 let mut names = Vec::new();
1153 for frame in &self.frames {
1154 for (name, _) in &frame.hashes {
1155 if !names.contains(name) {
1156 names.push(name.clone());
1157 }
1158 }
1159 for (name, _) in &frame.shared_hashes {
1160 if !names.contains(name) {
1161 names.push(name.clone());
1162 }
1163 }
1164 for (name, _) in &frame.atomic_hashes {
1165 if !names.contains(name) {
1166 names.push(name.clone());
1167 }
1168 }
1169 }
1170 names
1171 }
1172
1173 #[inline]
1175 pub fn array_binding_exists(&self, name: &str) -> bool {
1176 canon_main!(name);
1177 if self.find_atomic_array(name).is_some() {
1178 return true;
1179 }
1180 for frame in self.frames.iter().rev() {
1181 if frame.has_array(name) {
1182 return true;
1183 }
1184 }
1185 false
1186 }
1187
1188 #[inline]
1190 pub fn hash_binding_exists(&self, name: &str) -> bool {
1191 if let Some(rest) = strip_main_prefix(name) {
1192 return self.hash_binding_exists(rest);
1193 }
1194 if self.find_atomic_hash(name).is_some() {
1195 return true;
1196 }
1197 for frame in self.frames.iter().rev() {
1198 if frame.has_hash(name) {
1199 return true;
1200 }
1201 }
1202 false
1203 }
1204
1205 #[inline]
1208 pub fn get_scalar_raw(&self, name: &str) -> StrykeValue {
1209 if let Some(rest) = strip_main_prefix(name) {
1210 return self.get_scalar_raw(rest);
1211 }
1212 for frame in self.frames.iter().rev() {
1213 if let Some(val) = frame.get_scalar(name) {
1214 return val.clone();
1215 }
1216 }
1217 StrykeValue::UNDEF
1218 }
1219
1220 pub fn atomic_mutate(
1226 &mut self,
1227 name: &str,
1228 f: impl FnOnce(&StrykeValue) -> StrykeValue,
1229 ) -> Result<StrykeValue, StrykeError> {
1230 for frame in self.frames.iter().rev() {
1231 if let Some(v) = frame.get_scalar(name) {
1232 if let Some(arc) = v.as_atomic_arc() {
1233 let mut guard = arc.lock();
1234 let old = guard.clone();
1235 let new_val = f(&guard);
1236 *guard = new_val.clone();
1237 crate::parallel_trace::emit_scalar_mutation(name, &old, &new_val);
1238 return Ok(new_val);
1239 }
1240 }
1241 }
1242 let old = self.get_scalar(name);
1245 let new_val = f(&old);
1246 self.set_scalar(name, new_val.clone())?;
1247 Ok(new_val)
1248 }
1249
1250 pub fn atomic_mutate_post(
1254 &mut self,
1255 name: &str,
1256 f: impl FnOnce(&StrykeValue) -> StrykeValue,
1257 ) -> Result<StrykeValue, StrykeError> {
1258 for frame in self.frames.iter().rev() {
1259 if let Some(v) = frame.get_scalar(name) {
1260 if let Some(arc) = v.as_atomic_arc() {
1261 let mut guard = arc.lock();
1262 let old = guard.clone();
1263 let new_val = f(&old);
1264 *guard = new_val.clone();
1265 crate::parallel_trace::emit_scalar_mutation(name, &old, &new_val);
1266 return Ok(old);
1267 }
1268 }
1269 }
1270 let old = self.get_scalar(name);
1272 self.set_scalar(name, f(&old))?;
1273 Ok(old)
1274 }
1275
1276 #[inline]
1283 pub fn scalar_concat_inplace(
1284 &mut self,
1285 name: &str,
1286 rhs: &StrykeValue,
1287 ) -> Result<StrykeValue, StrykeError> {
1288 canon_main!(name);
1289 self.check_parallel_scalar_write(name)?;
1290 for frame in self.frames.iter_mut().rev() {
1291 if let Some(entry) = frame.scalars.iter_mut().find(|(k, _)| k == name) {
1292 if let Some(atomic_arc) = entry.1.as_atomic_arc() {
1295 let mut guard = atomic_arc.lock();
1296 let inner = std::mem::replace(&mut *guard, StrykeValue::UNDEF);
1297 let new_val = inner.concat_append_owned(rhs);
1298 *guard = new_val.shallow_clone();
1299 return Ok(new_val);
1300 }
1301 if entry.1.try_concat_append_inplace(rhs) {
1304 return Ok(entry.1.shallow_clone());
1305 }
1306 let new_val =
1309 std::mem::replace(&mut entry.1, StrykeValue::UNDEF).concat_append_owned(rhs);
1310 entry.1 = new_val.shallow_clone();
1311 return Ok(new_val);
1312 }
1313 }
1314 let val = StrykeValue::UNDEF.concat_append_owned(rhs);
1316 self.frames[0].set_scalar(name, val.shallow_clone());
1317 Ok(val)
1318 }
1319 #[inline]
1321 pub fn set_scalar(&mut self, name: &str, val: StrykeValue) -> Result<(), StrykeError> {
1322 if let Some(rest) = strip_main_prefix(name) {
1323 return self.set_scalar(rest, val);
1324 }
1325 self.check_parallel_scalar_write(name)?;
1326 if Self::is_topic_variant_name(name) {
1333 if let Some(frame) = self.frames.last_mut() {
1334 frame.set_scalar_raw(name, val.clone());
1335 if let Some(alias) = topic_alias(name) {
1343 frame.set_scalar_raw(&alias, val);
1344 }
1345 }
1346 return Ok(());
1347 }
1348 for frame in self.frames.iter_mut().rev() {
1349 if let Some(v) = frame.get_scalar(name) {
1351 if let Some(arc) = v.as_atomic_arc() {
1352 let mut guard = arc.lock();
1353 let old = guard.clone();
1354 *guard = val.clone();
1355 crate::parallel_trace::emit_scalar_mutation(name, &old, &val);
1356 return Ok(());
1357 }
1358 if let Some(arc) = v.as_capture_cell() {
1360 *arc.write() = val;
1361 return Ok(());
1362 }
1363 }
1364 if frame.has_scalar(name) {
1365 if let Some(ty) = frame.typed_scalars.get(name) {
1366 ty.check_value(&val).map_err(|msg| {
1367 StrykeError::type_error(format!("`${}`: {}", name, msg), 0)
1368 })?;
1369 }
1370 frame.set_scalar(name, val);
1371 return Ok(());
1372 }
1373 }
1374 self.frames[0].set_scalar(name, val);
1375 Ok(())
1376 }
1377
1378 #[inline]
1384 fn topic_slot_key(slot: usize, level: usize) -> String {
1385 debug_assert!(level <= 5);
1386 if slot == 0 {
1387 if level == 0 {
1388 "_".to_string()
1389 } else {
1390 format!("_{}", "<".repeat(level))
1391 }
1392 } else if level == 0 {
1393 format!("_{}", slot)
1394 } else {
1395 format!("_{}{}", slot, "<".repeat(level))
1396 }
1397 }
1398
1399 #[inline]
1402 fn topic_slot_alias_key(slot: usize, level: usize) -> Option<String> {
1403 if slot != 0 {
1404 return None;
1405 }
1406 Some(if level == 0 {
1407 "_0".to_string()
1408 } else {
1409 format!("_0{}", "<".repeat(level))
1410 })
1411 }
1412
1413 #[inline]
1419 fn declare_topic_slot(&mut self, slot: usize, level: usize, val: StrykeValue) {
1420 if let Some(frame) = self.frames.last_mut() {
1427 let key = Self::topic_slot_key(slot, level);
1428 frame.set_scalar_raw(&key, val.clone());
1429 if let Some(alias) = Self::topic_slot_alias_key(slot, level) {
1430 frame.set_scalar_raw(&alias, val);
1431 }
1432 }
1433 }
1434
1435 #[inline]
1460 pub fn set_topic(&mut self, val: StrykeValue) {
1461 let already_shifted = self
1469 .frames
1470 .last()
1471 .map(|f| f.set_topic_called)
1472 .unwrap_or(false);
1473 if already_shifted {
1474 self.declare_topic_slot(0, 0, val);
1475 for slot in 1..=self.max_active_slot {
1476 self.declare_topic_slot(slot, 0, StrykeValue::UNDEF);
1477 }
1478 return;
1479 }
1480 if let Some(frame) = self.frames.last_mut() {
1481 frame.set_topic_called = true;
1482 }
1483 self.shift_slot_chain(0, val);
1484 for slot in 1..=self.max_active_slot {
1485 self.shift_slot_chain(slot, StrykeValue::UNDEF);
1486 }
1487 }
1488
1489 #[inline]
1498 pub fn set_topic_local(&mut self, val: StrykeValue) {
1499 self.declare_topic_slot(0, 0, val);
1500 }
1501
1502 #[inline]
1514 pub fn set_closure_args(&mut self, args: &[StrykeValue]) {
1515 let n = args.len();
1516 if n == 0 {
1517 return;
1518 }
1519 let high = n.saturating_sub(1).max(self.max_active_slot);
1520 for slot in 0..=high {
1521 let val = args.get(slot).cloned().unwrap_or(StrykeValue::UNDEF);
1522 self.shift_slot_chain(slot, val);
1523 }
1524 if n > 0 && n - 1 > self.max_active_slot {
1525 self.max_active_slot = n - 1;
1526 }
1527 }
1528
1529 #[inline]
1538 fn shift_slot_chain(&mut self, slot: usize, val: StrykeValue) {
1539 let l4 = self.get_scalar(&Self::topic_slot_key(slot, 4));
1540 let l3 = self.get_scalar(&Self::topic_slot_key(slot, 3));
1541 let l2 = self.get_scalar(&Self::topic_slot_key(slot, 2));
1542 let l1 = self.get_scalar(&Self::topic_slot_key(slot, 1));
1543 let cur = self.get_scalar(&Self::topic_slot_key(slot, 0));
1544
1545 self.declare_topic_slot(slot, 0, val);
1546 self.declare_topic_slot(slot, 1, cur);
1547 self.declare_topic_slot(slot, 2, l1);
1548 self.declare_topic_slot(slot, 3, l2);
1549 self.declare_topic_slot(slot, 4, l3);
1550 self.declare_topic_slot(slot, 5, l4);
1551 }
1552
1553 #[inline]
1561 pub fn set_sort_pair(&mut self, a: StrykeValue, b: StrykeValue) {
1562 let _ = self.set_scalar("a", a.clone());
1563 let _ = self.set_scalar("b", b.clone());
1564 self.declare_topic_slot(0, 0, a);
1576 self.declare_topic_slot(1, 0, b);
1577 }
1578
1579 #[inline]
1583 pub fn save_topic_chain(&self) -> [StrykeValue; 6] {
1584 [
1585 self.get_scalar(&Self::topic_slot_key(0, 0)),
1586 self.get_scalar(&Self::topic_slot_key(0, 1)),
1587 self.get_scalar(&Self::topic_slot_key(0, 2)),
1588 self.get_scalar(&Self::topic_slot_key(0, 3)),
1589 self.get_scalar(&Self::topic_slot_key(0, 4)),
1590 self.get_scalar(&Self::topic_slot_key(0, 5)),
1591 ]
1592 }
1593
1594 #[inline]
1596 pub fn restore_topic_chain(&mut self, saved: [StrykeValue; 6]) {
1597 for (level, val) in saved.into_iter().enumerate() {
1598 self.declare_topic_slot(0, level, val);
1599 }
1600 }
1601
1602 #[inline]
1604 pub fn push_defer(&mut self, coderef: StrykeValue) {
1605 if let Some(frame) = self.frames.last_mut() {
1606 frame.defers.push(coderef);
1607 }
1608 }
1609
1610 #[inline]
1613 pub fn take_defers(&mut self) -> Vec<StrykeValue> {
1614 if let Some(frame) = self.frames.last_mut() {
1615 let mut defers = std::mem::take(&mut frame.defers);
1616 defers.reverse();
1617 defers
1618 } else {
1619 Vec::new()
1620 }
1621 }
1622
1623 pub fn declare_atomic_array(&mut self, name: &str, val: Vec<StrykeValue>) {
1626 canon_main!(name);
1627 if let Some(frame) = self.frames.last_mut() {
1628 frame
1629 .atomic_arrays
1630 .push((name.to_string(), AtomicArray(Arc::new(Mutex::new(val)))));
1631 }
1632 }
1633 pub fn declare_atomic_hash(&mut self, name: &str, val: IndexMap<String, StrykeValue>) {
1635 canon_main!(name);
1636 if let Some(frame) = self.frames.last_mut() {
1637 frame
1638 .atomic_hashes
1639 .push((name.to_string(), AtomicHash(Arc::new(Mutex::new(val)))));
1640 }
1641 }
1642
1643 fn find_atomic_array(&self, name: &str) -> Option<&AtomicArray> {
1645 let name = strip_main_prefix(name).unwrap_or(name);
1646 for frame in self.frames.iter().rev() {
1647 if let Some(aa) = frame.atomic_arrays.iter().find(|(k, _)| k == name) {
1648 return Some(&aa.1);
1649 }
1650 }
1651 None
1652 }
1653
1654 fn find_atomic_hash(&self, name: &str) -> Option<&AtomicHash> {
1656 let name = strip_main_prefix(name).unwrap_or(name);
1657 for frame in self.frames.iter().rev() {
1658 if let Some(ah) = frame.atomic_hashes.iter().find(|(k, _)| k == name) {
1659 return Some(&ah.1);
1660 }
1661 }
1662 None
1663 }
1664
1665 #[inline]
1670 pub fn take_sub_underscore(&mut self) -> Option<Vec<StrykeValue>> {
1671 self.frames.last_mut()?.sub_underscore.take()
1672 }
1673 pub fn declare_array(&mut self, name: &str, val: Vec<StrykeValue>) {
1675 self.declare_array_frozen(name, val, false);
1676 }
1677 pub fn declare_array_frozen(&mut self, name: &str, val: Vec<StrykeValue>, frozen: bool) {
1679 let is_package_qualified = name.contains("::");
1687 canon_main!(name);
1688 let idx = if is_package_qualified {
1692 0
1693 } else {
1694 self.frames.len().saturating_sub(1)
1695 };
1696 if let Some(frame) = self.frames.get_mut(idx) {
1697 frame.shared_arrays.retain(|(k, _)| k != name);
1699 frame.set_array(name, val);
1700 if frozen {
1701 frame.frozen_arrays.insert(name.to_string());
1702 } else {
1703 frame.frozen_arrays.remove(name);
1705 }
1706 }
1707 }
1708 pub fn get_array(&self, name: &str) -> Vec<StrykeValue> {
1710 if let Some(rest) = strip_main_prefix(name) {
1716 return self.get_array(rest);
1717 }
1718 if let Some(aa) = self.find_atomic_array(name) {
1720 return aa.0.lock().clone();
1721 }
1722 if let Some(arc) = self.find_shared_array(name) {
1724 return arc.read().clone();
1725 }
1726 if name.contains("::") {
1727 if let Some(f) = self.frames.first() {
1728 if let Some(val) = f.get_array(name) {
1729 return val.clone();
1730 }
1731 }
1732 return Vec::new();
1733 }
1734 for frame in self.frames.iter().rev() {
1735 if let Some(val) = frame.get_array(name) {
1736 return val.clone();
1737 }
1738 }
1739 Vec::new()
1740 }
1741
1742 #[inline]
1745 pub fn get_array_borrow(&self, name: &str) -> Option<&[StrykeValue]> {
1746 if let Some(rest) = strip_main_prefix(name) {
1747 return self.get_array_borrow(rest);
1748 }
1749 if self.find_atomic_array(name).is_some() {
1750 return None;
1751 }
1752 if name.contains("::") {
1753 return self
1754 .frames
1755 .first()
1756 .and_then(|f| f.get_array(name))
1757 .map(|v| v.as_slice());
1758 }
1759 for frame in self.frames.iter().rev() {
1760 if let Some(val) = frame.get_array(name) {
1761 return Some(val.as_slice());
1762 }
1763 }
1764 None
1765 }
1766
1767 fn resolve_array_frame_idx(&self, name: &str) -> Option<usize> {
1768 if name.contains("::") {
1769 return Some(0);
1770 }
1771 (0..self.frames.len())
1772 .rev()
1773 .find(|&i| self.frames[i].has_array(name))
1774 }
1775
1776 fn check_parallel_array_write(&self, name: &str) -> Result<(), StrykeError> {
1777 if !self.parallel_guard
1778 || Self::parallel_skip_special_name(name)
1779 || Self::parallel_allowed_internal_array(name)
1780 {
1781 return Ok(());
1782 }
1783 let baseline = self.parallel_guard_baseline;
1785 match self.resolve_array_frame_idx(name) {
1786 None => Err(StrykeError::runtime(
1787 format!(
1788 "cannot modify undeclared array `@{}` in a parallel block",
1789 name
1790 ),
1791 0,
1792 )),
1793 Some(idx) if idx < baseline => Err(StrykeError::runtime(
1794 format!(
1795 "cannot modify captured non-mysync array `@{}` in a parallel block",
1796 name
1797 ),
1798 0,
1799 )),
1800 Some(_) => Ok(()),
1801 }
1802 }
1803
1804 #[inline]
1809 pub fn resolve_container_binding_ref(&self, val: StrykeValue) -> StrykeValue {
1810 if let Some(name) = val.as_array_binding_name() {
1811 let data = self.get_array(&name);
1812 return StrykeValue::array_ref(Arc::new(parking_lot::RwLock::new(data)));
1813 }
1814 if let Some(name) = val.as_hash_binding_name() {
1815 let data = self.get_hash(&name);
1816 return StrykeValue::hash_ref(Arc::new(parking_lot::RwLock::new(data)));
1817 }
1818 val
1819 }
1820
1821 pub fn promote_array_to_shared(
1825 &mut self,
1826 name: &str,
1827 ) -> Arc<parking_lot::RwLock<Vec<StrykeValue>>> {
1828 let name = strip_main_prefix(name).unwrap_or(name);
1836
1837 if let Some(aa) = self.find_atomic_array(name) {
1840 let data = aa.0.lock().clone();
1841 return Arc::new(parking_lot::RwLock::new(data));
1842 }
1843 let idx = self.resolve_array_frame_idx(name).unwrap_or_default();
1845 let frame = &mut self.frames[idx];
1846 if let Some(entry) = frame.shared_arrays.iter().find(|(k, _)| k == name) {
1847 return Arc::clone(&entry.1);
1848 }
1849 let data = if let Some(pos) = frame.arrays.iter().position(|(k, _)| k == name) {
1851 frame.arrays.swap_remove(pos).1
1852 } else if name == "_" {
1853 frame.sub_underscore.take().unwrap_or_default()
1854 } else {
1855 Vec::new()
1856 };
1857 let arc = Arc::new(parking_lot::RwLock::new(data));
1858 frame
1859 .shared_arrays
1860 .push((name.to_string(), Arc::clone(&arc)));
1861 arc
1862 }
1863
1864 pub fn promote_hash_to_shared(
1867 &mut self,
1868 name: &str,
1869 ) -> Arc<parking_lot::RwLock<IndexMap<String, StrykeValue>>> {
1870 let name = strip_main_prefix(name).unwrap_or(name);
1880
1881 let idx = self.resolve_hash_frame_idx(name).unwrap_or_default();
1882 let frame = &mut self.frames[idx];
1883 if let Some(entry) = frame.shared_hashes.iter().find(|(k, _)| k == name) {
1884 return Arc::clone(&entry.1);
1885 }
1886 let data = if let Some(pos) = frame.hashes.iter().position(|(k, _)| k == name) {
1887 frame.hashes.swap_remove(pos).1
1888 } else {
1889 IndexMap::new()
1890 };
1891 let arc = Arc::new(parking_lot::RwLock::new(data));
1892 frame
1893 .shared_hashes
1894 .push((name.to_string(), Arc::clone(&arc)));
1895 arc
1896 }
1897
1898 fn find_shared_array(&self, name: &str) -> Option<Arc<parking_lot::RwLock<Vec<StrykeValue>>>> {
1900 let name = strip_main_prefix(name).unwrap_or(name);
1901 for frame in self.frames.iter().rev() {
1902 if let Some(entry) = frame.shared_arrays.iter().find(|(k, _)| k == name) {
1903 return Some(Arc::clone(&entry.1));
1904 }
1905 if frame.arrays.iter().any(|(k, _)| k == name) {
1907 return None;
1908 }
1909 }
1910 None
1911 }
1912
1913 fn find_shared_hash(
1915 &self,
1916 name: &str,
1917 ) -> Option<Arc<parking_lot::RwLock<IndexMap<String, StrykeValue>>>> {
1918 let name = strip_main_prefix(name).unwrap_or(name);
1919 for frame in self.frames.iter().rev() {
1920 if let Some(entry) = frame.shared_hashes.iter().find(|(k, _)| k == name) {
1921 return Some(Arc::clone(&entry.1));
1922 }
1923 if frame.hashes.iter().any(|(k, _)| k == name) {
1924 return None;
1925 }
1926 }
1927 None
1928 }
1929 pub fn get_array_mut(&mut self, name: &str) -> Result<&mut Vec<StrykeValue>, StrykeError> {
1931 if self.find_atomic_array(name).is_some() {
1934 return Err(StrykeError::runtime(
1935 "get_array_mut: use atomic path for mysync arrays",
1936 0,
1937 ));
1938 }
1939 self.check_parallel_array_write(name)?;
1940 let idx = self.resolve_array_frame_idx(name).unwrap_or_default();
1941 let frame = &mut self.frames[idx];
1942 if frame.get_array_mut(name).is_none() {
1943 frame.arrays.push((name.to_string(), Vec::new()));
1944 }
1945 Ok(frame.get_array_mut(name).unwrap())
1946 }
1947
1948 pub fn push_to_array(&mut self, name: &str, val: StrykeValue) -> Result<(), StrykeError> {
1950 let val = self.resolve_container_binding_ref(val);
1951 if let Some(aa) = self.find_atomic_array(name) {
1952 aa.0.lock().push(val);
1953 return Ok(());
1954 }
1955 if let Some(arc) = self.find_shared_array(name) {
1956 arc.write().push(val);
1957 return Ok(());
1958 }
1959 self.get_array_mut(name)?.push(val);
1960 Ok(())
1961 }
1962
1963 pub fn push_int_range_to_array(
1967 &mut self,
1968 name: &str,
1969 start: i64,
1970 end: i64,
1971 ) -> Result<(), StrykeError> {
1972 if end <= start {
1973 return Ok(());
1974 }
1975 let count = (end - start) as usize;
1976 if let Some(aa) = self.find_atomic_array(name) {
1977 let mut g = aa.0.lock();
1978 g.reserve(count);
1979 for i in start..end {
1980 g.push(StrykeValue::integer(i));
1981 }
1982 return Ok(());
1983 }
1984 let arr = self.get_array_mut(name)?;
1985 arr.reserve(count);
1986 for i in start..end {
1987 arr.push(StrykeValue::integer(i));
1988 }
1989 Ok(())
1990 }
1991
1992 pub fn pop_from_array(&mut self, name: &str) -> Result<StrykeValue, StrykeError> {
1994 if let Some(aa) = self.find_atomic_array(name) {
1995 return Ok(aa.0.lock().pop().unwrap_or(StrykeValue::UNDEF));
1996 }
1997 if let Some(arc) = self.find_shared_array(name) {
1998 return Ok(arc.write().pop().unwrap_or(StrykeValue::UNDEF));
1999 }
2000 Ok(self
2001 .get_array_mut(name)?
2002 .pop()
2003 .unwrap_or(StrykeValue::UNDEF))
2004 }
2005
2006 pub fn shift_from_array(&mut self, name: &str) -> Result<StrykeValue, StrykeError> {
2008 if let Some(aa) = self.find_atomic_array(name) {
2009 let mut guard = aa.0.lock();
2010 return Ok(if guard.is_empty() {
2011 StrykeValue::UNDEF
2012 } else {
2013 guard.remove(0)
2014 });
2015 }
2016 if let Some(arc) = self.find_shared_array(name) {
2017 let mut arr = arc.write();
2018 return Ok(if arr.is_empty() {
2019 StrykeValue::UNDEF
2020 } else {
2021 arr.remove(0)
2022 });
2023 }
2024 let arr = self.get_array_mut(name)?;
2025 Ok(if arr.is_empty() {
2026 StrykeValue::UNDEF
2027 } else {
2028 arr.remove(0)
2029 })
2030 }
2031
2032 pub fn splice_in_place(
2036 &mut self,
2037 name: &str,
2038 off: usize,
2039 end: usize,
2040 rep_vals: Vec<StrykeValue>,
2041 ) -> Result<Vec<StrykeValue>, StrykeError> {
2042 if let Some(aa) = self.find_atomic_array(name) {
2043 let mut g = aa.0.lock();
2044 let removed: Vec<StrykeValue> = g.drain(off..end).collect();
2045 for (i, v) in rep_vals.into_iter().enumerate() {
2046 g.insert(off + i, v);
2047 }
2048 return Ok(removed);
2049 }
2050 if let Some(arc) = self.find_shared_array(name) {
2051 let mut g = arc.write();
2052 let removed: Vec<StrykeValue> = g.drain(off..end).collect();
2053 for (i, v) in rep_vals.into_iter().enumerate() {
2054 g.insert(off + i, v);
2055 }
2056 return Ok(removed);
2057 }
2058 let arr = self.get_array_mut(name)?;
2059 let removed: Vec<StrykeValue> = arr.drain(off..end).collect();
2060 for (i, v) in rep_vals.into_iter().enumerate() {
2061 arr.insert(off + i, v);
2062 }
2063 Ok(removed)
2064 }
2065
2066 pub fn array_len(&self, name: &str) -> usize {
2068 canon_main!(name);
2069 if let Some(aa) = self.find_atomic_array(name) {
2070 return aa.0.lock().len();
2071 }
2072 if let Some(arc) = self.find_shared_array(name) {
2073 return arc.read().len();
2074 }
2075 if name.contains("::") {
2076 return self
2077 .frames
2078 .first()
2079 .and_then(|f| f.get_array(name))
2080 .map(|a| a.len())
2081 .unwrap_or(0);
2082 }
2083 for frame in self.frames.iter().rev() {
2084 if let Some(arr) = frame.get_array(name) {
2085 return arr.len();
2086 }
2087 }
2088 0
2089 }
2090 pub fn set_array(&mut self, name: &str, val: Vec<StrykeValue>) -> Result<(), StrykeError> {
2092 if let Some(aa) = self.find_atomic_array(name) {
2093 *aa.0.lock() = val;
2094 return Ok(());
2095 }
2096 if let Some(arc) = self.find_shared_array(name) {
2097 *arc.write() = val;
2098 return Ok(());
2099 }
2100 self.check_parallel_array_write(name)?;
2101 for frame in self.frames.iter_mut().rev() {
2102 if frame.has_array(name) {
2103 frame.set_array(name, val);
2104 return Ok(());
2105 }
2106 }
2107 self.frames[0].set_array(name, val);
2108 Ok(())
2109 }
2110
2111 #[inline]
2113 pub fn get_array_element(&self, name: &str, index: i64) -> StrykeValue {
2114 canon_main!(name);
2115 if let Some(aa) = self.find_atomic_array(name) {
2116 let arr = aa.0.lock();
2117 let idx = if index < 0 {
2118 (arr.len() as i64 + index) as usize
2119 } else {
2120 index as usize
2121 };
2122 return arr.get(idx).cloned().unwrap_or(StrykeValue::UNDEF);
2123 }
2124 if let Some(arc) = self.find_shared_array(name) {
2125 let arr = arc.read();
2126 let idx = if index < 0 {
2127 (arr.len() as i64 + index) as usize
2128 } else {
2129 index as usize
2130 };
2131 return arr.get(idx).cloned().unwrap_or(StrykeValue::UNDEF);
2132 }
2133 for frame in self.frames.iter().rev() {
2134 if let Some(arr) = frame.get_array(name) {
2135 let idx = if index < 0 {
2136 (arr.len() as i64 + index) as usize
2137 } else {
2138 index as usize
2139 };
2140 return arr.get(idx).cloned().unwrap_or(StrykeValue::UNDEF);
2141 }
2142 }
2143 StrykeValue::UNDEF
2144 }
2145 pub fn set_array_element(
2147 &mut self,
2148 name: &str,
2149 index: i64,
2150 val: StrykeValue,
2151 ) -> Result<(), StrykeError> {
2152 let val = self.resolve_container_binding_ref(val);
2153 if let Some(aa) = self.find_atomic_array(name) {
2154 let mut arr = aa.0.lock();
2155 let idx = if index < 0 {
2156 (arr.len() as i64 + index).max(0) as usize
2157 } else {
2158 index as usize
2159 };
2160 if idx >= arr.len() {
2161 arr.resize(idx + 1, StrykeValue::UNDEF);
2162 }
2163 arr[idx] = val;
2164 return Ok(());
2165 }
2166 if let Some(arc) = self.find_shared_array(name) {
2167 let mut arr = arc.write();
2168 let idx = if index < 0 {
2169 (arr.len() as i64 + index).max(0) as usize
2170 } else {
2171 index as usize
2172 };
2173 if idx >= arr.len() {
2174 arr.resize(idx + 1, StrykeValue::UNDEF);
2175 }
2176 arr[idx] = val;
2177 return Ok(());
2178 }
2179 let arr = self.get_array_mut(name)?;
2180 let idx = if index < 0 {
2181 let len = arr.len() as i64;
2182 (len + index).max(0) as usize
2183 } else {
2184 index as usize
2185 };
2186 if idx >= arr.len() {
2187 arr.resize(idx + 1, StrykeValue::UNDEF);
2188 }
2189 arr[idx] = val;
2190 Ok(())
2191 }
2192
2193 pub fn exists_array_element(&self, name: &str, index: i64) -> bool {
2195 canon_main!(name);
2196 if let Some(aa) = self.find_atomic_array(name) {
2197 let arr = aa.0.lock();
2198 let idx = if index < 0 {
2199 (arr.len() as i64 + index) as usize
2200 } else {
2201 index as usize
2202 };
2203 return idx < arr.len();
2204 }
2205 for frame in self.frames.iter().rev() {
2206 if let Some(arr) = frame.get_array(name) {
2207 let idx = if index < 0 {
2208 (arr.len() as i64 + index) as usize
2209 } else {
2210 index as usize
2211 };
2212 return idx < arr.len();
2213 }
2214 }
2215 false
2216 }
2217
2218 pub fn delete_array_element(
2220 &mut self,
2221 name: &str,
2222 index: i64,
2223 ) -> Result<StrykeValue, StrykeError> {
2224 if let Some(aa) = self.find_atomic_array(name) {
2225 let mut arr = aa.0.lock();
2226 let idx = if index < 0 {
2227 (arr.len() as i64 + index) as usize
2228 } else {
2229 index as usize
2230 };
2231 if idx >= arr.len() {
2232 return Ok(StrykeValue::UNDEF);
2233 }
2234 let old = arr.get(idx).cloned().unwrap_or(StrykeValue::UNDEF);
2235 arr[idx] = StrykeValue::UNDEF;
2236 return Ok(old);
2237 }
2238 let arr = self.get_array_mut(name)?;
2239 let idx = if index < 0 {
2240 (arr.len() as i64 + index) as usize
2241 } else {
2242 index as usize
2243 };
2244 if idx >= arr.len() {
2245 return Ok(StrykeValue::UNDEF);
2246 }
2247 let old = arr.get(idx).cloned().unwrap_or(StrykeValue::UNDEF);
2248 arr[idx] = StrykeValue::UNDEF;
2249 Ok(old)
2250 }
2251
2252 #[inline]
2255 pub fn declare_hash(&mut self, name: &str, val: IndexMap<String, StrykeValue>) {
2256 self.declare_hash_frozen(name, val, false);
2257 }
2258 pub fn declare_hash_frozen(
2260 &mut self,
2261 name: &str,
2262 val: IndexMap<String, StrykeValue>,
2263 frozen: bool,
2264 ) {
2265 let is_package_qualified = name.contains("::");
2266 canon_main!(name);
2267 let frame_opt = if is_package_qualified {
2268 self.frames.first_mut()
2269 } else {
2270 self.frames.last_mut()
2271 };
2272 if let Some(frame) = frame_opt {
2273 frame.shared_hashes.retain(|(k, _)| k != name);
2275 frame.set_hash(name, val);
2276 if frozen {
2277 frame.frozen_hashes.insert(name.to_string());
2278 }
2279 }
2280 }
2281
2282 pub fn declare_hash_global(&mut self, name: &str, val: IndexMap<String, StrykeValue>) {
2284 canon_main!(name);
2285 if let Some(frame) = self.frames.first_mut() {
2286 frame.set_hash(name, val);
2287 }
2288 }
2289
2290 pub fn declare_hash_global_frozen(&mut self, name: &str, val: IndexMap<String, StrykeValue>) {
2292 canon_main!(name);
2293 if let Some(frame) = self.frames.first_mut() {
2294 frame.set_hash(name, val);
2295 frame.frozen_hashes.insert(name.to_string());
2296 }
2297 }
2298
2299 pub fn has_lexical_hash(&self, name: &str) -> bool {
2301 canon_main!(name);
2302 self.frames.iter().skip(1).any(|f| f.has_hash(name))
2303 }
2304
2305 pub fn any_frame_has_hash(&self, name: &str) -> bool {
2307 canon_main!(name);
2308 self.frames.iter().any(|f| f.has_hash(name))
2309 }
2310 pub fn get_hash(&self, name: &str) -> IndexMap<String, StrykeValue> {
2312 if let Some(rest) = strip_main_prefix(name) {
2314 return self.get_hash(rest);
2315 }
2316 if let Some(ah) = self.find_atomic_hash(name) {
2317 return ah.0.lock().clone();
2318 }
2319 if let Some(arc) = self.find_shared_hash(name) {
2320 return arc.read().clone();
2321 }
2322 for frame in self.frames.iter().rev() {
2323 if let Some(val) = frame.get_hash(name) {
2324 return val.clone();
2325 }
2326 }
2327 IndexMap::new()
2328 }
2329
2330 fn resolve_hash_frame_idx(&self, name: &str) -> Option<usize> {
2331 if name.contains("::") {
2332 return Some(0);
2333 }
2334 (0..self.frames.len())
2335 .rev()
2336 .find(|&i| self.frames[i].has_hash(name))
2337 }
2338
2339 fn check_parallel_hash_write(&self, name: &str) -> Result<(), StrykeError> {
2340 if !self.parallel_guard
2341 || Self::parallel_skip_special_name(name)
2342 || Self::parallel_allowed_internal_hash(name)
2343 {
2344 return Ok(());
2345 }
2346 let baseline = self.parallel_guard_baseline;
2348 match self.resolve_hash_frame_idx(name) {
2349 None => Err(StrykeError::runtime(
2350 format!(
2351 "cannot modify undeclared hash `%{}` in a parallel block",
2352 name
2353 ),
2354 0,
2355 )),
2356 Some(idx) if idx < baseline => Err(StrykeError::runtime(
2357 format!(
2358 "cannot modify captured non-mysync hash `%{}` in a parallel block",
2359 name
2360 ),
2361 0,
2362 )),
2363 Some(_) => Ok(()),
2364 }
2365 }
2366 pub fn get_hash_mut(
2368 &mut self,
2369 name: &str,
2370 ) -> Result<&mut IndexMap<String, StrykeValue>, StrykeError> {
2371 if self.find_atomic_hash(name).is_some() {
2372 return Err(StrykeError::runtime(
2373 "get_hash_mut: use atomic path for mysync hashes",
2374 0,
2375 ));
2376 }
2377 self.check_parallel_hash_write(name)?;
2378 let idx = self.resolve_hash_frame_idx(name).unwrap_or_default();
2379 let frame = &mut self.frames[idx];
2380 if frame.get_hash_mut(name).is_none() {
2381 frame.hashes.push((name.to_string(), IndexMap::new()));
2382 }
2383 Ok(frame.get_hash_mut(name).unwrap())
2384 }
2385 pub fn set_hash(
2387 &mut self,
2388 name: &str,
2389 val: IndexMap<String, StrykeValue>,
2390 ) -> Result<(), StrykeError> {
2391 if let Some(ah) = self.find_atomic_hash(name) {
2392 *ah.0.lock() = val;
2393 return Ok(());
2394 }
2395 self.check_parallel_hash_write(name)?;
2396 for frame in self.frames.iter_mut().rev() {
2397 if frame.has_hash(name) {
2398 frame.set_hash(name, val);
2399 return Ok(());
2400 }
2401 }
2402 self.frames[0].set_hash(name, val);
2403 Ok(())
2404 }
2405 #[inline]
2407 pub fn get_hash_element(&self, name: &str, key: &str) -> StrykeValue {
2408 canon_main!(name);
2409 if let Some(ah) = self.find_atomic_hash(name) {
2410 return ah.0.lock().get(key).cloned().unwrap_or(StrykeValue::UNDEF);
2411 }
2412 if let Some(arc) = self.find_shared_hash(name) {
2413 return arc.read().get(key).cloned().unwrap_or(StrykeValue::UNDEF);
2414 }
2415 for frame in self.frames.iter().rev() {
2416 if let Some(hash) = frame.get_hash(name) {
2417 return hash.get(key).cloned().unwrap_or(StrykeValue::UNDEF);
2418 }
2419 }
2420 StrykeValue::UNDEF
2421 }
2422
2423 pub fn atomic_hash_mutate(
2426 &mut self,
2427 name: &str,
2428 key: &str,
2429 f: impl FnOnce(&StrykeValue) -> StrykeValue,
2430 ) -> Result<StrykeValue, StrykeError> {
2431 if let Some(ah) = self.find_atomic_hash(name) {
2432 let mut guard = ah.0.lock();
2433 let old = guard.get(key).cloned().unwrap_or(StrykeValue::UNDEF);
2434 let new_val = f(&old);
2435 guard.insert(key.to_string(), new_val.clone());
2436 return Ok(new_val);
2437 }
2438 let old = self.get_hash_element(name, key);
2440 let new_val = f(&old);
2441 self.set_hash_element(name, key, new_val.clone())?;
2442 Ok(new_val)
2443 }
2444
2445 pub fn atomic_array_mutate(
2447 &mut self,
2448 name: &str,
2449 index: i64,
2450 f: impl FnOnce(&StrykeValue) -> StrykeValue,
2451 ) -> Result<StrykeValue, StrykeError> {
2452 if let Some(aa) = self.find_atomic_array(name) {
2453 let mut guard = aa.0.lock();
2454 let idx = if index < 0 {
2455 (guard.len() as i64 + index).max(0) as usize
2456 } else {
2457 index as usize
2458 };
2459 if idx >= guard.len() {
2460 guard.resize(idx + 1, StrykeValue::UNDEF);
2461 }
2462 let old = guard[idx].clone();
2463 let new_val = f(&old);
2464 guard[idx] = new_val.clone();
2465 return Ok(new_val);
2466 }
2467 let old = self.get_array_element(name, index);
2469 let new_val = f(&old);
2470 self.set_array_element(name, index, new_val.clone())?;
2471 Ok(new_val)
2472 }
2473 pub fn set_hash_element(
2475 &mut self,
2476 name: &str,
2477 key: &str,
2478 val: StrykeValue,
2479 ) -> Result<(), StrykeError> {
2480 let val = self.resolve_container_binding_ref(val);
2481 if name == "SIG" {
2484 crate::perl_signal::install(key);
2485 }
2486 if name == "ENV" {
2491 std::env::set_var(key, val.to_string());
2495 }
2496 if let Some(ah) = self.find_atomic_hash(name) {
2497 ah.0.lock().insert(key.to_string(), val);
2498 return Ok(());
2499 }
2500 if let Some(arc) = self.find_shared_hash(name) {
2501 arc.write().insert(key.to_string(), val);
2502 return Ok(());
2503 }
2504 let hash = self.get_hash_mut(name)?;
2505 hash.insert(key.to_string(), val);
2506 Ok(())
2507 }
2508
2509 pub fn set_hash_int_times_range(
2513 &mut self,
2514 name: &str,
2515 start: i64,
2516 end: i64,
2517 k: i64,
2518 ) -> Result<(), StrykeError> {
2519 if end <= start {
2520 return Ok(());
2521 }
2522 let count = (end - start) as usize;
2523 if let Some(ah) = self.find_atomic_hash(name) {
2524 let mut g = ah.0.lock();
2525 g.reserve(count);
2526 let mut buf = itoa::Buffer::new();
2527 for i in start..end {
2528 let key = buf.format(i).to_owned();
2529 g.insert(key, StrykeValue::integer(i.wrapping_mul(k)));
2530 }
2531 return Ok(());
2532 }
2533 let hash = self.get_hash_mut(name)?;
2534 hash.reserve(count);
2535 let mut buf = itoa::Buffer::new();
2536 for i in start..end {
2537 let key = buf.format(i).to_owned();
2538 hash.insert(key, StrykeValue::integer(i.wrapping_mul(k)));
2539 }
2540 Ok(())
2541 }
2542 pub fn delete_hash_element(
2544 &mut self,
2545 name: &str,
2546 key: &str,
2547 ) -> Result<StrykeValue, StrykeError> {
2548 canon_main!(name);
2549 if name == "ENV" {
2551 std::env::remove_var(key);
2552 }
2553 if let Some(ah) = self.find_atomic_hash(name) {
2554 return Ok(ah.0.lock().shift_remove(key).unwrap_or(StrykeValue::UNDEF));
2555 }
2556 let hash = self.get_hash_mut(name)?;
2557 Ok(hash.shift_remove(key).unwrap_or(StrykeValue::UNDEF))
2558 }
2559 #[inline]
2561 pub fn exists_hash_element(&self, name: &str, key: &str) -> bool {
2562 canon_main!(name);
2563 if let Some(ah) = self.find_atomic_hash(name) {
2564 return ah.0.lock().contains_key(key);
2565 }
2566 for frame in self.frames.iter().rev() {
2567 if let Some(hash) = frame.get_hash(name) {
2568 return hash.contains_key(key);
2569 }
2570 }
2571 false
2572 }
2573
2574 #[inline]
2579 pub fn for_each_hash_value(&self, name: &str, mut visit: impl FnMut(&StrykeValue)) {
2580 canon_main!(name);
2581 if let Some(ah) = self.find_atomic_hash(name) {
2582 let g = ah.0.lock();
2583 for v in g.values() {
2584 visit(v);
2585 }
2586 return;
2587 }
2588 for frame in self.frames.iter().rev() {
2589 if let Some(hash) = frame.get_hash(name) {
2590 for v in hash.values() {
2591 visit(v);
2592 }
2593 return;
2594 }
2595 }
2596 }
2597
2598 pub fn frames_for_introspection(&self) -> Vec<(Vec<&str>, Vec<&str>, Vec<&str>)> {
2604 self.frames
2605 .iter()
2606 .map(|f| {
2607 let mut scalars: Vec<&str> = f.scalars.iter().map(|(n, _)| n.as_str()).collect();
2608 scalars.extend(f.scalar_slot_names.iter().filter_map(|opt| match opt {
2610 Some(n) if !n.is_empty() => Some(n.as_str()),
2611 _ => None,
2612 }));
2613 let mut arrays: Vec<&str> = f.arrays.iter().map(|(n, _)| n.as_str()).collect();
2614 arrays.extend(f.atomic_arrays.iter().map(|(n, _)| n.as_str()));
2615 arrays.extend(f.shared_arrays.iter().map(|(n, _)| n.as_str()));
2616 let mut hashes: Vec<&str> = f.hashes.iter().map(|(n, _)| n.as_str()).collect();
2617 hashes.extend(f.atomic_hashes.iter().map(|(n, _)| n.as_str()));
2618 hashes.extend(f.shared_hashes.iter().map(|(n, _)| n.as_str()));
2619 scalars.sort_unstable();
2620 arrays.sort_unstable();
2621 hashes.sort_unstable();
2622 (scalars, arrays, hashes)
2623 })
2624 .collect()
2625 }
2626
2627 pub fn parameters_pairs(&self) -> Vec<(String, &'static str)> {
2633 let mut seen: HashSet<String> = HashSet::new();
2634 let mut out: Vec<(String, &'static str)> = Vec::new();
2635 for frame in self.frames.iter().rev() {
2638 for n in frame.scalar_slot_names.iter().flatten() {
2642 if !n.is_empty() {
2643 let s = format!("${}", n);
2644 if seen.insert(s.clone()) {
2645 out.push((s, "scalar"));
2646 }
2647 }
2648 }
2649 for (name, _) in &frame.scalars {
2650 let s = format!("${}", name);
2651 if seen.insert(s.clone()) {
2652 out.push((s, "scalar"));
2653 }
2654 }
2655 for (name, _) in &frame.arrays {
2656 let s = format!("@{}", name);
2657 if seen.insert(s.clone()) {
2658 out.push((s, "array"));
2659 }
2660 }
2661 for (name, _) in &frame.hashes {
2662 let s = format!("%{}", name);
2663 if seen.insert(s.clone()) {
2664 out.push((s, "hash"));
2665 }
2666 }
2667 for (name, _) in &frame.atomic_arrays {
2668 let s = format!("@{}", name);
2669 if seen.insert(s.clone()) {
2670 out.push((s, "atomic_array"));
2671 }
2672 }
2673 for (name, _) in &frame.atomic_hashes {
2674 let s = format!("%{}", name);
2675 if seen.insert(s.clone()) {
2676 out.push((s, "atomic_hash"));
2677 }
2678 }
2679 for (name, _) in &frame.shared_arrays {
2680 let s = format!("@{}", name);
2681 if seen.insert(s.clone()) {
2682 out.push((s, "shared_array"));
2683 }
2684 }
2685 for (name, _) in &frame.shared_hashes {
2686 let s = format!("%{}", name);
2687 if seen.insert(s.clone()) {
2688 out.push((s, "shared_hash"));
2689 }
2690 }
2691 }
2692 out.sort_by(|a, b| a.0.cmp(&b.0));
2693 out
2694 }
2695
2696 pub fn repl_binding_names(&self) -> Vec<String> {
2698 let mut seen = HashSet::new();
2699 let mut out = Vec::new();
2700 for frame in &self.frames {
2701 for (name, _) in &frame.scalars {
2702 let s = format!("${}", name);
2703 if seen.insert(s.clone()) {
2704 out.push(s);
2705 }
2706 }
2707 for (name, _) in &frame.arrays {
2708 let s = format!("@{}", name);
2709 if seen.insert(s.clone()) {
2710 out.push(s);
2711 }
2712 }
2713 for (name, _) in &frame.hashes {
2714 let s = format!("%{}", name);
2715 if seen.insert(s.clone()) {
2716 out.push(s);
2717 }
2718 }
2719 for (name, _) in &frame.atomic_arrays {
2720 let s = format!("@{}", name);
2721 if seen.insert(s.clone()) {
2722 out.push(s);
2723 }
2724 }
2725 for (name, _) in &frame.atomic_hashes {
2726 let s = format!("%{}", name);
2727 if seen.insert(s.clone()) {
2728 out.push(s);
2729 }
2730 }
2731 }
2732 out.sort();
2733 out
2734 }
2735 pub fn capture(&mut self) -> Vec<(String, StrykeValue)> {
2737 let by_ref = crate::compat_mode();
2750 let mut captured = Vec::new();
2751 let mut seen_hash_scalars: HashSet<String> = HashSet::new();
2766 for frame in self.frames.iter_mut().rev() {
2767 for (k, v) in &mut frame.scalars {
2768 if !seen_hash_scalars.insert(k.clone()) {
2769 continue;
2770 }
2771 if v.as_capture_cell().is_some() || v.as_scalar_ref().is_some() {
2772 captured.push((format!("${}", k), v.clone()));
2773 } else if v.is_simple_scalar() {
2774 let wrapped = StrykeValue::capture_cell(Arc::new(RwLock::new(v.clone())));
2775 *v = wrapped.clone();
2776 captured.push((format!("${}", k), wrapped));
2777 } else {
2778 captured.push((format!("${}", k), v.clone()));
2779 }
2780 }
2781 }
2782 for frame in &mut self.frames {
2783 for (i, v) in frame.scalar_slots.iter_mut().enumerate() {
2791 if let Some(Some(name)) = frame.scalar_slot_names.get(i) {
2792 if !name.is_empty() && seen_hash_scalars.contains(name) {
2802 continue;
2803 }
2804 let cap_val = if v.as_capture_cell().is_some() || v.as_scalar_ref().is_some() {
2805 v.clone()
2806 } else {
2807 let wrapped = StrykeValue::capture_cell(Arc::new(RwLock::new(v.clone())));
2808 if by_ref {
2809 *v = wrapped.clone();
2810 }
2811 wrapped
2812 };
2813 captured.push((format!("$slot:{}:{}", i, name), cap_val));
2814 }
2815 }
2816 for (k, v) in &frame.arrays {
2817 if capture_skip_bootstrap_array(k) {
2818 continue;
2819 }
2820 if frame.frozen_arrays.contains(k) {
2821 captured.push((format!("@frozen:{}", k), StrykeValue::array(v.clone())));
2822 } else {
2823 captured.push((format!("@{}", k), StrykeValue::array(v.clone())));
2824 }
2825 }
2826 for (k, v) in &frame.hashes {
2827 if capture_skip_bootstrap_hash(k) {
2828 continue;
2829 }
2830 if frame.frozen_hashes.contains(k) {
2831 captured.push((format!("%frozen:{}", k), StrykeValue::hash(v.clone())));
2832 } else {
2833 captured.push((format!("%{}", k), StrykeValue::hash(v.clone())));
2834 }
2835 }
2836 for (k, _aa) in &frame.atomic_arrays {
2837 captured.push((
2838 format!("@sync_{}", k),
2839 StrykeValue::atomic(Arc::new(Mutex::new(StrykeValue::string(String::new())))),
2840 ));
2841 }
2842 for (k, _ah) in &frame.atomic_hashes {
2843 captured.push((
2844 format!("%sync_{}", k),
2845 StrykeValue::atomic(Arc::new(Mutex::new(StrykeValue::string(String::new())))),
2846 ));
2847 }
2848 }
2849 captured
2850 }
2851
2852 pub fn capture_with_atomics(&self) -> ScopeCaptureWithAtomics {
2854 let mut scalars = Vec::new();
2855 let mut arrays = Vec::new();
2856 let mut hashes = Vec::new();
2857 for frame in &self.frames {
2858 for (k, v) in &frame.scalars {
2859 scalars.push((format!("${}", k), v.clone()));
2860 }
2861 for (i, v) in frame.scalar_slots.iter().enumerate() {
2862 if let Some(Some(name)) = frame.scalar_slot_names.get(i) {
2863 scalars.push((format!("$slot:{}:{}", i, name), v.clone()));
2864 }
2865 }
2866 for (k, v) in &frame.arrays {
2867 if capture_skip_bootstrap_array(k) {
2868 continue;
2869 }
2870 if frame.frozen_arrays.contains(k) {
2871 scalars.push((format!("@frozen:{}", k), StrykeValue::array(v.clone())));
2872 } else {
2873 scalars.push((format!("@{}", k), StrykeValue::array(v.clone())));
2874 }
2875 }
2876 for (k, v) in &frame.hashes {
2877 if capture_skip_bootstrap_hash(k) {
2878 continue;
2879 }
2880 if frame.frozen_hashes.contains(k) {
2881 scalars.push((format!("%frozen:{}", k), StrykeValue::hash(v.clone())));
2882 } else {
2883 scalars.push((format!("%{}", k), StrykeValue::hash(v.clone())));
2884 }
2885 }
2886 for (k, aa) in &frame.atomic_arrays {
2887 arrays.push((k.clone(), aa.clone()));
2888 }
2889 for (k, ah) in &frame.atomic_hashes {
2890 hashes.push((k.clone(), ah.clone()));
2891 }
2892 }
2893 (scalars, arrays, hashes)
2894 }
2895 pub fn restore_capture(&mut self, captured: &[(String, StrykeValue)]) {
2897 for (name, val) in captured {
2898 if let Some(rest) = name.strip_prefix("$slot:") {
2899 if let Some(colon) = rest.find(':') {
2905 let idx: usize = rest[..colon].parse().unwrap_or(0);
2906 let sname = &rest[colon + 1..];
2907 self.declare_scalar_slot(idx as u8, val.clone(), Some(sname));
2908 }
2909 } else if let Some(stripped) = name.strip_prefix('$') {
2910 self.declare_scalar(stripped, val.clone());
2911 if let Some(slot) = parse_positional_topic_slot(stripped) {
2918 if slot > self.max_active_slot {
2919 self.max_active_slot = slot;
2920 }
2921 }
2922 } else if let Some(rest) = name.strip_prefix("@frozen:") {
2923 let arr = val.as_array_vec().unwrap_or_else(|| val.to_list());
2924 self.declare_array_frozen(rest, arr, true);
2925 } else if let Some(rest) = name.strip_prefix("%frozen:") {
2926 if let Some(h) = val.as_hash_map() {
2927 self.declare_hash_frozen(rest, h.clone(), true);
2928 }
2929 } else if let Some(rest) = name.strip_prefix('@') {
2930 if rest.starts_with("sync_") {
2931 continue;
2932 }
2933 let arr = val.as_array_vec().unwrap_or_else(|| val.to_list());
2934 self.declare_array(rest, arr);
2935 } else if let Some(rest) = name.strip_prefix('%') {
2936 if rest.starts_with("sync_") {
2937 continue;
2938 }
2939 if let Some(h) = val.as_hash_map() {
2940 self.declare_hash(rest, h.clone());
2941 }
2942 }
2943 }
2944 }
2945
2946 pub fn restore_atomics(
2948 &mut self,
2949 arrays: &[(String, AtomicArray)],
2950 hashes: &[(String, AtomicHash)],
2951 ) {
2952 if let Some(frame) = self.frames.last_mut() {
2953 for (name, aa) in arrays {
2954 frame.atomic_arrays.push((name.clone(), aa.clone()));
2955 }
2956 for (name, ah) in hashes {
2957 frame.atomic_hashes.push((name.clone(), ah.clone()));
2958 }
2959 }
2960 }
2961}
2962
2963#[cfg(test)]
2964mod tests {
2965 use super::*;
2966 use crate::value::StrykeValue;
2967
2968 #[test]
2969 fn missing_scalar_is_undef() {
2970 let s = Scope::new();
2971 assert!(s.get_scalar("not_declared").is_undef());
2972 }
2973
2974 #[test]
2975 fn inner_frame_shadows_outer_scalar() {
2976 let mut s = Scope::new();
2977 s.declare_scalar("a", StrykeValue::integer(1));
2978 s.push_frame();
2979 s.declare_scalar("a", StrykeValue::integer(2));
2980 assert_eq!(s.get_scalar("a").to_int(), 2);
2981 s.pop_frame();
2982 assert_eq!(s.get_scalar("a").to_int(), 1);
2983 }
2984
2985 #[test]
2986 fn set_scalar_updates_innermost_binding() {
2987 let mut s = Scope::new();
2988 s.declare_scalar("a", StrykeValue::integer(1));
2989 s.push_frame();
2990 s.declare_scalar("a", StrykeValue::integer(2));
2991 let _ = s.set_scalar("a", StrykeValue::integer(99));
2992 assert_eq!(s.get_scalar("a").to_int(), 99);
2993 s.pop_frame();
2994 assert_eq!(s.get_scalar("a").to_int(), 1);
2995 }
2996
2997 #[test]
2998 fn array_negative_index_reads_from_end() {
2999 let mut s = Scope::new();
3000 s.declare_array(
3001 "a",
3002 vec![
3003 StrykeValue::integer(10),
3004 StrykeValue::integer(20),
3005 StrykeValue::integer(30),
3006 ],
3007 );
3008 assert_eq!(s.get_array_element("a", -1).to_int(), 30);
3009 }
3010
3011 #[test]
3012 fn set_array_element_extends_array_with_undef_gaps() {
3013 let mut s = Scope::new();
3014 s.declare_array("a", vec![]);
3015 s.set_array_element("a", 2, StrykeValue::integer(7))
3016 .unwrap();
3017 assert_eq!(s.get_array_element("a", 2).to_int(), 7);
3018 assert!(s.get_array_element("a", 0).is_undef());
3019 }
3020
3021 #[test]
3022 fn capture_restore_roundtrip_scalar() {
3023 let mut s = Scope::new();
3024 s.declare_scalar("n", StrykeValue::integer(42));
3025 let cap = s.capture();
3026 let mut t = Scope::new();
3027 t.restore_capture(&cap);
3028 assert_eq!(t.get_scalar("n").to_int(), 42);
3029 }
3030
3031 #[test]
3032 fn capture_restore_roundtrip_lexical_array_and_hash() {
3033 let mut s = Scope::new();
3034 s.declare_array("a", vec![StrykeValue::integer(1), StrykeValue::integer(2)]);
3035 let mut m = IndexMap::new();
3036 m.insert("k".to_string(), StrykeValue::integer(99));
3037 s.declare_hash("h", m);
3038 let cap = s.capture();
3039 let mut t = Scope::new();
3040 t.restore_capture(&cap);
3041 assert_eq!(t.get_array_element("a", 1).to_int(), 2);
3042 assert_eq!(t.get_hash_element("h", "k").to_int(), 99);
3043 }
3044
3045 #[test]
3046 fn hash_get_set_delete_exists() {
3047 let mut s = Scope::new();
3048 let mut m = IndexMap::new();
3049 m.insert("k".to_string(), StrykeValue::integer(1));
3050 s.declare_hash("h", m);
3051 assert_eq!(s.get_hash_element("h", "k").to_int(), 1);
3052 assert!(s.exists_hash_element("h", "k"));
3053 s.set_hash_element("h", "k", StrykeValue::integer(99))
3054 .unwrap();
3055 assert_eq!(s.get_hash_element("h", "k").to_int(), 99);
3056 let del = s.delete_hash_element("h", "k").unwrap();
3057 assert_eq!(del.to_int(), 99);
3058 assert!(!s.exists_hash_element("h", "k"));
3059 }
3060
3061 #[test]
3062 fn inner_frame_shadows_outer_hash_name() {
3063 let mut s = Scope::new();
3064 let mut outer = IndexMap::new();
3065 outer.insert("k".to_string(), StrykeValue::integer(1));
3066 s.declare_hash("h", outer);
3067 s.push_frame();
3068 let mut inner = IndexMap::new();
3069 inner.insert("k".to_string(), StrykeValue::integer(2));
3070 s.declare_hash("h", inner);
3071 assert_eq!(s.get_hash_element("h", "k").to_int(), 2);
3072 s.pop_frame();
3073 assert_eq!(s.get_hash_element("h", "k").to_int(), 1);
3074 }
3075
3076 #[test]
3077 fn inner_frame_shadows_outer_array_name() {
3078 let mut s = Scope::new();
3079 s.declare_array("a", vec![StrykeValue::integer(1)]);
3080 s.push_frame();
3081 s.declare_array("a", vec![StrykeValue::integer(2), StrykeValue::integer(3)]);
3082 assert_eq!(s.get_array_element("a", 1).to_int(), 3);
3083 s.pop_frame();
3084 assert_eq!(s.get_array_element("a", 0).to_int(), 1);
3085 }
3086
3087 #[test]
3088 fn pop_frame_never_removes_global_frame() {
3089 let mut s = Scope::new();
3090 s.declare_scalar("x", StrykeValue::integer(1));
3091 s.pop_frame();
3092 s.pop_frame();
3093 assert_eq!(s.get_scalar("x").to_int(), 1);
3094 }
3095
3096 #[test]
3097 fn empty_array_declared_has_zero_length() {
3098 let mut s = Scope::new();
3099 s.declare_array("a", vec![]);
3100 assert_eq!(s.get_array("a").len(), 0);
3101 }
3102
3103 #[test]
3104 fn depth_increments_with_push_frame() {
3105 let mut s = Scope::new();
3106 let d0 = s.depth();
3107 s.push_frame();
3108 assert_eq!(s.depth(), d0 + 1);
3109 s.pop_frame();
3110 assert_eq!(s.depth(), d0);
3111 }
3112
3113 #[test]
3114 fn pop_to_depth_unwinds_to_target() {
3115 let mut s = Scope::new();
3116 s.push_frame();
3117 s.push_frame();
3118 let target = s.depth() - 1;
3119 s.pop_to_depth(target);
3120 assert_eq!(s.depth(), target);
3121 }
3122
3123 #[test]
3124 fn array_len_and_push_pop_roundtrip() {
3125 let mut s = Scope::new();
3126 s.declare_array("a", vec![]);
3127 assert_eq!(s.array_len("a"), 0);
3128 s.push_to_array("a", StrykeValue::integer(1)).unwrap();
3129 s.push_to_array("a", StrykeValue::integer(2)).unwrap();
3130 assert_eq!(s.array_len("a"), 2);
3131 assert_eq!(s.pop_from_array("a").unwrap().to_int(), 2);
3132 assert_eq!(s.pop_from_array("a").unwrap().to_int(), 1);
3133 assert!(s.pop_from_array("a").unwrap().is_undef());
3134 }
3135
3136 #[test]
3137 fn shift_from_array_drops_front() {
3138 let mut s = Scope::new();
3139 s.declare_array("a", vec![StrykeValue::integer(1), StrykeValue::integer(2)]);
3140 assert_eq!(s.shift_from_array("a").unwrap().to_int(), 1);
3141 assert_eq!(s.array_len("a"), 1);
3142 }
3143
3144 #[test]
3145 fn atomic_mutate_increments_wrapped_scalar() {
3146 use parking_lot::Mutex;
3147 use std::sync::Arc;
3148 let mut s = Scope::new();
3149 s.declare_scalar(
3150 "n",
3151 StrykeValue::atomic(Arc::new(Mutex::new(StrykeValue::integer(10)))),
3152 );
3153 let v = s
3154 .atomic_mutate("n", |old| StrykeValue::integer(old.to_int() + 5))
3155 .expect("atomic_mutate on atomic-backed scalar must not fail");
3156 assert_eq!(v.to_int(), 15);
3157 assert_eq!(s.get_scalar("n").to_int(), 15);
3158 }
3159
3160 #[test]
3161 fn atomic_mutate_post_returns_old_value() {
3162 use parking_lot::Mutex;
3163 use std::sync::Arc;
3164 let mut s = Scope::new();
3165 s.declare_scalar(
3166 "n",
3167 StrykeValue::atomic(Arc::new(Mutex::new(StrykeValue::integer(7)))),
3168 );
3169 let old = s
3170 .atomic_mutate_post("n", |v| StrykeValue::integer(v.to_int() + 1))
3171 .expect("atomic_mutate_post on atomic-backed scalar must not fail");
3172 assert_eq!(old.to_int(), 7);
3173 assert_eq!(s.get_scalar("n").to_int(), 8);
3174 }
3175
3176 #[test]
3177 fn get_scalar_raw_keeps_atomic_wrapper() {
3178 use parking_lot::Mutex;
3179 use std::sync::Arc;
3180 let mut s = Scope::new();
3181 s.declare_scalar(
3182 "n",
3183 StrykeValue::atomic(Arc::new(Mutex::new(StrykeValue::integer(3)))),
3184 );
3185 assert!(s.get_scalar_raw("n").is_atomic());
3186 assert!(!s.get_scalar("n").is_atomic());
3187 }
3188
3189 #[test]
3190 fn missing_array_element_is_undef() {
3191 let mut s = Scope::new();
3192 s.declare_array("a", vec![StrykeValue::integer(1)]);
3193 assert!(s.get_array_element("a", 99).is_undef());
3194 }
3195
3196 #[test]
3197 fn restore_atomics_puts_atomic_containers_in_frame() {
3198 use indexmap::IndexMap;
3199 use parking_lot::Mutex;
3200 use std::sync::Arc;
3201 let mut s = Scope::new();
3202 let aa = AtomicArray(Arc::new(Mutex::new(vec![StrykeValue::integer(1)])));
3203 let ah = AtomicHash(Arc::new(Mutex::new(IndexMap::new())));
3204 s.restore_atomics(&[("ax".into(), aa.clone())], &[("hx".into(), ah.clone())]);
3205 assert_eq!(s.get_array_element("ax", 0).to_int(), 1);
3206 assert_eq!(s.array_len("ax"), 1);
3207 s.set_hash_element("hx", "k", StrykeValue::integer(2))
3208 .unwrap();
3209 assert_eq!(s.get_hash_element("hx", "k").to_int(), 2);
3210 }
3211
3212 #[test]
3220 fn topic_alias_pairs_underscore_with_zero() {
3221 assert_eq!(topic_alias("_").as_deref(), Some("_0"));
3222 assert_eq!(topic_alias("_0").as_deref(), Some("_"));
3223 }
3224
3225 #[test]
3226 fn topic_alias_pairs_outer_chain_with_zero_form() {
3227 assert_eq!(topic_alias("_<").as_deref(), Some("_0<"));
3229 assert_eq!(topic_alias("_0<").as_deref(), Some("_<"));
3230 assert_eq!(topic_alias("_<<<").as_deref(), Some("_0<<<"));
3231 assert_eq!(topic_alias("_0<<<").as_deref(), Some("_<<<"));
3232 }
3233
3234 #[test]
3235 fn topic_alias_has_no_pair_for_other_positionals() {
3236 assert!(topic_alias("_1").is_none());
3238 assert!(topic_alias("_2").is_none());
3239 assert!(topic_alias("_42").is_none());
3240 assert!(topic_alias("_<5").is_none());
3242 assert!(topic_alias("foo").is_none());
3244 assert!(topic_alias("_foo").is_none());
3245 }
3246
3247 #[test]
3250 fn positional_topic_slot_parses_underscore_n() {
3251 assert_eq!(parse_positional_topic_slot("_1"), Some(1));
3254 assert_eq!(parse_positional_topic_slot("_2"), Some(2));
3255 assert_eq!(parse_positional_topic_slot("_42"), Some(42));
3256 }
3257
3258 #[test]
3259 fn positional_topic_slot_rejects_non_positional_names() {
3260 assert!(
3261 parse_positional_topic_slot("_").is_none(),
3262 "bare _ has no slot"
3263 );
3264 assert!(
3265 parse_positional_topic_slot("_0").is_none(),
3266 "_0 is the topic alias, not positional"
3267 );
3268 assert!(parse_positional_topic_slot("_foo").is_none(), "named");
3269 assert!(parse_positional_topic_slot("foo").is_none());
3270 assert!(parse_positional_topic_slot("").is_none());
3271 }
3272}