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_raw(name, val);
986 if frozen {
987 frame.frozen_scalars.insert(name.to_string());
988 } else {
989 frame.frozen_scalars.remove(name);
996 }
997 if let Some(t) = ty {
998 frame.typed_scalars.insert(name.to_string(), t);
999 }
1000 }
1001 Ok(())
1002 }
1003
1004 pub fn is_scalar_frozen(&self, name: &str) -> bool {
1006 for frame in self.frames.iter().rev() {
1007 if frame.has_scalar(name) {
1008 return frame.frozen_scalars.contains(name);
1009 }
1010 }
1011 false
1012 }
1013
1014 pub fn is_array_frozen(&self, name: &str) -> bool {
1016 for frame in self.frames.iter().rev() {
1017 if frame.has_array(name) {
1018 return frame.frozen_arrays.contains(name);
1019 }
1020 }
1021 false
1022 }
1023
1024 pub fn is_hash_frozen(&self, name: &str) -> bool {
1026 for frame in self.frames.iter().rev() {
1027 if frame.has_hash(name) {
1028 return frame.frozen_hashes.contains(name);
1029 }
1030 }
1031 false
1032 }
1033
1034 pub fn check_frozen(&self, sigil: &str, name: &str) -> Option<&'static str> {
1036 match sigil {
1037 "$" => {
1038 if self.is_scalar_frozen(name) {
1039 Some("scalar")
1040 } else {
1041 None
1042 }
1043 }
1044 "@" => {
1045 if self.is_array_frozen(name) {
1046 Some("array")
1047 } else {
1048 None
1049 }
1050 }
1051 "%" => {
1052 if self.is_hash_frozen(name) {
1053 Some("hash")
1054 } else {
1055 None
1056 }
1057 }
1058 _ => None,
1059 }
1060 }
1061 #[inline]
1063 pub fn get_scalar(&self, name: &str) -> StrykeValue {
1064 if let Some(rest) = strip_main_prefix(name) {
1066 return self.get_scalar(rest);
1067 }
1068 for frame in self.frames.iter().rev() {
1069 if let Some(val) = frame.get_scalar(name) {
1070 if let Some(arc) = val.as_atomic_arc() {
1072 return arc.lock().clone();
1073 }
1074 if let Some(arc) = val.as_capture_cell() {
1077 return arc.read().clone();
1078 }
1079 return val.clone();
1086 }
1087 }
1088 StrykeValue::UNDEF
1089 }
1090
1091 #[inline]
1100 pub(crate) fn is_topic_variant_name(name: &str) -> bool {
1101 let bytes = name.as_bytes();
1102 if bytes.is_empty() || bytes[0] != b'_' {
1103 return false;
1104 }
1105 let mut i = 1;
1106 while i < bytes.len() && bytes[i].is_ascii_digit() {
1107 i += 1;
1108 }
1109 while i < bytes.len() && bytes[i] == b'<' {
1110 i += 1;
1111 }
1112 i == bytes.len()
1113 }
1114
1115 #[inline]
1117 pub fn scalar_binding_exists(&self, name: &str) -> bool {
1118 canon_main!(name);
1119 for frame in self.frames.iter().rev() {
1120 if frame.has_scalar(name) {
1121 return true;
1122 }
1123 }
1124 false
1125 }
1126
1127 pub fn all_scalar_names(&self) -> Vec<String> {
1129 let mut names = Vec::new();
1130 for frame in &self.frames {
1131 for (name, _) in &frame.scalars {
1132 if !names.contains(name) {
1133 names.push(name.clone());
1134 }
1135 }
1136 for name in frame.scalar_slot_names.iter().flatten() {
1137 if !names.contains(name) {
1138 names.push(name.clone());
1139 }
1140 }
1141 }
1142 names
1143 }
1144
1145 pub fn all_array_names(&self) -> Vec<String> {
1147 let mut names = Vec::new();
1148 for frame in &self.frames {
1149 for (name, _) in &frame.arrays {
1150 if !names.contains(name) {
1151 names.push(name.clone());
1152 }
1153 }
1154 for (name, _) in &frame.shared_arrays {
1155 if !names.contains(name) {
1156 names.push(name.clone());
1157 }
1158 }
1159 for (name, _) in &frame.atomic_arrays {
1160 if !names.contains(name) {
1161 names.push(name.clone());
1162 }
1163 }
1164 }
1165 names
1166 }
1167
1168 pub fn all_hash_names(&self) -> Vec<String> {
1170 let mut names = Vec::new();
1171 for frame in &self.frames {
1172 for (name, _) in &frame.hashes {
1173 if !names.contains(name) {
1174 names.push(name.clone());
1175 }
1176 }
1177 for (name, _) in &frame.shared_hashes {
1178 if !names.contains(name) {
1179 names.push(name.clone());
1180 }
1181 }
1182 for (name, _) in &frame.atomic_hashes {
1183 if !names.contains(name) {
1184 names.push(name.clone());
1185 }
1186 }
1187 }
1188 names
1189 }
1190
1191 #[inline]
1193 pub fn array_binding_exists(&self, name: &str) -> bool {
1194 canon_main!(name);
1195 if self.find_atomic_array(name).is_some() {
1196 return true;
1197 }
1198 for frame in self.frames.iter().rev() {
1199 if frame.has_array(name) {
1200 return true;
1201 }
1202 }
1203 false
1204 }
1205
1206 #[inline]
1208 pub fn hash_binding_exists(&self, name: &str) -> bool {
1209 if let Some(rest) = strip_main_prefix(name) {
1210 return self.hash_binding_exists(rest);
1211 }
1212 if self.find_atomic_hash(name).is_some() {
1213 return true;
1214 }
1215 for frame in self.frames.iter().rev() {
1216 if frame.has_hash(name) {
1217 return true;
1218 }
1219 }
1220 false
1221 }
1222
1223 #[inline]
1226 pub fn get_scalar_raw(&self, name: &str) -> StrykeValue {
1227 if let Some(rest) = strip_main_prefix(name) {
1228 return self.get_scalar_raw(rest);
1229 }
1230 for frame in self.frames.iter().rev() {
1231 if let Some(val) = frame.get_scalar(name) {
1232 return val.clone();
1233 }
1234 }
1235 StrykeValue::UNDEF
1236 }
1237
1238 pub fn atomic_mutate(
1244 &mut self,
1245 name: &str,
1246 f: impl FnOnce(&StrykeValue) -> StrykeValue,
1247 ) -> Result<StrykeValue, StrykeError> {
1248 for frame in self.frames.iter().rev() {
1249 if let Some(v) = frame.get_scalar(name) {
1250 if let Some(arc) = v.as_atomic_arc() {
1251 let mut guard = arc.lock();
1252 let old = guard.clone();
1253 let new_val = f(&guard);
1254 *guard = new_val.clone();
1255 crate::parallel_trace::emit_scalar_mutation(name, &old, &new_val);
1256 return Ok(new_val);
1257 }
1258 }
1259 }
1260 let old = self.get_scalar(name);
1263 let new_val = f(&old);
1264 self.set_scalar(name, new_val.clone())?;
1265 Ok(new_val)
1266 }
1267
1268 pub fn atomic_mutate_post(
1272 &mut self,
1273 name: &str,
1274 f: impl FnOnce(&StrykeValue) -> StrykeValue,
1275 ) -> Result<StrykeValue, StrykeError> {
1276 for frame in self.frames.iter().rev() {
1277 if let Some(v) = frame.get_scalar(name) {
1278 if let Some(arc) = v.as_atomic_arc() {
1279 let mut guard = arc.lock();
1280 let old = guard.clone();
1281 let new_val = f(&old);
1282 *guard = new_val.clone();
1283 crate::parallel_trace::emit_scalar_mutation(name, &old, &new_val);
1284 return Ok(old);
1285 }
1286 }
1287 }
1288 let old = self.get_scalar(name);
1290 self.set_scalar(name, f(&old))?;
1291 Ok(old)
1292 }
1293
1294 #[inline]
1301 pub fn scalar_concat_inplace(
1302 &mut self,
1303 name: &str,
1304 rhs: &StrykeValue,
1305 ) -> Result<StrykeValue, StrykeError> {
1306 canon_main!(name);
1307 self.check_parallel_scalar_write(name)?;
1308 for frame in self.frames.iter_mut().rev() {
1309 if let Some(entry) = frame.scalars.iter_mut().find(|(k, _)| k == name) {
1310 if let Some(atomic_arc) = entry.1.as_atomic_arc() {
1313 let mut guard = atomic_arc.lock();
1314 let inner = std::mem::replace(&mut *guard, StrykeValue::UNDEF);
1315 let new_val = inner.concat_append_owned(rhs);
1316 *guard = new_val.shallow_clone();
1317 return Ok(new_val);
1318 }
1319 if entry.1.try_concat_append_inplace(rhs) {
1322 return Ok(entry.1.shallow_clone());
1323 }
1324 let new_val =
1327 std::mem::replace(&mut entry.1, StrykeValue::UNDEF).concat_append_owned(rhs);
1328 entry.1 = new_val.shallow_clone();
1329 return Ok(new_val);
1330 }
1331 }
1332 let val = StrykeValue::UNDEF.concat_append_owned(rhs);
1334 self.frames[0].set_scalar(name, val.shallow_clone());
1335 Ok(val)
1336 }
1337 #[inline]
1339 pub fn set_scalar(&mut self, name: &str, val: StrykeValue) -> Result<(), StrykeError> {
1340 if let Some(rest) = strip_main_prefix(name) {
1341 return self.set_scalar(rest, val);
1342 }
1343 self.check_parallel_scalar_write(name)?;
1344 if Self::is_topic_variant_name(name) {
1351 if let Some(frame) = self.frames.last_mut() {
1352 frame.set_scalar_raw(name, val.clone());
1353 if let Some(alias) = topic_alias(name) {
1361 frame.set_scalar_raw(&alias, val);
1362 }
1363 }
1364 return Ok(());
1365 }
1366 for frame in self.frames.iter_mut().rev() {
1367 if let Some(v) = frame.get_scalar(name) {
1369 if let Some(arc) = v.as_atomic_arc() {
1370 let mut guard = arc.lock();
1371 let old = guard.clone();
1372 *guard = val.clone();
1373 crate::parallel_trace::emit_scalar_mutation(name, &old, &val);
1374 return Ok(());
1375 }
1376 if let Some(arc) = v.as_capture_cell() {
1378 *arc.write() = val;
1379 return Ok(());
1380 }
1381 }
1382 if frame.has_scalar(name) {
1383 if let Some(ty) = frame.typed_scalars.get(name) {
1384 ty.check_value(&val).map_err(|msg| {
1385 StrykeError::type_error(format!("`${}`: {}", name, msg), 0)
1386 })?;
1387 }
1388 frame.set_scalar(name, val);
1389 return Ok(());
1390 }
1391 }
1392 self.frames[0].set_scalar(name, val);
1393 Ok(())
1394 }
1395
1396 #[inline]
1402 fn topic_slot_key(slot: usize, level: usize) -> String {
1403 debug_assert!(level <= 5);
1404 if slot == 0 {
1405 if level == 0 {
1406 "_".to_string()
1407 } else {
1408 format!("_{}", "<".repeat(level))
1409 }
1410 } else if level == 0 {
1411 format!("_{}", slot)
1412 } else {
1413 format!("_{}{}", slot, "<".repeat(level))
1414 }
1415 }
1416
1417 #[inline]
1420 fn topic_slot_alias_key(slot: usize, level: usize) -> Option<String> {
1421 if slot != 0 {
1422 return None;
1423 }
1424 Some(if level == 0 {
1425 "_0".to_string()
1426 } else {
1427 format!("_0{}", "<".repeat(level))
1428 })
1429 }
1430
1431 #[inline]
1437 fn declare_topic_slot(&mut self, slot: usize, level: usize, val: StrykeValue) {
1438 if let Some(frame) = self.frames.last_mut() {
1445 let key = Self::topic_slot_key(slot, level);
1446 frame.set_scalar_raw(&key, val.clone());
1447 if let Some(alias) = Self::topic_slot_alias_key(slot, level) {
1448 frame.set_scalar_raw(&alias, val);
1449 }
1450 }
1451 }
1452
1453 #[inline]
1478 pub fn set_topic(&mut self, val: StrykeValue) {
1479 let already_shifted = self
1487 .frames
1488 .last()
1489 .map(|f| f.set_topic_called)
1490 .unwrap_or(false);
1491 if already_shifted {
1492 self.declare_topic_slot(0, 0, val);
1493 for slot in 1..=self.max_active_slot {
1494 self.declare_topic_slot(slot, 0, StrykeValue::UNDEF);
1495 }
1496 return;
1497 }
1498 if let Some(frame) = self.frames.last_mut() {
1499 frame.set_topic_called = true;
1500 }
1501 self.shift_slot_chain(0, val);
1502 for slot in 1..=self.max_active_slot {
1503 self.shift_slot_chain(slot, StrykeValue::UNDEF);
1504 }
1505 }
1506
1507 #[inline]
1516 pub fn set_topic_local(&mut self, val: StrykeValue) {
1517 self.declare_topic_slot(0, 0, val);
1518 }
1519
1520 #[inline]
1532 pub fn set_closure_args(&mut self, args: &[StrykeValue]) {
1533 let n = args.len();
1534 if n == 0 {
1535 return;
1536 }
1537 let high = n.saturating_sub(1).max(self.max_active_slot);
1538 for slot in 0..=high {
1539 let val = args.get(slot).cloned().unwrap_or(StrykeValue::UNDEF);
1540 self.shift_slot_chain(slot, val);
1541 }
1542 if n > 0 && n - 1 > self.max_active_slot {
1543 self.max_active_slot = n - 1;
1544 }
1545 }
1546
1547 #[inline]
1556 fn shift_slot_chain(&mut self, slot: usize, val: StrykeValue) {
1557 let l4 = self.get_scalar(&Self::topic_slot_key(slot, 4));
1558 let l3 = self.get_scalar(&Self::topic_slot_key(slot, 3));
1559 let l2 = self.get_scalar(&Self::topic_slot_key(slot, 2));
1560 let l1 = self.get_scalar(&Self::topic_slot_key(slot, 1));
1561 let cur = self.get_scalar(&Self::topic_slot_key(slot, 0));
1562
1563 self.declare_topic_slot(slot, 0, val);
1564 self.declare_topic_slot(slot, 1, cur);
1565 self.declare_topic_slot(slot, 2, l1);
1566 self.declare_topic_slot(slot, 3, l2);
1567 self.declare_topic_slot(slot, 4, l3);
1568 self.declare_topic_slot(slot, 5, l4);
1569 }
1570
1571 #[inline]
1579 pub fn set_sort_pair(&mut self, a: StrykeValue, b: StrykeValue) {
1580 let _ = self.set_scalar("a", a.clone());
1581 let _ = self.set_scalar("b", b.clone());
1582 self.declare_topic_slot(0, 0, a);
1594 self.declare_topic_slot(1, 0, b);
1595 }
1596
1597 #[inline]
1601 pub fn save_topic_chain(&self) -> [StrykeValue; 6] {
1602 [
1603 self.get_scalar(&Self::topic_slot_key(0, 0)),
1604 self.get_scalar(&Self::topic_slot_key(0, 1)),
1605 self.get_scalar(&Self::topic_slot_key(0, 2)),
1606 self.get_scalar(&Self::topic_slot_key(0, 3)),
1607 self.get_scalar(&Self::topic_slot_key(0, 4)),
1608 self.get_scalar(&Self::topic_slot_key(0, 5)),
1609 ]
1610 }
1611
1612 #[inline]
1614 pub fn restore_topic_chain(&mut self, saved: [StrykeValue; 6]) {
1615 for (level, val) in saved.into_iter().enumerate() {
1616 self.declare_topic_slot(0, level, val);
1617 }
1618 }
1619
1620 #[inline]
1622 pub fn push_defer(&mut self, coderef: StrykeValue) {
1623 if let Some(frame) = self.frames.last_mut() {
1624 frame.defers.push(coderef);
1625 }
1626 }
1627
1628 #[inline]
1631 pub fn take_defers(&mut self) -> Vec<StrykeValue> {
1632 if let Some(frame) = self.frames.last_mut() {
1633 let mut defers = std::mem::take(&mut frame.defers);
1634 defers.reverse();
1635 defers
1636 } else {
1637 Vec::new()
1638 }
1639 }
1640
1641 pub fn declare_atomic_array(&mut self, name: &str, val: Vec<StrykeValue>) {
1644 canon_main!(name);
1645 if let Some(frame) = self.frames.last_mut() {
1646 frame
1647 .atomic_arrays
1648 .push((name.to_string(), AtomicArray(Arc::new(Mutex::new(val)))));
1649 }
1650 }
1651 pub fn declare_atomic_hash(&mut self, name: &str, val: IndexMap<String, StrykeValue>) {
1653 canon_main!(name);
1654 if let Some(frame) = self.frames.last_mut() {
1655 frame
1656 .atomic_hashes
1657 .push((name.to_string(), AtomicHash(Arc::new(Mutex::new(val)))));
1658 }
1659 }
1660
1661 fn find_atomic_array(&self, name: &str) -> Option<&AtomicArray> {
1663 let name = strip_main_prefix(name).unwrap_or(name);
1664 for frame in self.frames.iter().rev() {
1665 if let Some(aa) = frame.atomic_arrays.iter().find(|(k, _)| k == name) {
1666 return Some(&aa.1);
1667 }
1668 }
1669 None
1670 }
1671
1672 fn find_atomic_hash(&self, name: &str) -> Option<&AtomicHash> {
1674 let name = strip_main_prefix(name).unwrap_or(name);
1675 for frame in self.frames.iter().rev() {
1676 if let Some(ah) = frame.atomic_hashes.iter().find(|(k, _)| k == name) {
1677 return Some(&ah.1);
1678 }
1679 }
1680 None
1681 }
1682
1683 #[inline]
1688 pub fn take_sub_underscore(&mut self) -> Option<Vec<StrykeValue>> {
1689 self.frames.last_mut()?.sub_underscore.take()
1690 }
1691 pub fn declare_array(&mut self, name: &str, val: Vec<StrykeValue>) {
1693 self.declare_array_frozen(name, val, false);
1694 }
1695 pub fn declare_array_frozen(&mut self, name: &str, val: Vec<StrykeValue>, frozen: bool) {
1697 let is_package_qualified = name.contains("::");
1705 canon_main!(name);
1706 let idx = if is_package_qualified {
1710 0
1711 } else {
1712 self.frames.len().saturating_sub(1)
1713 };
1714 if let Some(frame) = self.frames.get_mut(idx) {
1715 frame.shared_arrays.retain(|(k, _)| k != name);
1717 frame.set_array(name, val);
1718 if frozen {
1719 frame.frozen_arrays.insert(name.to_string());
1720 } else {
1721 frame.frozen_arrays.remove(name);
1723 }
1724 }
1725 }
1726 pub fn get_array(&self, name: &str) -> Vec<StrykeValue> {
1728 if let Some(rest) = strip_main_prefix(name) {
1734 return self.get_array(rest);
1735 }
1736 if let Some(aa) = self.find_atomic_array(name) {
1738 return aa.0.lock().clone();
1739 }
1740 if let Some(arc) = self.find_shared_array(name) {
1742 return arc.read().clone();
1743 }
1744 if name.contains("::") {
1745 if let Some(f) = self.frames.first() {
1746 if let Some(val) = f.get_array(name) {
1747 return val.clone();
1748 }
1749 }
1750 return Vec::new();
1751 }
1752 for frame in self.frames.iter().rev() {
1753 if let Some(val) = frame.get_array(name) {
1754 return val.clone();
1755 }
1756 }
1757 Vec::new()
1758 }
1759
1760 #[inline]
1763 pub fn get_array_borrow(&self, name: &str) -> Option<&[StrykeValue]> {
1764 if let Some(rest) = strip_main_prefix(name) {
1765 return self.get_array_borrow(rest);
1766 }
1767 if self.find_atomic_array(name).is_some() {
1768 return None;
1769 }
1770 if name.contains("::") {
1771 return self
1772 .frames
1773 .first()
1774 .and_then(|f| f.get_array(name))
1775 .map(|v| v.as_slice());
1776 }
1777 for frame in self.frames.iter().rev() {
1778 if let Some(val) = frame.get_array(name) {
1779 return Some(val.as_slice());
1780 }
1781 }
1782 None
1783 }
1784
1785 fn resolve_array_frame_idx(&self, name: &str) -> Option<usize> {
1786 if name.contains("::") {
1787 return Some(0);
1788 }
1789 (0..self.frames.len())
1790 .rev()
1791 .find(|&i| self.frames[i].has_array(name))
1792 }
1793
1794 fn check_parallel_array_write(&self, name: &str) -> Result<(), StrykeError> {
1795 if !self.parallel_guard
1796 || Self::parallel_skip_special_name(name)
1797 || Self::parallel_allowed_internal_array(name)
1798 {
1799 return Ok(());
1800 }
1801 let baseline = self.parallel_guard_baseline;
1803 match self.resolve_array_frame_idx(name) {
1804 None => Err(StrykeError::runtime(
1805 format!(
1806 "cannot modify undeclared array `@{}` in a parallel block",
1807 name
1808 ),
1809 0,
1810 )),
1811 Some(idx) if idx < baseline => Err(StrykeError::runtime(
1812 format!(
1813 "cannot modify captured non-mysync array `@{}` in a parallel block",
1814 name
1815 ),
1816 0,
1817 )),
1818 Some(_) => Ok(()),
1819 }
1820 }
1821
1822 #[inline]
1827 pub fn resolve_container_binding_ref(&self, val: StrykeValue) -> StrykeValue {
1828 if let Some(name) = val.as_array_binding_name() {
1829 let data = self.get_array(&name);
1830 return StrykeValue::array_ref(Arc::new(parking_lot::RwLock::new(data)));
1831 }
1832 if let Some(name) = val.as_hash_binding_name() {
1833 let data = self.get_hash(&name);
1834 return StrykeValue::hash_ref(Arc::new(parking_lot::RwLock::new(data)));
1835 }
1836 val
1837 }
1838
1839 pub fn promote_array_to_shared(
1843 &mut self,
1844 name: &str,
1845 ) -> Arc<parking_lot::RwLock<Vec<StrykeValue>>> {
1846 let name = strip_main_prefix(name).unwrap_or(name);
1854
1855 if let Some(aa) = self.find_atomic_array(name) {
1858 let data = aa.0.lock().clone();
1859 return Arc::new(parking_lot::RwLock::new(data));
1860 }
1861 let idx = self.resolve_array_frame_idx(name).unwrap_or_default();
1863 let frame = &mut self.frames[idx];
1864 if let Some(entry) = frame.shared_arrays.iter().find(|(k, _)| k == name) {
1865 return Arc::clone(&entry.1);
1866 }
1867 let data = if let Some(pos) = frame.arrays.iter().position(|(k, _)| k == name) {
1869 frame.arrays.swap_remove(pos).1
1870 } else if name == "_" {
1871 frame.sub_underscore.take().unwrap_or_default()
1872 } else {
1873 Vec::new()
1874 };
1875 let arc = Arc::new(parking_lot::RwLock::new(data));
1876 frame
1877 .shared_arrays
1878 .push((name.to_string(), Arc::clone(&arc)));
1879 arc
1880 }
1881
1882 pub fn promote_hash_to_shared(
1885 &mut self,
1886 name: &str,
1887 ) -> Arc<parking_lot::RwLock<IndexMap<String, StrykeValue>>> {
1888 let name = strip_main_prefix(name).unwrap_or(name);
1898
1899 let idx = self.resolve_hash_frame_idx(name).unwrap_or_default();
1900 let frame = &mut self.frames[idx];
1901 if let Some(entry) = frame.shared_hashes.iter().find(|(k, _)| k == name) {
1902 return Arc::clone(&entry.1);
1903 }
1904 let data = if let Some(pos) = frame.hashes.iter().position(|(k, _)| k == name) {
1905 frame.hashes.swap_remove(pos).1
1906 } else {
1907 IndexMap::new()
1908 };
1909 let arc = Arc::new(parking_lot::RwLock::new(data));
1910 frame
1911 .shared_hashes
1912 .push((name.to_string(), Arc::clone(&arc)));
1913 arc
1914 }
1915
1916 fn find_shared_array(&self, name: &str) -> Option<Arc<parking_lot::RwLock<Vec<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_arrays.iter().find(|(k, _)| k == name) {
1921 return Some(Arc::clone(&entry.1));
1922 }
1923 if frame.arrays.iter().any(|(k, _)| k == name) {
1925 return None;
1926 }
1927 }
1928 None
1929 }
1930
1931 fn find_shared_hash(
1933 &self,
1934 name: &str,
1935 ) -> Option<Arc<parking_lot::RwLock<IndexMap<String, StrykeValue>>>> {
1936 let name = strip_main_prefix(name).unwrap_or(name);
1937 for frame in self.frames.iter().rev() {
1938 if let Some(entry) = frame.shared_hashes.iter().find(|(k, _)| k == name) {
1939 return Some(Arc::clone(&entry.1));
1940 }
1941 if frame.hashes.iter().any(|(k, _)| k == name) {
1942 return None;
1943 }
1944 }
1945 None
1946 }
1947 pub fn get_array_mut(&mut self, name: &str) -> Result<&mut Vec<StrykeValue>, StrykeError> {
1949 canon_main!(name);
1954 if self.find_atomic_array(name).is_some() {
1957 return Err(StrykeError::runtime(
1958 "get_array_mut: use atomic path for mysync arrays",
1959 0,
1960 ));
1961 }
1962 self.check_parallel_array_write(name)?;
1963 let idx = self.resolve_array_frame_idx(name).unwrap_or_default();
1964 let frame = &mut self.frames[idx];
1965 if frame.get_array_mut(name).is_none() {
1966 frame.arrays.push((name.to_string(), Vec::new()));
1967 }
1968 Ok(frame.get_array_mut(name).unwrap())
1969 }
1970
1971 pub fn push_to_array(&mut self, name: &str, val: StrykeValue) -> Result<(), StrykeError> {
1973 let val = self.resolve_container_binding_ref(val);
1974 if let Some(aa) = self.find_atomic_array(name) {
1975 aa.0.lock().push(val);
1976 return Ok(());
1977 }
1978 if let Some(arc) = self.find_shared_array(name) {
1979 arc.write().push(val);
1980 return Ok(());
1981 }
1982 self.get_array_mut(name)?.push(val);
1983 Ok(())
1984 }
1985
1986 pub fn push_int_range_to_array(
1990 &mut self,
1991 name: &str,
1992 start: i64,
1993 end: i64,
1994 ) -> Result<(), StrykeError> {
1995 if end <= start {
1996 return Ok(());
1997 }
1998 let count = (end - start) as usize;
1999 if let Some(aa) = self.find_atomic_array(name) {
2000 let mut g = aa.0.lock();
2001 g.reserve(count);
2002 for i in start..end {
2003 g.push(StrykeValue::integer(i));
2004 }
2005 return Ok(());
2006 }
2007 let arr = self.get_array_mut(name)?;
2008 arr.reserve(count);
2009 for i in start..end {
2010 arr.push(StrykeValue::integer(i));
2011 }
2012 Ok(())
2013 }
2014
2015 pub fn pop_from_array(&mut self, name: &str) -> Result<StrykeValue, StrykeError> {
2017 if let Some(aa) = self.find_atomic_array(name) {
2018 return Ok(aa.0.lock().pop().unwrap_or(StrykeValue::UNDEF));
2019 }
2020 if let Some(arc) = self.find_shared_array(name) {
2021 return Ok(arc.write().pop().unwrap_or(StrykeValue::UNDEF));
2022 }
2023 Ok(self
2024 .get_array_mut(name)?
2025 .pop()
2026 .unwrap_or(StrykeValue::UNDEF))
2027 }
2028
2029 pub fn shift_from_array(&mut self, name: &str) -> Result<StrykeValue, StrykeError> {
2031 if let Some(aa) = self.find_atomic_array(name) {
2032 let mut guard = aa.0.lock();
2033 return Ok(if guard.is_empty() {
2034 StrykeValue::UNDEF
2035 } else {
2036 guard.remove(0)
2037 });
2038 }
2039 if let Some(arc) = self.find_shared_array(name) {
2040 let mut arr = arc.write();
2041 return Ok(if arr.is_empty() {
2042 StrykeValue::UNDEF
2043 } else {
2044 arr.remove(0)
2045 });
2046 }
2047 let arr = self.get_array_mut(name)?;
2048 Ok(if arr.is_empty() {
2049 StrykeValue::UNDEF
2050 } else {
2051 arr.remove(0)
2052 })
2053 }
2054
2055 pub fn splice_in_place(
2059 &mut self,
2060 name: &str,
2061 off: usize,
2062 end: usize,
2063 rep_vals: Vec<StrykeValue>,
2064 ) -> Result<Vec<StrykeValue>, StrykeError> {
2065 if let Some(aa) = self.find_atomic_array(name) {
2066 let mut g = aa.0.lock();
2067 let removed: Vec<StrykeValue> = g.drain(off..end).collect();
2068 for (i, v) in rep_vals.into_iter().enumerate() {
2069 g.insert(off + i, v);
2070 }
2071 return Ok(removed);
2072 }
2073 if let Some(arc) = self.find_shared_array(name) {
2074 let mut g = arc.write();
2075 let removed: Vec<StrykeValue> = g.drain(off..end).collect();
2076 for (i, v) in rep_vals.into_iter().enumerate() {
2077 g.insert(off + i, v);
2078 }
2079 return Ok(removed);
2080 }
2081 let arr = self.get_array_mut(name)?;
2082 let removed: Vec<StrykeValue> = arr.drain(off..end).collect();
2083 for (i, v) in rep_vals.into_iter().enumerate() {
2084 arr.insert(off + i, v);
2085 }
2086 Ok(removed)
2087 }
2088
2089 pub fn array_len(&self, name: &str) -> usize {
2091 canon_main!(name);
2092 if let Some(aa) = self.find_atomic_array(name) {
2093 return aa.0.lock().len();
2094 }
2095 if let Some(arc) = self.find_shared_array(name) {
2096 return arc.read().len();
2097 }
2098 if name.contains("::") {
2099 return self
2100 .frames
2101 .first()
2102 .and_then(|f| f.get_array(name))
2103 .map(|a| a.len())
2104 .unwrap_or(0);
2105 }
2106 for frame in self.frames.iter().rev() {
2107 if let Some(arr) = frame.get_array(name) {
2108 return arr.len();
2109 }
2110 }
2111 0
2112 }
2113 pub fn set_array(&mut self, name: &str, val: Vec<StrykeValue>) -> Result<(), StrykeError> {
2115 if let Some(aa) = self.find_atomic_array(name) {
2116 *aa.0.lock() = val;
2117 return Ok(());
2118 }
2119 if let Some(arc) = self.find_shared_array(name) {
2120 *arc.write() = val;
2121 return Ok(());
2122 }
2123 self.check_parallel_array_write(name)?;
2124 for frame in self.frames.iter_mut().rev() {
2125 if frame.has_array(name) {
2126 frame.set_array(name, val);
2127 return Ok(());
2128 }
2129 }
2130 self.frames[0].set_array(name, val);
2131 Ok(())
2132 }
2133
2134 #[inline]
2136 pub fn get_array_element(&self, name: &str, index: i64) -> StrykeValue {
2137 canon_main!(name);
2138 if let Some(aa) = self.find_atomic_array(name) {
2139 let arr = aa.0.lock();
2140 let idx = if index < 0 {
2141 (arr.len() as i64 + index) as usize
2142 } else {
2143 index as usize
2144 };
2145 return arr.get(idx).cloned().unwrap_or(StrykeValue::UNDEF);
2146 }
2147 if let Some(arc) = self.find_shared_array(name) {
2148 let arr = arc.read();
2149 let idx = if index < 0 {
2150 (arr.len() as i64 + index) as usize
2151 } else {
2152 index as usize
2153 };
2154 return arr.get(idx).cloned().unwrap_or(StrykeValue::UNDEF);
2155 }
2156 for frame in self.frames.iter().rev() {
2157 if let Some(arr) = frame.get_array(name) {
2158 let idx = if index < 0 {
2159 (arr.len() as i64 + index) as usize
2160 } else {
2161 index as usize
2162 };
2163 return arr.get(idx).cloned().unwrap_or(StrykeValue::UNDEF);
2164 }
2165 }
2166 StrykeValue::UNDEF
2167 }
2168 pub fn set_array_element(
2170 &mut self,
2171 name: &str,
2172 index: i64,
2173 val: StrykeValue,
2174 ) -> Result<(), StrykeError> {
2175 let val = self.resolve_container_binding_ref(val);
2176 if let Some(aa) = self.find_atomic_array(name) {
2177 let mut arr = aa.0.lock();
2178 let idx = if index < 0 {
2179 (arr.len() as i64 + index).max(0) as usize
2180 } else {
2181 index as usize
2182 };
2183 if idx >= arr.len() {
2184 arr.resize(idx + 1, StrykeValue::UNDEF);
2185 }
2186 arr[idx] = val;
2187 return Ok(());
2188 }
2189 if let Some(arc) = self.find_shared_array(name) {
2190 let mut arr = arc.write();
2191 let idx = if index < 0 {
2192 (arr.len() as i64 + index).max(0) as usize
2193 } else {
2194 index as usize
2195 };
2196 if idx >= arr.len() {
2197 arr.resize(idx + 1, StrykeValue::UNDEF);
2198 }
2199 arr[idx] = val;
2200 return Ok(());
2201 }
2202 let arr = self.get_array_mut(name)?;
2203 let idx = if index < 0 {
2204 let len = arr.len() as i64;
2205 (len + index).max(0) as usize
2206 } else {
2207 index as usize
2208 };
2209 if idx >= arr.len() {
2210 arr.resize(idx + 1, StrykeValue::UNDEF);
2211 }
2212 arr[idx] = val;
2213 Ok(())
2214 }
2215
2216 pub fn exists_array_element(&self, name: &str, index: i64) -> bool {
2218 canon_main!(name);
2219 if let Some(aa) = self.find_atomic_array(name) {
2220 let arr = aa.0.lock();
2221 let idx = if index < 0 {
2222 (arr.len() as i64 + index) as usize
2223 } else {
2224 index as usize
2225 };
2226 return idx < arr.len();
2227 }
2228 for frame in self.frames.iter().rev() {
2229 if let Some(arr) = frame.get_array(name) {
2230 let idx = if index < 0 {
2231 (arr.len() as i64 + index) as usize
2232 } else {
2233 index as usize
2234 };
2235 return idx < arr.len();
2236 }
2237 }
2238 false
2239 }
2240
2241 pub fn delete_array_element(
2243 &mut self,
2244 name: &str,
2245 index: i64,
2246 ) -> Result<StrykeValue, StrykeError> {
2247 if let Some(aa) = self.find_atomic_array(name) {
2248 let mut arr = aa.0.lock();
2249 let idx = if index < 0 {
2250 (arr.len() as i64 + index) as usize
2251 } else {
2252 index as usize
2253 };
2254 if idx >= arr.len() {
2255 return Ok(StrykeValue::UNDEF);
2256 }
2257 let old = arr.get(idx).cloned().unwrap_or(StrykeValue::UNDEF);
2258 arr[idx] = StrykeValue::UNDEF;
2259 return Ok(old);
2260 }
2261 let arr = self.get_array_mut(name)?;
2262 let idx = if index < 0 {
2263 (arr.len() as i64 + index) as usize
2264 } else {
2265 index as usize
2266 };
2267 if idx >= arr.len() {
2268 return Ok(StrykeValue::UNDEF);
2269 }
2270 let old = arr.get(idx).cloned().unwrap_or(StrykeValue::UNDEF);
2271 arr[idx] = StrykeValue::UNDEF;
2272 Ok(old)
2273 }
2274
2275 #[inline]
2278 pub fn declare_hash(&mut self, name: &str, val: IndexMap<String, StrykeValue>) {
2279 self.declare_hash_frozen(name, val, false);
2280 }
2281 pub fn declare_hash_frozen(
2283 &mut self,
2284 name: &str,
2285 val: IndexMap<String, StrykeValue>,
2286 frozen: bool,
2287 ) {
2288 let is_package_qualified = name.contains("::");
2289 canon_main!(name);
2290 let frame_opt = if is_package_qualified {
2291 self.frames.first_mut()
2292 } else {
2293 self.frames.last_mut()
2294 };
2295 if let Some(frame) = frame_opt {
2296 frame.shared_hashes.retain(|(k, _)| k != name);
2298 frame.set_hash(name, val);
2299 if frozen {
2300 frame.frozen_hashes.insert(name.to_string());
2301 } else {
2302 frame.frozen_hashes.remove(name);
2304 }
2305 }
2306 }
2307
2308 pub fn declare_hash_global(&mut self, name: &str, val: IndexMap<String, StrykeValue>) {
2310 canon_main!(name);
2311 if let Some(frame) = self.frames.first_mut() {
2312 frame.set_hash(name, val);
2313 }
2314 }
2315
2316 pub fn declare_hash_global_frozen(&mut self, name: &str, val: IndexMap<String, StrykeValue>) {
2318 canon_main!(name);
2319 if let Some(frame) = self.frames.first_mut() {
2320 frame.set_hash(name, val);
2321 frame.frozen_hashes.insert(name.to_string());
2322 }
2323 }
2324
2325 pub fn has_lexical_hash(&self, name: &str) -> bool {
2327 canon_main!(name);
2328 self.frames.iter().skip(1).any(|f| f.has_hash(name))
2329 }
2330
2331 pub fn any_frame_has_hash(&self, name: &str) -> bool {
2333 canon_main!(name);
2334 self.frames.iter().any(|f| f.has_hash(name))
2335 }
2336 pub fn get_hash(&self, name: &str) -> IndexMap<String, StrykeValue> {
2338 if let Some(rest) = strip_main_prefix(name) {
2340 return self.get_hash(rest);
2341 }
2342 if let Some(ah) = self.find_atomic_hash(name) {
2343 return ah.0.lock().clone();
2344 }
2345 if let Some(arc) = self.find_shared_hash(name) {
2346 return arc.read().clone();
2347 }
2348 for frame in self.frames.iter().rev() {
2349 if let Some(val) = frame.get_hash(name) {
2350 return val.clone();
2351 }
2352 }
2353 IndexMap::new()
2354 }
2355
2356 fn resolve_hash_frame_idx(&self, name: &str) -> Option<usize> {
2357 if name.contains("::") {
2358 return Some(0);
2359 }
2360 (0..self.frames.len())
2361 .rev()
2362 .find(|&i| self.frames[i].has_hash(name))
2363 }
2364
2365 fn check_parallel_hash_write(&self, name: &str) -> Result<(), StrykeError> {
2366 if !self.parallel_guard
2367 || Self::parallel_skip_special_name(name)
2368 || Self::parallel_allowed_internal_hash(name)
2369 {
2370 return Ok(());
2371 }
2372 let baseline = self.parallel_guard_baseline;
2374 match self.resolve_hash_frame_idx(name) {
2375 None => Err(StrykeError::runtime(
2376 format!(
2377 "cannot modify undeclared hash `%{}` in a parallel block",
2378 name
2379 ),
2380 0,
2381 )),
2382 Some(idx) if idx < baseline => Err(StrykeError::runtime(
2383 format!(
2384 "cannot modify captured non-mysync hash `%{}` in a parallel block",
2385 name
2386 ),
2387 0,
2388 )),
2389 Some(_) => Ok(()),
2390 }
2391 }
2392 pub fn get_hash_mut(
2394 &mut self,
2395 name: &str,
2396 ) -> Result<&mut IndexMap<String, StrykeValue>, StrykeError> {
2397 canon_main!(name);
2400 if self.find_atomic_hash(name).is_some() {
2401 return Err(StrykeError::runtime(
2402 "get_hash_mut: use atomic path for mysync hashes",
2403 0,
2404 ));
2405 }
2406 self.check_parallel_hash_write(name)?;
2407 let idx = self.resolve_hash_frame_idx(name).unwrap_or_default();
2408 let frame = &mut self.frames[idx];
2409 if frame.get_hash_mut(name).is_none() {
2410 frame.hashes.push((name.to_string(), IndexMap::new()));
2411 }
2412 Ok(frame.get_hash_mut(name).unwrap())
2413 }
2414 pub fn set_hash(
2416 &mut self,
2417 name: &str,
2418 val: IndexMap<String, StrykeValue>,
2419 ) -> Result<(), StrykeError> {
2420 if let Some(ah) = self.find_atomic_hash(name) {
2421 *ah.0.lock() = val;
2422 return Ok(());
2423 }
2424 self.check_parallel_hash_write(name)?;
2425 for frame in self.frames.iter_mut().rev() {
2426 if frame.has_hash(name) {
2427 frame.set_hash(name, val);
2428 return Ok(());
2429 }
2430 }
2431 self.frames[0].set_hash(name, val);
2432 Ok(())
2433 }
2434 #[inline]
2436 pub fn get_hash_element(&self, name: &str, key: &str) -> StrykeValue {
2437 canon_main!(name);
2438 if let Some(ah) = self.find_atomic_hash(name) {
2439 return ah.0.lock().get(key).cloned().unwrap_or(StrykeValue::UNDEF);
2440 }
2441 if let Some(arc) = self.find_shared_hash(name) {
2442 return arc.read().get(key).cloned().unwrap_or(StrykeValue::UNDEF);
2443 }
2444 for frame in self.frames.iter().rev() {
2445 if let Some(hash) = frame.get_hash(name) {
2446 return hash.get(key).cloned().unwrap_or(StrykeValue::UNDEF);
2447 }
2448 }
2449 StrykeValue::UNDEF
2450 }
2451
2452 pub fn atomic_hash_mutate(
2455 &mut self,
2456 name: &str,
2457 key: &str,
2458 f: impl FnOnce(&StrykeValue) -> StrykeValue,
2459 ) -> Result<StrykeValue, StrykeError> {
2460 if let Some(ah) = self.find_atomic_hash(name) {
2461 let mut guard = ah.0.lock();
2462 let old = guard.get(key).cloned().unwrap_or(StrykeValue::UNDEF);
2463 let new_val = f(&old);
2464 guard.insert(key.to_string(), new_val.clone());
2465 return Ok(new_val);
2466 }
2467 let old = self.get_hash_element(name, key);
2469 let new_val = f(&old);
2470 self.set_hash_element(name, key, new_val.clone())?;
2471 Ok(new_val)
2472 }
2473
2474 pub fn atomic_array_mutate(
2476 &mut self,
2477 name: &str,
2478 index: i64,
2479 f: impl FnOnce(&StrykeValue) -> StrykeValue,
2480 ) -> Result<StrykeValue, StrykeError> {
2481 if let Some(aa) = self.find_atomic_array(name) {
2482 let mut guard = aa.0.lock();
2483 let idx = if index < 0 {
2484 (guard.len() as i64 + index).max(0) as usize
2485 } else {
2486 index as usize
2487 };
2488 if idx >= guard.len() {
2489 guard.resize(idx + 1, StrykeValue::UNDEF);
2490 }
2491 let old = guard[idx].clone();
2492 let new_val = f(&old);
2493 guard[idx] = new_val.clone();
2494 return Ok(new_val);
2495 }
2496 let old = self.get_array_element(name, index);
2498 let new_val = f(&old);
2499 self.set_array_element(name, index, new_val.clone())?;
2500 Ok(new_val)
2501 }
2502 pub fn set_hash_element(
2504 &mut self,
2505 name: &str,
2506 key: &str,
2507 val: StrykeValue,
2508 ) -> Result<(), StrykeError> {
2509 let val = self.resolve_container_binding_ref(val);
2510 if name == "SIG" {
2513 crate::perl_signal::install(key);
2514 }
2515 if name == "ENV" {
2520 std::env::set_var(key, val.to_string());
2524 }
2525 if let Some(ah) = self.find_atomic_hash(name) {
2526 ah.0.lock().insert(key.to_string(), val);
2527 return Ok(());
2528 }
2529 if let Some(arc) = self.find_shared_hash(name) {
2530 arc.write().insert(key.to_string(), val);
2531 return Ok(());
2532 }
2533 let hash = self.get_hash_mut(name)?;
2534 hash.insert(key.to_string(), val);
2535 Ok(())
2536 }
2537
2538 pub fn set_hash_int_times_range(
2542 &mut self,
2543 name: &str,
2544 start: i64,
2545 end: i64,
2546 k: i64,
2547 ) -> Result<(), StrykeError> {
2548 if end <= start {
2549 return Ok(());
2550 }
2551 let count = (end - start) as usize;
2552 if let Some(ah) = self.find_atomic_hash(name) {
2553 let mut g = ah.0.lock();
2554 g.reserve(count);
2555 let mut buf = itoa::Buffer::new();
2556 for i in start..end {
2557 let key = buf.format(i).to_owned();
2558 g.insert(key, StrykeValue::integer(i.wrapping_mul(k)));
2559 }
2560 return Ok(());
2561 }
2562 let hash = self.get_hash_mut(name)?;
2563 hash.reserve(count);
2564 let mut buf = itoa::Buffer::new();
2565 for i in start..end {
2566 let key = buf.format(i).to_owned();
2567 hash.insert(key, StrykeValue::integer(i.wrapping_mul(k)));
2568 }
2569 Ok(())
2570 }
2571 pub fn delete_hash_element(
2573 &mut self,
2574 name: &str,
2575 key: &str,
2576 ) -> Result<StrykeValue, StrykeError> {
2577 canon_main!(name);
2578 if name == "ENV" {
2580 std::env::remove_var(key);
2581 }
2582 if let Some(ah) = self.find_atomic_hash(name) {
2583 return Ok(ah.0.lock().shift_remove(key).unwrap_or(StrykeValue::UNDEF));
2584 }
2585 let hash = self.get_hash_mut(name)?;
2586 Ok(hash.shift_remove(key).unwrap_or(StrykeValue::UNDEF))
2587 }
2588 #[inline]
2590 pub fn exists_hash_element(&self, name: &str, key: &str) -> bool {
2591 canon_main!(name);
2592 if let Some(ah) = self.find_atomic_hash(name) {
2593 return ah.0.lock().contains_key(key);
2594 }
2595 for frame in self.frames.iter().rev() {
2596 if let Some(hash) = frame.get_hash(name) {
2597 return hash.contains_key(key);
2598 }
2599 }
2600 false
2601 }
2602
2603 #[inline]
2608 pub fn for_each_hash_value(&self, name: &str, mut visit: impl FnMut(&StrykeValue)) {
2609 canon_main!(name);
2610 if let Some(ah) = self.find_atomic_hash(name) {
2611 let g = ah.0.lock();
2612 for v in g.values() {
2613 visit(v);
2614 }
2615 return;
2616 }
2617 for frame in self.frames.iter().rev() {
2618 if let Some(hash) = frame.get_hash(name) {
2619 for v in hash.values() {
2620 visit(v);
2621 }
2622 return;
2623 }
2624 }
2625 }
2626
2627 pub fn frames_for_introspection(&self) -> Vec<(Vec<&str>, Vec<&str>, Vec<&str>)> {
2633 self.frames
2634 .iter()
2635 .map(|f| {
2636 let mut scalars: Vec<&str> = f.scalars.iter().map(|(n, _)| n.as_str()).collect();
2637 scalars.extend(f.scalar_slot_names.iter().filter_map(|opt| match opt {
2639 Some(n) if !n.is_empty() => Some(n.as_str()),
2640 _ => None,
2641 }));
2642 let mut arrays: Vec<&str> = f.arrays.iter().map(|(n, _)| n.as_str()).collect();
2643 arrays.extend(f.atomic_arrays.iter().map(|(n, _)| n.as_str()));
2644 arrays.extend(f.shared_arrays.iter().map(|(n, _)| n.as_str()));
2645 let mut hashes: Vec<&str> = f.hashes.iter().map(|(n, _)| n.as_str()).collect();
2646 hashes.extend(f.atomic_hashes.iter().map(|(n, _)| n.as_str()));
2647 hashes.extend(f.shared_hashes.iter().map(|(n, _)| n.as_str()));
2648 scalars.sort_unstable();
2649 arrays.sort_unstable();
2650 hashes.sort_unstable();
2651 (scalars, arrays, hashes)
2652 })
2653 .collect()
2654 }
2655
2656 pub fn parameters_pairs(&self) -> Vec<(String, &'static str)> {
2662 let mut seen: HashSet<String> = HashSet::new();
2663 let mut out: Vec<(String, &'static str)> = Vec::new();
2664 for frame in self.frames.iter().rev() {
2667 for n in frame.scalar_slot_names.iter().flatten() {
2671 if !n.is_empty() {
2672 let s = format!("${}", n);
2673 if seen.insert(s.clone()) {
2674 out.push((s, "scalar"));
2675 }
2676 }
2677 }
2678 for (name, _) in &frame.scalars {
2679 let s = format!("${}", name);
2680 if seen.insert(s.clone()) {
2681 out.push((s, "scalar"));
2682 }
2683 }
2684 for (name, _) in &frame.arrays {
2685 let s = format!("@{}", name);
2686 if seen.insert(s.clone()) {
2687 out.push((s, "array"));
2688 }
2689 }
2690 for (name, _) in &frame.hashes {
2691 let s = format!("%{}", name);
2692 if seen.insert(s.clone()) {
2693 out.push((s, "hash"));
2694 }
2695 }
2696 for (name, _) in &frame.atomic_arrays {
2697 let s = format!("@{}", name);
2698 if seen.insert(s.clone()) {
2699 out.push((s, "atomic_array"));
2700 }
2701 }
2702 for (name, _) in &frame.atomic_hashes {
2703 let s = format!("%{}", name);
2704 if seen.insert(s.clone()) {
2705 out.push((s, "atomic_hash"));
2706 }
2707 }
2708 for (name, _) in &frame.shared_arrays {
2709 let s = format!("@{}", name);
2710 if seen.insert(s.clone()) {
2711 out.push((s, "shared_array"));
2712 }
2713 }
2714 for (name, _) in &frame.shared_hashes {
2715 let s = format!("%{}", name);
2716 if seen.insert(s.clone()) {
2717 out.push((s, "shared_hash"));
2718 }
2719 }
2720 }
2721 out.sort_by(|a, b| a.0.cmp(&b.0));
2722 out
2723 }
2724
2725 pub fn repl_binding_names(&self) -> Vec<String> {
2727 let mut seen = HashSet::new();
2728 let mut out = Vec::new();
2729 for frame in &self.frames {
2730 for (name, _) in &frame.scalars {
2731 let s = format!("${}", name);
2732 if seen.insert(s.clone()) {
2733 out.push(s);
2734 }
2735 }
2736 for (name, _) in &frame.arrays {
2737 let s = format!("@{}", name);
2738 if seen.insert(s.clone()) {
2739 out.push(s);
2740 }
2741 }
2742 for (name, _) in &frame.hashes {
2743 let s = format!("%{}", name);
2744 if seen.insert(s.clone()) {
2745 out.push(s);
2746 }
2747 }
2748 for (name, _) in &frame.atomic_arrays {
2749 let s = format!("@{}", name);
2750 if seen.insert(s.clone()) {
2751 out.push(s);
2752 }
2753 }
2754 for (name, _) in &frame.atomic_hashes {
2755 let s = format!("%{}", name);
2756 if seen.insert(s.clone()) {
2757 out.push(s);
2758 }
2759 }
2760 }
2761 out.sort();
2762 out
2763 }
2764 pub fn capture(&mut self) -> Vec<(String, StrykeValue)> {
2766 let by_ref = crate::compat_mode();
2779 let mut captured = Vec::new();
2780 let mut seen_hash_scalars: HashSet<String> = HashSet::new();
2795 for frame in self.frames.iter_mut().rev() {
2796 for (k, v) in &mut frame.scalars {
2797 if !seen_hash_scalars.insert(k.clone()) {
2798 continue;
2799 }
2800 if v.as_capture_cell().is_some() || v.as_scalar_ref().is_some() {
2801 captured.push((format!("${}", k), v.clone()));
2802 } else if v.is_simple_scalar() {
2803 let wrapped = StrykeValue::capture_cell(Arc::new(RwLock::new(v.clone())));
2804 *v = wrapped.clone();
2805 captured.push((format!("${}", k), wrapped));
2806 } else {
2807 captured.push((format!("${}", k), v.clone()));
2808 }
2809 }
2810 }
2811 for frame in &mut self.frames {
2812 for (i, v) in frame.scalar_slots.iter_mut().enumerate() {
2820 if let Some(Some(name)) = frame.scalar_slot_names.get(i) {
2821 if !name.is_empty() && seen_hash_scalars.contains(name) {
2831 continue;
2832 }
2833 let cap_val = if v.as_capture_cell().is_some() || v.as_scalar_ref().is_some() {
2834 v.clone()
2835 } else {
2836 let wrapped = StrykeValue::capture_cell(Arc::new(RwLock::new(v.clone())));
2837 if by_ref {
2838 *v = wrapped.clone();
2839 }
2840 wrapped
2841 };
2842 captured.push((format!("$slot:{}:{}", i, name), cap_val));
2843 }
2844 }
2845 for (k, v) in &frame.arrays {
2846 if capture_skip_bootstrap_array(k) {
2847 continue;
2848 }
2849 if frame.frozen_arrays.contains(k) {
2850 captured.push((format!("@frozen:{}", k), StrykeValue::array(v.clone())));
2851 } else {
2852 captured.push((format!("@{}", k), StrykeValue::array(v.clone())));
2853 }
2854 }
2855 for (k, v) in &frame.hashes {
2856 if capture_skip_bootstrap_hash(k) {
2857 continue;
2858 }
2859 if frame.frozen_hashes.contains(k) {
2860 captured.push((format!("%frozen:{}", k), StrykeValue::hash(v.clone())));
2861 } else {
2862 captured.push((format!("%{}", k), StrykeValue::hash(v.clone())));
2863 }
2864 }
2865 for (k, _aa) in &frame.atomic_arrays {
2866 captured.push((
2867 format!("@sync_{}", k),
2868 StrykeValue::atomic(Arc::new(Mutex::new(StrykeValue::string(String::new())))),
2869 ));
2870 }
2871 for (k, _ah) in &frame.atomic_hashes {
2872 captured.push((
2873 format!("%sync_{}", k),
2874 StrykeValue::atomic(Arc::new(Mutex::new(StrykeValue::string(String::new())))),
2875 ));
2876 }
2877 }
2878 captured
2879 }
2880
2881 pub fn capture_with_atomics(&self) -> ScopeCaptureWithAtomics {
2883 let mut scalars = Vec::new();
2884 let mut arrays = Vec::new();
2885 let mut hashes = Vec::new();
2886 for frame in &self.frames {
2887 for (k, v) in &frame.scalars {
2888 scalars.push((format!("${}", k), v.clone()));
2889 }
2890 for (i, v) in frame.scalar_slots.iter().enumerate() {
2891 if let Some(Some(name)) = frame.scalar_slot_names.get(i) {
2892 scalars.push((format!("$slot:{}:{}", i, name), v.clone()));
2893 }
2894 }
2895 for (k, v) in &frame.arrays {
2896 if capture_skip_bootstrap_array(k) {
2897 continue;
2898 }
2899 if frame.frozen_arrays.contains(k) {
2900 scalars.push((format!("@frozen:{}", k), StrykeValue::array(v.clone())));
2901 } else {
2902 scalars.push((format!("@{}", k), StrykeValue::array(v.clone())));
2903 }
2904 }
2905 for (k, v) in &frame.hashes {
2906 if capture_skip_bootstrap_hash(k) {
2907 continue;
2908 }
2909 if frame.frozen_hashes.contains(k) {
2910 scalars.push((format!("%frozen:{}", k), StrykeValue::hash(v.clone())));
2911 } else {
2912 scalars.push((format!("%{}", k), StrykeValue::hash(v.clone())));
2913 }
2914 }
2915 for (k, aa) in &frame.atomic_arrays {
2916 arrays.push((k.clone(), aa.clone()));
2917 }
2918 for (k, ah) in &frame.atomic_hashes {
2919 hashes.push((k.clone(), ah.clone()));
2920 }
2921 }
2922 (scalars, arrays, hashes)
2923 }
2924 pub fn restore_capture(&mut self, captured: &[(String, StrykeValue)]) {
2926 for (name, val) in captured {
2927 if let Some(rest) = name.strip_prefix("$slot:") {
2928 if let Some(colon) = rest.find(':') {
2934 let idx: usize = rest[..colon].parse().unwrap_or(0);
2935 let sname = &rest[colon + 1..];
2936 self.declare_scalar_slot(idx as u8, val.clone(), Some(sname));
2937 }
2938 } else if let Some(stripped) = name.strip_prefix('$') {
2939 self.declare_scalar(stripped, val.clone());
2940 if let Some(slot) = parse_positional_topic_slot(stripped) {
2947 if slot > self.max_active_slot {
2948 self.max_active_slot = slot;
2949 }
2950 }
2951 } else if let Some(rest) = name.strip_prefix("@frozen:") {
2952 let arr = val.as_array_vec().unwrap_or_else(|| val.to_list());
2953 self.declare_array_frozen(rest, arr, true);
2954 } else if let Some(rest) = name.strip_prefix("%frozen:") {
2955 if let Some(h) = val.as_hash_map() {
2956 self.declare_hash_frozen(rest, h.clone(), true);
2957 }
2958 } else if let Some(rest) = name.strip_prefix('@') {
2959 if rest.starts_with("sync_") {
2960 continue;
2961 }
2962 let arr = val.as_array_vec().unwrap_or_else(|| val.to_list());
2963 self.declare_array(rest, arr);
2964 } else if let Some(rest) = name.strip_prefix('%') {
2965 if rest.starts_with("sync_") {
2966 continue;
2967 }
2968 if let Some(h) = val.as_hash_map() {
2969 self.declare_hash(rest, h.clone());
2970 }
2971 }
2972 }
2973 }
2974
2975 pub fn restore_atomics(
2977 &mut self,
2978 arrays: &[(String, AtomicArray)],
2979 hashes: &[(String, AtomicHash)],
2980 ) {
2981 if let Some(frame) = self.frames.last_mut() {
2982 for (name, aa) in arrays {
2983 frame.atomic_arrays.push((name.clone(), aa.clone()));
2984 }
2985 for (name, ah) in hashes {
2986 frame.atomic_hashes.push((name.clone(), ah.clone()));
2987 }
2988 }
2989 }
2990}
2991
2992#[cfg(test)]
2993mod tests {
2994 use super::*;
2995 use crate::value::StrykeValue;
2996
2997 #[test]
2998 fn missing_scalar_is_undef() {
2999 let s = Scope::new();
3000 assert!(s.get_scalar("not_declared").is_undef());
3001 }
3002
3003 #[test]
3004 fn inner_frame_shadows_outer_scalar() {
3005 let mut s = Scope::new();
3006 s.declare_scalar("a", StrykeValue::integer(1));
3007 s.push_frame();
3008 s.declare_scalar("a", StrykeValue::integer(2));
3009 assert_eq!(s.get_scalar("a").to_int(), 2);
3010 s.pop_frame();
3011 assert_eq!(s.get_scalar("a").to_int(), 1);
3012 }
3013
3014 #[test]
3015 fn set_scalar_updates_innermost_binding() {
3016 let mut s = Scope::new();
3017 s.declare_scalar("a", StrykeValue::integer(1));
3018 s.push_frame();
3019 s.declare_scalar("a", StrykeValue::integer(2));
3020 let _ = s.set_scalar("a", StrykeValue::integer(99));
3021 assert_eq!(s.get_scalar("a").to_int(), 99);
3022 s.pop_frame();
3023 assert_eq!(s.get_scalar("a").to_int(), 1);
3024 }
3025
3026 #[test]
3027 fn array_negative_index_reads_from_end() {
3028 let mut s = Scope::new();
3029 s.declare_array(
3030 "a",
3031 vec![
3032 StrykeValue::integer(10),
3033 StrykeValue::integer(20),
3034 StrykeValue::integer(30),
3035 ],
3036 );
3037 assert_eq!(s.get_array_element("a", -1).to_int(), 30);
3038 }
3039
3040 #[test]
3041 fn set_array_element_extends_array_with_undef_gaps() {
3042 let mut s = Scope::new();
3043 s.declare_array("a", vec![]);
3044 s.set_array_element("a", 2, StrykeValue::integer(7))
3045 .unwrap();
3046 assert_eq!(s.get_array_element("a", 2).to_int(), 7);
3047 assert!(s.get_array_element("a", 0).is_undef());
3048 }
3049
3050 #[test]
3051 fn capture_restore_roundtrip_scalar() {
3052 let mut s = Scope::new();
3053 s.declare_scalar("n", StrykeValue::integer(42));
3054 let cap = s.capture();
3055 let mut t = Scope::new();
3056 t.restore_capture(&cap);
3057 assert_eq!(t.get_scalar("n").to_int(), 42);
3058 }
3059
3060 #[test]
3061 fn capture_restore_roundtrip_lexical_array_and_hash() {
3062 let mut s = Scope::new();
3063 s.declare_array("a", vec![StrykeValue::integer(1), StrykeValue::integer(2)]);
3064 let mut m = IndexMap::new();
3065 m.insert("k".to_string(), StrykeValue::integer(99));
3066 s.declare_hash("h", m);
3067 let cap = s.capture();
3068 let mut t = Scope::new();
3069 t.restore_capture(&cap);
3070 assert_eq!(t.get_array_element("a", 1).to_int(), 2);
3071 assert_eq!(t.get_hash_element("h", "k").to_int(), 99);
3072 }
3073
3074 #[test]
3075 fn hash_get_set_delete_exists() {
3076 let mut s = Scope::new();
3077 let mut m = IndexMap::new();
3078 m.insert("k".to_string(), StrykeValue::integer(1));
3079 s.declare_hash("h", m);
3080 assert_eq!(s.get_hash_element("h", "k").to_int(), 1);
3081 assert!(s.exists_hash_element("h", "k"));
3082 s.set_hash_element("h", "k", StrykeValue::integer(99))
3083 .unwrap();
3084 assert_eq!(s.get_hash_element("h", "k").to_int(), 99);
3085 let del = s.delete_hash_element("h", "k").unwrap();
3086 assert_eq!(del.to_int(), 99);
3087 assert!(!s.exists_hash_element("h", "k"));
3088 }
3089
3090 #[test]
3091 fn inner_frame_shadows_outer_hash_name() {
3092 let mut s = Scope::new();
3093 let mut outer = IndexMap::new();
3094 outer.insert("k".to_string(), StrykeValue::integer(1));
3095 s.declare_hash("h", outer);
3096 s.push_frame();
3097 let mut inner = IndexMap::new();
3098 inner.insert("k".to_string(), StrykeValue::integer(2));
3099 s.declare_hash("h", inner);
3100 assert_eq!(s.get_hash_element("h", "k").to_int(), 2);
3101 s.pop_frame();
3102 assert_eq!(s.get_hash_element("h", "k").to_int(), 1);
3103 }
3104
3105 #[test]
3106 fn inner_frame_shadows_outer_array_name() {
3107 let mut s = Scope::new();
3108 s.declare_array("a", vec![StrykeValue::integer(1)]);
3109 s.push_frame();
3110 s.declare_array("a", vec![StrykeValue::integer(2), StrykeValue::integer(3)]);
3111 assert_eq!(s.get_array_element("a", 1).to_int(), 3);
3112 s.pop_frame();
3113 assert_eq!(s.get_array_element("a", 0).to_int(), 1);
3114 }
3115
3116 #[test]
3117 fn pop_frame_never_removes_global_frame() {
3118 let mut s = Scope::new();
3119 s.declare_scalar("x", StrykeValue::integer(1));
3120 s.pop_frame();
3121 s.pop_frame();
3122 assert_eq!(s.get_scalar("x").to_int(), 1);
3123 }
3124
3125 #[test]
3126 fn empty_array_declared_has_zero_length() {
3127 let mut s = Scope::new();
3128 s.declare_array("a", vec![]);
3129 assert_eq!(s.get_array("a").len(), 0);
3130 }
3131
3132 #[test]
3133 fn depth_increments_with_push_frame() {
3134 let mut s = Scope::new();
3135 let d0 = s.depth();
3136 s.push_frame();
3137 assert_eq!(s.depth(), d0 + 1);
3138 s.pop_frame();
3139 assert_eq!(s.depth(), d0);
3140 }
3141
3142 #[test]
3143 fn pop_to_depth_unwinds_to_target() {
3144 let mut s = Scope::new();
3145 s.push_frame();
3146 s.push_frame();
3147 let target = s.depth() - 1;
3148 s.pop_to_depth(target);
3149 assert_eq!(s.depth(), target);
3150 }
3151
3152 #[test]
3153 fn array_len_and_push_pop_roundtrip() {
3154 let mut s = Scope::new();
3155 s.declare_array("a", vec![]);
3156 assert_eq!(s.array_len("a"), 0);
3157 s.push_to_array("a", StrykeValue::integer(1)).unwrap();
3158 s.push_to_array("a", StrykeValue::integer(2)).unwrap();
3159 assert_eq!(s.array_len("a"), 2);
3160 assert_eq!(s.pop_from_array("a").unwrap().to_int(), 2);
3161 assert_eq!(s.pop_from_array("a").unwrap().to_int(), 1);
3162 assert!(s.pop_from_array("a").unwrap().is_undef());
3163 }
3164
3165 #[test]
3166 fn shift_from_array_drops_front() {
3167 let mut s = Scope::new();
3168 s.declare_array("a", vec![StrykeValue::integer(1), StrykeValue::integer(2)]);
3169 assert_eq!(s.shift_from_array("a").unwrap().to_int(), 1);
3170 assert_eq!(s.array_len("a"), 1);
3171 }
3172
3173 #[test]
3174 fn atomic_mutate_increments_wrapped_scalar() {
3175 use parking_lot::Mutex;
3176 use std::sync::Arc;
3177 let mut s = Scope::new();
3178 s.declare_scalar(
3179 "n",
3180 StrykeValue::atomic(Arc::new(Mutex::new(StrykeValue::integer(10)))),
3181 );
3182 let v = s
3183 .atomic_mutate("n", |old| StrykeValue::integer(old.to_int() + 5))
3184 .expect("atomic_mutate on atomic-backed scalar must not fail");
3185 assert_eq!(v.to_int(), 15);
3186 assert_eq!(s.get_scalar("n").to_int(), 15);
3187 }
3188
3189 #[test]
3190 fn atomic_mutate_post_returns_old_value() {
3191 use parking_lot::Mutex;
3192 use std::sync::Arc;
3193 let mut s = Scope::new();
3194 s.declare_scalar(
3195 "n",
3196 StrykeValue::atomic(Arc::new(Mutex::new(StrykeValue::integer(7)))),
3197 );
3198 let old = s
3199 .atomic_mutate_post("n", |v| StrykeValue::integer(v.to_int() + 1))
3200 .expect("atomic_mutate_post on atomic-backed scalar must not fail");
3201 assert_eq!(old.to_int(), 7);
3202 assert_eq!(s.get_scalar("n").to_int(), 8);
3203 }
3204
3205 #[test]
3206 fn get_scalar_raw_keeps_atomic_wrapper() {
3207 use parking_lot::Mutex;
3208 use std::sync::Arc;
3209 let mut s = Scope::new();
3210 s.declare_scalar(
3211 "n",
3212 StrykeValue::atomic(Arc::new(Mutex::new(StrykeValue::integer(3)))),
3213 );
3214 assert!(s.get_scalar_raw("n").is_atomic());
3215 assert!(!s.get_scalar("n").is_atomic());
3216 }
3217
3218 #[test]
3219 fn missing_array_element_is_undef() {
3220 let mut s = Scope::new();
3221 s.declare_array("a", vec![StrykeValue::integer(1)]);
3222 assert!(s.get_array_element("a", 99).is_undef());
3223 }
3224
3225 #[test]
3226 fn restore_atomics_puts_atomic_containers_in_frame() {
3227 use indexmap::IndexMap;
3228 use parking_lot::Mutex;
3229 use std::sync::Arc;
3230 let mut s = Scope::new();
3231 let aa = AtomicArray(Arc::new(Mutex::new(vec![StrykeValue::integer(1)])));
3232 let ah = AtomicHash(Arc::new(Mutex::new(IndexMap::new())));
3233 s.restore_atomics(&[("ax".into(), aa.clone())], &[("hx".into(), ah.clone())]);
3234 assert_eq!(s.get_array_element("ax", 0).to_int(), 1);
3235 assert_eq!(s.array_len("ax"), 1);
3236 s.set_hash_element("hx", "k", StrykeValue::integer(2))
3237 .unwrap();
3238 assert_eq!(s.get_hash_element("hx", "k").to_int(), 2);
3239 }
3240
3241 #[test]
3249 fn topic_alias_pairs_underscore_with_zero() {
3250 assert_eq!(topic_alias("_").as_deref(), Some("_0"));
3251 assert_eq!(topic_alias("_0").as_deref(), Some("_"));
3252 }
3253
3254 #[test]
3255 fn topic_alias_pairs_outer_chain_with_zero_form() {
3256 assert_eq!(topic_alias("_<").as_deref(), Some("_0<"));
3258 assert_eq!(topic_alias("_0<").as_deref(), Some("_<"));
3259 assert_eq!(topic_alias("_<<<").as_deref(), Some("_0<<<"));
3260 assert_eq!(topic_alias("_0<<<").as_deref(), Some("_<<<"));
3261 }
3262
3263 #[test]
3264 fn topic_alias_has_no_pair_for_other_positionals() {
3265 assert!(topic_alias("_1").is_none());
3267 assert!(topic_alias("_2").is_none());
3268 assert!(topic_alias("_42").is_none());
3269 assert!(topic_alias("_<5").is_none());
3271 assert!(topic_alias("foo").is_none());
3273 assert!(topic_alias("_foo").is_none());
3274 }
3275
3276 #[test]
3279 fn positional_topic_slot_parses_underscore_n() {
3280 assert_eq!(parse_positional_topic_slot("_1"), Some(1));
3283 assert_eq!(parse_positional_topic_slot("_2"), Some(2));
3284 assert_eq!(parse_positional_topic_slot("_42"), Some(42));
3285 }
3286
3287 #[test]
3288 fn positional_topic_slot_rejects_non_positional_names() {
3289 assert!(
3290 parse_positional_topic_slot("_").is_none(),
3291 "bare _ has no slot"
3292 );
3293 assert!(
3294 parse_positional_topic_slot("_0").is_none(),
3295 "_0 is the topic alias, not positional"
3296 );
3297 assert!(parse_positional_topic_slot("_foo").is_none(), "named");
3298 assert!(parse_positional_topic_slot("foo").is_none());
3299 assert!(parse_positional_topic_slot("").is_none());
3300 }
3301}