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>,
420 frame_pool: Vec<Frame>,
422 parallel_guard: bool,
427 parallel_guard_baseline: usize,
436 max_active_slot: usize,
443}
444
445impl Default for Scope {
446 fn default() -> Self {
447 Self::new()
448 }
449}
450
451impl Scope {
452 pub fn new() -> Self {
453 let mut s = Self {
454 frames: Vec::with_capacity(32),
455 frame_pool: Vec::with_capacity(32),
456 parallel_guard: false,
457 parallel_guard_baseline: 0,
458 max_active_slot: 0,
459 };
460 s.frames.push(Frame::new());
461 s
462 }
463
464 #[inline]
469 pub fn set_parallel_guard(&mut self, enabled: bool) {
470 self.parallel_guard = enabled;
471 self.parallel_guard_baseline = if enabled { self.frames.len() } else { 0 };
472 }
473
474 #[inline]
475 pub fn parallel_guard(&self) -> bool {
476 self.parallel_guard
477 }
478
479 #[inline]
486 fn parallel_skip_special_name(_name: &str) -> bool {
487 false
488 }
489
490 #[inline]
492 fn parallel_allowed_topic_scalar(name: &str) -> bool {
493 matches!(name, "_" | "a" | "b")
494 }
495
496 #[inline]
498 fn parallel_allowed_internal_array(name: &str) -> bool {
499 matches!(name, "-" | "+" | "^CAPTURE" | "^CAPTURE_ALL")
500 }
501
502 #[inline]
504 fn parallel_allowed_internal_hash(name: &str) -> bool {
505 matches!(name, "+" | "-" | "ENV" | "INC")
506 }
507
508 fn check_parallel_scalar_write(&self, name: &str) -> Result<(), StrykeError> {
509 if !self.parallel_guard || Self::parallel_skip_special_name(name) {
510 return Ok(());
511 }
512 if Self::parallel_allowed_topic_scalar(name) {
513 return Ok(());
514 }
515 if crate::special_vars::is_regex_match_scalar_name(name) {
516 return Ok(());
517 }
518 let baseline = self.parallel_guard_baseline;
522 for (i, frame) in self.frames.iter().enumerate().rev() {
523 if frame.has_scalar(name) {
524 if let Some(v) = frame.get_scalar(name) {
525 if v.as_atomic_arc().is_some() {
526 return Ok(());
527 }
528 }
529 if i < baseline {
530 let directive = if name.contains("::") {
534 "declare `oursync` for shared package-global state"
535 } else {
536 "declare `mysync` for shared lexical state"
537 };
538 return Err(StrykeError::runtime(
539 format!(
540 "cannot assign to captured non-atomic variable `${}` in a parallel block — {}",
541 name, directive
542 ),
543 0,
544 ));
545 }
546 return Ok(());
547 }
548 }
549 Err(StrykeError::runtime(
550 format!(
551 "cannot assign to undeclared variable `${}` in a parallel block",
552 name
553 ),
554 0,
555 ))
556 }
557
558 #[inline]
559 pub fn depth(&self) -> usize {
560 self.frames.len()
561 }
562
563 #[inline]
566 pub fn pop_to_depth(&mut self, target_depth: usize) {
567 while self.frames.len() > target_depth && self.frames.len() > 1 {
568 self.pop_frame();
569 }
570 }
571
572 #[inline]
573 pub fn push_frame(&mut self) {
574 if let Some(mut frame) = self.frame_pool.pop() {
575 frame.clear_all_bindings();
576 self.frames.push(frame);
577 } else {
578 self.frames.push(Frame::new());
579 }
580 }
581
582 #[inline]
587 pub fn get_scalar_slot(&self, slot: u8) -> StrykeValue {
588 let idx = slot as usize;
589 for frame in self.frames.iter().rev() {
590 if idx < frame.scalar_slots.len() && frame.owns_scalar_slot_index(idx) {
591 let val = &frame.scalar_slots[idx];
592 if let Some(arc) = val.as_capture_cell() {
595 return arc.read().clone();
596 }
597 return val.clone();
598 }
599 }
600 StrykeValue::UNDEF
601 }
602
603 #[inline]
605 pub fn set_scalar_slot(&mut self, slot: u8, val: StrykeValue) {
606 let idx = slot as usize;
607 let len = self.frames.len();
608 for i in (0..len).rev() {
609 if idx < self.frames[i].scalar_slots.len() && self.frames[i].owns_scalar_slot_index(idx)
610 {
611 if let Some(r) = self.frames[i].scalar_slots[idx].as_capture_cell() {
613 *r.write() = val;
614 } else {
615 self.frames[i].scalar_slots[idx] = val;
616 }
617 return;
618 }
619 }
620 let top = self.frames.last_mut().unwrap();
621 top.scalar_slots.resize(idx + 1, StrykeValue::UNDEF);
622 if idx >= top.scalar_slot_names.len() {
623 top.scalar_slot_names.resize(idx + 1, None);
624 }
625 top.scalar_slot_names[idx] = Some(String::new());
626 top.scalar_slots[idx] = val;
627 }
628
629 #[inline]
633 pub fn set_scalar_slot_checked(
634 &mut self,
635 slot: u8,
636 val: StrykeValue,
637 slot_name: Option<&str>,
638 ) -> Result<(), StrykeError> {
639 if self.parallel_guard {
640 let idx = slot as usize;
641 let len = self.frames.len();
642 let top_has = idx < self.frames[len - 1].scalar_slots.len()
643 && self.frames[len - 1].owns_scalar_slot_index(idx);
644 if !top_has {
645 let name_owned: String = {
646 let mut found = String::new();
647 for i in (0..len).rev() {
648 if let Some(Some(n)) = self.frames[i].scalar_slot_names.get(idx) {
649 found = n.clone();
650 break;
651 }
652 }
653 if found.is_empty() {
654 if let Some(sn) = slot_name {
655 found = sn.to_string();
656 }
657 }
658 found
659 };
660 let name = name_owned.as_str();
661 if !name.is_empty() && !Self::parallel_allowed_topic_scalar(name) {
662 let baseline = self.parallel_guard_baseline;
663 for (fi, frame) in self.frames.iter().enumerate().rev() {
664 if frame.has_scalar(name)
665 || (idx < frame.scalar_slots.len() && frame.owns_scalar_slot_index(idx))
666 {
667 if fi < baseline {
668 return Err(StrykeError::runtime(
669 format!(
670 "cannot assign to captured outer lexical `${}` inside a parallel block (use `mysync`)",
671 name
672 ),
673 0,
674 ));
675 }
676 break;
677 }
678 }
679 }
680 }
681 }
682 self.set_scalar_slot(slot, val);
683 Ok(())
684 }
685
686 #[inline]
690 pub fn declare_scalar_slot(&mut self, slot: u8, val: StrykeValue, name: Option<&str>) {
691 let idx = slot as usize;
692 let frame = self.frames.last_mut().unwrap();
693 if idx >= frame.scalar_slots.len() {
694 frame.scalar_slots.resize(idx + 1, StrykeValue::UNDEF);
695 }
696 frame.scalar_slots[idx] = val;
697 if idx >= frame.scalar_slot_names.len() {
698 frame.scalar_slot_names.resize(idx + 1, None);
699 }
700 match name {
701 Some(n) => frame.scalar_slot_names[idx] = Some(n.to_string()),
702 None => frame.scalar_slot_names[idx] = Some(String::new()),
704 }
705 }
706
707 #[inline]
718 pub fn scalar_slot_concat_repeat_inplace(&mut self, slot: u8, rhs: &str, n: usize) -> bool {
719 let idx = slot as usize;
720 let len = self.frames.len();
721 let fi = {
722 let mut found = len - 1;
723 if idx >= self.frames[found].scalar_slots.len()
724 || !self.frames[found].owns_scalar_slot_index(idx)
725 {
726 for i in (0..len - 1).rev() {
727 if idx < self.frames[i].scalar_slots.len()
728 && self.frames[i].owns_scalar_slot_index(idx)
729 {
730 found = i;
731 break;
732 }
733 }
734 }
735 found
736 };
737 let frame = &mut self.frames[fi];
738 if idx >= frame.scalar_slots.len() {
739 frame.scalar_slots.resize(idx + 1, StrykeValue::UNDEF);
740 }
741 frame.scalar_slots[idx].try_concat_repeat_inplace(rhs, n)
742 }
743
744 #[inline]
749 pub fn scalar_slot_concat_repeat_slow(&mut self, slot: u8, rhs: &str, n: usize) {
750 let pv = StrykeValue::string(rhs.to_owned());
751 for _ in 0..n {
752 let _ = self.scalar_slot_concat_inplace(slot, &pv);
753 }
754 }
755
756 #[inline]
757 pub fn scalar_slot_concat_inplace(&mut self, slot: u8, rhs: &StrykeValue) -> StrykeValue {
758 let idx = slot as usize;
759 let len = self.frames.len();
760 let fi = {
761 let mut found = len - 1;
762 if idx >= self.frames[found].scalar_slots.len()
763 || !self.frames[found].owns_scalar_slot_index(idx)
764 {
765 for i in (0..len - 1).rev() {
766 if idx < self.frames[i].scalar_slots.len()
767 && self.frames[i].owns_scalar_slot_index(idx)
768 {
769 found = i;
770 break;
771 }
772 }
773 }
774 found
775 };
776 let frame = &mut self.frames[fi];
777 if idx >= frame.scalar_slots.len() {
778 frame.scalar_slots.resize(idx + 1, StrykeValue::UNDEF);
779 }
780 if frame.scalar_slots[idx].try_concat_append_inplace(rhs) {
788 return frame.scalar_slots[idx].shallow_clone();
789 }
790 let new_val = std::mem::replace(&mut frame.scalar_slots[idx], StrykeValue::UNDEF)
791 .concat_append_owned(rhs);
792 let handle = new_val.shallow_clone();
793 frame.scalar_slots[idx] = new_val;
794 handle
795 }
796
797 #[inline]
798 pub(crate) fn can_pop_frame(&self) -> bool {
799 self.frames.len() > 1
800 }
801
802 #[inline]
803 pub fn pop_frame(&mut self) {
804 if self.frames.len() > 1 {
805 let mut frame = self.frames.pop().expect("pop_frame");
806 let saved_guard = self.parallel_guard;
809 self.parallel_guard = false;
810 for entry in frame.local_restores.drain(..).rev() {
811 match entry {
812 LocalRestore::Scalar(name, old) => {
813 let _ = self.set_scalar(&name, old);
814 }
815 LocalRestore::Array(name, old) => {
816 let _ = self.set_array(&name, old);
817 }
818 LocalRestore::Hash(name, old) => {
819 let _ = self.set_hash(&name, old);
820 }
821 LocalRestore::HashElement(name, key, old) => match old {
822 Some(v) => {
823 let _ = self.set_hash_element(&name, &key, v);
824 }
825 None => {
826 let _ = self.delete_hash_element(&name, &key);
827 }
828 },
829 LocalRestore::ArrayElement(name, index, old) => {
830 let _ = self.set_array_element(&name, index, old);
831 }
832 }
833 }
834 self.parallel_guard = saved_guard;
835 frame.clear_all_bindings();
836 if self.frame_pool.len() < 64 {
838 self.frame_pool.push(frame);
839 }
840 }
841 }
842
843 pub fn local_set_scalar(&mut self, name: &str, val: StrykeValue) -> Result<(), StrykeError> {
845 let old = self.get_scalar(name);
846 if let Some(frame) = self.frames.last_mut() {
847 frame
848 .local_restores
849 .push(LocalRestore::Scalar(name.to_string(), old));
850 }
851 self.set_scalar(name, val)
852 }
853
854 pub fn local_set_array(
856 &mut self,
857 name: &str,
858 val: Vec<StrykeValue>,
859 ) -> Result<(), StrykeError> {
860 if self.find_atomic_array(name).is_some() {
861 return Err(StrykeError::runtime(
862 "local cannot be used on mysync arrays",
863 0,
864 ));
865 }
866 let old = self.get_array(name);
867 if let Some(frame) = self.frames.last_mut() {
868 frame
869 .local_restores
870 .push(LocalRestore::Array(name.to_string(), old));
871 }
872 self.set_array(name, val)?;
873 Ok(())
874 }
875
876 pub fn local_set_hash(
878 &mut self,
879 name: &str,
880 val: IndexMap<String, StrykeValue>,
881 ) -> Result<(), StrykeError> {
882 if self.find_atomic_hash(name).is_some() {
883 return Err(StrykeError::runtime(
884 "local cannot be used on mysync hashes",
885 0,
886 ));
887 }
888 let old = self.get_hash(name);
889 if let Some(frame) = self.frames.last_mut() {
890 frame
891 .local_restores
892 .push(LocalRestore::Hash(name.to_string(), old));
893 }
894 self.set_hash(name, val)?;
895 Ok(())
896 }
897
898 pub fn local_set_hash_element(
900 &mut self,
901 name: &str,
902 key: &str,
903 val: StrykeValue,
904 ) -> Result<(), StrykeError> {
905 if self.find_atomic_hash(name).is_some() {
906 return Err(StrykeError::runtime(
907 "local cannot be used on mysync hash elements",
908 0,
909 ));
910 }
911 let old = if self.exists_hash_element(name, key) {
912 Some(self.get_hash_element(name, key))
913 } else {
914 None
915 };
916 if let Some(frame) = self.frames.last_mut() {
917 frame.local_restores.push(LocalRestore::HashElement(
918 name.to_string(),
919 key.to_string(),
920 old,
921 ));
922 }
923 self.set_hash_element(name, key, val)?;
924 Ok(())
925 }
926
927 pub fn local_set_array_element(
930 &mut self,
931 name: &str,
932 index: i64,
933 val: StrykeValue,
934 ) -> Result<(), StrykeError> {
935 if self.find_atomic_array(name).is_some() {
936 return Err(StrykeError::runtime(
937 "local cannot be used on mysync array elements",
938 0,
939 ));
940 }
941 let old = self.get_array_element(name, index);
942 if let Some(frame) = self.frames.last_mut() {
943 frame
944 .local_restores
945 .push(LocalRestore::ArrayElement(name.to_string(), index, old));
946 }
947 self.set_array_element(name, index, val)?;
948 Ok(())
949 }
950
951 #[inline]
954 pub fn declare_scalar(&mut self, name: &str, val: StrykeValue) {
955 let _ = self.declare_scalar_frozen(name, val, false, None);
956 }
957
958 pub fn declare_scalar_frozen(
961 &mut self,
962 name: &str,
963 val: StrykeValue,
964 frozen: bool,
965 ty: Option<PerlTypeName>,
966 ) -> Result<(), StrykeError> {
967 canon_main!(name);
968 if let Some(ref t) = ty {
969 t.check_value(&val)
970 .map_err(|msg| StrykeError::type_error(format!("`${}`: {}", name, msg), 0))?;
971 }
972 if let Some(frame) = self.frames.last_mut() {
973 frame.set_scalar(name, val);
974 if frozen {
975 frame.frozen_scalars.insert(name.to_string());
976 }
977 if let Some(t) = ty {
978 frame.typed_scalars.insert(name.to_string(), t);
979 }
980 }
981 Ok(())
982 }
983
984 pub fn is_scalar_frozen(&self, name: &str) -> bool {
986 for frame in self.frames.iter().rev() {
987 if frame.has_scalar(name) {
988 return frame.frozen_scalars.contains(name);
989 }
990 }
991 false
992 }
993
994 pub fn is_array_frozen(&self, name: &str) -> bool {
996 for frame in self.frames.iter().rev() {
997 if frame.has_array(name) {
998 return frame.frozen_arrays.contains(name);
999 }
1000 }
1001 false
1002 }
1003
1004 pub fn is_hash_frozen(&self, name: &str) -> bool {
1006 for frame in self.frames.iter().rev() {
1007 if frame.has_hash(name) {
1008 return frame.frozen_hashes.contains(name);
1009 }
1010 }
1011 false
1012 }
1013
1014 pub fn check_frozen(&self, sigil: &str, name: &str) -> Option<&'static str> {
1016 match sigil {
1017 "$" => {
1018 if self.is_scalar_frozen(name) {
1019 Some("scalar")
1020 } else {
1021 None
1022 }
1023 }
1024 "@" => {
1025 if self.is_array_frozen(name) {
1026 Some("array")
1027 } else {
1028 None
1029 }
1030 }
1031 "%" => {
1032 if self.is_hash_frozen(name) {
1033 Some("hash")
1034 } else {
1035 None
1036 }
1037 }
1038 _ => None,
1039 }
1040 }
1041
1042 #[inline]
1043 pub fn get_scalar(&self, name: &str) -> StrykeValue {
1044 if let Some(rest) = strip_main_prefix(name) {
1046 return self.get_scalar(rest);
1047 }
1048 for frame in self.frames.iter().rev() {
1049 if let Some(val) = frame.get_scalar(name) {
1050 if let Some(arc) = val.as_atomic_arc() {
1052 return arc.lock().clone();
1053 }
1054 if let Some(arc) = val.as_capture_cell() {
1057 return arc.read().clone();
1058 }
1059 return val.clone();
1066 }
1067 }
1068 StrykeValue::UNDEF
1069 }
1070
1071 #[inline]
1080 pub(crate) fn is_topic_variant_name(name: &str) -> bool {
1081 let bytes = name.as_bytes();
1082 if bytes.is_empty() || bytes[0] != b'_' {
1083 return false;
1084 }
1085 let mut i = 1;
1086 while i < bytes.len() && bytes[i].is_ascii_digit() {
1087 i += 1;
1088 }
1089 while i < bytes.len() && bytes[i] == b'<' {
1090 i += 1;
1091 }
1092 i == bytes.len()
1093 }
1094
1095 #[inline]
1097 pub fn scalar_binding_exists(&self, name: &str) -> bool {
1098 canon_main!(name);
1099 for frame in self.frames.iter().rev() {
1100 if frame.has_scalar(name) {
1101 return true;
1102 }
1103 }
1104 false
1105 }
1106
1107 pub fn all_scalar_names(&self) -> Vec<String> {
1109 let mut names = Vec::new();
1110 for frame in &self.frames {
1111 for (name, _) in &frame.scalars {
1112 if !names.contains(name) {
1113 names.push(name.clone());
1114 }
1115 }
1116 for name in frame.scalar_slot_names.iter().flatten() {
1117 if !names.contains(name) {
1118 names.push(name.clone());
1119 }
1120 }
1121 }
1122 names
1123 }
1124
1125 pub fn all_array_names(&self) -> Vec<String> {
1127 let mut names = Vec::new();
1128 for frame in &self.frames {
1129 for (name, _) in &frame.arrays {
1130 if !names.contains(name) {
1131 names.push(name.clone());
1132 }
1133 }
1134 for (name, _) in &frame.shared_arrays {
1135 if !names.contains(name) {
1136 names.push(name.clone());
1137 }
1138 }
1139 for (name, _) in &frame.atomic_arrays {
1140 if !names.contains(name) {
1141 names.push(name.clone());
1142 }
1143 }
1144 }
1145 names
1146 }
1147
1148 pub fn all_hash_names(&self) -> Vec<String> {
1150 let mut names = Vec::new();
1151 for frame in &self.frames {
1152 for (name, _) in &frame.hashes {
1153 if !names.contains(name) {
1154 names.push(name.clone());
1155 }
1156 }
1157 for (name, _) in &frame.shared_hashes {
1158 if !names.contains(name) {
1159 names.push(name.clone());
1160 }
1161 }
1162 for (name, _) in &frame.atomic_hashes {
1163 if !names.contains(name) {
1164 names.push(name.clone());
1165 }
1166 }
1167 }
1168 names
1169 }
1170
1171 #[inline]
1173 pub fn array_binding_exists(&self, name: &str) -> bool {
1174 canon_main!(name);
1175 if self.find_atomic_array(name).is_some() {
1176 return true;
1177 }
1178 for frame in self.frames.iter().rev() {
1179 if frame.has_array(name) {
1180 return true;
1181 }
1182 }
1183 false
1184 }
1185
1186 #[inline]
1188 pub fn hash_binding_exists(&self, name: &str) -> bool {
1189 if let Some(rest) = strip_main_prefix(name) {
1190 return self.hash_binding_exists(rest);
1191 }
1192 if self.find_atomic_hash(name).is_some() {
1193 return true;
1194 }
1195 for frame in self.frames.iter().rev() {
1196 if frame.has_hash(name) {
1197 return true;
1198 }
1199 }
1200 false
1201 }
1202
1203 #[inline]
1206 pub fn get_scalar_raw(&self, name: &str) -> StrykeValue {
1207 if let Some(rest) = strip_main_prefix(name) {
1208 return self.get_scalar_raw(rest);
1209 }
1210 for frame in self.frames.iter().rev() {
1211 if let Some(val) = frame.get_scalar(name) {
1212 return val.clone();
1213 }
1214 }
1215 StrykeValue::UNDEF
1216 }
1217
1218 pub fn atomic_mutate(
1224 &mut self,
1225 name: &str,
1226 f: impl FnOnce(&StrykeValue) -> StrykeValue,
1227 ) -> Result<StrykeValue, StrykeError> {
1228 for frame in self.frames.iter().rev() {
1229 if let Some(v) = frame.get_scalar(name) {
1230 if let Some(arc) = v.as_atomic_arc() {
1231 let mut guard = arc.lock();
1232 let old = guard.clone();
1233 let new_val = f(&guard);
1234 *guard = new_val.clone();
1235 crate::parallel_trace::emit_scalar_mutation(name, &old, &new_val);
1236 return Ok(new_val);
1237 }
1238 }
1239 }
1240 let old = self.get_scalar(name);
1243 let new_val = f(&old);
1244 self.set_scalar(name, new_val.clone())?;
1245 Ok(new_val)
1246 }
1247
1248 pub fn atomic_mutate_post(
1252 &mut self,
1253 name: &str,
1254 f: impl FnOnce(&StrykeValue) -> StrykeValue,
1255 ) -> Result<StrykeValue, StrykeError> {
1256 for frame in self.frames.iter().rev() {
1257 if let Some(v) = frame.get_scalar(name) {
1258 if let Some(arc) = v.as_atomic_arc() {
1259 let mut guard = arc.lock();
1260 let old = guard.clone();
1261 let new_val = f(&old);
1262 *guard = new_val.clone();
1263 crate::parallel_trace::emit_scalar_mutation(name, &old, &new_val);
1264 return Ok(old);
1265 }
1266 }
1267 }
1268 let old = self.get_scalar(name);
1270 self.set_scalar(name, f(&old))?;
1271 Ok(old)
1272 }
1273
1274 #[inline]
1281 pub fn scalar_concat_inplace(
1282 &mut self,
1283 name: &str,
1284 rhs: &StrykeValue,
1285 ) -> Result<StrykeValue, StrykeError> {
1286 canon_main!(name);
1287 self.check_parallel_scalar_write(name)?;
1288 for frame in self.frames.iter_mut().rev() {
1289 if let Some(entry) = frame.scalars.iter_mut().find(|(k, _)| k == name) {
1290 if let Some(atomic_arc) = entry.1.as_atomic_arc() {
1293 let mut guard = atomic_arc.lock();
1294 let inner = std::mem::replace(&mut *guard, StrykeValue::UNDEF);
1295 let new_val = inner.concat_append_owned(rhs);
1296 *guard = new_val.shallow_clone();
1297 return Ok(new_val);
1298 }
1299 if entry.1.try_concat_append_inplace(rhs) {
1302 return Ok(entry.1.shallow_clone());
1303 }
1304 let new_val =
1307 std::mem::replace(&mut entry.1, StrykeValue::UNDEF).concat_append_owned(rhs);
1308 entry.1 = new_val.shallow_clone();
1309 return Ok(new_val);
1310 }
1311 }
1312 let val = StrykeValue::UNDEF.concat_append_owned(rhs);
1314 self.frames[0].set_scalar(name, val.shallow_clone());
1315 Ok(val)
1316 }
1317
1318 #[inline]
1319 pub fn set_scalar(&mut self, name: &str, val: StrykeValue) -> Result<(), StrykeError> {
1320 if let Some(rest) = strip_main_prefix(name) {
1321 return self.set_scalar(rest, val);
1322 }
1323 self.check_parallel_scalar_write(name)?;
1324 if Self::is_topic_variant_name(name) {
1331 if let Some(frame) = self.frames.last_mut() {
1332 frame.set_scalar_raw(name, val.clone());
1333 if let Some(alias) = topic_alias(name) {
1341 frame.set_scalar_raw(&alias, val);
1342 }
1343 }
1344 return Ok(());
1345 }
1346 for frame in self.frames.iter_mut().rev() {
1347 if let Some(v) = frame.get_scalar(name) {
1349 if let Some(arc) = v.as_atomic_arc() {
1350 let mut guard = arc.lock();
1351 let old = guard.clone();
1352 *guard = val.clone();
1353 crate::parallel_trace::emit_scalar_mutation(name, &old, &val);
1354 return Ok(());
1355 }
1356 if let Some(arc) = v.as_capture_cell() {
1358 *arc.write() = val;
1359 return Ok(());
1360 }
1361 }
1362 if frame.has_scalar(name) {
1363 if let Some(ty) = frame.typed_scalars.get(name) {
1364 ty.check_value(&val).map_err(|msg| {
1365 StrykeError::type_error(format!("`${}`: {}", name, msg), 0)
1366 })?;
1367 }
1368 frame.set_scalar(name, val);
1369 return Ok(());
1370 }
1371 }
1372 self.frames[0].set_scalar(name, val);
1373 Ok(())
1374 }
1375
1376 #[inline]
1382 fn topic_slot_key(slot: usize, level: usize) -> String {
1383 debug_assert!(level <= 5);
1384 if slot == 0 {
1385 if level == 0 {
1386 "_".to_string()
1387 } else {
1388 format!("_{}", "<".repeat(level))
1389 }
1390 } else if level == 0 {
1391 format!("_{}", slot)
1392 } else {
1393 format!("_{}{}", slot, "<".repeat(level))
1394 }
1395 }
1396
1397 #[inline]
1400 fn topic_slot_alias_key(slot: usize, level: usize) -> Option<String> {
1401 if slot != 0 {
1402 return None;
1403 }
1404 Some(if level == 0 {
1405 "_0".to_string()
1406 } else {
1407 format!("_0{}", "<".repeat(level))
1408 })
1409 }
1410
1411 #[inline]
1417 fn declare_topic_slot(&mut self, slot: usize, level: usize, val: StrykeValue) {
1418 if let Some(frame) = self.frames.last_mut() {
1425 let key = Self::topic_slot_key(slot, level);
1426 frame.set_scalar_raw(&key, val.clone());
1427 if let Some(alias) = Self::topic_slot_alias_key(slot, level) {
1428 frame.set_scalar_raw(&alias, val);
1429 }
1430 }
1431 }
1432
1433 #[inline]
1458 pub fn set_topic(&mut self, val: StrykeValue) {
1459 let already_shifted = self
1467 .frames
1468 .last()
1469 .map(|f| f.set_topic_called)
1470 .unwrap_or(false);
1471 if already_shifted {
1472 self.declare_topic_slot(0, 0, val);
1473 for slot in 1..=self.max_active_slot {
1474 self.declare_topic_slot(slot, 0, StrykeValue::UNDEF);
1475 }
1476 return;
1477 }
1478 if let Some(frame) = self.frames.last_mut() {
1479 frame.set_topic_called = true;
1480 }
1481 self.shift_slot_chain(0, val);
1482 for slot in 1..=self.max_active_slot {
1483 self.shift_slot_chain(slot, StrykeValue::UNDEF);
1484 }
1485 }
1486
1487 #[inline]
1496 pub fn set_topic_local(&mut self, val: StrykeValue) {
1497 self.declare_topic_slot(0, 0, val);
1498 }
1499
1500 #[inline]
1512 pub fn set_closure_args(&mut self, args: &[StrykeValue]) {
1513 let n = args.len();
1514 if n == 0 {
1515 return;
1516 }
1517 let high = n.saturating_sub(1).max(self.max_active_slot);
1518 for slot in 0..=high {
1519 let val = args.get(slot).cloned().unwrap_or(StrykeValue::UNDEF);
1520 self.shift_slot_chain(slot, val);
1521 }
1522 if n > 0 && n - 1 > self.max_active_slot {
1523 self.max_active_slot = n - 1;
1524 }
1525 }
1526
1527 #[inline]
1536 fn shift_slot_chain(&mut self, slot: usize, val: StrykeValue) {
1537 let l4 = self.get_scalar(&Self::topic_slot_key(slot, 4));
1538 let l3 = self.get_scalar(&Self::topic_slot_key(slot, 3));
1539 let l2 = self.get_scalar(&Self::topic_slot_key(slot, 2));
1540 let l1 = self.get_scalar(&Self::topic_slot_key(slot, 1));
1541 let cur = self.get_scalar(&Self::topic_slot_key(slot, 0));
1542
1543 self.declare_topic_slot(slot, 0, val);
1544 self.declare_topic_slot(slot, 1, cur);
1545 self.declare_topic_slot(slot, 2, l1);
1546 self.declare_topic_slot(slot, 3, l2);
1547 self.declare_topic_slot(slot, 4, l3);
1548 self.declare_topic_slot(slot, 5, l4);
1549 }
1550
1551 #[inline]
1559 pub fn set_sort_pair(&mut self, a: StrykeValue, b: StrykeValue) {
1560 let _ = self.set_scalar("a", a.clone());
1561 let _ = self.set_scalar("b", b.clone());
1562 let _ = self.set_scalar("_0", a.clone());
1563 let _ = self.set_scalar("_1", b);
1564 let _ = self.set_scalar("_", a);
1570 }
1571
1572 #[inline]
1576 pub fn save_topic_chain(&self) -> [StrykeValue; 6] {
1577 [
1578 self.get_scalar(&Self::topic_slot_key(0, 0)),
1579 self.get_scalar(&Self::topic_slot_key(0, 1)),
1580 self.get_scalar(&Self::topic_slot_key(0, 2)),
1581 self.get_scalar(&Self::topic_slot_key(0, 3)),
1582 self.get_scalar(&Self::topic_slot_key(0, 4)),
1583 self.get_scalar(&Self::topic_slot_key(0, 5)),
1584 ]
1585 }
1586
1587 #[inline]
1589 pub fn restore_topic_chain(&mut self, saved: [StrykeValue; 6]) {
1590 for (level, val) in saved.into_iter().enumerate() {
1591 self.declare_topic_slot(0, level, val);
1592 }
1593 }
1594
1595 #[inline]
1597 pub fn push_defer(&mut self, coderef: StrykeValue) {
1598 if let Some(frame) = self.frames.last_mut() {
1599 frame.defers.push(coderef);
1600 }
1601 }
1602
1603 #[inline]
1606 pub fn take_defers(&mut self) -> Vec<StrykeValue> {
1607 if let Some(frame) = self.frames.last_mut() {
1608 let mut defers = std::mem::take(&mut frame.defers);
1609 defers.reverse();
1610 defers
1611 } else {
1612 Vec::new()
1613 }
1614 }
1615
1616 pub fn declare_atomic_array(&mut self, name: &str, val: Vec<StrykeValue>) {
1619 canon_main!(name);
1620 if let Some(frame) = self.frames.last_mut() {
1621 frame
1622 .atomic_arrays
1623 .push((name.to_string(), AtomicArray(Arc::new(Mutex::new(val)))));
1624 }
1625 }
1626
1627 pub fn declare_atomic_hash(&mut self, name: &str, val: IndexMap<String, StrykeValue>) {
1628 canon_main!(name);
1629 if let Some(frame) = self.frames.last_mut() {
1630 frame
1631 .atomic_hashes
1632 .push((name.to_string(), AtomicHash(Arc::new(Mutex::new(val)))));
1633 }
1634 }
1635
1636 fn find_atomic_array(&self, name: &str) -> Option<&AtomicArray> {
1638 let name = strip_main_prefix(name).unwrap_or(name);
1639 for frame in self.frames.iter().rev() {
1640 if let Some(aa) = frame.atomic_arrays.iter().find(|(k, _)| k == name) {
1641 return Some(&aa.1);
1642 }
1643 }
1644 None
1645 }
1646
1647 fn find_atomic_hash(&self, name: &str) -> Option<&AtomicHash> {
1649 let name = strip_main_prefix(name).unwrap_or(name);
1650 for frame in self.frames.iter().rev() {
1651 if let Some(ah) = frame.atomic_hashes.iter().find(|(k, _)| k == name) {
1652 return Some(&ah.1);
1653 }
1654 }
1655 None
1656 }
1657
1658 #[inline]
1663 pub fn take_sub_underscore(&mut self) -> Option<Vec<StrykeValue>> {
1664 self.frames.last_mut()?.sub_underscore.take()
1665 }
1666
1667 pub fn declare_array(&mut self, name: &str, val: Vec<StrykeValue>) {
1668 self.declare_array_frozen(name, val, false);
1669 }
1670
1671 pub fn declare_array_frozen(&mut self, name: &str, val: Vec<StrykeValue>, frozen: bool) {
1672 canon_main!(name);
1673 let idx = if name.contains("::") {
1676 0
1677 } else {
1678 self.frames.len().saturating_sub(1)
1679 };
1680 if let Some(frame) = self.frames.get_mut(idx) {
1681 frame.shared_arrays.retain(|(k, _)| k != name);
1683 frame.set_array(name, val);
1684 if frozen {
1685 frame.frozen_arrays.insert(name.to_string());
1686 } else {
1687 frame.frozen_arrays.remove(name);
1689 }
1690 }
1691 }
1692
1693 pub fn get_array(&self, name: &str) -> Vec<StrykeValue> {
1694 if let Some(rest) = strip_main_prefix(name) {
1700 return self.get_array(rest);
1701 }
1702 if let Some(aa) = self.find_atomic_array(name) {
1704 return aa.0.lock().clone();
1705 }
1706 if let Some(arc) = self.find_shared_array(name) {
1708 return arc.read().clone();
1709 }
1710 if name.contains("::") {
1711 if let Some(f) = self.frames.first() {
1712 if let Some(val) = f.get_array(name) {
1713 return val.clone();
1714 }
1715 }
1716 return Vec::new();
1717 }
1718 for frame in self.frames.iter().rev() {
1719 if let Some(val) = frame.get_array(name) {
1720 return val.clone();
1721 }
1722 }
1723 Vec::new()
1724 }
1725
1726 #[inline]
1729 pub fn get_array_borrow(&self, name: &str) -> Option<&[StrykeValue]> {
1730 if let Some(rest) = strip_main_prefix(name) {
1731 return self.get_array_borrow(rest);
1732 }
1733 if self.find_atomic_array(name).is_some() {
1734 return None;
1735 }
1736 if name.contains("::") {
1737 return self
1738 .frames
1739 .first()
1740 .and_then(|f| f.get_array(name))
1741 .map(|v| v.as_slice());
1742 }
1743 for frame in self.frames.iter().rev() {
1744 if let Some(val) = frame.get_array(name) {
1745 return Some(val.as_slice());
1746 }
1747 }
1748 None
1749 }
1750
1751 fn resolve_array_frame_idx(&self, name: &str) -> Option<usize> {
1752 if name.contains("::") {
1753 return Some(0);
1754 }
1755 (0..self.frames.len())
1756 .rev()
1757 .find(|&i| self.frames[i].has_array(name))
1758 }
1759
1760 fn check_parallel_array_write(&self, name: &str) -> Result<(), StrykeError> {
1761 if !self.parallel_guard
1762 || Self::parallel_skip_special_name(name)
1763 || Self::parallel_allowed_internal_array(name)
1764 {
1765 return Ok(());
1766 }
1767 let baseline = self.parallel_guard_baseline;
1769 match self.resolve_array_frame_idx(name) {
1770 None => Err(StrykeError::runtime(
1771 format!(
1772 "cannot modify undeclared array `@{}` in a parallel block",
1773 name
1774 ),
1775 0,
1776 )),
1777 Some(idx) if idx < baseline => Err(StrykeError::runtime(
1778 format!(
1779 "cannot modify captured non-mysync array `@{}` in a parallel block",
1780 name
1781 ),
1782 0,
1783 )),
1784 Some(_) => Ok(()),
1785 }
1786 }
1787
1788 #[inline]
1793 pub fn resolve_container_binding_ref(&self, val: StrykeValue) -> StrykeValue {
1794 if let Some(name) = val.as_array_binding_name() {
1795 let data = self.get_array(&name);
1796 return StrykeValue::array_ref(Arc::new(parking_lot::RwLock::new(data)));
1797 }
1798 if let Some(name) = val.as_hash_binding_name() {
1799 let data = self.get_hash(&name);
1800 return StrykeValue::hash_ref(Arc::new(parking_lot::RwLock::new(data)));
1801 }
1802 val
1803 }
1804
1805 pub fn promote_array_to_shared(
1809 &mut self,
1810 name: &str,
1811 ) -> Arc<parking_lot::RwLock<Vec<StrykeValue>>> {
1812 if let Some(aa) = self.find_atomic_array(name) {
1815 let data = aa.0.lock().clone();
1816 return Arc::new(parking_lot::RwLock::new(data));
1817 }
1818 let idx = self.resolve_array_frame_idx(name).unwrap_or_default();
1820 let frame = &mut self.frames[idx];
1821 if let Some(entry) = frame.shared_arrays.iter().find(|(k, _)| k == name) {
1822 return Arc::clone(&entry.1);
1823 }
1824 let data = if let Some(pos) = frame.arrays.iter().position(|(k, _)| k == name) {
1826 frame.arrays.swap_remove(pos).1
1827 } else if name == "_" {
1828 frame.sub_underscore.take().unwrap_or_default()
1829 } else {
1830 Vec::new()
1831 };
1832 let arc = Arc::new(parking_lot::RwLock::new(data));
1833 frame
1834 .shared_arrays
1835 .push((name.to_string(), Arc::clone(&arc)));
1836 arc
1837 }
1838
1839 pub fn promote_hash_to_shared(
1842 &mut self,
1843 name: &str,
1844 ) -> Arc<parking_lot::RwLock<IndexMap<String, StrykeValue>>> {
1845 let idx = self.resolve_hash_frame_idx(name).unwrap_or_default();
1846 let frame = &mut self.frames[idx];
1847 if let Some(entry) = frame.shared_hashes.iter().find(|(k, _)| k == name) {
1848 return Arc::clone(&entry.1);
1849 }
1850 let data = if let Some(pos) = frame.hashes.iter().position(|(k, _)| k == name) {
1851 frame.hashes.swap_remove(pos).1
1852 } else {
1853 IndexMap::new()
1854 };
1855 let arc = Arc::new(parking_lot::RwLock::new(data));
1856 frame
1857 .shared_hashes
1858 .push((name.to_string(), Arc::clone(&arc)));
1859 arc
1860 }
1861
1862 fn find_shared_array(&self, name: &str) -> Option<Arc<parking_lot::RwLock<Vec<StrykeValue>>>> {
1864 let name = strip_main_prefix(name).unwrap_or(name);
1865 for frame in self.frames.iter().rev() {
1866 if let Some(entry) = frame.shared_arrays.iter().find(|(k, _)| k == name) {
1867 return Some(Arc::clone(&entry.1));
1868 }
1869 if frame.arrays.iter().any(|(k, _)| k == name) {
1871 return None;
1872 }
1873 }
1874 None
1875 }
1876
1877 fn find_shared_hash(
1879 &self,
1880 name: &str,
1881 ) -> Option<Arc<parking_lot::RwLock<IndexMap<String, StrykeValue>>>> {
1882 let name = strip_main_prefix(name).unwrap_or(name);
1883 for frame in self.frames.iter().rev() {
1884 if let Some(entry) = frame.shared_hashes.iter().find(|(k, _)| k == name) {
1885 return Some(Arc::clone(&entry.1));
1886 }
1887 if frame.hashes.iter().any(|(k, _)| k == name) {
1888 return None;
1889 }
1890 }
1891 None
1892 }
1893
1894 pub fn get_array_mut(&mut self, name: &str) -> Result<&mut Vec<StrykeValue>, StrykeError> {
1895 if self.find_atomic_array(name).is_some() {
1898 return Err(StrykeError::runtime(
1899 "get_array_mut: use atomic path for mysync arrays",
1900 0,
1901 ));
1902 }
1903 self.check_parallel_array_write(name)?;
1904 let idx = self.resolve_array_frame_idx(name).unwrap_or_default();
1905 let frame = &mut self.frames[idx];
1906 if frame.get_array_mut(name).is_none() {
1907 frame.arrays.push((name.to_string(), Vec::new()));
1908 }
1909 Ok(frame.get_array_mut(name).unwrap())
1910 }
1911
1912 pub fn push_to_array(&mut self, name: &str, val: StrykeValue) -> Result<(), StrykeError> {
1914 let val = self.resolve_container_binding_ref(val);
1915 if let Some(aa) = self.find_atomic_array(name) {
1916 aa.0.lock().push(val);
1917 return Ok(());
1918 }
1919 if let Some(arc) = self.find_shared_array(name) {
1920 arc.write().push(val);
1921 return Ok(());
1922 }
1923 self.get_array_mut(name)?.push(val);
1924 Ok(())
1925 }
1926
1927 pub fn push_int_range_to_array(
1931 &mut self,
1932 name: &str,
1933 start: i64,
1934 end: i64,
1935 ) -> Result<(), StrykeError> {
1936 if end <= start {
1937 return Ok(());
1938 }
1939 let count = (end - start) as usize;
1940 if let Some(aa) = self.find_atomic_array(name) {
1941 let mut g = aa.0.lock();
1942 g.reserve(count);
1943 for i in start..end {
1944 g.push(StrykeValue::integer(i));
1945 }
1946 return Ok(());
1947 }
1948 let arr = self.get_array_mut(name)?;
1949 arr.reserve(count);
1950 for i in start..end {
1951 arr.push(StrykeValue::integer(i));
1952 }
1953 Ok(())
1954 }
1955
1956 pub fn pop_from_array(&mut self, name: &str) -> Result<StrykeValue, StrykeError> {
1958 if let Some(aa) = self.find_atomic_array(name) {
1959 return Ok(aa.0.lock().pop().unwrap_or(StrykeValue::UNDEF));
1960 }
1961 if let Some(arc) = self.find_shared_array(name) {
1962 return Ok(arc.write().pop().unwrap_or(StrykeValue::UNDEF));
1963 }
1964 Ok(self
1965 .get_array_mut(name)?
1966 .pop()
1967 .unwrap_or(StrykeValue::UNDEF))
1968 }
1969
1970 pub fn shift_from_array(&mut self, name: &str) -> Result<StrykeValue, StrykeError> {
1972 if let Some(aa) = self.find_atomic_array(name) {
1973 let mut guard = aa.0.lock();
1974 return Ok(if guard.is_empty() {
1975 StrykeValue::UNDEF
1976 } else {
1977 guard.remove(0)
1978 });
1979 }
1980 if let Some(arc) = self.find_shared_array(name) {
1981 let mut arr = arc.write();
1982 return Ok(if arr.is_empty() {
1983 StrykeValue::UNDEF
1984 } else {
1985 arr.remove(0)
1986 });
1987 }
1988 let arr = self.get_array_mut(name)?;
1989 Ok(if arr.is_empty() {
1990 StrykeValue::UNDEF
1991 } else {
1992 arr.remove(0)
1993 })
1994 }
1995
1996 pub fn splice_in_place(
2000 &mut self,
2001 name: &str,
2002 off: usize,
2003 end: usize,
2004 rep_vals: Vec<StrykeValue>,
2005 ) -> Result<Vec<StrykeValue>, StrykeError> {
2006 if let Some(aa) = self.find_atomic_array(name) {
2007 let mut g = aa.0.lock();
2008 let removed: Vec<StrykeValue> = g.drain(off..end).collect();
2009 for (i, v) in rep_vals.into_iter().enumerate() {
2010 g.insert(off + i, v);
2011 }
2012 return Ok(removed);
2013 }
2014 if let Some(arc) = self.find_shared_array(name) {
2015 let mut g = arc.write();
2016 let removed: Vec<StrykeValue> = g.drain(off..end).collect();
2017 for (i, v) in rep_vals.into_iter().enumerate() {
2018 g.insert(off + i, v);
2019 }
2020 return Ok(removed);
2021 }
2022 let arr = self.get_array_mut(name)?;
2023 let removed: Vec<StrykeValue> = arr.drain(off..end).collect();
2024 for (i, v) in rep_vals.into_iter().enumerate() {
2025 arr.insert(off + i, v);
2026 }
2027 Ok(removed)
2028 }
2029
2030 pub fn array_len(&self, name: &str) -> usize {
2032 canon_main!(name);
2033 if let Some(aa) = self.find_atomic_array(name) {
2034 return aa.0.lock().len();
2035 }
2036 if let Some(arc) = self.find_shared_array(name) {
2037 return arc.read().len();
2038 }
2039 if name.contains("::") {
2040 return self
2041 .frames
2042 .first()
2043 .and_then(|f| f.get_array(name))
2044 .map(|a| a.len())
2045 .unwrap_or(0);
2046 }
2047 for frame in self.frames.iter().rev() {
2048 if let Some(arr) = frame.get_array(name) {
2049 return arr.len();
2050 }
2051 }
2052 0
2053 }
2054
2055 pub fn set_array(&mut self, name: &str, val: Vec<StrykeValue>) -> Result<(), StrykeError> {
2056 if let Some(aa) = self.find_atomic_array(name) {
2057 *aa.0.lock() = val;
2058 return Ok(());
2059 }
2060 if let Some(arc) = self.find_shared_array(name) {
2061 *arc.write() = val;
2062 return Ok(());
2063 }
2064 self.check_parallel_array_write(name)?;
2065 for frame in self.frames.iter_mut().rev() {
2066 if frame.has_array(name) {
2067 frame.set_array(name, val);
2068 return Ok(());
2069 }
2070 }
2071 self.frames[0].set_array(name, val);
2072 Ok(())
2073 }
2074
2075 #[inline]
2077 pub fn get_array_element(&self, name: &str, index: i64) -> StrykeValue {
2078 canon_main!(name);
2079 if let Some(aa) = self.find_atomic_array(name) {
2080 let arr = aa.0.lock();
2081 let idx = if index < 0 {
2082 (arr.len() as i64 + index) as usize
2083 } else {
2084 index as usize
2085 };
2086 return arr.get(idx).cloned().unwrap_or(StrykeValue::UNDEF);
2087 }
2088 if let Some(arc) = self.find_shared_array(name) {
2089 let arr = arc.read();
2090 let idx = if index < 0 {
2091 (arr.len() as i64 + index) as usize
2092 } else {
2093 index as usize
2094 };
2095 return arr.get(idx).cloned().unwrap_or(StrykeValue::UNDEF);
2096 }
2097 for frame in self.frames.iter().rev() {
2098 if let Some(arr) = frame.get_array(name) {
2099 let idx = if index < 0 {
2100 (arr.len() as i64 + index) as usize
2101 } else {
2102 index as usize
2103 };
2104 return arr.get(idx).cloned().unwrap_or(StrykeValue::UNDEF);
2105 }
2106 }
2107 StrykeValue::UNDEF
2108 }
2109
2110 pub fn set_array_element(
2111 &mut self,
2112 name: &str,
2113 index: i64,
2114 val: StrykeValue,
2115 ) -> Result<(), StrykeError> {
2116 let val = self.resolve_container_binding_ref(val);
2117 if let Some(aa) = self.find_atomic_array(name) {
2118 let mut arr = aa.0.lock();
2119 let idx = if index < 0 {
2120 (arr.len() as i64 + index).max(0) as usize
2121 } else {
2122 index as usize
2123 };
2124 if idx >= arr.len() {
2125 arr.resize(idx + 1, StrykeValue::UNDEF);
2126 }
2127 arr[idx] = val;
2128 return Ok(());
2129 }
2130 if let Some(arc) = self.find_shared_array(name) {
2131 let mut arr = arc.write();
2132 let idx = if index < 0 {
2133 (arr.len() as i64 + index).max(0) as usize
2134 } else {
2135 index as usize
2136 };
2137 if idx >= arr.len() {
2138 arr.resize(idx + 1, StrykeValue::UNDEF);
2139 }
2140 arr[idx] = val;
2141 return Ok(());
2142 }
2143 let arr = self.get_array_mut(name)?;
2144 let idx = if index < 0 {
2145 let len = arr.len() as i64;
2146 (len + index).max(0) as usize
2147 } else {
2148 index as usize
2149 };
2150 if idx >= arr.len() {
2151 arr.resize(idx + 1, StrykeValue::UNDEF);
2152 }
2153 arr[idx] = val;
2154 Ok(())
2155 }
2156
2157 pub fn exists_array_element(&self, name: &str, index: i64) -> bool {
2159 canon_main!(name);
2160 if let Some(aa) = self.find_atomic_array(name) {
2161 let arr = aa.0.lock();
2162 let idx = if index < 0 {
2163 (arr.len() as i64 + index) as usize
2164 } else {
2165 index as usize
2166 };
2167 return idx < arr.len();
2168 }
2169 for frame in self.frames.iter().rev() {
2170 if let Some(arr) = frame.get_array(name) {
2171 let idx = if index < 0 {
2172 (arr.len() as i64 + index) as usize
2173 } else {
2174 index as usize
2175 };
2176 return idx < arr.len();
2177 }
2178 }
2179 false
2180 }
2181
2182 pub fn delete_array_element(
2184 &mut self,
2185 name: &str,
2186 index: i64,
2187 ) -> Result<StrykeValue, StrykeError> {
2188 if let Some(aa) = self.find_atomic_array(name) {
2189 let mut arr = aa.0.lock();
2190 let idx = if index < 0 {
2191 (arr.len() as i64 + index) as usize
2192 } else {
2193 index as usize
2194 };
2195 if idx >= arr.len() {
2196 return Ok(StrykeValue::UNDEF);
2197 }
2198 let old = arr.get(idx).cloned().unwrap_or(StrykeValue::UNDEF);
2199 arr[idx] = StrykeValue::UNDEF;
2200 return Ok(old);
2201 }
2202 let arr = self.get_array_mut(name)?;
2203 let idx = if index < 0 {
2204 (arr.len() as i64 + index) as usize
2205 } else {
2206 index as usize
2207 };
2208 if idx >= arr.len() {
2209 return Ok(StrykeValue::UNDEF);
2210 }
2211 let old = arr.get(idx).cloned().unwrap_or(StrykeValue::UNDEF);
2212 arr[idx] = StrykeValue::UNDEF;
2213 Ok(old)
2214 }
2215
2216 #[inline]
2219 pub fn declare_hash(&mut self, name: &str, val: IndexMap<String, StrykeValue>) {
2220 self.declare_hash_frozen(name, val, false);
2221 }
2222
2223 pub fn declare_hash_frozen(
2224 &mut self,
2225 name: &str,
2226 val: IndexMap<String, StrykeValue>,
2227 frozen: bool,
2228 ) {
2229 canon_main!(name);
2230 if let Some(frame) = self.frames.last_mut() {
2231 frame.shared_hashes.retain(|(k, _)| k != name);
2233 frame.set_hash(name, val);
2234 if frozen {
2235 frame.frozen_hashes.insert(name.to_string());
2236 }
2237 }
2238 }
2239
2240 pub fn declare_hash_global(&mut self, name: &str, val: IndexMap<String, StrykeValue>) {
2242 canon_main!(name);
2243 if let Some(frame) = self.frames.first_mut() {
2244 frame.set_hash(name, val);
2245 }
2246 }
2247
2248 pub fn declare_hash_global_frozen(&mut self, name: &str, val: IndexMap<String, StrykeValue>) {
2250 canon_main!(name);
2251 if let Some(frame) = self.frames.first_mut() {
2252 frame.set_hash(name, val);
2253 frame.frozen_hashes.insert(name.to_string());
2254 }
2255 }
2256
2257 pub fn has_lexical_hash(&self, name: &str) -> bool {
2259 canon_main!(name);
2260 self.frames.iter().skip(1).any(|f| f.has_hash(name))
2261 }
2262
2263 pub fn any_frame_has_hash(&self, name: &str) -> bool {
2265 canon_main!(name);
2266 self.frames.iter().any(|f| f.has_hash(name))
2267 }
2268
2269 pub fn get_hash(&self, name: &str) -> IndexMap<String, StrykeValue> {
2270 if let Some(rest) = strip_main_prefix(name) {
2272 return self.get_hash(rest);
2273 }
2274 if let Some(ah) = self.find_atomic_hash(name) {
2275 return ah.0.lock().clone();
2276 }
2277 if let Some(arc) = self.find_shared_hash(name) {
2278 return arc.read().clone();
2279 }
2280 for frame in self.frames.iter().rev() {
2281 if let Some(val) = frame.get_hash(name) {
2282 return val.clone();
2283 }
2284 }
2285 IndexMap::new()
2286 }
2287
2288 fn resolve_hash_frame_idx(&self, name: &str) -> Option<usize> {
2289 if name.contains("::") {
2290 return Some(0);
2291 }
2292 (0..self.frames.len())
2293 .rev()
2294 .find(|&i| self.frames[i].has_hash(name))
2295 }
2296
2297 fn check_parallel_hash_write(&self, name: &str) -> Result<(), StrykeError> {
2298 if !self.parallel_guard
2299 || Self::parallel_skip_special_name(name)
2300 || Self::parallel_allowed_internal_hash(name)
2301 {
2302 return Ok(());
2303 }
2304 let baseline = self.parallel_guard_baseline;
2306 match self.resolve_hash_frame_idx(name) {
2307 None => Err(StrykeError::runtime(
2308 format!(
2309 "cannot modify undeclared hash `%{}` in a parallel block",
2310 name
2311 ),
2312 0,
2313 )),
2314 Some(idx) if idx < baseline => Err(StrykeError::runtime(
2315 format!(
2316 "cannot modify captured non-mysync hash `%{}` in a parallel block",
2317 name
2318 ),
2319 0,
2320 )),
2321 Some(_) => Ok(()),
2322 }
2323 }
2324
2325 pub fn get_hash_mut(
2326 &mut self,
2327 name: &str,
2328 ) -> Result<&mut IndexMap<String, StrykeValue>, StrykeError> {
2329 if self.find_atomic_hash(name).is_some() {
2330 return Err(StrykeError::runtime(
2331 "get_hash_mut: use atomic path for mysync hashes",
2332 0,
2333 ));
2334 }
2335 self.check_parallel_hash_write(name)?;
2336 let idx = self.resolve_hash_frame_idx(name).unwrap_or_default();
2337 let frame = &mut self.frames[idx];
2338 if frame.get_hash_mut(name).is_none() {
2339 frame.hashes.push((name.to_string(), IndexMap::new()));
2340 }
2341 Ok(frame.get_hash_mut(name).unwrap())
2342 }
2343
2344 pub fn set_hash(
2345 &mut self,
2346 name: &str,
2347 val: IndexMap<String, StrykeValue>,
2348 ) -> Result<(), StrykeError> {
2349 if let Some(ah) = self.find_atomic_hash(name) {
2350 *ah.0.lock() = val;
2351 return Ok(());
2352 }
2353 self.check_parallel_hash_write(name)?;
2354 for frame in self.frames.iter_mut().rev() {
2355 if frame.has_hash(name) {
2356 frame.set_hash(name, val);
2357 return Ok(());
2358 }
2359 }
2360 self.frames[0].set_hash(name, val);
2361 Ok(())
2362 }
2363
2364 #[inline]
2365 pub fn get_hash_element(&self, name: &str, key: &str) -> StrykeValue {
2366 canon_main!(name);
2367 if let Some(ah) = self.find_atomic_hash(name) {
2368 return ah.0.lock().get(key).cloned().unwrap_or(StrykeValue::UNDEF);
2369 }
2370 if let Some(arc) = self.find_shared_hash(name) {
2371 return arc.read().get(key).cloned().unwrap_or(StrykeValue::UNDEF);
2372 }
2373 for frame in self.frames.iter().rev() {
2374 if let Some(hash) = frame.get_hash(name) {
2375 return hash.get(key).cloned().unwrap_or(StrykeValue::UNDEF);
2376 }
2377 }
2378 StrykeValue::UNDEF
2379 }
2380
2381 pub fn atomic_hash_mutate(
2384 &mut self,
2385 name: &str,
2386 key: &str,
2387 f: impl FnOnce(&StrykeValue) -> StrykeValue,
2388 ) -> Result<StrykeValue, StrykeError> {
2389 if let Some(ah) = self.find_atomic_hash(name) {
2390 let mut guard = ah.0.lock();
2391 let old = guard.get(key).cloned().unwrap_or(StrykeValue::UNDEF);
2392 let new_val = f(&old);
2393 guard.insert(key.to_string(), new_val.clone());
2394 return Ok(new_val);
2395 }
2396 let old = self.get_hash_element(name, key);
2398 let new_val = f(&old);
2399 self.set_hash_element(name, key, new_val.clone())?;
2400 Ok(new_val)
2401 }
2402
2403 pub fn atomic_array_mutate(
2405 &mut self,
2406 name: &str,
2407 index: i64,
2408 f: impl FnOnce(&StrykeValue) -> StrykeValue,
2409 ) -> Result<StrykeValue, StrykeError> {
2410 if let Some(aa) = self.find_atomic_array(name) {
2411 let mut guard = aa.0.lock();
2412 let idx = if index < 0 {
2413 (guard.len() as i64 + index).max(0) as usize
2414 } else {
2415 index as usize
2416 };
2417 if idx >= guard.len() {
2418 guard.resize(idx + 1, StrykeValue::UNDEF);
2419 }
2420 let old = guard[idx].clone();
2421 let new_val = f(&old);
2422 guard[idx] = new_val.clone();
2423 return Ok(new_val);
2424 }
2425 let old = self.get_array_element(name, index);
2427 let new_val = f(&old);
2428 self.set_array_element(name, index, new_val.clone())?;
2429 Ok(new_val)
2430 }
2431
2432 pub fn set_hash_element(
2433 &mut self,
2434 name: &str,
2435 key: &str,
2436 val: StrykeValue,
2437 ) -> Result<(), StrykeError> {
2438 let val = self.resolve_container_binding_ref(val);
2439 if name == "SIG" {
2442 crate::perl_signal::install(key);
2443 }
2444 if name == "ENV" {
2449 std::env::set_var(key, val.to_string());
2453 }
2454 if let Some(ah) = self.find_atomic_hash(name) {
2455 ah.0.lock().insert(key.to_string(), val);
2456 return Ok(());
2457 }
2458 if let Some(arc) = self.find_shared_hash(name) {
2459 arc.write().insert(key.to_string(), val);
2460 return Ok(());
2461 }
2462 let hash = self.get_hash_mut(name)?;
2463 hash.insert(key.to_string(), val);
2464 Ok(())
2465 }
2466
2467 pub fn set_hash_int_times_range(
2471 &mut self,
2472 name: &str,
2473 start: i64,
2474 end: i64,
2475 k: i64,
2476 ) -> Result<(), StrykeError> {
2477 if end <= start {
2478 return Ok(());
2479 }
2480 let count = (end - start) as usize;
2481 if let Some(ah) = self.find_atomic_hash(name) {
2482 let mut g = ah.0.lock();
2483 g.reserve(count);
2484 let mut buf = itoa::Buffer::new();
2485 for i in start..end {
2486 let key = buf.format(i).to_owned();
2487 g.insert(key, StrykeValue::integer(i.wrapping_mul(k)));
2488 }
2489 return Ok(());
2490 }
2491 let hash = self.get_hash_mut(name)?;
2492 hash.reserve(count);
2493 let mut buf = itoa::Buffer::new();
2494 for i in start..end {
2495 let key = buf.format(i).to_owned();
2496 hash.insert(key, StrykeValue::integer(i.wrapping_mul(k)));
2497 }
2498 Ok(())
2499 }
2500
2501 pub fn delete_hash_element(
2502 &mut self,
2503 name: &str,
2504 key: &str,
2505 ) -> Result<StrykeValue, StrykeError> {
2506 canon_main!(name);
2507 if name == "ENV" {
2509 std::env::remove_var(key);
2510 }
2511 if let Some(ah) = self.find_atomic_hash(name) {
2512 return Ok(ah.0.lock().shift_remove(key).unwrap_or(StrykeValue::UNDEF));
2513 }
2514 let hash = self.get_hash_mut(name)?;
2515 Ok(hash.shift_remove(key).unwrap_or(StrykeValue::UNDEF))
2516 }
2517
2518 #[inline]
2519 pub fn exists_hash_element(&self, name: &str, key: &str) -> bool {
2520 canon_main!(name);
2521 if let Some(ah) = self.find_atomic_hash(name) {
2522 return ah.0.lock().contains_key(key);
2523 }
2524 for frame in self.frames.iter().rev() {
2525 if let Some(hash) = frame.get_hash(name) {
2526 return hash.contains_key(key);
2527 }
2528 }
2529 false
2530 }
2531
2532 #[inline]
2537 pub fn for_each_hash_value(&self, name: &str, mut visit: impl FnMut(&StrykeValue)) {
2538 canon_main!(name);
2539 if let Some(ah) = self.find_atomic_hash(name) {
2540 let g = ah.0.lock();
2541 for v in g.values() {
2542 visit(v);
2543 }
2544 return;
2545 }
2546 for frame in self.frames.iter().rev() {
2547 if let Some(hash) = frame.get_hash(name) {
2548 for v in hash.values() {
2549 visit(v);
2550 }
2551 return;
2552 }
2553 }
2554 }
2555
2556 pub fn frames_for_introspection(&self) -> Vec<(Vec<&str>, Vec<&str>, Vec<&str>)> {
2562 self.frames
2563 .iter()
2564 .map(|f| {
2565 let mut scalars: Vec<&str> = f.scalars.iter().map(|(n, _)| n.as_str()).collect();
2566 scalars.extend(f.scalar_slot_names.iter().filter_map(|opt| match opt {
2568 Some(n) if !n.is_empty() => Some(n.as_str()),
2569 _ => None,
2570 }));
2571 let mut arrays: Vec<&str> = f.arrays.iter().map(|(n, _)| n.as_str()).collect();
2572 arrays.extend(f.atomic_arrays.iter().map(|(n, _)| n.as_str()));
2573 arrays.extend(f.shared_arrays.iter().map(|(n, _)| n.as_str()));
2574 let mut hashes: Vec<&str> = f.hashes.iter().map(|(n, _)| n.as_str()).collect();
2575 hashes.extend(f.atomic_hashes.iter().map(|(n, _)| n.as_str()));
2576 hashes.extend(f.shared_hashes.iter().map(|(n, _)| n.as_str()));
2577 scalars.sort_unstable();
2578 arrays.sort_unstable();
2579 hashes.sort_unstable();
2580 (scalars, arrays, hashes)
2581 })
2582 .collect()
2583 }
2584
2585 pub fn parameters_pairs(&self) -> Vec<(String, &'static str)> {
2591 let mut seen: HashSet<String> = HashSet::new();
2592 let mut out: Vec<(String, &'static str)> = Vec::new();
2593 for frame in self.frames.iter().rev() {
2596 for n in frame.scalar_slot_names.iter().flatten() {
2600 if !n.is_empty() {
2601 let s = format!("${}", n);
2602 if seen.insert(s.clone()) {
2603 out.push((s, "scalar"));
2604 }
2605 }
2606 }
2607 for (name, _) in &frame.scalars {
2608 let s = format!("${}", name);
2609 if seen.insert(s.clone()) {
2610 out.push((s, "scalar"));
2611 }
2612 }
2613 for (name, _) in &frame.arrays {
2614 let s = format!("@{}", name);
2615 if seen.insert(s.clone()) {
2616 out.push((s, "array"));
2617 }
2618 }
2619 for (name, _) in &frame.hashes {
2620 let s = format!("%{}", name);
2621 if seen.insert(s.clone()) {
2622 out.push((s, "hash"));
2623 }
2624 }
2625 for (name, _) in &frame.atomic_arrays {
2626 let s = format!("@{}", name);
2627 if seen.insert(s.clone()) {
2628 out.push((s, "atomic_array"));
2629 }
2630 }
2631 for (name, _) in &frame.atomic_hashes {
2632 let s = format!("%{}", name);
2633 if seen.insert(s.clone()) {
2634 out.push((s, "atomic_hash"));
2635 }
2636 }
2637 for (name, _) in &frame.shared_arrays {
2638 let s = format!("@{}", name);
2639 if seen.insert(s.clone()) {
2640 out.push((s, "shared_array"));
2641 }
2642 }
2643 for (name, _) in &frame.shared_hashes {
2644 let s = format!("%{}", name);
2645 if seen.insert(s.clone()) {
2646 out.push((s, "shared_hash"));
2647 }
2648 }
2649 }
2650 out.sort_by(|a, b| a.0.cmp(&b.0));
2651 out
2652 }
2653
2654 pub fn repl_binding_names(&self) -> Vec<String> {
2656 let mut seen = HashSet::new();
2657 let mut out = Vec::new();
2658 for frame in &self.frames {
2659 for (name, _) in &frame.scalars {
2660 let s = format!("${}", name);
2661 if seen.insert(s.clone()) {
2662 out.push(s);
2663 }
2664 }
2665 for (name, _) in &frame.arrays {
2666 let s = format!("@{}", name);
2667 if seen.insert(s.clone()) {
2668 out.push(s);
2669 }
2670 }
2671 for (name, _) in &frame.hashes {
2672 let s = format!("%{}", name);
2673 if seen.insert(s.clone()) {
2674 out.push(s);
2675 }
2676 }
2677 for (name, _) in &frame.atomic_arrays {
2678 let s = format!("@{}", name);
2679 if seen.insert(s.clone()) {
2680 out.push(s);
2681 }
2682 }
2683 for (name, _) in &frame.atomic_hashes {
2684 let s = format!("%{}", name);
2685 if seen.insert(s.clone()) {
2686 out.push(s);
2687 }
2688 }
2689 }
2690 out.sort();
2691 out
2692 }
2693
2694 pub fn capture(&mut self) -> Vec<(String, StrykeValue)> {
2695 let by_ref = crate::compat_mode();
2708 let mut captured = Vec::new();
2709 let mut seen_hash_scalars: HashSet<String> = HashSet::new();
2724 for frame in self.frames.iter_mut().rev() {
2725 for (k, v) in &mut frame.scalars {
2726 if !seen_hash_scalars.insert(k.clone()) {
2727 continue;
2728 }
2729 if v.as_capture_cell().is_some() || v.as_scalar_ref().is_some() {
2730 captured.push((format!("${}", k), v.clone()));
2731 } else if v.is_simple_scalar() {
2732 let wrapped = StrykeValue::capture_cell(Arc::new(RwLock::new(v.clone())));
2733 *v = wrapped.clone();
2734 captured.push((format!("${}", k), wrapped));
2735 } else {
2736 captured.push((format!("${}", k), v.clone()));
2737 }
2738 }
2739 }
2740 for frame in &mut self.frames {
2741 for (i, v) in frame.scalar_slots.iter_mut().enumerate() {
2749 if let Some(Some(name)) = frame.scalar_slot_names.get(i) {
2750 if !name.is_empty() && seen_hash_scalars.contains(name) {
2760 continue;
2761 }
2762 let cap_val = if v.as_capture_cell().is_some() || v.as_scalar_ref().is_some() {
2763 v.clone()
2764 } else {
2765 let wrapped = StrykeValue::capture_cell(Arc::new(RwLock::new(v.clone())));
2766 if by_ref {
2767 *v = wrapped.clone();
2768 }
2769 wrapped
2770 };
2771 captured.push((format!("$slot:{}:{}", i, name), cap_val));
2772 }
2773 }
2774 for (k, v) in &frame.arrays {
2775 if capture_skip_bootstrap_array(k) {
2776 continue;
2777 }
2778 if frame.frozen_arrays.contains(k) {
2779 captured.push((format!("@frozen:{}", k), StrykeValue::array(v.clone())));
2780 } else {
2781 captured.push((format!("@{}", k), StrykeValue::array(v.clone())));
2782 }
2783 }
2784 for (k, v) in &frame.hashes {
2785 if capture_skip_bootstrap_hash(k) {
2786 continue;
2787 }
2788 if frame.frozen_hashes.contains(k) {
2789 captured.push((format!("%frozen:{}", k), StrykeValue::hash(v.clone())));
2790 } else {
2791 captured.push((format!("%{}", k), StrykeValue::hash(v.clone())));
2792 }
2793 }
2794 for (k, _aa) in &frame.atomic_arrays {
2795 captured.push((
2796 format!("@sync_{}", k),
2797 StrykeValue::atomic(Arc::new(Mutex::new(StrykeValue::string(String::new())))),
2798 ));
2799 }
2800 for (k, _ah) in &frame.atomic_hashes {
2801 captured.push((
2802 format!("%sync_{}", k),
2803 StrykeValue::atomic(Arc::new(Mutex::new(StrykeValue::string(String::new())))),
2804 ));
2805 }
2806 }
2807 captured
2808 }
2809
2810 pub fn capture_with_atomics(&self) -> ScopeCaptureWithAtomics {
2812 let mut scalars = Vec::new();
2813 let mut arrays = Vec::new();
2814 let mut hashes = Vec::new();
2815 for frame in &self.frames {
2816 for (k, v) in &frame.scalars {
2817 scalars.push((format!("${}", k), v.clone()));
2818 }
2819 for (i, v) in frame.scalar_slots.iter().enumerate() {
2820 if let Some(Some(name)) = frame.scalar_slot_names.get(i) {
2821 scalars.push((format!("$slot:{}:{}", i, name), v.clone()));
2822 }
2823 }
2824 for (k, v) in &frame.arrays {
2825 if capture_skip_bootstrap_array(k) {
2826 continue;
2827 }
2828 if frame.frozen_arrays.contains(k) {
2829 scalars.push((format!("@frozen:{}", k), StrykeValue::array(v.clone())));
2830 } else {
2831 scalars.push((format!("@{}", k), StrykeValue::array(v.clone())));
2832 }
2833 }
2834 for (k, v) in &frame.hashes {
2835 if capture_skip_bootstrap_hash(k) {
2836 continue;
2837 }
2838 if frame.frozen_hashes.contains(k) {
2839 scalars.push((format!("%frozen:{}", k), StrykeValue::hash(v.clone())));
2840 } else {
2841 scalars.push((format!("%{}", k), StrykeValue::hash(v.clone())));
2842 }
2843 }
2844 for (k, aa) in &frame.atomic_arrays {
2845 arrays.push((k.clone(), aa.clone()));
2846 }
2847 for (k, ah) in &frame.atomic_hashes {
2848 hashes.push((k.clone(), ah.clone()));
2849 }
2850 }
2851 (scalars, arrays, hashes)
2852 }
2853
2854 pub fn restore_capture(&mut self, captured: &[(String, StrykeValue)]) {
2855 for (name, val) in captured {
2856 if let Some(rest) = name.strip_prefix("$slot:") {
2857 if let Some(colon) = rest.find(':') {
2863 let idx: usize = rest[..colon].parse().unwrap_or(0);
2864 let sname = &rest[colon + 1..];
2865 self.declare_scalar_slot(idx as u8, val.clone(), Some(sname));
2866 }
2867 } else if let Some(stripped) = name.strip_prefix('$') {
2868 self.declare_scalar(stripped, val.clone());
2869 if let Some(slot) = parse_positional_topic_slot(stripped) {
2876 if slot > self.max_active_slot {
2877 self.max_active_slot = slot;
2878 }
2879 }
2880 } else if let Some(rest) = name.strip_prefix("@frozen:") {
2881 let arr = val.as_array_vec().unwrap_or_else(|| val.to_list());
2882 self.declare_array_frozen(rest, arr, true);
2883 } else if let Some(rest) = name.strip_prefix("%frozen:") {
2884 if let Some(h) = val.as_hash_map() {
2885 self.declare_hash_frozen(rest, h.clone(), true);
2886 }
2887 } else if let Some(rest) = name.strip_prefix('@') {
2888 if rest.starts_with("sync_") {
2889 continue;
2890 }
2891 let arr = val.as_array_vec().unwrap_or_else(|| val.to_list());
2892 self.declare_array(rest, arr);
2893 } else if let Some(rest) = name.strip_prefix('%') {
2894 if rest.starts_with("sync_") {
2895 continue;
2896 }
2897 if let Some(h) = val.as_hash_map() {
2898 self.declare_hash(rest, h.clone());
2899 }
2900 }
2901 }
2902 }
2903
2904 pub fn restore_atomics(
2906 &mut self,
2907 arrays: &[(String, AtomicArray)],
2908 hashes: &[(String, AtomicHash)],
2909 ) {
2910 if let Some(frame) = self.frames.last_mut() {
2911 for (name, aa) in arrays {
2912 frame.atomic_arrays.push((name.clone(), aa.clone()));
2913 }
2914 for (name, ah) in hashes {
2915 frame.atomic_hashes.push((name.clone(), ah.clone()));
2916 }
2917 }
2918 }
2919}
2920
2921#[cfg(test)]
2922mod tests {
2923 use super::*;
2924 use crate::value::StrykeValue;
2925
2926 #[test]
2927 fn missing_scalar_is_undef() {
2928 let s = Scope::new();
2929 assert!(s.get_scalar("not_declared").is_undef());
2930 }
2931
2932 #[test]
2933 fn inner_frame_shadows_outer_scalar() {
2934 let mut s = Scope::new();
2935 s.declare_scalar("a", StrykeValue::integer(1));
2936 s.push_frame();
2937 s.declare_scalar("a", StrykeValue::integer(2));
2938 assert_eq!(s.get_scalar("a").to_int(), 2);
2939 s.pop_frame();
2940 assert_eq!(s.get_scalar("a").to_int(), 1);
2941 }
2942
2943 #[test]
2944 fn set_scalar_updates_innermost_binding() {
2945 let mut s = Scope::new();
2946 s.declare_scalar("a", StrykeValue::integer(1));
2947 s.push_frame();
2948 s.declare_scalar("a", StrykeValue::integer(2));
2949 let _ = s.set_scalar("a", StrykeValue::integer(99));
2950 assert_eq!(s.get_scalar("a").to_int(), 99);
2951 s.pop_frame();
2952 assert_eq!(s.get_scalar("a").to_int(), 1);
2953 }
2954
2955 #[test]
2956 fn array_negative_index_reads_from_end() {
2957 let mut s = Scope::new();
2958 s.declare_array(
2959 "a",
2960 vec![
2961 StrykeValue::integer(10),
2962 StrykeValue::integer(20),
2963 StrykeValue::integer(30),
2964 ],
2965 );
2966 assert_eq!(s.get_array_element("a", -1).to_int(), 30);
2967 }
2968
2969 #[test]
2970 fn set_array_element_extends_array_with_undef_gaps() {
2971 let mut s = Scope::new();
2972 s.declare_array("a", vec![]);
2973 s.set_array_element("a", 2, StrykeValue::integer(7))
2974 .unwrap();
2975 assert_eq!(s.get_array_element("a", 2).to_int(), 7);
2976 assert!(s.get_array_element("a", 0).is_undef());
2977 }
2978
2979 #[test]
2980 fn capture_restore_roundtrip_scalar() {
2981 let mut s = Scope::new();
2982 s.declare_scalar("n", StrykeValue::integer(42));
2983 let cap = s.capture();
2984 let mut t = Scope::new();
2985 t.restore_capture(&cap);
2986 assert_eq!(t.get_scalar("n").to_int(), 42);
2987 }
2988
2989 #[test]
2990 fn capture_restore_roundtrip_lexical_array_and_hash() {
2991 let mut s = Scope::new();
2992 s.declare_array("a", vec![StrykeValue::integer(1), StrykeValue::integer(2)]);
2993 let mut m = IndexMap::new();
2994 m.insert("k".to_string(), StrykeValue::integer(99));
2995 s.declare_hash("h", m);
2996 let cap = s.capture();
2997 let mut t = Scope::new();
2998 t.restore_capture(&cap);
2999 assert_eq!(t.get_array_element("a", 1).to_int(), 2);
3000 assert_eq!(t.get_hash_element("h", "k").to_int(), 99);
3001 }
3002
3003 #[test]
3004 fn hash_get_set_delete_exists() {
3005 let mut s = Scope::new();
3006 let mut m = IndexMap::new();
3007 m.insert("k".to_string(), StrykeValue::integer(1));
3008 s.declare_hash("h", m);
3009 assert_eq!(s.get_hash_element("h", "k").to_int(), 1);
3010 assert!(s.exists_hash_element("h", "k"));
3011 s.set_hash_element("h", "k", StrykeValue::integer(99))
3012 .unwrap();
3013 assert_eq!(s.get_hash_element("h", "k").to_int(), 99);
3014 let del = s.delete_hash_element("h", "k").unwrap();
3015 assert_eq!(del.to_int(), 99);
3016 assert!(!s.exists_hash_element("h", "k"));
3017 }
3018
3019 #[test]
3020 fn inner_frame_shadows_outer_hash_name() {
3021 let mut s = Scope::new();
3022 let mut outer = IndexMap::new();
3023 outer.insert("k".to_string(), StrykeValue::integer(1));
3024 s.declare_hash("h", outer);
3025 s.push_frame();
3026 let mut inner = IndexMap::new();
3027 inner.insert("k".to_string(), StrykeValue::integer(2));
3028 s.declare_hash("h", inner);
3029 assert_eq!(s.get_hash_element("h", "k").to_int(), 2);
3030 s.pop_frame();
3031 assert_eq!(s.get_hash_element("h", "k").to_int(), 1);
3032 }
3033
3034 #[test]
3035 fn inner_frame_shadows_outer_array_name() {
3036 let mut s = Scope::new();
3037 s.declare_array("a", vec![StrykeValue::integer(1)]);
3038 s.push_frame();
3039 s.declare_array("a", vec![StrykeValue::integer(2), StrykeValue::integer(3)]);
3040 assert_eq!(s.get_array_element("a", 1).to_int(), 3);
3041 s.pop_frame();
3042 assert_eq!(s.get_array_element("a", 0).to_int(), 1);
3043 }
3044
3045 #[test]
3046 fn pop_frame_never_removes_global_frame() {
3047 let mut s = Scope::new();
3048 s.declare_scalar("x", StrykeValue::integer(1));
3049 s.pop_frame();
3050 s.pop_frame();
3051 assert_eq!(s.get_scalar("x").to_int(), 1);
3052 }
3053
3054 #[test]
3055 fn empty_array_declared_has_zero_length() {
3056 let mut s = Scope::new();
3057 s.declare_array("a", vec![]);
3058 assert_eq!(s.get_array("a").len(), 0);
3059 }
3060
3061 #[test]
3062 fn depth_increments_with_push_frame() {
3063 let mut s = Scope::new();
3064 let d0 = s.depth();
3065 s.push_frame();
3066 assert_eq!(s.depth(), d0 + 1);
3067 s.pop_frame();
3068 assert_eq!(s.depth(), d0);
3069 }
3070
3071 #[test]
3072 fn pop_to_depth_unwinds_to_target() {
3073 let mut s = Scope::new();
3074 s.push_frame();
3075 s.push_frame();
3076 let target = s.depth() - 1;
3077 s.pop_to_depth(target);
3078 assert_eq!(s.depth(), target);
3079 }
3080
3081 #[test]
3082 fn array_len_and_push_pop_roundtrip() {
3083 let mut s = Scope::new();
3084 s.declare_array("a", vec![]);
3085 assert_eq!(s.array_len("a"), 0);
3086 s.push_to_array("a", StrykeValue::integer(1)).unwrap();
3087 s.push_to_array("a", StrykeValue::integer(2)).unwrap();
3088 assert_eq!(s.array_len("a"), 2);
3089 assert_eq!(s.pop_from_array("a").unwrap().to_int(), 2);
3090 assert_eq!(s.pop_from_array("a").unwrap().to_int(), 1);
3091 assert!(s.pop_from_array("a").unwrap().is_undef());
3092 }
3093
3094 #[test]
3095 fn shift_from_array_drops_front() {
3096 let mut s = Scope::new();
3097 s.declare_array("a", vec![StrykeValue::integer(1), StrykeValue::integer(2)]);
3098 assert_eq!(s.shift_from_array("a").unwrap().to_int(), 1);
3099 assert_eq!(s.array_len("a"), 1);
3100 }
3101
3102 #[test]
3103 fn atomic_mutate_increments_wrapped_scalar() {
3104 use parking_lot::Mutex;
3105 use std::sync::Arc;
3106 let mut s = Scope::new();
3107 s.declare_scalar(
3108 "n",
3109 StrykeValue::atomic(Arc::new(Mutex::new(StrykeValue::integer(10)))),
3110 );
3111 let v = s
3112 .atomic_mutate("n", |old| StrykeValue::integer(old.to_int() + 5))
3113 .expect("atomic_mutate on atomic-backed scalar must not fail");
3114 assert_eq!(v.to_int(), 15);
3115 assert_eq!(s.get_scalar("n").to_int(), 15);
3116 }
3117
3118 #[test]
3119 fn atomic_mutate_post_returns_old_value() {
3120 use parking_lot::Mutex;
3121 use std::sync::Arc;
3122 let mut s = Scope::new();
3123 s.declare_scalar(
3124 "n",
3125 StrykeValue::atomic(Arc::new(Mutex::new(StrykeValue::integer(7)))),
3126 );
3127 let old = s
3128 .atomic_mutate_post("n", |v| StrykeValue::integer(v.to_int() + 1))
3129 .expect("atomic_mutate_post on atomic-backed scalar must not fail");
3130 assert_eq!(old.to_int(), 7);
3131 assert_eq!(s.get_scalar("n").to_int(), 8);
3132 }
3133
3134 #[test]
3135 fn get_scalar_raw_keeps_atomic_wrapper() {
3136 use parking_lot::Mutex;
3137 use std::sync::Arc;
3138 let mut s = Scope::new();
3139 s.declare_scalar(
3140 "n",
3141 StrykeValue::atomic(Arc::new(Mutex::new(StrykeValue::integer(3)))),
3142 );
3143 assert!(s.get_scalar_raw("n").is_atomic());
3144 assert!(!s.get_scalar("n").is_atomic());
3145 }
3146
3147 #[test]
3148 fn missing_array_element_is_undef() {
3149 let mut s = Scope::new();
3150 s.declare_array("a", vec![StrykeValue::integer(1)]);
3151 assert!(s.get_array_element("a", 99).is_undef());
3152 }
3153
3154 #[test]
3155 fn restore_atomics_puts_atomic_containers_in_frame() {
3156 use indexmap::IndexMap;
3157 use parking_lot::Mutex;
3158 use std::sync::Arc;
3159 let mut s = Scope::new();
3160 let aa = AtomicArray(Arc::new(Mutex::new(vec![StrykeValue::integer(1)])));
3161 let ah = AtomicHash(Arc::new(Mutex::new(IndexMap::new())));
3162 s.restore_atomics(&[("ax".into(), aa.clone())], &[("hx".into(), ah.clone())]);
3163 assert_eq!(s.get_array_element("ax", 0).to_int(), 1);
3164 assert_eq!(s.array_len("ax"), 1);
3165 s.set_hash_element("hx", "k", StrykeValue::integer(2))
3166 .unwrap();
3167 assert_eq!(s.get_hash_element("hx", "k").to_int(), 2);
3168 }
3169
3170 #[test]
3178 fn topic_alias_pairs_underscore_with_zero() {
3179 assert_eq!(topic_alias("_").as_deref(), Some("_0"));
3180 assert_eq!(topic_alias("_0").as_deref(), Some("_"));
3181 }
3182
3183 #[test]
3184 fn topic_alias_pairs_outer_chain_with_zero_form() {
3185 assert_eq!(topic_alias("_<").as_deref(), Some("_0<"));
3187 assert_eq!(topic_alias("_0<").as_deref(), Some("_<"));
3188 assert_eq!(topic_alias("_<<<").as_deref(), Some("_0<<<"));
3189 assert_eq!(topic_alias("_0<<<").as_deref(), Some("_<<<"));
3190 }
3191
3192 #[test]
3193 fn topic_alias_has_no_pair_for_other_positionals() {
3194 assert!(topic_alias("_1").is_none());
3196 assert!(topic_alias("_2").is_none());
3197 assert!(topic_alias("_42").is_none());
3198 assert!(topic_alias("_<5").is_none());
3200 assert!(topic_alias("foo").is_none());
3202 assert!(topic_alias("_foo").is_none());
3203 }
3204
3205 #[test]
3208 fn positional_topic_slot_parses_underscore_n() {
3209 assert_eq!(parse_positional_topic_slot("_1"), Some(1));
3212 assert_eq!(parse_positional_topic_slot("_2"), Some(2));
3213 assert_eq!(parse_positional_topic_slot("_42"), Some(42));
3214 }
3215
3216 #[test]
3217 fn positional_topic_slot_rejects_non_positional_names() {
3218 assert!(
3219 parse_positional_topic_slot("_").is_none(),
3220 "bare _ has no slot"
3221 );
3222 assert!(
3223 parse_positional_topic_slot("_0").is_none(),
3224 "_0 is the topic alias, not positional"
3225 );
3226 assert!(parse_positional_topic_slot("_foo").is_none(), "named");
3227 assert!(parse_positional_topic_slot("foo").is_none());
3228 assert!(parse_positional_topic_slot("").is_none());
3229 }
3230}