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::PerlError;
9use crate::value::PerlValue;
10
11#[derive(Debug, Clone)]
13pub struct AtomicArray(pub Arc<Mutex<Vec<PerlValue>>>);
14
15#[derive(Debug, Clone)]
17pub struct AtomicHash(pub Arc<Mutex<IndexMap<String, PerlValue>>>);
18
19type ScopeCaptureWithAtomics = (
20 Vec<(String, PerlValue)>,
21 Vec<(String, AtomicArray)>,
22 Vec<(String, AtomicHash)>,
23);
24
25type SharedHashEntry = (
28 String,
29 Arc<parking_lot::RwLock<IndexMap<String, PerlValue>>>,
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 parse_positional_topic_slot(name: &str) -> Option<usize> {
80 let bytes = name.as_bytes();
81 if bytes.len() < 2 || bytes[0] != b'_' || !bytes[1].is_ascii_digit() {
82 return None;
83 }
84 let mut i = 1;
85 while i < bytes.len() && bytes[i].is_ascii_digit() {
86 i += 1;
87 }
88 let digits = &name[1..i];
89 while i < bytes.len() && bytes[i] == b'<' {
90 i += 1;
91 }
92 if i != bytes.len() {
93 return None;
94 }
95 digits.parse().ok().filter(|&n: &usize| n >= 1)
96}
97
98#[derive(Clone, Debug)]
100enum LocalRestore {
101 Scalar(String, PerlValue),
102 Array(String, Vec<PerlValue>),
103 Hash(String, IndexMap<String, PerlValue>),
104 HashElement(String, String, Option<PerlValue>),
106 ArrayElement(String, i64, PerlValue),
108}
109
110#[derive(Debug, Clone)]
115struct Frame {
116 scalars: Vec<(String, PerlValue)>,
117 arrays: Vec<(String, Vec<PerlValue>)>,
118 sub_underscore: Option<Vec<PerlValue>>,
121 hashes: Vec<(String, IndexMap<String, PerlValue>)>,
122 scalar_slots: Vec<PerlValue>,
126 scalar_slot_names: Vec<Option<String>>,
129 local_restores: Vec<LocalRestore>,
131 frozen_scalars: HashSet<String>,
133 frozen_arrays: HashSet<String>,
134 frozen_hashes: HashSet<String>,
135 typed_scalars: HashMap<String, PerlTypeName>,
137 shared_arrays: Vec<(String, Arc<parking_lot::RwLock<Vec<PerlValue>>>)>,
141 shared_hashes: Vec<SharedHashEntry>,
143 atomic_arrays: Vec<(String, AtomicArray)>,
145 atomic_hashes: Vec<(String, AtomicHash)>,
147 defers: Vec<PerlValue>,
149 set_topic_called: bool,
156}
157
158impl Frame {
159 #[inline]
162 fn clear_all_bindings(&mut self) {
163 self.scalars.clear();
164 self.arrays.clear();
165 self.sub_underscore = None;
166 self.hashes.clear();
167 self.scalar_slots.clear();
168 self.scalar_slot_names.clear();
169 self.local_restores.clear();
170 self.frozen_scalars.clear();
171 self.frozen_arrays.clear();
172 self.frozen_hashes.clear();
173 self.typed_scalars.clear();
174 self.shared_arrays.clear();
175 self.shared_hashes.clear();
176 self.atomic_arrays.clear();
177 self.defers.clear();
178 self.atomic_hashes.clear();
179 self.set_topic_called = false;
180 }
181
182 #[inline]
186 fn owns_scalar_slot_index(&self, idx: usize) -> bool {
187 self.scalar_slot_names.get(idx).is_some_and(|n| n.is_some())
188 }
189
190 #[inline]
191 fn new() -> Self {
192 Self {
193 scalars: Vec::new(),
194 arrays: Vec::new(),
195 sub_underscore: None,
196 hashes: Vec::new(),
197 scalar_slots: Vec::new(),
198 scalar_slot_names: Vec::new(),
199 frozen_scalars: HashSet::new(),
200 frozen_arrays: HashSet::new(),
201 frozen_hashes: HashSet::new(),
202 shared_arrays: Vec::new(),
203 shared_hashes: Vec::new(),
204 typed_scalars: HashMap::new(),
205 atomic_arrays: Vec::new(),
206 atomic_hashes: Vec::new(),
207 local_restores: Vec::new(),
208 defers: Vec::new(),
209 set_topic_called: false,
210 }
211 }
212
213 #[inline]
214 fn get_scalar(&self, name: &str) -> Option<&PerlValue> {
215 let name = strip_main_prefix(name).unwrap_or(name);
216 if let Some(v) = self.get_scalar_from_slot(name) {
217 return Some(v);
218 }
219 self.scalars.iter().find(|(k, _)| k == name).map(|(_, v)| v)
220 }
221
222 #[inline]
225 fn get_scalar_from_slot(&self, name: &str) -> Option<&PerlValue> {
226 let name = strip_main_prefix(name).unwrap_or(name);
227 for (i, sn) in self.scalar_slot_names.iter().enumerate() {
228 if let Some(ref n) = sn {
229 if n == name {
230 return self.scalar_slots.get(i);
231 }
232 }
233 }
234 None
235 }
236
237 #[inline]
238 fn has_scalar(&self, name: &str) -> bool {
239 let name = strip_main_prefix(name).unwrap_or(name);
240 if self
241 .scalar_slot_names
242 .iter()
243 .any(|sn| sn.as_deref() == Some(name))
244 {
245 return true;
246 }
247 self.scalars.iter().any(|(k, _)| k == name)
248 }
249
250 #[inline]
251 fn set_scalar(&mut self, name: &str, val: PerlValue) {
252 let name = strip_main_prefix(name).unwrap_or(name);
253 for (i, sn) in self.scalar_slot_names.iter().enumerate() {
254 if let Some(ref n) = sn {
255 if n == name {
256 if i < self.scalar_slots.len() {
257 if let Some(r) = self.scalar_slots[i].as_capture_cell() {
259 *r.write() = val;
260 } else {
261 self.scalar_slots[i] = val;
262 }
263 }
264 return;
265 }
266 }
267 }
268 if let Some(entry) = self.scalars.iter_mut().find(|(k, _)| k == name) {
269 if let Some(r) = entry.1.as_capture_cell() {
271 *r.write() = val;
272 } else {
273 entry.1 = val;
274 }
275 } else {
276 self.scalars.push((name.to_string(), val));
277 }
278 }
279
280 #[inline]
289 fn set_scalar_raw(&mut self, name: &str, val: PerlValue) {
290 let name = strip_main_prefix(name).unwrap_or(name);
291 for (i, sn) in self.scalar_slot_names.iter().enumerate() {
292 if let Some(ref n) = sn {
293 if n == name {
294 if i < self.scalar_slots.len() {
295 self.scalar_slots[i] = val;
296 }
297 return;
298 }
299 }
300 }
301 if let Some(entry) = self.scalars.iter_mut().find(|(k, _)| k == name) {
302 entry.1 = val;
303 } else {
304 self.scalars.push((name.to_string(), val));
305 }
306 }
307
308 #[inline]
309 fn get_array(&self, name: &str) -> Option<&Vec<PerlValue>> {
310 let name = strip_main_prefix(name).unwrap_or(name);
311 if name == "_" {
312 if let Some(ref v) = self.sub_underscore {
313 return Some(v);
314 }
315 }
316 self.arrays.iter().find(|(k, _)| k == name).map(|(_, v)| v)
317 }
318
319 #[inline]
320 fn has_array(&self, name: &str) -> bool {
321 let name = strip_main_prefix(name).unwrap_or(name);
322 if name == "_" && self.sub_underscore.is_some() {
323 return true;
324 }
325 self.arrays.iter().any(|(k, _)| k == name)
326 || self.shared_arrays.iter().any(|(k, _)| k == name)
327 }
328
329 #[inline]
330 fn get_array_mut(&mut self, name: &str) -> Option<&mut Vec<PerlValue>> {
331 let name = strip_main_prefix(name).unwrap_or(name);
332 if name == "_" {
333 return self.sub_underscore.as_mut();
334 }
335 self.arrays
336 .iter_mut()
337 .find(|(k, _)| k == name)
338 .map(|(_, v)| v)
339 }
340
341 #[inline]
342 fn set_array(&mut self, name: &str, val: Vec<PerlValue>) {
343 let name = strip_main_prefix(name).unwrap_or(name);
344 if name == "_" {
345 if let Some(pos) = self.arrays.iter().position(|(k, _)| k == name) {
346 self.arrays.swap_remove(pos);
347 }
348 self.sub_underscore = Some(val);
349 return;
350 }
351 if let Some(entry) = self.arrays.iter_mut().find(|(k, _)| k == name) {
352 entry.1 = val;
353 } else {
354 self.arrays.push((name.to_string(), val));
355 }
356 }
357
358 #[inline]
359 fn get_hash(&self, name: &str) -> Option<&IndexMap<String, PerlValue>> {
360 let name = strip_main_prefix(name).unwrap_or(name);
361 self.hashes.iter().find(|(k, _)| k == name).map(|(_, v)| v)
362 }
363
364 #[inline]
365 fn has_hash(&self, name: &str) -> bool {
366 let name = strip_main_prefix(name).unwrap_or(name);
367 self.hashes.iter().any(|(k, _)| k == name)
368 || self.shared_hashes.iter().any(|(k, _)| k == name)
369 }
370
371 #[inline]
372 fn get_hash_mut(&mut self, name: &str) -> Option<&mut IndexMap<String, PerlValue>> {
373 let name = strip_main_prefix(name).unwrap_or(name);
374 self.hashes
375 .iter_mut()
376 .find(|(k, _)| k == name)
377 .map(|(_, v)| v)
378 }
379
380 #[inline]
381 fn set_hash(&mut self, name: &str, val: IndexMap<String, PerlValue>) {
382 let name = strip_main_prefix(name).unwrap_or(name);
383 if let Some(entry) = self.hashes.iter_mut().find(|(k, _)| k == name) {
384 entry.1 = val;
385 } else {
386 self.hashes.push((name.to_string(), val));
387 }
388 }
389}
390
391#[derive(Debug, Clone)]
394pub struct Scope {
395 frames: Vec<Frame>,
396 frame_pool: Vec<Frame>,
398 parallel_guard: bool,
403 max_active_slot: usize,
410}
411
412impl Default for Scope {
413 fn default() -> Self {
414 Self::new()
415 }
416}
417
418impl Scope {
419 pub fn new() -> Self {
420 let mut s = Self {
421 frames: Vec::with_capacity(32),
422 frame_pool: Vec::with_capacity(32),
423 parallel_guard: false,
424 max_active_slot: 0,
425 };
426 s.frames.push(Frame::new());
427 s
428 }
429
430 #[inline]
432 pub fn set_parallel_guard(&mut self, enabled: bool) {
433 self.parallel_guard = enabled;
434 }
435
436 #[inline]
437 pub fn parallel_guard(&self) -> bool {
438 self.parallel_guard
439 }
440
441 #[inline]
448 fn parallel_skip_special_name(_name: &str) -> bool {
449 false
450 }
451
452 #[inline]
454 fn parallel_allowed_topic_scalar(name: &str) -> bool {
455 matches!(name, "_" | "a" | "b")
456 }
457
458 #[inline]
460 fn parallel_allowed_internal_array(name: &str) -> bool {
461 matches!(name, "-" | "+" | "^CAPTURE" | "^CAPTURE_ALL")
462 }
463
464 #[inline]
466 fn parallel_allowed_internal_hash(name: &str) -> bool {
467 matches!(name, "+" | "-" | "ENV" | "INC")
468 }
469
470 fn check_parallel_scalar_write(&self, name: &str) -> Result<(), PerlError> {
471 if !self.parallel_guard || Self::parallel_skip_special_name(name) {
472 return Ok(());
473 }
474 if Self::parallel_allowed_topic_scalar(name) {
475 return Ok(());
476 }
477 if crate::special_vars::is_regex_match_scalar_name(name) {
478 return Ok(());
479 }
480 let inner = self.frames.len().saturating_sub(1);
481 for (i, frame) in self.frames.iter().enumerate().rev() {
482 if frame.has_scalar(name) {
483 if let Some(v) = frame.get_scalar(name) {
484 if v.as_atomic_arc().is_some() {
485 return Ok(());
486 }
487 }
488 if i != inner {
489 let directive = if name.contains("::") {
493 "declare `oursync` for shared package-global state"
494 } else {
495 "declare `mysync` for shared lexical state"
496 };
497 return Err(PerlError::runtime(
498 format!(
499 "cannot assign to captured non-atomic variable `${}` in a parallel block — {}",
500 name, directive
501 ),
502 0,
503 ));
504 }
505 return Ok(());
506 }
507 }
508 Err(PerlError::runtime(
509 format!(
510 "cannot assign to undeclared variable `${}` in a parallel block",
511 name
512 ),
513 0,
514 ))
515 }
516
517 #[inline]
518 pub fn depth(&self) -> usize {
519 self.frames.len()
520 }
521
522 #[inline]
525 pub fn pop_to_depth(&mut self, target_depth: usize) {
526 while self.frames.len() > target_depth && self.frames.len() > 1 {
527 self.pop_frame();
528 }
529 }
530
531 #[inline]
532 pub fn push_frame(&mut self) {
533 if let Some(mut frame) = self.frame_pool.pop() {
534 frame.clear_all_bindings();
535 self.frames.push(frame);
536 } else {
537 self.frames.push(Frame::new());
538 }
539 }
540
541 #[inline]
546 pub fn get_scalar_slot(&self, slot: u8) -> PerlValue {
547 let idx = slot as usize;
548 for frame in self.frames.iter().rev() {
549 if idx < frame.scalar_slots.len() && frame.owns_scalar_slot_index(idx) {
550 let val = &frame.scalar_slots[idx];
551 if let Some(arc) = val.as_capture_cell() {
554 return arc.read().clone();
555 }
556 return val.clone();
557 }
558 }
559 PerlValue::UNDEF
560 }
561
562 #[inline]
564 pub fn set_scalar_slot(&mut self, slot: u8, val: PerlValue) {
565 let idx = slot as usize;
566 let len = self.frames.len();
567 for i in (0..len).rev() {
568 if idx < self.frames[i].scalar_slots.len() && self.frames[i].owns_scalar_slot_index(idx)
569 {
570 if let Some(r) = self.frames[i].scalar_slots[idx].as_capture_cell() {
572 *r.write() = val;
573 } else {
574 self.frames[i].scalar_slots[idx] = val;
575 }
576 return;
577 }
578 }
579 let top = self.frames.last_mut().unwrap();
580 top.scalar_slots.resize(idx + 1, PerlValue::UNDEF);
581 if idx >= top.scalar_slot_names.len() {
582 top.scalar_slot_names.resize(idx + 1, None);
583 }
584 top.scalar_slot_names[idx] = Some(String::new());
585 top.scalar_slots[idx] = val;
586 }
587
588 #[inline]
592 pub fn set_scalar_slot_checked(
593 &mut self,
594 slot: u8,
595 val: PerlValue,
596 slot_name: Option<&str>,
597 ) -> Result<(), PerlError> {
598 if self.parallel_guard {
599 let idx = slot as usize;
600 let len = self.frames.len();
601 let top_has = idx < self.frames[len - 1].scalar_slots.len()
602 && self.frames[len - 1].owns_scalar_slot_index(idx);
603 if !top_has {
604 let name_owned: String = {
605 let mut found = String::new();
606 for i in (0..len).rev() {
607 if let Some(Some(n)) = self.frames[i].scalar_slot_names.get(idx) {
608 found = n.clone();
609 break;
610 }
611 }
612 if found.is_empty() {
613 if let Some(sn) = slot_name {
614 found = sn.to_string();
615 }
616 }
617 found
618 };
619 let name = name_owned.as_str();
620 if !name.is_empty() && !Self::parallel_allowed_topic_scalar(name) {
621 let inner = len.saturating_sub(1);
622 for (fi, frame) in self.frames.iter().enumerate().rev() {
623 if frame.has_scalar(name)
624 || (idx < frame.scalar_slots.len() && frame.owns_scalar_slot_index(idx))
625 {
626 if fi != inner {
627 return Err(PerlError::runtime(
628 format!(
629 "cannot assign to captured outer lexical `${}` inside a parallel block (use `mysync`)",
630 name
631 ),
632 0,
633 ));
634 }
635 break;
636 }
637 }
638 }
639 }
640 }
641 self.set_scalar_slot(slot, val);
642 Ok(())
643 }
644
645 #[inline]
649 pub fn declare_scalar_slot(&mut self, slot: u8, val: PerlValue, name: Option<&str>) {
650 let idx = slot as usize;
651 let frame = self.frames.last_mut().unwrap();
652 if idx >= frame.scalar_slots.len() {
653 frame.scalar_slots.resize(idx + 1, PerlValue::UNDEF);
654 }
655 frame.scalar_slots[idx] = val;
656 if idx >= frame.scalar_slot_names.len() {
657 frame.scalar_slot_names.resize(idx + 1, None);
658 }
659 match name {
660 Some(n) => frame.scalar_slot_names[idx] = Some(n.to_string()),
661 None => frame.scalar_slot_names[idx] = Some(String::new()),
663 }
664 }
665
666 #[inline]
677 pub fn scalar_slot_concat_repeat_inplace(&mut self, slot: u8, rhs: &str, n: usize) -> bool {
678 let idx = slot as usize;
679 let len = self.frames.len();
680 let fi = {
681 let mut found = len - 1;
682 if idx >= self.frames[found].scalar_slots.len()
683 || !self.frames[found].owns_scalar_slot_index(idx)
684 {
685 for i in (0..len - 1).rev() {
686 if idx < self.frames[i].scalar_slots.len()
687 && self.frames[i].owns_scalar_slot_index(idx)
688 {
689 found = i;
690 break;
691 }
692 }
693 }
694 found
695 };
696 let frame = &mut self.frames[fi];
697 if idx >= frame.scalar_slots.len() {
698 frame.scalar_slots.resize(idx + 1, PerlValue::UNDEF);
699 }
700 frame.scalar_slots[idx].try_concat_repeat_inplace(rhs, n)
701 }
702
703 #[inline]
708 pub fn scalar_slot_concat_repeat_slow(&mut self, slot: u8, rhs: &str, n: usize) {
709 let pv = PerlValue::string(rhs.to_owned());
710 for _ in 0..n {
711 let _ = self.scalar_slot_concat_inplace(slot, &pv);
712 }
713 }
714
715 #[inline]
716 pub fn scalar_slot_concat_inplace(&mut self, slot: u8, rhs: &PerlValue) -> PerlValue {
717 let idx = slot as usize;
718 let len = self.frames.len();
719 let fi = {
720 let mut found = len - 1;
721 if idx >= self.frames[found].scalar_slots.len()
722 || !self.frames[found].owns_scalar_slot_index(idx)
723 {
724 for i in (0..len - 1).rev() {
725 if idx < self.frames[i].scalar_slots.len()
726 && self.frames[i].owns_scalar_slot_index(idx)
727 {
728 found = i;
729 break;
730 }
731 }
732 }
733 found
734 };
735 let frame = &mut self.frames[fi];
736 if idx >= frame.scalar_slots.len() {
737 frame.scalar_slots.resize(idx + 1, PerlValue::UNDEF);
738 }
739 if frame.scalar_slots[idx].try_concat_append_inplace(rhs) {
747 return frame.scalar_slots[idx].shallow_clone();
748 }
749 let new_val = std::mem::replace(&mut frame.scalar_slots[idx], PerlValue::UNDEF)
750 .concat_append_owned(rhs);
751 let handle = new_val.shallow_clone();
752 frame.scalar_slots[idx] = new_val;
753 handle
754 }
755
756 #[inline]
757 pub(crate) fn can_pop_frame(&self) -> bool {
758 self.frames.len() > 1
759 }
760
761 #[inline]
762 pub fn pop_frame(&mut self) {
763 if self.frames.len() > 1 {
764 let mut frame = self.frames.pop().expect("pop_frame");
765 let saved_guard = self.parallel_guard;
768 self.parallel_guard = false;
769 for entry in frame.local_restores.drain(..).rev() {
770 match entry {
771 LocalRestore::Scalar(name, old) => {
772 let _ = self.set_scalar(&name, old);
773 }
774 LocalRestore::Array(name, old) => {
775 let _ = self.set_array(&name, old);
776 }
777 LocalRestore::Hash(name, old) => {
778 let _ = self.set_hash(&name, old);
779 }
780 LocalRestore::HashElement(name, key, old) => match old {
781 Some(v) => {
782 let _ = self.set_hash_element(&name, &key, v);
783 }
784 None => {
785 let _ = self.delete_hash_element(&name, &key);
786 }
787 },
788 LocalRestore::ArrayElement(name, index, old) => {
789 let _ = self.set_array_element(&name, index, old);
790 }
791 }
792 }
793 self.parallel_guard = saved_guard;
794 frame.clear_all_bindings();
795 if self.frame_pool.len() < 64 {
797 self.frame_pool.push(frame);
798 }
799 }
800 }
801
802 pub fn local_set_scalar(&mut self, name: &str, val: PerlValue) -> Result<(), PerlError> {
804 let old = self.get_scalar(name);
805 if let Some(frame) = self.frames.last_mut() {
806 frame
807 .local_restores
808 .push(LocalRestore::Scalar(name.to_string(), old));
809 }
810 self.set_scalar(name, val)
811 }
812
813 pub fn local_set_array(&mut self, name: &str, val: Vec<PerlValue>) -> Result<(), PerlError> {
815 if self.find_atomic_array(name).is_some() {
816 return Err(PerlError::runtime(
817 "local cannot be used on mysync arrays",
818 0,
819 ));
820 }
821 let old = self.get_array(name);
822 if let Some(frame) = self.frames.last_mut() {
823 frame
824 .local_restores
825 .push(LocalRestore::Array(name.to_string(), old));
826 }
827 self.set_array(name, val)?;
828 Ok(())
829 }
830
831 pub fn local_set_hash(
833 &mut self,
834 name: &str,
835 val: IndexMap<String, PerlValue>,
836 ) -> Result<(), PerlError> {
837 if self.find_atomic_hash(name).is_some() {
838 return Err(PerlError::runtime(
839 "local cannot be used on mysync hashes",
840 0,
841 ));
842 }
843 let old = self.get_hash(name);
844 if let Some(frame) = self.frames.last_mut() {
845 frame
846 .local_restores
847 .push(LocalRestore::Hash(name.to_string(), old));
848 }
849 self.set_hash(name, val)?;
850 Ok(())
851 }
852
853 pub fn local_set_hash_element(
855 &mut self,
856 name: &str,
857 key: &str,
858 val: PerlValue,
859 ) -> Result<(), PerlError> {
860 if self.find_atomic_hash(name).is_some() {
861 return Err(PerlError::runtime(
862 "local cannot be used on mysync hash elements",
863 0,
864 ));
865 }
866 let old = if self.exists_hash_element(name, key) {
867 Some(self.get_hash_element(name, key))
868 } else {
869 None
870 };
871 if let Some(frame) = self.frames.last_mut() {
872 frame.local_restores.push(LocalRestore::HashElement(
873 name.to_string(),
874 key.to_string(),
875 old,
876 ));
877 }
878 self.set_hash_element(name, key, val)?;
879 Ok(())
880 }
881
882 pub fn local_set_array_element(
885 &mut self,
886 name: &str,
887 index: i64,
888 val: PerlValue,
889 ) -> Result<(), PerlError> {
890 if self.find_atomic_array(name).is_some() {
891 return Err(PerlError::runtime(
892 "local cannot be used on mysync array elements",
893 0,
894 ));
895 }
896 let old = self.get_array_element(name, index);
897 if let Some(frame) = self.frames.last_mut() {
898 frame
899 .local_restores
900 .push(LocalRestore::ArrayElement(name.to_string(), index, old));
901 }
902 self.set_array_element(name, index, val)?;
903 Ok(())
904 }
905
906 #[inline]
909 pub fn declare_scalar(&mut self, name: &str, val: PerlValue) {
910 let _ = self.declare_scalar_frozen(name, val, false, None);
911 }
912
913 pub fn declare_scalar_frozen(
916 &mut self,
917 name: &str,
918 val: PerlValue,
919 frozen: bool,
920 ty: Option<PerlTypeName>,
921 ) -> Result<(), PerlError> {
922 canon_main!(name);
923 if let Some(ref t) = ty {
924 t.check_value(&val)
925 .map_err(|msg| PerlError::type_error(format!("`${}`: {}", name, msg), 0))?;
926 }
927 if let Some(frame) = self.frames.last_mut() {
928 frame.set_scalar(name, val);
929 if frozen {
930 frame.frozen_scalars.insert(name.to_string());
931 }
932 if let Some(t) = ty {
933 frame.typed_scalars.insert(name.to_string(), t);
934 }
935 }
936 Ok(())
937 }
938
939 pub fn is_scalar_frozen(&self, name: &str) -> bool {
941 for frame in self.frames.iter().rev() {
942 if frame.has_scalar(name) {
943 return frame.frozen_scalars.contains(name);
944 }
945 }
946 false
947 }
948
949 pub fn is_array_frozen(&self, name: &str) -> bool {
951 for frame in self.frames.iter().rev() {
952 if frame.has_array(name) {
953 return frame.frozen_arrays.contains(name);
954 }
955 }
956 false
957 }
958
959 pub fn is_hash_frozen(&self, name: &str) -> bool {
961 for frame in self.frames.iter().rev() {
962 if frame.has_hash(name) {
963 return frame.frozen_hashes.contains(name);
964 }
965 }
966 false
967 }
968
969 pub fn check_frozen(&self, sigil: &str, name: &str) -> Option<&'static str> {
971 match sigil {
972 "$" => {
973 if self.is_scalar_frozen(name) {
974 Some("scalar")
975 } else {
976 None
977 }
978 }
979 "@" => {
980 if self.is_array_frozen(name) {
981 Some("array")
982 } else {
983 None
984 }
985 }
986 "%" => {
987 if self.is_hash_frozen(name) {
988 Some("hash")
989 } else {
990 None
991 }
992 }
993 _ => None,
994 }
995 }
996
997 #[inline]
998 pub fn get_scalar(&self, name: &str) -> PerlValue {
999 if let Some(rest) = strip_main_prefix(name) {
1001 return self.get_scalar(rest);
1002 }
1003 for frame in self.frames.iter().rev() {
1004 if let Some(val) = frame.get_scalar(name) {
1005 if let Some(arc) = val.as_atomic_arc() {
1007 return arc.lock().clone();
1008 }
1009 if let Some(arc) = val.as_capture_cell() {
1012 return arc.read().clone();
1013 }
1014 if val.is_undef() && Self::is_topic_chain_name(name) {
1024 return self.get_scalar("_");
1025 }
1026 return val.clone();
1027 }
1028 }
1029 PerlValue::UNDEF
1030 }
1031
1032 #[inline]
1035 fn is_topic_chain_name(name: &str) -> bool {
1036 let bytes = name.as_bytes();
1037 if bytes.is_empty() || bytes[0] != b'_' {
1038 return false;
1039 }
1040 let mut i = 1;
1041 while i < bytes.len() && bytes[i].is_ascii_digit() {
1042 i += 1;
1043 }
1044 if i >= bytes.len() || bytes[i] != b'<' {
1045 return false;
1046 }
1047 while i < bytes.len() && bytes[i] == b'<' {
1048 i += 1;
1049 }
1050 i == bytes.len()
1051 }
1052
1053 #[inline]
1062 pub(crate) fn is_topic_variant_name(name: &str) -> bool {
1063 let bytes = name.as_bytes();
1064 if bytes.is_empty() || bytes[0] != b'_' {
1065 return false;
1066 }
1067 let mut i = 1;
1068 while i < bytes.len() && bytes[i].is_ascii_digit() {
1069 i += 1;
1070 }
1071 while i < bytes.len() && bytes[i] == b'<' {
1072 i += 1;
1073 }
1074 i == bytes.len()
1075 }
1076
1077 #[inline]
1079 pub fn scalar_binding_exists(&self, name: &str) -> bool {
1080 canon_main!(name);
1081 for frame in self.frames.iter().rev() {
1082 if frame.has_scalar(name) {
1083 return true;
1084 }
1085 }
1086 false
1087 }
1088
1089 pub fn all_scalar_names(&self) -> Vec<String> {
1091 let mut names = Vec::new();
1092 for frame in &self.frames {
1093 for (name, _) in &frame.scalars {
1094 if !names.contains(name) {
1095 names.push(name.clone());
1096 }
1097 }
1098 for name in frame.scalar_slot_names.iter().flatten() {
1099 if !names.contains(name) {
1100 names.push(name.clone());
1101 }
1102 }
1103 }
1104 names
1105 }
1106
1107 #[inline]
1109 pub fn array_binding_exists(&self, name: &str) -> bool {
1110 canon_main!(name);
1111 if self.find_atomic_array(name).is_some() {
1112 return true;
1113 }
1114 for frame in self.frames.iter().rev() {
1115 if frame.has_array(name) {
1116 return true;
1117 }
1118 }
1119 false
1120 }
1121
1122 #[inline]
1124 pub fn hash_binding_exists(&self, name: &str) -> bool {
1125 if let Some(rest) = strip_main_prefix(name) {
1126 return self.hash_binding_exists(rest);
1127 }
1128 if self.find_atomic_hash(name).is_some() {
1129 return true;
1130 }
1131 for frame in self.frames.iter().rev() {
1132 if frame.has_hash(name) {
1133 return true;
1134 }
1135 }
1136 false
1137 }
1138
1139 #[inline]
1142 pub fn get_scalar_raw(&self, name: &str) -> PerlValue {
1143 if let Some(rest) = strip_main_prefix(name) {
1144 return self.get_scalar_raw(rest);
1145 }
1146 for frame in self.frames.iter().rev() {
1147 if let Some(val) = frame.get_scalar(name) {
1148 return val.clone();
1149 }
1150 }
1151 PerlValue::UNDEF
1152 }
1153
1154 pub fn atomic_mutate(
1160 &mut self,
1161 name: &str,
1162 f: impl FnOnce(&PerlValue) -> PerlValue,
1163 ) -> Result<PerlValue, PerlError> {
1164 for frame in self.frames.iter().rev() {
1165 if let Some(v) = frame.get_scalar(name) {
1166 if let Some(arc) = v.as_atomic_arc() {
1167 let mut guard = arc.lock();
1168 let old = guard.clone();
1169 let new_val = f(&guard);
1170 *guard = new_val.clone();
1171 crate::parallel_trace::emit_scalar_mutation(name, &old, &new_val);
1172 return Ok(new_val);
1173 }
1174 }
1175 }
1176 let old = self.get_scalar(name);
1179 let new_val = f(&old);
1180 self.set_scalar(name, new_val.clone())?;
1181 Ok(new_val)
1182 }
1183
1184 pub fn atomic_mutate_post(
1188 &mut self,
1189 name: &str,
1190 f: impl FnOnce(&PerlValue) -> PerlValue,
1191 ) -> Result<PerlValue, PerlError> {
1192 for frame in self.frames.iter().rev() {
1193 if let Some(v) = frame.get_scalar(name) {
1194 if let Some(arc) = v.as_atomic_arc() {
1195 let mut guard = arc.lock();
1196 let old = guard.clone();
1197 let new_val = f(&old);
1198 *guard = new_val.clone();
1199 crate::parallel_trace::emit_scalar_mutation(name, &old, &new_val);
1200 return Ok(old);
1201 }
1202 }
1203 }
1204 let old = self.get_scalar(name);
1206 self.set_scalar(name, f(&old))?;
1207 Ok(old)
1208 }
1209
1210 #[inline]
1217 pub fn scalar_concat_inplace(
1218 &mut self,
1219 name: &str,
1220 rhs: &PerlValue,
1221 ) -> Result<PerlValue, PerlError> {
1222 canon_main!(name);
1223 self.check_parallel_scalar_write(name)?;
1224 for frame in self.frames.iter_mut().rev() {
1225 if let Some(entry) = frame.scalars.iter_mut().find(|(k, _)| k == name) {
1226 if let Some(atomic_arc) = entry.1.as_atomic_arc() {
1229 let mut guard = atomic_arc.lock();
1230 let inner = std::mem::replace(&mut *guard, PerlValue::UNDEF);
1231 let new_val = inner.concat_append_owned(rhs);
1232 *guard = new_val.shallow_clone();
1233 return Ok(new_val);
1234 }
1235 if entry.1.try_concat_append_inplace(rhs) {
1238 return Ok(entry.1.shallow_clone());
1239 }
1240 let new_val =
1243 std::mem::replace(&mut entry.1, PerlValue::UNDEF).concat_append_owned(rhs);
1244 entry.1 = new_val.shallow_clone();
1245 return Ok(new_val);
1246 }
1247 }
1248 let val = PerlValue::UNDEF.concat_append_owned(rhs);
1250 self.frames[0].set_scalar(name, val.shallow_clone());
1251 Ok(val)
1252 }
1253
1254 #[inline]
1255 pub fn set_scalar(&mut self, name: &str, val: PerlValue) -> Result<(), PerlError> {
1256 if let Some(rest) = strip_main_prefix(name) {
1257 return self.set_scalar(rest, val);
1258 }
1259 self.check_parallel_scalar_write(name)?;
1260 if Self::is_topic_variant_name(name) {
1267 if let Some(frame) = self.frames.last_mut() {
1268 frame.set_scalar_raw(name, val);
1269 }
1270 return Ok(());
1271 }
1272 for frame in self.frames.iter_mut().rev() {
1273 if let Some(v) = frame.get_scalar(name) {
1275 if let Some(arc) = v.as_atomic_arc() {
1276 let mut guard = arc.lock();
1277 let old = guard.clone();
1278 *guard = val.clone();
1279 crate::parallel_trace::emit_scalar_mutation(name, &old, &val);
1280 return Ok(());
1281 }
1282 if let Some(arc) = v.as_capture_cell() {
1284 *arc.write() = val;
1285 return Ok(());
1286 }
1287 }
1288 if frame.has_scalar(name) {
1289 if let Some(ty) = frame.typed_scalars.get(name) {
1290 ty.check_value(&val)
1291 .map_err(|msg| PerlError::type_error(format!("`${}`: {}", name, msg), 0))?;
1292 }
1293 frame.set_scalar(name, val);
1294 return Ok(());
1295 }
1296 }
1297 self.frames[0].set_scalar(name, val);
1298 Ok(())
1299 }
1300
1301 #[inline]
1307 fn topic_slot_key(slot: usize, level: usize) -> String {
1308 debug_assert!(level <= 5);
1309 if slot == 0 {
1310 if level == 0 {
1311 "_".to_string()
1312 } else {
1313 format!("_{}", "<".repeat(level))
1314 }
1315 } else if level == 0 {
1316 format!("_{}", slot)
1317 } else {
1318 format!("_{}{}", slot, "<".repeat(level))
1319 }
1320 }
1321
1322 #[inline]
1325 fn topic_slot_alias_key(slot: usize, level: usize) -> Option<String> {
1326 if slot != 0 {
1327 return None;
1328 }
1329 Some(if level == 0 {
1330 "_0".to_string()
1331 } else {
1332 format!("_0{}", "<".repeat(level))
1333 })
1334 }
1335
1336 #[inline]
1342 fn declare_topic_slot(&mut self, slot: usize, level: usize, val: PerlValue) {
1343 if let Some(frame) = self.frames.last_mut() {
1350 let key = Self::topic_slot_key(slot, level);
1351 frame.set_scalar_raw(&key, val.clone());
1352 if let Some(alias) = Self::topic_slot_alias_key(slot, level) {
1353 frame.set_scalar_raw(&alias, val);
1354 }
1355 }
1356 }
1357
1358 #[inline]
1383 pub fn set_topic(&mut self, val: PerlValue) {
1384 let already_shifted = self
1392 .frames
1393 .last()
1394 .map(|f| f.set_topic_called)
1395 .unwrap_or(false);
1396 if already_shifted {
1397 self.declare_topic_slot(0, 0, val);
1398 for slot in 1..=self.max_active_slot {
1399 self.declare_topic_slot(slot, 0, PerlValue::UNDEF);
1400 }
1401 return;
1402 }
1403 if let Some(frame) = self.frames.last_mut() {
1404 frame.set_topic_called = true;
1405 }
1406 self.shift_slot_chain(0, val);
1407 for slot in 1..=self.max_active_slot {
1408 self.shift_slot_chain(slot, PerlValue::UNDEF);
1409 }
1410 }
1411
1412 #[inline]
1421 pub fn set_topic_local(&mut self, val: PerlValue) {
1422 self.declare_topic_slot(0, 0, val);
1423 }
1424
1425 #[inline]
1437 pub fn set_closure_args(&mut self, args: &[PerlValue]) {
1438 let n = args.len();
1439 if n == 0 {
1440 return;
1441 }
1442 let high = n.saturating_sub(1).max(self.max_active_slot);
1443 for slot in 0..=high {
1444 let val = args.get(slot).cloned().unwrap_or(PerlValue::UNDEF);
1445 self.shift_slot_chain(slot, val);
1446 }
1447 if n > 0 && n - 1 > self.max_active_slot {
1448 self.max_active_slot = n - 1;
1449 }
1450 }
1451
1452 #[inline]
1461 fn shift_slot_chain(&mut self, slot: usize, val: PerlValue) {
1462 let l4 = self.get_scalar(&Self::topic_slot_key(slot, 4));
1463 let l3 = self.get_scalar(&Self::topic_slot_key(slot, 3));
1464 let l2 = self.get_scalar(&Self::topic_slot_key(slot, 2));
1465 let l1 = self.get_scalar(&Self::topic_slot_key(slot, 1));
1466 let cur = self.get_scalar(&Self::topic_slot_key(slot, 0));
1467
1468 self.declare_topic_slot(slot, 0, val);
1469 self.declare_topic_slot(slot, 1, cur);
1470 self.declare_topic_slot(slot, 2, l1);
1471 self.declare_topic_slot(slot, 3, l2);
1472 self.declare_topic_slot(slot, 4, l3);
1473 self.declare_topic_slot(slot, 5, l4);
1474 }
1475
1476 #[inline]
1484 pub fn set_sort_pair(&mut self, a: PerlValue, b: PerlValue) {
1485 let _ = self.set_scalar("a", a.clone());
1486 let _ = self.set_scalar("b", b.clone());
1487 let _ = self.set_scalar("_0", a);
1488 let _ = self.set_scalar("_1", b);
1489 }
1490
1491 #[inline]
1493 pub fn push_defer(&mut self, coderef: PerlValue) {
1494 if let Some(frame) = self.frames.last_mut() {
1495 frame.defers.push(coderef);
1496 }
1497 }
1498
1499 #[inline]
1502 pub fn take_defers(&mut self) -> Vec<PerlValue> {
1503 if let Some(frame) = self.frames.last_mut() {
1504 let mut defers = std::mem::take(&mut frame.defers);
1505 defers.reverse();
1506 defers
1507 } else {
1508 Vec::new()
1509 }
1510 }
1511
1512 pub fn declare_atomic_array(&mut self, name: &str, val: Vec<PerlValue>) {
1515 canon_main!(name);
1516 if let Some(frame) = self.frames.last_mut() {
1517 frame
1518 .atomic_arrays
1519 .push((name.to_string(), AtomicArray(Arc::new(Mutex::new(val)))));
1520 }
1521 }
1522
1523 pub fn declare_atomic_hash(&mut self, name: &str, val: IndexMap<String, PerlValue>) {
1524 canon_main!(name);
1525 if let Some(frame) = self.frames.last_mut() {
1526 frame
1527 .atomic_hashes
1528 .push((name.to_string(), AtomicHash(Arc::new(Mutex::new(val)))));
1529 }
1530 }
1531
1532 fn find_atomic_array(&self, name: &str) -> Option<&AtomicArray> {
1534 let name = strip_main_prefix(name).unwrap_or(name);
1535 for frame in self.frames.iter().rev() {
1536 if let Some(aa) = frame.atomic_arrays.iter().find(|(k, _)| k == name) {
1537 return Some(&aa.1);
1538 }
1539 }
1540 None
1541 }
1542
1543 fn find_atomic_hash(&self, name: &str) -> Option<&AtomicHash> {
1545 let name = strip_main_prefix(name).unwrap_or(name);
1546 for frame in self.frames.iter().rev() {
1547 if let Some(ah) = frame.atomic_hashes.iter().find(|(k, _)| k == name) {
1548 return Some(&ah.1);
1549 }
1550 }
1551 None
1552 }
1553
1554 #[inline]
1559 pub fn take_sub_underscore(&mut self) -> Option<Vec<PerlValue>> {
1560 self.frames.last_mut()?.sub_underscore.take()
1561 }
1562
1563 pub fn declare_array(&mut self, name: &str, val: Vec<PerlValue>) {
1564 self.declare_array_frozen(name, val, false);
1565 }
1566
1567 pub fn declare_array_frozen(&mut self, name: &str, val: Vec<PerlValue>, frozen: bool) {
1568 canon_main!(name);
1569 let idx = if name.contains("::") {
1572 0
1573 } else {
1574 self.frames.len().saturating_sub(1)
1575 };
1576 if let Some(frame) = self.frames.get_mut(idx) {
1577 frame.shared_arrays.retain(|(k, _)| k != name);
1579 frame.set_array(name, val);
1580 if frozen {
1581 frame.frozen_arrays.insert(name.to_string());
1582 } else {
1583 frame.frozen_arrays.remove(name);
1585 }
1586 }
1587 }
1588
1589 pub fn get_array(&self, name: &str) -> Vec<PerlValue> {
1590 if let Some(rest) = strip_main_prefix(name) {
1596 return self.get_array(rest);
1597 }
1598 if let Some(aa) = self.find_atomic_array(name) {
1600 return aa.0.lock().clone();
1601 }
1602 if let Some(arc) = self.find_shared_array(name) {
1604 return arc.read().clone();
1605 }
1606 if name.contains("::") {
1607 if let Some(f) = self.frames.first() {
1608 if let Some(val) = f.get_array(name) {
1609 return val.clone();
1610 }
1611 }
1612 return Vec::new();
1613 }
1614 for frame in self.frames.iter().rev() {
1615 if let Some(val) = frame.get_array(name) {
1616 return val.clone();
1617 }
1618 }
1619 Vec::new()
1620 }
1621
1622 #[inline]
1625 pub fn get_array_borrow(&self, name: &str) -> Option<&[PerlValue]> {
1626 if let Some(rest) = strip_main_prefix(name) {
1627 return self.get_array_borrow(rest);
1628 }
1629 if self.find_atomic_array(name).is_some() {
1630 return None;
1631 }
1632 if name.contains("::") {
1633 return self
1634 .frames
1635 .first()
1636 .and_then(|f| f.get_array(name))
1637 .map(|v| v.as_slice());
1638 }
1639 for frame in self.frames.iter().rev() {
1640 if let Some(val) = frame.get_array(name) {
1641 return Some(val.as_slice());
1642 }
1643 }
1644 None
1645 }
1646
1647 fn resolve_array_frame_idx(&self, name: &str) -> Option<usize> {
1648 if name.contains("::") {
1649 return Some(0);
1650 }
1651 (0..self.frames.len())
1652 .rev()
1653 .find(|&i| self.frames[i].has_array(name))
1654 }
1655
1656 fn check_parallel_array_write(&self, name: &str) -> Result<(), PerlError> {
1657 if !self.parallel_guard
1658 || Self::parallel_skip_special_name(name)
1659 || Self::parallel_allowed_internal_array(name)
1660 {
1661 return Ok(());
1662 }
1663 let inner = self.frames.len().saturating_sub(1);
1664 match self.resolve_array_frame_idx(name) {
1665 None => Err(PerlError::runtime(
1666 format!(
1667 "cannot modify undeclared array `@{}` in a parallel block",
1668 name
1669 ),
1670 0,
1671 )),
1672 Some(idx) if idx != inner => Err(PerlError::runtime(
1673 format!(
1674 "cannot modify captured non-mysync array `@{}` in a parallel block",
1675 name
1676 ),
1677 0,
1678 )),
1679 Some(_) => Ok(()),
1680 }
1681 }
1682
1683 #[inline]
1688 pub fn resolve_container_binding_ref(&self, val: PerlValue) -> PerlValue {
1689 if let Some(name) = val.as_array_binding_name() {
1690 let data = self.get_array(&name);
1691 return PerlValue::array_ref(Arc::new(parking_lot::RwLock::new(data)));
1692 }
1693 if let Some(name) = val.as_hash_binding_name() {
1694 let data = self.get_hash(&name);
1695 return PerlValue::hash_ref(Arc::new(parking_lot::RwLock::new(data)));
1696 }
1697 val
1698 }
1699
1700 pub fn promote_array_to_shared(
1704 &mut self,
1705 name: &str,
1706 ) -> Arc<parking_lot::RwLock<Vec<PerlValue>>> {
1707 if let Some(aa) = self.find_atomic_array(name) {
1710 let data = aa.0.lock().clone();
1711 return Arc::new(parking_lot::RwLock::new(data));
1712 }
1713 let idx = self.resolve_array_frame_idx(name).unwrap_or_default();
1715 let frame = &mut self.frames[idx];
1716 if let Some(entry) = frame.shared_arrays.iter().find(|(k, _)| k == name) {
1717 return Arc::clone(&entry.1);
1718 }
1719 let data = if let Some(pos) = frame.arrays.iter().position(|(k, _)| k == name) {
1721 frame.arrays.swap_remove(pos).1
1722 } else if name == "_" {
1723 frame.sub_underscore.take().unwrap_or_default()
1724 } else {
1725 Vec::new()
1726 };
1727 let arc = Arc::new(parking_lot::RwLock::new(data));
1728 frame
1729 .shared_arrays
1730 .push((name.to_string(), Arc::clone(&arc)));
1731 arc
1732 }
1733
1734 pub fn promote_hash_to_shared(
1737 &mut self,
1738 name: &str,
1739 ) -> Arc<parking_lot::RwLock<IndexMap<String, PerlValue>>> {
1740 let idx = self.resolve_hash_frame_idx(name).unwrap_or_default();
1741 let frame = &mut self.frames[idx];
1742 if let Some(entry) = frame.shared_hashes.iter().find(|(k, _)| k == name) {
1743 return Arc::clone(&entry.1);
1744 }
1745 let data = if let Some(pos) = frame.hashes.iter().position(|(k, _)| k == name) {
1746 frame.hashes.swap_remove(pos).1
1747 } else {
1748 IndexMap::new()
1749 };
1750 let arc = Arc::new(parking_lot::RwLock::new(data));
1751 frame
1752 .shared_hashes
1753 .push((name.to_string(), Arc::clone(&arc)));
1754 arc
1755 }
1756
1757 fn find_shared_array(&self, name: &str) -> Option<Arc<parking_lot::RwLock<Vec<PerlValue>>>> {
1759 let name = strip_main_prefix(name).unwrap_or(name);
1760 for frame in self.frames.iter().rev() {
1761 if let Some(entry) = frame.shared_arrays.iter().find(|(k, _)| k == name) {
1762 return Some(Arc::clone(&entry.1));
1763 }
1764 if frame.arrays.iter().any(|(k, _)| k == name) {
1766 return None;
1767 }
1768 }
1769 None
1770 }
1771
1772 fn find_shared_hash(
1774 &self,
1775 name: &str,
1776 ) -> Option<Arc<parking_lot::RwLock<IndexMap<String, PerlValue>>>> {
1777 let name = strip_main_prefix(name).unwrap_or(name);
1778 for frame in self.frames.iter().rev() {
1779 if let Some(entry) = frame.shared_hashes.iter().find(|(k, _)| k == name) {
1780 return Some(Arc::clone(&entry.1));
1781 }
1782 if frame.hashes.iter().any(|(k, _)| k == name) {
1783 return None;
1784 }
1785 }
1786 None
1787 }
1788
1789 pub fn get_array_mut(&mut self, name: &str) -> Result<&mut Vec<PerlValue>, PerlError> {
1790 if self.find_atomic_array(name).is_some() {
1793 return Err(PerlError::runtime(
1794 "get_array_mut: use atomic path for mysync arrays",
1795 0,
1796 ));
1797 }
1798 self.check_parallel_array_write(name)?;
1799 let idx = self.resolve_array_frame_idx(name).unwrap_or_default();
1800 let frame = &mut self.frames[idx];
1801 if frame.get_array_mut(name).is_none() {
1802 frame.arrays.push((name.to_string(), Vec::new()));
1803 }
1804 Ok(frame.get_array_mut(name).unwrap())
1805 }
1806
1807 pub fn push_to_array(&mut self, name: &str, val: PerlValue) -> Result<(), PerlError> {
1809 let val = self.resolve_container_binding_ref(val);
1810 if let Some(aa) = self.find_atomic_array(name) {
1811 aa.0.lock().push(val);
1812 return Ok(());
1813 }
1814 if let Some(arc) = self.find_shared_array(name) {
1815 arc.write().push(val);
1816 return Ok(());
1817 }
1818 self.get_array_mut(name)?.push(val);
1819 Ok(())
1820 }
1821
1822 pub fn push_int_range_to_array(
1826 &mut self,
1827 name: &str,
1828 start: i64,
1829 end: i64,
1830 ) -> Result<(), PerlError> {
1831 if end <= start {
1832 return Ok(());
1833 }
1834 let count = (end - start) as usize;
1835 if let Some(aa) = self.find_atomic_array(name) {
1836 let mut g = aa.0.lock();
1837 g.reserve(count);
1838 for i in start..end {
1839 g.push(PerlValue::integer(i));
1840 }
1841 return Ok(());
1842 }
1843 let arr = self.get_array_mut(name)?;
1844 arr.reserve(count);
1845 for i in start..end {
1846 arr.push(PerlValue::integer(i));
1847 }
1848 Ok(())
1849 }
1850
1851 pub fn pop_from_array(&mut self, name: &str) -> Result<PerlValue, PerlError> {
1853 if let Some(aa) = self.find_atomic_array(name) {
1854 return Ok(aa.0.lock().pop().unwrap_or(PerlValue::UNDEF));
1855 }
1856 if let Some(arc) = self.find_shared_array(name) {
1857 return Ok(arc.write().pop().unwrap_or(PerlValue::UNDEF));
1858 }
1859 Ok(self.get_array_mut(name)?.pop().unwrap_or(PerlValue::UNDEF))
1860 }
1861
1862 pub fn shift_from_array(&mut self, name: &str) -> Result<PerlValue, PerlError> {
1864 if let Some(aa) = self.find_atomic_array(name) {
1865 let mut guard = aa.0.lock();
1866 return Ok(if guard.is_empty() {
1867 PerlValue::UNDEF
1868 } else {
1869 guard.remove(0)
1870 });
1871 }
1872 if let Some(arc) = self.find_shared_array(name) {
1873 let mut arr = arc.write();
1874 return Ok(if arr.is_empty() {
1875 PerlValue::UNDEF
1876 } else {
1877 arr.remove(0)
1878 });
1879 }
1880 let arr = self.get_array_mut(name)?;
1881 Ok(if arr.is_empty() {
1882 PerlValue::UNDEF
1883 } else {
1884 arr.remove(0)
1885 })
1886 }
1887
1888 pub fn splice_in_place(
1892 &mut self,
1893 name: &str,
1894 off: usize,
1895 end: usize,
1896 rep_vals: Vec<PerlValue>,
1897 ) -> Result<Vec<PerlValue>, PerlError> {
1898 if let Some(aa) = self.find_atomic_array(name) {
1899 let mut g = aa.0.lock();
1900 let removed: Vec<PerlValue> = g.drain(off..end).collect();
1901 for (i, v) in rep_vals.into_iter().enumerate() {
1902 g.insert(off + i, v);
1903 }
1904 return Ok(removed);
1905 }
1906 if let Some(arc) = self.find_shared_array(name) {
1907 let mut g = arc.write();
1908 let removed: Vec<PerlValue> = g.drain(off..end).collect();
1909 for (i, v) in rep_vals.into_iter().enumerate() {
1910 g.insert(off + i, v);
1911 }
1912 return Ok(removed);
1913 }
1914 let arr = self.get_array_mut(name)?;
1915 let removed: Vec<PerlValue> = arr.drain(off..end).collect();
1916 for (i, v) in rep_vals.into_iter().enumerate() {
1917 arr.insert(off + i, v);
1918 }
1919 Ok(removed)
1920 }
1921
1922 pub fn array_len(&self, name: &str) -> usize {
1924 canon_main!(name);
1925 if let Some(aa) = self.find_atomic_array(name) {
1926 return aa.0.lock().len();
1927 }
1928 if let Some(arc) = self.find_shared_array(name) {
1929 return arc.read().len();
1930 }
1931 if name.contains("::") {
1932 return self
1933 .frames
1934 .first()
1935 .and_then(|f| f.get_array(name))
1936 .map(|a| a.len())
1937 .unwrap_or(0);
1938 }
1939 for frame in self.frames.iter().rev() {
1940 if let Some(arr) = frame.get_array(name) {
1941 return arr.len();
1942 }
1943 }
1944 0
1945 }
1946
1947 pub fn set_array(&mut self, name: &str, val: Vec<PerlValue>) -> Result<(), PerlError> {
1948 if let Some(aa) = self.find_atomic_array(name) {
1949 *aa.0.lock() = val;
1950 return Ok(());
1951 }
1952 if let Some(arc) = self.find_shared_array(name) {
1953 *arc.write() = val;
1954 return Ok(());
1955 }
1956 self.check_parallel_array_write(name)?;
1957 for frame in self.frames.iter_mut().rev() {
1958 if frame.has_array(name) {
1959 frame.set_array(name, val);
1960 return Ok(());
1961 }
1962 }
1963 self.frames[0].set_array(name, val);
1964 Ok(())
1965 }
1966
1967 #[inline]
1969 pub fn get_array_element(&self, name: &str, index: i64) -> PerlValue {
1970 canon_main!(name);
1971 if let Some(aa) = self.find_atomic_array(name) {
1972 let arr = aa.0.lock();
1973 let idx = if index < 0 {
1974 (arr.len() as i64 + index) as usize
1975 } else {
1976 index as usize
1977 };
1978 return arr.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
1979 }
1980 if let Some(arc) = self.find_shared_array(name) {
1981 let arr = arc.read();
1982 let idx = if index < 0 {
1983 (arr.len() as i64 + index) as usize
1984 } else {
1985 index as usize
1986 };
1987 return arr.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
1988 }
1989 for frame in self.frames.iter().rev() {
1990 if let Some(arr) = frame.get_array(name) {
1991 let idx = if index < 0 {
1992 (arr.len() as i64 + index) as usize
1993 } else {
1994 index as usize
1995 };
1996 return arr.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
1997 }
1998 }
1999 PerlValue::UNDEF
2000 }
2001
2002 pub fn set_array_element(
2003 &mut self,
2004 name: &str,
2005 index: i64,
2006 val: PerlValue,
2007 ) -> Result<(), PerlError> {
2008 let val = self.resolve_container_binding_ref(val);
2009 if let Some(aa) = self.find_atomic_array(name) {
2010 let mut arr = aa.0.lock();
2011 let idx = if index < 0 {
2012 (arr.len() as i64 + index).max(0) as usize
2013 } else {
2014 index as usize
2015 };
2016 if idx >= arr.len() {
2017 arr.resize(idx + 1, PerlValue::UNDEF);
2018 }
2019 arr[idx] = val;
2020 return Ok(());
2021 }
2022 if let Some(arc) = self.find_shared_array(name) {
2023 let mut arr = arc.write();
2024 let idx = if index < 0 {
2025 (arr.len() as i64 + index).max(0) as usize
2026 } else {
2027 index as usize
2028 };
2029 if idx >= arr.len() {
2030 arr.resize(idx + 1, PerlValue::UNDEF);
2031 }
2032 arr[idx] = val;
2033 return Ok(());
2034 }
2035 let arr = self.get_array_mut(name)?;
2036 let idx = if index < 0 {
2037 let len = arr.len() as i64;
2038 (len + index).max(0) as usize
2039 } else {
2040 index as usize
2041 };
2042 if idx >= arr.len() {
2043 arr.resize(idx + 1, PerlValue::UNDEF);
2044 }
2045 arr[idx] = val;
2046 Ok(())
2047 }
2048
2049 pub fn exists_array_element(&self, name: &str, index: i64) -> bool {
2051 canon_main!(name);
2052 if let Some(aa) = self.find_atomic_array(name) {
2053 let arr = aa.0.lock();
2054 let idx = if index < 0 {
2055 (arr.len() as i64 + index) as usize
2056 } else {
2057 index as usize
2058 };
2059 return idx < arr.len();
2060 }
2061 for frame in self.frames.iter().rev() {
2062 if let Some(arr) = frame.get_array(name) {
2063 let idx = if index < 0 {
2064 (arr.len() as i64 + index) as usize
2065 } else {
2066 index as usize
2067 };
2068 return idx < arr.len();
2069 }
2070 }
2071 false
2072 }
2073
2074 pub fn delete_array_element(&mut self, name: &str, index: i64) -> Result<PerlValue, PerlError> {
2076 if let Some(aa) = self.find_atomic_array(name) {
2077 let mut arr = aa.0.lock();
2078 let idx = if index < 0 {
2079 (arr.len() as i64 + index) as usize
2080 } else {
2081 index as usize
2082 };
2083 if idx >= arr.len() {
2084 return Ok(PerlValue::UNDEF);
2085 }
2086 let old = arr.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
2087 arr[idx] = PerlValue::UNDEF;
2088 return Ok(old);
2089 }
2090 let arr = self.get_array_mut(name)?;
2091 let idx = if index < 0 {
2092 (arr.len() as i64 + index) as usize
2093 } else {
2094 index as usize
2095 };
2096 if idx >= arr.len() {
2097 return Ok(PerlValue::UNDEF);
2098 }
2099 let old = arr.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
2100 arr[idx] = PerlValue::UNDEF;
2101 Ok(old)
2102 }
2103
2104 #[inline]
2107 pub fn declare_hash(&mut self, name: &str, val: IndexMap<String, PerlValue>) {
2108 self.declare_hash_frozen(name, val, false);
2109 }
2110
2111 pub fn declare_hash_frozen(
2112 &mut self,
2113 name: &str,
2114 val: IndexMap<String, PerlValue>,
2115 frozen: bool,
2116 ) {
2117 canon_main!(name);
2118 if let Some(frame) = self.frames.last_mut() {
2119 frame.shared_hashes.retain(|(k, _)| k != name);
2121 frame.set_hash(name, val);
2122 if frozen {
2123 frame.frozen_hashes.insert(name.to_string());
2124 }
2125 }
2126 }
2127
2128 pub fn declare_hash_global(&mut self, name: &str, val: IndexMap<String, PerlValue>) {
2130 canon_main!(name);
2131 if let Some(frame) = self.frames.first_mut() {
2132 frame.set_hash(name, val);
2133 }
2134 }
2135
2136 pub fn declare_hash_global_frozen(&mut self, name: &str, val: IndexMap<String, PerlValue>) {
2138 canon_main!(name);
2139 if let Some(frame) = self.frames.first_mut() {
2140 frame.set_hash(name, val);
2141 frame.frozen_hashes.insert(name.to_string());
2142 }
2143 }
2144
2145 pub fn has_lexical_hash(&self, name: &str) -> bool {
2147 canon_main!(name);
2148 self.frames.iter().skip(1).any(|f| f.has_hash(name))
2149 }
2150
2151 pub fn any_frame_has_hash(&self, name: &str) -> bool {
2153 canon_main!(name);
2154 self.frames.iter().any(|f| f.has_hash(name))
2155 }
2156
2157 pub fn get_hash(&self, name: &str) -> IndexMap<String, PerlValue> {
2158 if let Some(rest) = strip_main_prefix(name) {
2160 return self.get_hash(rest);
2161 }
2162 if let Some(ah) = self.find_atomic_hash(name) {
2163 return ah.0.lock().clone();
2164 }
2165 if let Some(arc) = self.find_shared_hash(name) {
2166 return arc.read().clone();
2167 }
2168 for frame in self.frames.iter().rev() {
2169 if let Some(val) = frame.get_hash(name) {
2170 return val.clone();
2171 }
2172 }
2173 IndexMap::new()
2174 }
2175
2176 fn resolve_hash_frame_idx(&self, name: &str) -> Option<usize> {
2177 if name.contains("::") {
2178 return Some(0);
2179 }
2180 (0..self.frames.len())
2181 .rev()
2182 .find(|&i| self.frames[i].has_hash(name))
2183 }
2184
2185 fn check_parallel_hash_write(&self, name: &str) -> Result<(), PerlError> {
2186 if !self.parallel_guard
2187 || Self::parallel_skip_special_name(name)
2188 || Self::parallel_allowed_internal_hash(name)
2189 {
2190 return Ok(());
2191 }
2192 let inner = self.frames.len().saturating_sub(1);
2193 match self.resolve_hash_frame_idx(name) {
2194 None => Err(PerlError::runtime(
2195 format!(
2196 "cannot modify undeclared hash `%{}` in a parallel block",
2197 name
2198 ),
2199 0,
2200 )),
2201 Some(idx) if idx != inner => Err(PerlError::runtime(
2202 format!(
2203 "cannot modify captured non-mysync hash `%{}` in a parallel block",
2204 name
2205 ),
2206 0,
2207 )),
2208 Some(_) => Ok(()),
2209 }
2210 }
2211
2212 pub fn get_hash_mut(
2213 &mut self,
2214 name: &str,
2215 ) -> Result<&mut IndexMap<String, PerlValue>, PerlError> {
2216 if self.find_atomic_hash(name).is_some() {
2217 return Err(PerlError::runtime(
2218 "get_hash_mut: use atomic path for mysync hashes",
2219 0,
2220 ));
2221 }
2222 self.check_parallel_hash_write(name)?;
2223 let idx = self.resolve_hash_frame_idx(name).unwrap_or_default();
2224 let frame = &mut self.frames[idx];
2225 if frame.get_hash_mut(name).is_none() {
2226 frame.hashes.push((name.to_string(), IndexMap::new()));
2227 }
2228 Ok(frame.get_hash_mut(name).unwrap())
2229 }
2230
2231 pub fn set_hash(
2232 &mut self,
2233 name: &str,
2234 val: IndexMap<String, PerlValue>,
2235 ) -> Result<(), PerlError> {
2236 if let Some(ah) = self.find_atomic_hash(name) {
2237 *ah.0.lock() = val;
2238 return Ok(());
2239 }
2240 self.check_parallel_hash_write(name)?;
2241 for frame in self.frames.iter_mut().rev() {
2242 if frame.has_hash(name) {
2243 frame.set_hash(name, val);
2244 return Ok(());
2245 }
2246 }
2247 self.frames[0].set_hash(name, val);
2248 Ok(())
2249 }
2250
2251 #[inline]
2252 pub fn get_hash_element(&self, name: &str, key: &str) -> PerlValue {
2253 canon_main!(name);
2254 if let Some(ah) = self.find_atomic_hash(name) {
2255 return ah.0.lock().get(key).cloned().unwrap_or(PerlValue::UNDEF);
2256 }
2257 if let Some(arc) = self.find_shared_hash(name) {
2258 return arc.read().get(key).cloned().unwrap_or(PerlValue::UNDEF);
2259 }
2260 for frame in self.frames.iter().rev() {
2261 if let Some(hash) = frame.get_hash(name) {
2262 return hash.get(key).cloned().unwrap_or(PerlValue::UNDEF);
2263 }
2264 }
2265 PerlValue::UNDEF
2266 }
2267
2268 pub fn atomic_hash_mutate(
2271 &mut self,
2272 name: &str,
2273 key: &str,
2274 f: impl FnOnce(&PerlValue) -> PerlValue,
2275 ) -> Result<PerlValue, PerlError> {
2276 if let Some(ah) = self.find_atomic_hash(name) {
2277 let mut guard = ah.0.lock();
2278 let old = guard.get(key).cloned().unwrap_or(PerlValue::UNDEF);
2279 let new_val = f(&old);
2280 guard.insert(key.to_string(), new_val.clone());
2281 return Ok(new_val);
2282 }
2283 let old = self.get_hash_element(name, key);
2285 let new_val = f(&old);
2286 self.set_hash_element(name, key, new_val.clone())?;
2287 Ok(new_val)
2288 }
2289
2290 pub fn atomic_array_mutate(
2292 &mut self,
2293 name: &str,
2294 index: i64,
2295 f: impl FnOnce(&PerlValue) -> PerlValue,
2296 ) -> Result<PerlValue, PerlError> {
2297 if let Some(aa) = self.find_atomic_array(name) {
2298 let mut guard = aa.0.lock();
2299 let idx = if index < 0 {
2300 (guard.len() as i64 + index).max(0) as usize
2301 } else {
2302 index as usize
2303 };
2304 if idx >= guard.len() {
2305 guard.resize(idx + 1, PerlValue::UNDEF);
2306 }
2307 let old = guard[idx].clone();
2308 let new_val = f(&old);
2309 guard[idx] = new_val.clone();
2310 return Ok(new_val);
2311 }
2312 let old = self.get_array_element(name, index);
2314 let new_val = f(&old);
2315 self.set_array_element(name, index, new_val.clone())?;
2316 Ok(new_val)
2317 }
2318
2319 pub fn set_hash_element(
2320 &mut self,
2321 name: &str,
2322 key: &str,
2323 val: PerlValue,
2324 ) -> Result<(), PerlError> {
2325 let val = self.resolve_container_binding_ref(val);
2326 if name == "SIG" {
2329 crate::perl_signal::install(key);
2330 }
2331 if let Some(ah) = self.find_atomic_hash(name) {
2332 ah.0.lock().insert(key.to_string(), val);
2333 return Ok(());
2334 }
2335 if let Some(arc) = self.find_shared_hash(name) {
2336 arc.write().insert(key.to_string(), val);
2337 return Ok(());
2338 }
2339 let hash = self.get_hash_mut(name)?;
2340 hash.insert(key.to_string(), val);
2341 Ok(())
2342 }
2343
2344 pub fn set_hash_int_times_range(
2348 &mut self,
2349 name: &str,
2350 start: i64,
2351 end: i64,
2352 k: i64,
2353 ) -> Result<(), PerlError> {
2354 if end <= start {
2355 return Ok(());
2356 }
2357 let count = (end - start) as usize;
2358 if let Some(ah) = self.find_atomic_hash(name) {
2359 let mut g = ah.0.lock();
2360 g.reserve(count);
2361 let mut buf = itoa::Buffer::new();
2362 for i in start..end {
2363 let key = buf.format(i).to_owned();
2364 g.insert(key, PerlValue::integer(i.wrapping_mul(k)));
2365 }
2366 return Ok(());
2367 }
2368 let hash = self.get_hash_mut(name)?;
2369 hash.reserve(count);
2370 let mut buf = itoa::Buffer::new();
2371 for i in start..end {
2372 let key = buf.format(i).to_owned();
2373 hash.insert(key, PerlValue::integer(i.wrapping_mul(k)));
2374 }
2375 Ok(())
2376 }
2377
2378 pub fn delete_hash_element(&mut self, name: &str, key: &str) -> Result<PerlValue, PerlError> {
2379 canon_main!(name);
2380 if let Some(ah) = self.find_atomic_hash(name) {
2381 return Ok(ah.0.lock().shift_remove(key).unwrap_or(PerlValue::UNDEF));
2382 }
2383 let hash = self.get_hash_mut(name)?;
2384 Ok(hash.shift_remove(key).unwrap_or(PerlValue::UNDEF))
2385 }
2386
2387 #[inline]
2388 pub fn exists_hash_element(&self, name: &str, key: &str) -> bool {
2389 canon_main!(name);
2390 if let Some(ah) = self.find_atomic_hash(name) {
2391 return ah.0.lock().contains_key(key);
2392 }
2393 for frame in self.frames.iter().rev() {
2394 if let Some(hash) = frame.get_hash(name) {
2395 return hash.contains_key(key);
2396 }
2397 }
2398 false
2399 }
2400
2401 #[inline]
2406 pub fn for_each_hash_value(&self, name: &str, mut visit: impl FnMut(&PerlValue)) {
2407 canon_main!(name);
2408 if let Some(ah) = self.find_atomic_hash(name) {
2409 let g = ah.0.lock();
2410 for v in g.values() {
2411 visit(v);
2412 }
2413 return;
2414 }
2415 for frame in self.frames.iter().rev() {
2416 if let Some(hash) = frame.get_hash(name) {
2417 for v in hash.values() {
2418 visit(v);
2419 }
2420 return;
2421 }
2422 }
2423 }
2424
2425 pub fn frames_for_introspection(&self) -> Vec<(Vec<&str>, Vec<&str>, Vec<&str>)> {
2431 self.frames
2432 .iter()
2433 .map(|f| {
2434 let mut scalars: Vec<&str> = f.scalars.iter().map(|(n, _)| n.as_str()).collect();
2435 scalars.extend(f.scalar_slot_names.iter().filter_map(|opt| match opt {
2437 Some(n) if !n.is_empty() => Some(n.as_str()),
2438 _ => None,
2439 }));
2440 let mut arrays: Vec<&str> = f.arrays.iter().map(|(n, _)| n.as_str()).collect();
2441 arrays.extend(f.atomic_arrays.iter().map(|(n, _)| n.as_str()));
2442 arrays.extend(f.shared_arrays.iter().map(|(n, _)| n.as_str()));
2443 let mut hashes: Vec<&str> = f.hashes.iter().map(|(n, _)| n.as_str()).collect();
2444 hashes.extend(f.atomic_hashes.iter().map(|(n, _)| n.as_str()));
2445 hashes.extend(f.shared_hashes.iter().map(|(n, _)| n.as_str()));
2446 scalars.sort_unstable();
2447 arrays.sort_unstable();
2448 hashes.sort_unstable();
2449 (scalars, arrays, hashes)
2450 })
2451 .collect()
2452 }
2453
2454 pub fn parameters_pairs(&self) -> Vec<(String, &'static str)> {
2460 let mut seen: HashSet<String> = HashSet::new();
2461 let mut out: Vec<(String, &'static str)> = Vec::new();
2462 for frame in self.frames.iter().rev() {
2465 for n in frame.scalar_slot_names.iter().flatten() {
2469 if !n.is_empty() {
2470 let s = format!("${}", n);
2471 if seen.insert(s.clone()) {
2472 out.push((s, "scalar"));
2473 }
2474 }
2475 }
2476 for (name, _) in &frame.scalars {
2477 let s = format!("${}", name);
2478 if seen.insert(s.clone()) {
2479 out.push((s, "scalar"));
2480 }
2481 }
2482 for (name, _) in &frame.arrays {
2483 let s = format!("@{}", name);
2484 if seen.insert(s.clone()) {
2485 out.push((s, "array"));
2486 }
2487 }
2488 for (name, _) in &frame.hashes {
2489 let s = format!("%{}", name);
2490 if seen.insert(s.clone()) {
2491 out.push((s, "hash"));
2492 }
2493 }
2494 for (name, _) in &frame.atomic_arrays {
2495 let s = format!("@{}", name);
2496 if seen.insert(s.clone()) {
2497 out.push((s, "atomic_array"));
2498 }
2499 }
2500 for (name, _) in &frame.atomic_hashes {
2501 let s = format!("%{}", name);
2502 if seen.insert(s.clone()) {
2503 out.push((s, "atomic_hash"));
2504 }
2505 }
2506 for (name, _) in &frame.shared_arrays {
2507 let s = format!("@{}", name);
2508 if seen.insert(s.clone()) {
2509 out.push((s, "shared_array"));
2510 }
2511 }
2512 for (name, _) in &frame.shared_hashes {
2513 let s = format!("%{}", name);
2514 if seen.insert(s.clone()) {
2515 out.push((s, "shared_hash"));
2516 }
2517 }
2518 }
2519 out.sort_by(|a, b| a.0.cmp(&b.0));
2520 out
2521 }
2522
2523 pub fn repl_binding_names(&self) -> Vec<String> {
2525 let mut seen = HashSet::new();
2526 let mut out = Vec::new();
2527 for frame in &self.frames {
2528 for (name, _) in &frame.scalars {
2529 let s = format!("${}", name);
2530 if seen.insert(s.clone()) {
2531 out.push(s);
2532 }
2533 }
2534 for (name, _) in &frame.arrays {
2535 let s = format!("@{}", name);
2536 if seen.insert(s.clone()) {
2537 out.push(s);
2538 }
2539 }
2540 for (name, _) in &frame.hashes {
2541 let s = format!("%{}", name);
2542 if seen.insert(s.clone()) {
2543 out.push(s);
2544 }
2545 }
2546 for (name, _) in &frame.atomic_arrays {
2547 let s = format!("@{}", name);
2548 if seen.insert(s.clone()) {
2549 out.push(s);
2550 }
2551 }
2552 for (name, _) in &frame.atomic_hashes {
2553 let s = format!("%{}", name);
2554 if seen.insert(s.clone()) {
2555 out.push(s);
2556 }
2557 }
2558 }
2559 out.sort();
2560 out
2561 }
2562
2563 pub fn capture(&mut self) -> Vec<(String, PerlValue)> {
2564 let by_ref = crate::compat_mode();
2577 let mut captured = Vec::new();
2578 let mut seen_hash_scalars: HashSet<String> = HashSet::new();
2593 for frame in self.frames.iter_mut().rev() {
2594 for (k, v) in &mut frame.scalars {
2595 if !seen_hash_scalars.insert(k.clone()) {
2596 continue;
2597 }
2598 if v.as_capture_cell().is_some() || v.as_scalar_ref().is_some() {
2599 captured.push((format!("${}", k), v.clone()));
2600 } else if v.is_simple_scalar() {
2601 let wrapped = PerlValue::capture_cell(Arc::new(RwLock::new(v.clone())));
2602 *v = wrapped.clone();
2603 captured.push((format!("${}", k), wrapped));
2604 } else {
2605 captured.push((format!("${}", k), v.clone()));
2606 }
2607 }
2608 }
2609 for frame in &mut self.frames {
2610 for (i, v) in frame.scalar_slots.iter_mut().enumerate() {
2618 if let Some(Some(name)) = frame.scalar_slot_names.get(i) {
2619 if !name.is_empty() && seen_hash_scalars.contains(name) {
2629 continue;
2630 }
2631 let cap_val = if v.as_capture_cell().is_some() || v.as_scalar_ref().is_some() {
2632 v.clone()
2633 } else {
2634 let wrapped = PerlValue::capture_cell(Arc::new(RwLock::new(v.clone())));
2635 if by_ref {
2636 *v = wrapped.clone();
2637 }
2638 wrapped
2639 };
2640 captured.push((format!("$slot:{}:{}", i, name), cap_val));
2641 }
2642 }
2643 for (k, v) in &frame.arrays {
2644 if capture_skip_bootstrap_array(k) {
2645 continue;
2646 }
2647 if frame.frozen_arrays.contains(k) {
2648 captured.push((format!("@frozen:{}", k), PerlValue::array(v.clone())));
2649 } else {
2650 captured.push((format!("@{}", k), PerlValue::array(v.clone())));
2651 }
2652 }
2653 for (k, v) in &frame.hashes {
2654 if capture_skip_bootstrap_hash(k) {
2655 continue;
2656 }
2657 if frame.frozen_hashes.contains(k) {
2658 captured.push((format!("%frozen:{}", k), PerlValue::hash(v.clone())));
2659 } else {
2660 captured.push((format!("%{}", k), PerlValue::hash(v.clone())));
2661 }
2662 }
2663 for (k, _aa) in &frame.atomic_arrays {
2664 captured.push((
2665 format!("@sync_{}", k),
2666 PerlValue::atomic(Arc::new(Mutex::new(PerlValue::string(String::new())))),
2667 ));
2668 }
2669 for (k, _ah) in &frame.atomic_hashes {
2670 captured.push((
2671 format!("%sync_{}", k),
2672 PerlValue::atomic(Arc::new(Mutex::new(PerlValue::string(String::new())))),
2673 ));
2674 }
2675 }
2676 captured
2677 }
2678
2679 pub fn capture_with_atomics(&self) -> ScopeCaptureWithAtomics {
2681 let mut scalars = Vec::new();
2682 let mut arrays = Vec::new();
2683 let mut hashes = Vec::new();
2684 for frame in &self.frames {
2685 for (k, v) in &frame.scalars {
2686 scalars.push((format!("${}", k), v.clone()));
2687 }
2688 for (i, v) in frame.scalar_slots.iter().enumerate() {
2689 if let Some(Some(name)) = frame.scalar_slot_names.get(i) {
2690 scalars.push((format!("$slot:{}:{}", i, name), v.clone()));
2691 }
2692 }
2693 for (k, v) in &frame.arrays {
2694 if capture_skip_bootstrap_array(k) {
2695 continue;
2696 }
2697 if frame.frozen_arrays.contains(k) {
2698 scalars.push((format!("@frozen:{}", k), PerlValue::array(v.clone())));
2699 } else {
2700 scalars.push((format!("@{}", k), PerlValue::array(v.clone())));
2701 }
2702 }
2703 for (k, v) in &frame.hashes {
2704 if capture_skip_bootstrap_hash(k) {
2705 continue;
2706 }
2707 if frame.frozen_hashes.contains(k) {
2708 scalars.push((format!("%frozen:{}", k), PerlValue::hash(v.clone())));
2709 } else {
2710 scalars.push((format!("%{}", k), PerlValue::hash(v.clone())));
2711 }
2712 }
2713 for (k, aa) in &frame.atomic_arrays {
2714 arrays.push((k.clone(), aa.clone()));
2715 }
2716 for (k, ah) in &frame.atomic_hashes {
2717 hashes.push((k.clone(), ah.clone()));
2718 }
2719 }
2720 (scalars, arrays, hashes)
2721 }
2722
2723 pub fn restore_capture(&mut self, captured: &[(String, PerlValue)]) {
2724 for (name, val) in captured {
2725 if let Some(rest) = name.strip_prefix("$slot:") {
2726 if let Some(colon) = rest.find(':') {
2732 let idx: usize = rest[..colon].parse().unwrap_or(0);
2733 let sname = &rest[colon + 1..];
2734 self.declare_scalar_slot(idx as u8, val.clone(), Some(sname));
2735 }
2736 } else if let Some(stripped) = name.strip_prefix('$') {
2737 self.declare_scalar(stripped, val.clone());
2738 if let Some(slot) = parse_positional_topic_slot(stripped) {
2745 if slot > self.max_active_slot {
2746 self.max_active_slot = slot;
2747 }
2748 }
2749 } else if let Some(rest) = name.strip_prefix("@frozen:") {
2750 let arr = val.as_array_vec().unwrap_or_else(|| val.to_list());
2751 self.declare_array_frozen(rest, arr, true);
2752 } else if let Some(rest) = name.strip_prefix("%frozen:") {
2753 if let Some(h) = val.as_hash_map() {
2754 self.declare_hash_frozen(rest, h.clone(), true);
2755 }
2756 } else if let Some(rest) = name.strip_prefix('@') {
2757 if rest.starts_with("sync_") {
2758 continue;
2759 }
2760 let arr = val.as_array_vec().unwrap_or_else(|| val.to_list());
2761 self.declare_array(rest, arr);
2762 } else if let Some(rest) = name.strip_prefix('%') {
2763 if rest.starts_with("sync_") {
2764 continue;
2765 }
2766 if let Some(h) = val.as_hash_map() {
2767 self.declare_hash(rest, h.clone());
2768 }
2769 }
2770 }
2771 }
2772
2773 pub fn restore_atomics(
2775 &mut self,
2776 arrays: &[(String, AtomicArray)],
2777 hashes: &[(String, AtomicHash)],
2778 ) {
2779 if let Some(frame) = self.frames.last_mut() {
2780 for (name, aa) in arrays {
2781 frame.atomic_arrays.push((name.clone(), aa.clone()));
2782 }
2783 for (name, ah) in hashes {
2784 frame.atomic_hashes.push((name.clone(), ah.clone()));
2785 }
2786 }
2787 }
2788}
2789
2790#[cfg(test)]
2791mod tests {
2792 use super::*;
2793 use crate::value::PerlValue;
2794
2795 #[test]
2796 fn missing_scalar_is_undef() {
2797 let s = Scope::new();
2798 assert!(s.get_scalar("not_declared").is_undef());
2799 }
2800
2801 #[test]
2802 fn inner_frame_shadows_outer_scalar() {
2803 let mut s = Scope::new();
2804 s.declare_scalar("a", PerlValue::integer(1));
2805 s.push_frame();
2806 s.declare_scalar("a", PerlValue::integer(2));
2807 assert_eq!(s.get_scalar("a").to_int(), 2);
2808 s.pop_frame();
2809 assert_eq!(s.get_scalar("a").to_int(), 1);
2810 }
2811
2812 #[test]
2813 fn set_scalar_updates_innermost_binding() {
2814 let mut s = Scope::new();
2815 s.declare_scalar("a", PerlValue::integer(1));
2816 s.push_frame();
2817 s.declare_scalar("a", PerlValue::integer(2));
2818 let _ = s.set_scalar("a", PerlValue::integer(99));
2819 assert_eq!(s.get_scalar("a").to_int(), 99);
2820 s.pop_frame();
2821 assert_eq!(s.get_scalar("a").to_int(), 1);
2822 }
2823
2824 #[test]
2825 fn array_negative_index_reads_from_end() {
2826 let mut s = Scope::new();
2827 s.declare_array(
2828 "a",
2829 vec![
2830 PerlValue::integer(10),
2831 PerlValue::integer(20),
2832 PerlValue::integer(30),
2833 ],
2834 );
2835 assert_eq!(s.get_array_element("a", -1).to_int(), 30);
2836 }
2837
2838 #[test]
2839 fn set_array_element_extends_array_with_undef_gaps() {
2840 let mut s = Scope::new();
2841 s.declare_array("a", vec![]);
2842 s.set_array_element("a", 2, PerlValue::integer(7)).unwrap();
2843 assert_eq!(s.get_array_element("a", 2).to_int(), 7);
2844 assert!(s.get_array_element("a", 0).is_undef());
2845 }
2846
2847 #[test]
2848 fn capture_restore_roundtrip_scalar() {
2849 let mut s = Scope::new();
2850 s.declare_scalar("n", PerlValue::integer(42));
2851 let cap = s.capture();
2852 let mut t = Scope::new();
2853 t.restore_capture(&cap);
2854 assert_eq!(t.get_scalar("n").to_int(), 42);
2855 }
2856
2857 #[test]
2858 fn capture_restore_roundtrip_lexical_array_and_hash() {
2859 let mut s = Scope::new();
2860 s.declare_array("a", vec![PerlValue::integer(1), PerlValue::integer(2)]);
2861 let mut m = IndexMap::new();
2862 m.insert("k".to_string(), PerlValue::integer(99));
2863 s.declare_hash("h", m);
2864 let cap = s.capture();
2865 let mut t = Scope::new();
2866 t.restore_capture(&cap);
2867 assert_eq!(t.get_array_element("a", 1).to_int(), 2);
2868 assert_eq!(t.get_hash_element("h", "k").to_int(), 99);
2869 }
2870
2871 #[test]
2872 fn hash_get_set_delete_exists() {
2873 let mut s = Scope::new();
2874 let mut m = IndexMap::new();
2875 m.insert("k".to_string(), PerlValue::integer(1));
2876 s.declare_hash("h", m);
2877 assert_eq!(s.get_hash_element("h", "k").to_int(), 1);
2878 assert!(s.exists_hash_element("h", "k"));
2879 s.set_hash_element("h", "k", PerlValue::integer(99))
2880 .unwrap();
2881 assert_eq!(s.get_hash_element("h", "k").to_int(), 99);
2882 let del = s.delete_hash_element("h", "k").unwrap();
2883 assert_eq!(del.to_int(), 99);
2884 assert!(!s.exists_hash_element("h", "k"));
2885 }
2886
2887 #[test]
2888 fn inner_frame_shadows_outer_hash_name() {
2889 let mut s = Scope::new();
2890 let mut outer = IndexMap::new();
2891 outer.insert("k".to_string(), PerlValue::integer(1));
2892 s.declare_hash("h", outer);
2893 s.push_frame();
2894 let mut inner = IndexMap::new();
2895 inner.insert("k".to_string(), PerlValue::integer(2));
2896 s.declare_hash("h", inner);
2897 assert_eq!(s.get_hash_element("h", "k").to_int(), 2);
2898 s.pop_frame();
2899 assert_eq!(s.get_hash_element("h", "k").to_int(), 1);
2900 }
2901
2902 #[test]
2903 fn inner_frame_shadows_outer_array_name() {
2904 let mut s = Scope::new();
2905 s.declare_array("a", vec![PerlValue::integer(1)]);
2906 s.push_frame();
2907 s.declare_array("a", vec![PerlValue::integer(2), PerlValue::integer(3)]);
2908 assert_eq!(s.get_array_element("a", 1).to_int(), 3);
2909 s.pop_frame();
2910 assert_eq!(s.get_array_element("a", 0).to_int(), 1);
2911 }
2912
2913 #[test]
2914 fn pop_frame_never_removes_global_frame() {
2915 let mut s = Scope::new();
2916 s.declare_scalar("x", PerlValue::integer(1));
2917 s.pop_frame();
2918 s.pop_frame();
2919 assert_eq!(s.get_scalar("x").to_int(), 1);
2920 }
2921
2922 #[test]
2923 fn empty_array_declared_has_zero_length() {
2924 let mut s = Scope::new();
2925 s.declare_array("a", vec![]);
2926 assert_eq!(s.get_array("a").len(), 0);
2927 }
2928
2929 #[test]
2930 fn depth_increments_with_push_frame() {
2931 let mut s = Scope::new();
2932 let d0 = s.depth();
2933 s.push_frame();
2934 assert_eq!(s.depth(), d0 + 1);
2935 s.pop_frame();
2936 assert_eq!(s.depth(), d0);
2937 }
2938
2939 #[test]
2940 fn pop_to_depth_unwinds_to_target() {
2941 let mut s = Scope::new();
2942 s.push_frame();
2943 s.push_frame();
2944 let target = s.depth() - 1;
2945 s.pop_to_depth(target);
2946 assert_eq!(s.depth(), target);
2947 }
2948
2949 #[test]
2950 fn array_len_and_push_pop_roundtrip() {
2951 let mut s = Scope::new();
2952 s.declare_array("a", vec![]);
2953 assert_eq!(s.array_len("a"), 0);
2954 s.push_to_array("a", PerlValue::integer(1)).unwrap();
2955 s.push_to_array("a", PerlValue::integer(2)).unwrap();
2956 assert_eq!(s.array_len("a"), 2);
2957 assert_eq!(s.pop_from_array("a").unwrap().to_int(), 2);
2958 assert_eq!(s.pop_from_array("a").unwrap().to_int(), 1);
2959 assert!(s.pop_from_array("a").unwrap().is_undef());
2960 }
2961
2962 #[test]
2963 fn shift_from_array_drops_front() {
2964 let mut s = Scope::new();
2965 s.declare_array("a", vec![PerlValue::integer(1), PerlValue::integer(2)]);
2966 assert_eq!(s.shift_from_array("a").unwrap().to_int(), 1);
2967 assert_eq!(s.array_len("a"), 1);
2968 }
2969
2970 #[test]
2971 fn atomic_mutate_increments_wrapped_scalar() {
2972 use parking_lot::Mutex;
2973 use std::sync::Arc;
2974 let mut s = Scope::new();
2975 s.declare_scalar(
2976 "n",
2977 PerlValue::atomic(Arc::new(Mutex::new(PerlValue::integer(10)))),
2978 );
2979 let v = s
2980 .atomic_mutate("n", |old| PerlValue::integer(old.to_int() + 5))
2981 .expect("atomic_mutate on atomic-backed scalar must not fail");
2982 assert_eq!(v.to_int(), 15);
2983 assert_eq!(s.get_scalar("n").to_int(), 15);
2984 }
2985
2986 #[test]
2987 fn atomic_mutate_post_returns_old_value() {
2988 use parking_lot::Mutex;
2989 use std::sync::Arc;
2990 let mut s = Scope::new();
2991 s.declare_scalar(
2992 "n",
2993 PerlValue::atomic(Arc::new(Mutex::new(PerlValue::integer(7)))),
2994 );
2995 let old = s
2996 .atomic_mutate_post("n", |v| PerlValue::integer(v.to_int() + 1))
2997 .expect("atomic_mutate_post on atomic-backed scalar must not fail");
2998 assert_eq!(old.to_int(), 7);
2999 assert_eq!(s.get_scalar("n").to_int(), 8);
3000 }
3001
3002 #[test]
3003 fn get_scalar_raw_keeps_atomic_wrapper() {
3004 use parking_lot::Mutex;
3005 use std::sync::Arc;
3006 let mut s = Scope::new();
3007 s.declare_scalar(
3008 "n",
3009 PerlValue::atomic(Arc::new(Mutex::new(PerlValue::integer(3)))),
3010 );
3011 assert!(s.get_scalar_raw("n").is_atomic());
3012 assert!(!s.get_scalar("n").is_atomic());
3013 }
3014
3015 #[test]
3016 fn missing_array_element_is_undef() {
3017 let mut s = Scope::new();
3018 s.declare_array("a", vec![PerlValue::integer(1)]);
3019 assert!(s.get_array_element("a", 99).is_undef());
3020 }
3021
3022 #[test]
3023 fn restore_atomics_puts_atomic_containers_in_frame() {
3024 use indexmap::IndexMap;
3025 use parking_lot::Mutex;
3026 use std::sync::Arc;
3027 let mut s = Scope::new();
3028 let aa = AtomicArray(Arc::new(Mutex::new(vec![PerlValue::integer(1)])));
3029 let ah = AtomicHash(Arc::new(Mutex::new(IndexMap::new())));
3030 s.restore_atomics(&[("ax".into(), aa.clone())], &[("hx".into(), ah.clone())]);
3031 assert_eq!(s.get_array_element("ax", 0).to_int(), 1);
3032 assert_eq!(s.array_len("ax"), 1);
3033 s.set_hash_element("hx", "k", PerlValue::integer(2))
3034 .unwrap();
3035 assert_eq!(s.get_hash_element("hx", "k").to_int(), 2);
3036 }
3037}