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]
36fn capture_skip_bootstrap_array(name: &str) -> bool {
37 matches!(
38 name,
39 "INC" | "ARGV" | "_" | "-" | "+" | "^CAPTURE" | "^CAPTURE_ALL"
40 )
41}
42
43#[inline]
45fn capture_skip_bootstrap_hash(name: &str) -> bool {
46 matches!(name, "INC" | "ENV" | "SIG" | "^HOOK")
47}
48
49#[inline]
54fn parse_positional_topic_slot(name: &str) -> Option<usize> {
55 let bytes = name.as_bytes();
56 if bytes.len() < 2 || bytes[0] != b'_' || !bytes[1].is_ascii_digit() {
57 return None;
58 }
59 let mut i = 1;
60 while i < bytes.len() && bytes[i].is_ascii_digit() {
61 i += 1;
62 }
63 let digits = &name[1..i];
64 while i < bytes.len() && bytes[i] == b'<' {
65 i += 1;
66 }
67 if i != bytes.len() {
68 return None;
69 }
70 digits.parse().ok().filter(|&n: &usize| n >= 1)
71}
72
73#[derive(Clone, Debug)]
75enum LocalRestore {
76 Scalar(String, PerlValue),
77 Array(String, Vec<PerlValue>),
78 Hash(String, IndexMap<String, PerlValue>),
79 HashElement(String, String, Option<PerlValue>),
81 ArrayElement(String, i64, PerlValue),
83}
84
85#[derive(Debug, Clone)]
90struct Frame {
91 scalars: Vec<(String, PerlValue)>,
92 arrays: Vec<(String, Vec<PerlValue>)>,
93 sub_underscore: Option<Vec<PerlValue>>,
96 hashes: Vec<(String, IndexMap<String, PerlValue>)>,
97 scalar_slots: Vec<PerlValue>,
101 scalar_slot_names: Vec<Option<String>>,
104 local_restores: Vec<LocalRestore>,
106 frozen_scalars: HashSet<String>,
108 frozen_arrays: HashSet<String>,
109 frozen_hashes: HashSet<String>,
110 typed_scalars: HashMap<String, PerlTypeName>,
112 shared_arrays: Vec<(String, Arc<parking_lot::RwLock<Vec<PerlValue>>>)>,
116 shared_hashes: Vec<SharedHashEntry>,
118 atomic_arrays: Vec<(String, AtomicArray)>,
120 atomic_hashes: Vec<(String, AtomicHash)>,
122 defers: Vec<PerlValue>,
124 set_topic_called: bool,
131}
132
133impl Frame {
134 #[inline]
137 fn clear_all_bindings(&mut self) {
138 self.scalars.clear();
139 self.arrays.clear();
140 self.sub_underscore = None;
141 self.hashes.clear();
142 self.scalar_slots.clear();
143 self.scalar_slot_names.clear();
144 self.local_restores.clear();
145 self.frozen_scalars.clear();
146 self.frozen_arrays.clear();
147 self.frozen_hashes.clear();
148 self.typed_scalars.clear();
149 self.shared_arrays.clear();
150 self.shared_hashes.clear();
151 self.atomic_arrays.clear();
152 self.defers.clear();
153 self.atomic_hashes.clear();
154 self.set_topic_called = false;
155 }
156
157 #[inline]
161 fn owns_scalar_slot_index(&self, idx: usize) -> bool {
162 self.scalar_slot_names.get(idx).is_some_and(|n| n.is_some())
163 }
164
165 #[inline]
166 fn new() -> Self {
167 Self {
168 scalars: Vec::new(),
169 arrays: Vec::new(),
170 sub_underscore: None,
171 hashes: Vec::new(),
172 scalar_slots: Vec::new(),
173 scalar_slot_names: Vec::new(),
174 frozen_scalars: HashSet::new(),
175 frozen_arrays: HashSet::new(),
176 frozen_hashes: HashSet::new(),
177 shared_arrays: Vec::new(),
178 shared_hashes: Vec::new(),
179 typed_scalars: HashMap::new(),
180 atomic_arrays: Vec::new(),
181 atomic_hashes: Vec::new(),
182 local_restores: Vec::new(),
183 defers: Vec::new(),
184 set_topic_called: false,
185 }
186 }
187
188 #[inline]
189 fn get_scalar(&self, name: &str) -> Option<&PerlValue> {
190 if let Some(v) = self.get_scalar_from_slot(name) {
191 return Some(v);
192 }
193 self.scalars.iter().find(|(k, _)| k == name).map(|(_, v)| v)
194 }
195
196 #[inline]
199 fn get_scalar_from_slot(&self, name: &str) -> Option<&PerlValue> {
200 for (i, sn) in self.scalar_slot_names.iter().enumerate() {
201 if let Some(ref n) = sn {
202 if n == name {
203 return self.scalar_slots.get(i);
204 }
205 }
206 }
207 None
208 }
209
210 #[inline]
211 fn has_scalar(&self, name: &str) -> bool {
212 if self
213 .scalar_slot_names
214 .iter()
215 .any(|sn| sn.as_deref() == Some(name))
216 {
217 return true;
218 }
219 self.scalars.iter().any(|(k, _)| k == name)
220 }
221
222 #[inline]
223 fn set_scalar(&mut self, name: &str, val: PerlValue) {
224 for (i, sn) in self.scalar_slot_names.iter().enumerate() {
225 if let Some(ref n) = sn {
226 if n == name {
227 if i < self.scalar_slots.len() {
228 if let Some(r) = self.scalar_slots[i].as_capture_cell() {
230 *r.write() = val;
231 } else {
232 self.scalar_slots[i] = val;
233 }
234 }
235 return;
236 }
237 }
238 }
239 if let Some(entry) = self.scalars.iter_mut().find(|(k, _)| k == name) {
240 if let Some(r) = entry.1.as_capture_cell() {
242 *r.write() = val;
243 } else {
244 entry.1 = val;
245 }
246 } else {
247 self.scalars.push((name.to_string(), val));
248 }
249 }
250
251 #[inline]
260 fn set_scalar_raw(&mut self, name: &str, val: PerlValue) {
261 for (i, sn) in self.scalar_slot_names.iter().enumerate() {
262 if let Some(ref n) = sn {
263 if n == name {
264 if i < self.scalar_slots.len() {
265 self.scalar_slots[i] = val;
266 }
267 return;
268 }
269 }
270 }
271 if let Some(entry) = self.scalars.iter_mut().find(|(k, _)| k == name) {
272 entry.1 = val;
273 } else {
274 self.scalars.push((name.to_string(), val));
275 }
276 }
277
278 #[inline]
279 fn get_array(&self, name: &str) -> Option<&Vec<PerlValue>> {
280 if name == "_" {
281 if let Some(ref v) = self.sub_underscore {
282 return Some(v);
283 }
284 }
285 self.arrays.iter().find(|(k, _)| k == name).map(|(_, v)| v)
286 }
287
288 #[inline]
289 fn has_array(&self, name: &str) -> bool {
290 if name == "_" && self.sub_underscore.is_some() {
291 return true;
292 }
293 self.arrays.iter().any(|(k, _)| k == name)
294 || self.shared_arrays.iter().any(|(k, _)| k == name)
295 }
296
297 #[inline]
298 fn get_array_mut(&mut self, name: &str) -> Option<&mut Vec<PerlValue>> {
299 if name == "_" {
300 return self.sub_underscore.as_mut();
301 }
302 self.arrays
303 .iter_mut()
304 .find(|(k, _)| k == name)
305 .map(|(_, v)| v)
306 }
307
308 #[inline]
309 fn set_array(&mut self, name: &str, val: Vec<PerlValue>) {
310 if name == "_" {
311 if let Some(pos) = self.arrays.iter().position(|(k, _)| k == name) {
312 self.arrays.swap_remove(pos);
313 }
314 self.sub_underscore = Some(val);
315 return;
316 }
317 if let Some(entry) = self.arrays.iter_mut().find(|(k, _)| k == name) {
318 entry.1 = val;
319 } else {
320 self.arrays.push((name.to_string(), val));
321 }
322 }
323
324 #[inline]
325 fn get_hash(&self, name: &str) -> Option<&IndexMap<String, PerlValue>> {
326 self.hashes.iter().find(|(k, _)| k == name).map(|(_, v)| v)
327 }
328
329 #[inline]
330 fn has_hash(&self, name: &str) -> bool {
331 self.hashes.iter().any(|(k, _)| k == name)
332 || self.shared_hashes.iter().any(|(k, _)| k == name)
333 }
334
335 #[inline]
336 fn get_hash_mut(&mut self, name: &str) -> Option<&mut IndexMap<String, PerlValue>> {
337 self.hashes
338 .iter_mut()
339 .find(|(k, _)| k == name)
340 .map(|(_, v)| v)
341 }
342
343 #[inline]
344 fn set_hash(&mut self, name: &str, val: IndexMap<String, PerlValue>) {
345 if let Some(entry) = self.hashes.iter_mut().find(|(k, _)| k == name) {
346 entry.1 = val;
347 } else {
348 self.hashes.push((name.to_string(), val));
349 }
350 }
351}
352
353#[derive(Debug, Clone)]
356pub struct Scope {
357 frames: Vec<Frame>,
358 frame_pool: Vec<Frame>,
360 parallel_guard: bool,
365 max_active_slot: usize,
372}
373
374impl Default for Scope {
375 fn default() -> Self {
376 Self::new()
377 }
378}
379
380impl Scope {
381 pub fn new() -> Self {
382 let mut s = Self {
383 frames: Vec::with_capacity(32),
384 frame_pool: Vec::with_capacity(32),
385 parallel_guard: false,
386 max_active_slot: 0,
387 };
388 s.frames.push(Frame::new());
389 s
390 }
391
392 #[inline]
394 pub fn set_parallel_guard(&mut self, enabled: bool) {
395 self.parallel_guard = enabled;
396 }
397
398 #[inline]
399 pub fn parallel_guard(&self) -> bool {
400 self.parallel_guard
401 }
402
403 #[inline]
410 fn parallel_skip_special_name(_name: &str) -> bool {
411 false
412 }
413
414 #[inline]
416 fn parallel_allowed_topic_scalar(name: &str) -> bool {
417 matches!(name, "_" | "a" | "b")
418 }
419
420 #[inline]
422 fn parallel_allowed_internal_array(name: &str) -> bool {
423 matches!(name, "-" | "+" | "^CAPTURE" | "^CAPTURE_ALL")
424 }
425
426 #[inline]
428 fn parallel_allowed_internal_hash(name: &str) -> bool {
429 matches!(name, "+" | "-" | "ENV" | "INC")
430 }
431
432 fn check_parallel_scalar_write(&self, name: &str) -> Result<(), PerlError> {
433 if !self.parallel_guard || Self::parallel_skip_special_name(name) {
434 return Ok(());
435 }
436 if Self::parallel_allowed_topic_scalar(name) {
437 return Ok(());
438 }
439 if crate::special_vars::is_regex_match_scalar_name(name) {
440 return Ok(());
441 }
442 let inner = self.frames.len().saturating_sub(1);
443 for (i, frame) in self.frames.iter().enumerate().rev() {
444 if frame.has_scalar(name) {
445 if let Some(v) = frame.get_scalar(name) {
446 if v.as_atomic_arc().is_some() {
447 return Ok(());
448 }
449 }
450 if i != inner {
451 let directive = if name.contains("::") {
455 "declare `oursync` for shared package-global state"
456 } else {
457 "declare `mysync` for shared lexical state"
458 };
459 return Err(PerlError::runtime(
460 format!(
461 "cannot assign to captured non-atomic variable `${}` in a parallel block — {}",
462 name, directive
463 ),
464 0,
465 ));
466 }
467 return Ok(());
468 }
469 }
470 Err(PerlError::runtime(
471 format!(
472 "cannot assign to undeclared variable `${}` in a parallel block",
473 name
474 ),
475 0,
476 ))
477 }
478
479 #[inline]
480 pub fn depth(&self) -> usize {
481 self.frames.len()
482 }
483
484 #[inline]
487 pub fn pop_to_depth(&mut self, target_depth: usize) {
488 while self.frames.len() > target_depth && self.frames.len() > 1 {
489 self.pop_frame();
490 }
491 }
492
493 #[inline]
494 pub fn push_frame(&mut self) {
495 if let Some(mut frame) = self.frame_pool.pop() {
496 frame.clear_all_bindings();
497 self.frames.push(frame);
498 } else {
499 self.frames.push(Frame::new());
500 }
501 }
502
503 #[inline]
508 pub fn get_scalar_slot(&self, slot: u8) -> PerlValue {
509 let idx = slot as usize;
510 for frame in self.frames.iter().rev() {
511 if idx < frame.scalar_slots.len() && frame.owns_scalar_slot_index(idx) {
512 let val = &frame.scalar_slots[idx];
513 if let Some(arc) = val.as_capture_cell() {
516 return arc.read().clone();
517 }
518 return val.clone();
519 }
520 }
521 PerlValue::UNDEF
522 }
523
524 #[inline]
526 pub fn set_scalar_slot(&mut self, slot: u8, val: PerlValue) {
527 let idx = slot as usize;
528 let len = self.frames.len();
529 for i in (0..len).rev() {
530 if idx < self.frames[i].scalar_slots.len() && self.frames[i].owns_scalar_slot_index(idx)
531 {
532 if let Some(r) = self.frames[i].scalar_slots[idx].as_capture_cell() {
534 *r.write() = val;
535 } else {
536 self.frames[i].scalar_slots[idx] = val;
537 }
538 return;
539 }
540 }
541 let top = self.frames.last_mut().unwrap();
542 top.scalar_slots.resize(idx + 1, PerlValue::UNDEF);
543 if idx >= top.scalar_slot_names.len() {
544 top.scalar_slot_names.resize(idx + 1, None);
545 }
546 top.scalar_slot_names[idx] = Some(String::new());
547 top.scalar_slots[idx] = val;
548 }
549
550 #[inline]
554 pub fn set_scalar_slot_checked(
555 &mut self,
556 slot: u8,
557 val: PerlValue,
558 slot_name: Option<&str>,
559 ) -> Result<(), PerlError> {
560 if self.parallel_guard {
561 let idx = slot as usize;
562 let len = self.frames.len();
563 let top_has = idx < self.frames[len - 1].scalar_slots.len()
564 && self.frames[len - 1].owns_scalar_slot_index(idx);
565 if !top_has {
566 let name_owned: String = {
567 let mut found = String::new();
568 for i in (0..len).rev() {
569 if let Some(Some(n)) = self.frames[i].scalar_slot_names.get(idx) {
570 found = n.clone();
571 break;
572 }
573 }
574 if found.is_empty() {
575 if let Some(sn) = slot_name {
576 found = sn.to_string();
577 }
578 }
579 found
580 };
581 let name = name_owned.as_str();
582 if !name.is_empty() && !Self::parallel_allowed_topic_scalar(name) {
583 let inner = len.saturating_sub(1);
584 for (fi, frame) in self.frames.iter().enumerate().rev() {
585 if frame.has_scalar(name)
586 || (idx < frame.scalar_slots.len() && frame.owns_scalar_slot_index(idx))
587 {
588 if fi != inner {
589 return Err(PerlError::runtime(
590 format!(
591 "cannot assign to captured outer lexical `${}` inside a parallel block (use `mysync`)",
592 name
593 ),
594 0,
595 ));
596 }
597 break;
598 }
599 }
600 }
601 }
602 }
603 self.set_scalar_slot(slot, val);
604 Ok(())
605 }
606
607 #[inline]
611 pub fn declare_scalar_slot(&mut self, slot: u8, val: PerlValue, name: Option<&str>) {
612 let idx = slot as usize;
613 let frame = self.frames.last_mut().unwrap();
614 if idx >= frame.scalar_slots.len() {
615 frame.scalar_slots.resize(idx + 1, PerlValue::UNDEF);
616 }
617 frame.scalar_slots[idx] = val;
618 if idx >= frame.scalar_slot_names.len() {
619 frame.scalar_slot_names.resize(idx + 1, None);
620 }
621 match name {
622 Some(n) => frame.scalar_slot_names[idx] = Some(n.to_string()),
623 None => frame.scalar_slot_names[idx] = Some(String::new()),
625 }
626 }
627
628 #[inline]
639 pub fn scalar_slot_concat_repeat_inplace(&mut self, slot: u8, rhs: &str, n: usize) -> bool {
640 let idx = slot as usize;
641 let len = self.frames.len();
642 let fi = {
643 let mut found = len - 1;
644 if idx >= self.frames[found].scalar_slots.len()
645 || !self.frames[found].owns_scalar_slot_index(idx)
646 {
647 for i in (0..len - 1).rev() {
648 if idx < self.frames[i].scalar_slots.len()
649 && self.frames[i].owns_scalar_slot_index(idx)
650 {
651 found = i;
652 break;
653 }
654 }
655 }
656 found
657 };
658 let frame = &mut self.frames[fi];
659 if idx >= frame.scalar_slots.len() {
660 frame.scalar_slots.resize(idx + 1, PerlValue::UNDEF);
661 }
662 frame.scalar_slots[idx].try_concat_repeat_inplace(rhs, n)
663 }
664
665 #[inline]
670 pub fn scalar_slot_concat_repeat_slow(&mut self, slot: u8, rhs: &str, n: usize) {
671 let pv = PerlValue::string(rhs.to_owned());
672 for _ in 0..n {
673 let _ = self.scalar_slot_concat_inplace(slot, &pv);
674 }
675 }
676
677 #[inline]
678 pub fn scalar_slot_concat_inplace(&mut self, slot: u8, rhs: &PerlValue) -> PerlValue {
679 let idx = slot as usize;
680 let len = self.frames.len();
681 let fi = {
682 let mut found = len - 1;
683 if idx >= self.frames[found].scalar_slots.len()
684 || !self.frames[found].owns_scalar_slot_index(idx)
685 {
686 for i in (0..len - 1).rev() {
687 if idx < self.frames[i].scalar_slots.len()
688 && self.frames[i].owns_scalar_slot_index(idx)
689 {
690 found = i;
691 break;
692 }
693 }
694 }
695 found
696 };
697 let frame = &mut self.frames[fi];
698 if idx >= frame.scalar_slots.len() {
699 frame.scalar_slots.resize(idx + 1, PerlValue::UNDEF);
700 }
701 if frame.scalar_slots[idx].try_concat_append_inplace(rhs) {
709 return frame.scalar_slots[idx].shallow_clone();
710 }
711 let new_val = std::mem::replace(&mut frame.scalar_slots[idx], PerlValue::UNDEF)
712 .concat_append_owned(rhs);
713 let handle = new_val.shallow_clone();
714 frame.scalar_slots[idx] = new_val;
715 handle
716 }
717
718 #[inline]
719 pub(crate) fn can_pop_frame(&self) -> bool {
720 self.frames.len() > 1
721 }
722
723 #[inline]
724 pub fn pop_frame(&mut self) {
725 if self.frames.len() > 1 {
726 let mut frame = self.frames.pop().expect("pop_frame");
727 let saved_guard = self.parallel_guard;
730 self.parallel_guard = false;
731 for entry in frame.local_restores.drain(..).rev() {
732 match entry {
733 LocalRestore::Scalar(name, old) => {
734 let _ = self.set_scalar(&name, old);
735 }
736 LocalRestore::Array(name, old) => {
737 let _ = self.set_array(&name, old);
738 }
739 LocalRestore::Hash(name, old) => {
740 let _ = self.set_hash(&name, old);
741 }
742 LocalRestore::HashElement(name, key, old) => match old {
743 Some(v) => {
744 let _ = self.set_hash_element(&name, &key, v);
745 }
746 None => {
747 let _ = self.delete_hash_element(&name, &key);
748 }
749 },
750 LocalRestore::ArrayElement(name, index, old) => {
751 let _ = self.set_array_element(&name, index, old);
752 }
753 }
754 }
755 self.parallel_guard = saved_guard;
756 frame.clear_all_bindings();
757 if self.frame_pool.len() < 64 {
759 self.frame_pool.push(frame);
760 }
761 }
762 }
763
764 pub fn local_set_scalar(&mut self, name: &str, val: PerlValue) -> Result<(), PerlError> {
766 let old = self.get_scalar(name);
767 if let Some(frame) = self.frames.last_mut() {
768 frame
769 .local_restores
770 .push(LocalRestore::Scalar(name.to_string(), old));
771 }
772 self.set_scalar(name, val)
773 }
774
775 pub fn local_set_array(&mut self, name: &str, val: Vec<PerlValue>) -> Result<(), PerlError> {
777 if self.find_atomic_array(name).is_some() {
778 return Err(PerlError::runtime(
779 "local cannot be used on mysync arrays",
780 0,
781 ));
782 }
783 let old = self.get_array(name);
784 if let Some(frame) = self.frames.last_mut() {
785 frame
786 .local_restores
787 .push(LocalRestore::Array(name.to_string(), old));
788 }
789 self.set_array(name, val)?;
790 Ok(())
791 }
792
793 pub fn local_set_hash(
795 &mut self,
796 name: &str,
797 val: IndexMap<String, PerlValue>,
798 ) -> Result<(), PerlError> {
799 if self.find_atomic_hash(name).is_some() {
800 return Err(PerlError::runtime(
801 "local cannot be used on mysync hashes",
802 0,
803 ));
804 }
805 let old = self.get_hash(name);
806 if let Some(frame) = self.frames.last_mut() {
807 frame
808 .local_restores
809 .push(LocalRestore::Hash(name.to_string(), old));
810 }
811 self.set_hash(name, val)?;
812 Ok(())
813 }
814
815 pub fn local_set_hash_element(
817 &mut self,
818 name: &str,
819 key: &str,
820 val: PerlValue,
821 ) -> Result<(), PerlError> {
822 if self.find_atomic_hash(name).is_some() {
823 return Err(PerlError::runtime(
824 "local cannot be used on mysync hash elements",
825 0,
826 ));
827 }
828 let old = if self.exists_hash_element(name, key) {
829 Some(self.get_hash_element(name, key))
830 } else {
831 None
832 };
833 if let Some(frame) = self.frames.last_mut() {
834 frame.local_restores.push(LocalRestore::HashElement(
835 name.to_string(),
836 key.to_string(),
837 old,
838 ));
839 }
840 self.set_hash_element(name, key, val)?;
841 Ok(())
842 }
843
844 pub fn local_set_array_element(
847 &mut self,
848 name: &str,
849 index: i64,
850 val: PerlValue,
851 ) -> Result<(), PerlError> {
852 if self.find_atomic_array(name).is_some() {
853 return Err(PerlError::runtime(
854 "local cannot be used on mysync array elements",
855 0,
856 ));
857 }
858 let old = self.get_array_element(name, index);
859 if let Some(frame) = self.frames.last_mut() {
860 frame
861 .local_restores
862 .push(LocalRestore::ArrayElement(name.to_string(), index, old));
863 }
864 self.set_array_element(name, index, val)?;
865 Ok(())
866 }
867
868 #[inline]
871 pub fn declare_scalar(&mut self, name: &str, val: PerlValue) {
872 let _ = self.declare_scalar_frozen(name, val, false, None);
873 }
874
875 pub fn declare_scalar_frozen(
878 &mut self,
879 name: &str,
880 val: PerlValue,
881 frozen: bool,
882 ty: Option<PerlTypeName>,
883 ) -> Result<(), PerlError> {
884 if let Some(ref t) = ty {
885 t.check_value(&val)
886 .map_err(|msg| PerlError::type_error(format!("`${}`: {}", name, msg), 0))?;
887 }
888 if let Some(frame) = self.frames.last_mut() {
889 frame.set_scalar(name, val);
890 if frozen {
891 frame.frozen_scalars.insert(name.to_string());
892 }
893 if let Some(t) = ty {
894 frame.typed_scalars.insert(name.to_string(), t);
895 }
896 }
897 Ok(())
898 }
899
900 pub fn is_scalar_frozen(&self, name: &str) -> bool {
902 for frame in self.frames.iter().rev() {
903 if frame.has_scalar(name) {
904 return frame.frozen_scalars.contains(name);
905 }
906 }
907 false
908 }
909
910 pub fn is_array_frozen(&self, name: &str) -> bool {
912 for frame in self.frames.iter().rev() {
913 if frame.has_array(name) {
914 return frame.frozen_arrays.contains(name);
915 }
916 }
917 false
918 }
919
920 pub fn is_hash_frozen(&self, name: &str) -> bool {
922 for frame in self.frames.iter().rev() {
923 if frame.has_hash(name) {
924 return frame.frozen_hashes.contains(name);
925 }
926 }
927 false
928 }
929
930 pub fn check_frozen(&self, sigil: &str, name: &str) -> Option<&'static str> {
932 match sigil {
933 "$" => {
934 if self.is_scalar_frozen(name) {
935 Some("scalar")
936 } else {
937 None
938 }
939 }
940 "@" => {
941 if self.is_array_frozen(name) {
942 Some("array")
943 } else {
944 None
945 }
946 }
947 "%" => {
948 if self.is_hash_frozen(name) {
949 Some("hash")
950 } else {
951 None
952 }
953 }
954 _ => None,
955 }
956 }
957
958 #[inline]
959 pub fn get_scalar(&self, name: &str) -> PerlValue {
960 for frame in self.frames.iter().rev() {
961 if let Some(val) = frame.get_scalar(name) {
962 if let Some(arc) = val.as_atomic_arc() {
964 return arc.lock().clone();
965 }
966 if let Some(arc) = val.as_capture_cell() {
969 return arc.read().clone();
970 }
971 if val.is_undef() && Self::is_topic_chain_name(name) {
981 return self.get_scalar("_");
982 }
983 return val.clone();
984 }
985 }
986 PerlValue::UNDEF
987 }
988
989 #[inline]
992 fn is_topic_chain_name(name: &str) -> bool {
993 let bytes = name.as_bytes();
994 if bytes.is_empty() || bytes[0] != b'_' {
995 return false;
996 }
997 let mut i = 1;
998 while i < bytes.len() && bytes[i].is_ascii_digit() {
999 i += 1;
1000 }
1001 if i >= bytes.len() || bytes[i] != b'<' {
1002 return false;
1003 }
1004 while i < bytes.len() && bytes[i] == b'<' {
1005 i += 1;
1006 }
1007 i == bytes.len()
1008 }
1009
1010 #[inline]
1019 pub(crate) fn is_topic_variant_name(name: &str) -> bool {
1020 let bytes = name.as_bytes();
1021 if bytes.is_empty() || bytes[0] != b'_' {
1022 return false;
1023 }
1024 let mut i = 1;
1025 while i < bytes.len() && bytes[i].is_ascii_digit() {
1026 i += 1;
1027 }
1028 while i < bytes.len() && bytes[i] == b'<' {
1029 i += 1;
1030 }
1031 i == bytes.len()
1032 }
1033
1034 #[inline]
1036 pub fn scalar_binding_exists(&self, name: &str) -> bool {
1037 for frame in self.frames.iter().rev() {
1038 if frame.has_scalar(name) {
1039 return true;
1040 }
1041 }
1042 false
1043 }
1044
1045 pub fn all_scalar_names(&self) -> Vec<String> {
1047 let mut names = Vec::new();
1048 for frame in &self.frames {
1049 for (name, _) in &frame.scalars {
1050 if !names.contains(name) {
1051 names.push(name.clone());
1052 }
1053 }
1054 for name in frame.scalar_slot_names.iter().flatten() {
1055 if !names.contains(name) {
1056 names.push(name.clone());
1057 }
1058 }
1059 }
1060 names
1061 }
1062
1063 #[inline]
1065 pub fn array_binding_exists(&self, name: &str) -> bool {
1066 if self.find_atomic_array(name).is_some() {
1067 return true;
1068 }
1069 for frame in self.frames.iter().rev() {
1070 if frame.has_array(name) {
1071 return true;
1072 }
1073 }
1074 false
1075 }
1076
1077 #[inline]
1079 pub fn hash_binding_exists(&self, name: &str) -> bool {
1080 if self.find_atomic_hash(name).is_some() {
1081 return true;
1082 }
1083 for frame in self.frames.iter().rev() {
1084 if frame.has_hash(name) {
1085 return true;
1086 }
1087 }
1088 false
1089 }
1090
1091 #[inline]
1094 pub fn get_scalar_raw(&self, name: &str) -> PerlValue {
1095 for frame in self.frames.iter().rev() {
1096 if let Some(val) = frame.get_scalar(name) {
1097 return val.clone();
1098 }
1099 }
1100 PerlValue::UNDEF
1101 }
1102
1103 pub fn atomic_mutate(
1109 &mut self,
1110 name: &str,
1111 f: impl FnOnce(&PerlValue) -> PerlValue,
1112 ) -> Result<PerlValue, PerlError> {
1113 for frame in self.frames.iter().rev() {
1114 if let Some(v) = frame.get_scalar(name) {
1115 if let Some(arc) = v.as_atomic_arc() {
1116 let mut guard = arc.lock();
1117 let old = guard.clone();
1118 let new_val = f(&guard);
1119 *guard = new_val.clone();
1120 crate::parallel_trace::emit_scalar_mutation(name, &old, &new_val);
1121 return Ok(new_val);
1122 }
1123 }
1124 }
1125 let old = self.get_scalar(name);
1128 let new_val = f(&old);
1129 self.set_scalar(name, new_val.clone())?;
1130 Ok(new_val)
1131 }
1132
1133 pub fn atomic_mutate_post(
1137 &mut self,
1138 name: &str,
1139 f: impl FnOnce(&PerlValue) -> PerlValue,
1140 ) -> Result<PerlValue, PerlError> {
1141 for frame in self.frames.iter().rev() {
1142 if let Some(v) = frame.get_scalar(name) {
1143 if let Some(arc) = v.as_atomic_arc() {
1144 let mut guard = arc.lock();
1145 let old = guard.clone();
1146 let new_val = f(&old);
1147 *guard = new_val.clone();
1148 crate::parallel_trace::emit_scalar_mutation(name, &old, &new_val);
1149 return Ok(old);
1150 }
1151 }
1152 }
1153 let old = self.get_scalar(name);
1155 self.set_scalar(name, f(&old))?;
1156 Ok(old)
1157 }
1158
1159 #[inline]
1166 pub fn scalar_concat_inplace(
1167 &mut self,
1168 name: &str,
1169 rhs: &PerlValue,
1170 ) -> Result<PerlValue, PerlError> {
1171 self.check_parallel_scalar_write(name)?;
1172 for frame in self.frames.iter_mut().rev() {
1173 if let Some(entry) = frame.scalars.iter_mut().find(|(k, _)| k == name) {
1174 if let Some(atomic_arc) = entry.1.as_atomic_arc() {
1177 let mut guard = atomic_arc.lock();
1178 let inner = std::mem::replace(&mut *guard, PerlValue::UNDEF);
1179 let new_val = inner.concat_append_owned(rhs);
1180 *guard = new_val.shallow_clone();
1181 return Ok(new_val);
1182 }
1183 if entry.1.try_concat_append_inplace(rhs) {
1186 return Ok(entry.1.shallow_clone());
1187 }
1188 let new_val =
1191 std::mem::replace(&mut entry.1, PerlValue::UNDEF).concat_append_owned(rhs);
1192 entry.1 = new_val.shallow_clone();
1193 return Ok(new_val);
1194 }
1195 }
1196 let val = PerlValue::UNDEF.concat_append_owned(rhs);
1198 self.frames[0].set_scalar(name, val.shallow_clone());
1199 Ok(val)
1200 }
1201
1202 #[inline]
1203 pub fn set_scalar(&mut self, name: &str, val: PerlValue) -> Result<(), PerlError> {
1204 self.check_parallel_scalar_write(name)?;
1205 if Self::is_topic_variant_name(name) {
1212 if let Some(frame) = self.frames.last_mut() {
1213 frame.set_scalar_raw(name, val);
1214 }
1215 return Ok(());
1216 }
1217 for frame in self.frames.iter_mut().rev() {
1218 if let Some(v) = frame.get_scalar(name) {
1220 if let Some(arc) = v.as_atomic_arc() {
1221 let mut guard = arc.lock();
1222 let old = guard.clone();
1223 *guard = val.clone();
1224 crate::parallel_trace::emit_scalar_mutation(name, &old, &val);
1225 return Ok(());
1226 }
1227 if let Some(arc) = v.as_capture_cell() {
1229 *arc.write() = val;
1230 return Ok(());
1231 }
1232 }
1233 if frame.has_scalar(name) {
1234 if let Some(ty) = frame.typed_scalars.get(name) {
1235 ty.check_value(&val)
1236 .map_err(|msg| PerlError::type_error(format!("`${}`: {}", name, msg), 0))?;
1237 }
1238 frame.set_scalar(name, val);
1239 return Ok(());
1240 }
1241 }
1242 self.frames[0].set_scalar(name, val);
1243 Ok(())
1244 }
1245
1246 #[inline]
1252 fn topic_slot_key(slot: usize, level: usize) -> String {
1253 debug_assert!(level <= 5);
1254 if slot == 0 {
1255 if level == 0 {
1256 "_".to_string()
1257 } else {
1258 format!("_{}", "<".repeat(level))
1259 }
1260 } else if level == 0 {
1261 format!("_{}", slot)
1262 } else {
1263 format!("_{}{}", slot, "<".repeat(level))
1264 }
1265 }
1266
1267 #[inline]
1270 fn topic_slot_alias_key(slot: usize, level: usize) -> Option<String> {
1271 if slot != 0 {
1272 return None;
1273 }
1274 Some(if level == 0 {
1275 "_0".to_string()
1276 } else {
1277 format!("_0{}", "<".repeat(level))
1278 })
1279 }
1280
1281 #[inline]
1287 fn declare_topic_slot(&mut self, slot: usize, level: usize, val: PerlValue) {
1288 if let Some(frame) = self.frames.last_mut() {
1295 let key = Self::topic_slot_key(slot, level);
1296 frame.set_scalar_raw(&key, val.clone());
1297 if let Some(alias) = Self::topic_slot_alias_key(slot, level) {
1298 frame.set_scalar_raw(&alias, val);
1299 }
1300 }
1301 }
1302
1303 #[inline]
1328 pub fn set_topic(&mut self, val: PerlValue) {
1329 let already_shifted = self
1337 .frames
1338 .last()
1339 .map(|f| f.set_topic_called)
1340 .unwrap_or(false);
1341 if already_shifted {
1342 self.declare_topic_slot(0, 0, val);
1343 for slot in 1..=self.max_active_slot {
1344 self.declare_topic_slot(slot, 0, PerlValue::UNDEF);
1345 }
1346 return;
1347 }
1348 if let Some(frame) = self.frames.last_mut() {
1349 frame.set_topic_called = true;
1350 }
1351 self.shift_slot_chain(0, val);
1352 for slot in 1..=self.max_active_slot {
1353 self.shift_slot_chain(slot, PerlValue::UNDEF);
1354 }
1355 }
1356
1357 #[inline]
1366 pub fn set_topic_local(&mut self, val: PerlValue) {
1367 self.declare_topic_slot(0, 0, val);
1368 }
1369
1370 #[inline]
1382 pub fn set_closure_args(&mut self, args: &[PerlValue]) {
1383 let n = args.len();
1384 if n == 0 {
1385 return;
1386 }
1387 let high = n.saturating_sub(1).max(self.max_active_slot);
1388 for slot in 0..=high {
1389 let val = args.get(slot).cloned().unwrap_or(PerlValue::UNDEF);
1390 self.shift_slot_chain(slot, val);
1391 }
1392 if n > 0 && n - 1 > self.max_active_slot {
1393 self.max_active_slot = n - 1;
1394 }
1395 }
1396
1397 #[inline]
1406 fn shift_slot_chain(&mut self, slot: usize, val: PerlValue) {
1407 let l4 = self.get_scalar(&Self::topic_slot_key(slot, 4));
1408 let l3 = self.get_scalar(&Self::topic_slot_key(slot, 3));
1409 let l2 = self.get_scalar(&Self::topic_slot_key(slot, 2));
1410 let l1 = self.get_scalar(&Self::topic_slot_key(slot, 1));
1411 let cur = self.get_scalar(&Self::topic_slot_key(slot, 0));
1412
1413 self.declare_topic_slot(slot, 0, val);
1414 self.declare_topic_slot(slot, 1, cur);
1415 self.declare_topic_slot(slot, 2, l1);
1416 self.declare_topic_slot(slot, 3, l2);
1417 self.declare_topic_slot(slot, 4, l3);
1418 self.declare_topic_slot(slot, 5, l4);
1419 }
1420
1421 #[inline]
1429 pub fn set_sort_pair(&mut self, a: PerlValue, b: PerlValue) {
1430 let _ = self.set_scalar("a", a.clone());
1431 let _ = self.set_scalar("b", b.clone());
1432 let _ = self.set_scalar("_0", a);
1433 let _ = self.set_scalar("_1", b);
1434 }
1435
1436 #[inline]
1438 pub fn push_defer(&mut self, coderef: PerlValue) {
1439 if let Some(frame) = self.frames.last_mut() {
1440 frame.defers.push(coderef);
1441 }
1442 }
1443
1444 #[inline]
1447 pub fn take_defers(&mut self) -> Vec<PerlValue> {
1448 if let Some(frame) = self.frames.last_mut() {
1449 let mut defers = std::mem::take(&mut frame.defers);
1450 defers.reverse();
1451 defers
1452 } else {
1453 Vec::new()
1454 }
1455 }
1456
1457 pub fn declare_atomic_array(&mut self, name: &str, val: Vec<PerlValue>) {
1460 if let Some(frame) = self.frames.last_mut() {
1461 frame
1462 .atomic_arrays
1463 .push((name.to_string(), AtomicArray(Arc::new(Mutex::new(val)))));
1464 }
1465 }
1466
1467 pub fn declare_atomic_hash(&mut self, name: &str, val: IndexMap<String, PerlValue>) {
1468 if let Some(frame) = self.frames.last_mut() {
1469 frame
1470 .atomic_hashes
1471 .push((name.to_string(), AtomicHash(Arc::new(Mutex::new(val)))));
1472 }
1473 }
1474
1475 fn find_atomic_array(&self, name: &str) -> Option<&AtomicArray> {
1477 for frame in self.frames.iter().rev() {
1478 if let Some(aa) = frame.atomic_arrays.iter().find(|(k, _)| k == name) {
1479 return Some(&aa.1);
1480 }
1481 }
1482 None
1483 }
1484
1485 fn find_atomic_hash(&self, name: &str) -> Option<&AtomicHash> {
1487 for frame in self.frames.iter().rev() {
1488 if let Some(ah) = frame.atomic_hashes.iter().find(|(k, _)| k == name) {
1489 return Some(&ah.1);
1490 }
1491 }
1492 None
1493 }
1494
1495 #[inline]
1500 pub fn take_sub_underscore(&mut self) -> Option<Vec<PerlValue>> {
1501 self.frames.last_mut()?.sub_underscore.take()
1502 }
1503
1504 pub fn declare_array(&mut self, name: &str, val: Vec<PerlValue>) {
1505 self.declare_array_frozen(name, val, false);
1506 }
1507
1508 pub fn declare_array_frozen(&mut self, name: &str, val: Vec<PerlValue>, frozen: bool) {
1509 let idx = if name.contains("::") {
1512 0
1513 } else {
1514 self.frames.len().saturating_sub(1)
1515 };
1516 if let Some(frame) = self.frames.get_mut(idx) {
1517 frame.shared_arrays.retain(|(k, _)| k != name);
1519 frame.set_array(name, val);
1520 if frozen {
1521 frame.frozen_arrays.insert(name.to_string());
1522 } else {
1523 frame.frozen_arrays.remove(name);
1525 }
1526 }
1527 }
1528
1529 pub fn get_array(&self, name: &str) -> Vec<PerlValue> {
1530 if let Some(aa) = self.find_atomic_array(name) {
1532 return aa.0.lock().clone();
1533 }
1534 if let Some(arc) = self.find_shared_array(name) {
1536 return arc.read().clone();
1537 }
1538 if name.contains("::") {
1539 if let Some(f) = self.frames.first() {
1540 if let Some(val) = f.get_array(name) {
1541 return val.clone();
1542 }
1543 }
1544 return Vec::new();
1545 }
1546 for frame in self.frames.iter().rev() {
1547 if let Some(val) = frame.get_array(name) {
1548 return val.clone();
1549 }
1550 }
1551 Vec::new()
1552 }
1553
1554 #[inline]
1557 pub fn get_array_borrow(&self, name: &str) -> Option<&[PerlValue]> {
1558 if self.find_atomic_array(name).is_some() {
1559 return None;
1560 }
1561 if name.contains("::") {
1562 return self
1563 .frames
1564 .first()
1565 .and_then(|f| f.get_array(name))
1566 .map(|v| v.as_slice());
1567 }
1568 for frame in self.frames.iter().rev() {
1569 if let Some(val) = frame.get_array(name) {
1570 return Some(val.as_slice());
1571 }
1572 }
1573 None
1574 }
1575
1576 fn resolve_array_frame_idx(&self, name: &str) -> Option<usize> {
1577 if name.contains("::") {
1578 return Some(0);
1579 }
1580 (0..self.frames.len())
1581 .rev()
1582 .find(|&i| self.frames[i].has_array(name))
1583 }
1584
1585 fn check_parallel_array_write(&self, name: &str) -> Result<(), PerlError> {
1586 if !self.parallel_guard
1587 || Self::parallel_skip_special_name(name)
1588 || Self::parallel_allowed_internal_array(name)
1589 {
1590 return Ok(());
1591 }
1592 let inner = self.frames.len().saturating_sub(1);
1593 match self.resolve_array_frame_idx(name) {
1594 None => Err(PerlError::runtime(
1595 format!(
1596 "cannot modify undeclared array `@{}` in a parallel block",
1597 name
1598 ),
1599 0,
1600 )),
1601 Some(idx) if idx != inner => Err(PerlError::runtime(
1602 format!(
1603 "cannot modify captured non-mysync array `@{}` in a parallel block",
1604 name
1605 ),
1606 0,
1607 )),
1608 Some(_) => Ok(()),
1609 }
1610 }
1611
1612 #[inline]
1617 pub fn resolve_container_binding_ref(&self, val: PerlValue) -> PerlValue {
1618 if let Some(name) = val.as_array_binding_name() {
1619 let data = self.get_array(&name);
1620 return PerlValue::array_ref(Arc::new(parking_lot::RwLock::new(data)));
1621 }
1622 if let Some(name) = val.as_hash_binding_name() {
1623 let data = self.get_hash(&name);
1624 return PerlValue::hash_ref(Arc::new(parking_lot::RwLock::new(data)));
1625 }
1626 val
1627 }
1628
1629 pub fn promote_array_to_shared(
1633 &mut self,
1634 name: &str,
1635 ) -> Arc<parking_lot::RwLock<Vec<PerlValue>>> {
1636 if let Some(aa) = self.find_atomic_array(name) {
1639 let data = aa.0.lock().clone();
1640 return Arc::new(parking_lot::RwLock::new(data));
1641 }
1642 let idx = self.resolve_array_frame_idx(name).unwrap_or_default();
1644 let frame = &mut self.frames[idx];
1645 if let Some(entry) = frame.shared_arrays.iter().find(|(k, _)| k == name) {
1646 return Arc::clone(&entry.1);
1647 }
1648 let data = if let Some(pos) = frame.arrays.iter().position(|(k, _)| k == name) {
1650 frame.arrays.swap_remove(pos).1
1651 } else if name == "_" {
1652 frame.sub_underscore.take().unwrap_or_default()
1653 } else {
1654 Vec::new()
1655 };
1656 let arc = Arc::new(parking_lot::RwLock::new(data));
1657 frame
1658 .shared_arrays
1659 .push((name.to_string(), Arc::clone(&arc)));
1660 arc
1661 }
1662
1663 pub fn promote_hash_to_shared(
1666 &mut self,
1667 name: &str,
1668 ) -> Arc<parking_lot::RwLock<IndexMap<String, PerlValue>>> {
1669 let idx = self.resolve_hash_frame_idx(name).unwrap_or_default();
1670 let frame = &mut self.frames[idx];
1671 if let Some(entry) = frame.shared_hashes.iter().find(|(k, _)| k == name) {
1672 return Arc::clone(&entry.1);
1673 }
1674 let data = if let Some(pos) = frame.hashes.iter().position(|(k, _)| k == name) {
1675 frame.hashes.swap_remove(pos).1
1676 } else {
1677 IndexMap::new()
1678 };
1679 let arc = Arc::new(parking_lot::RwLock::new(data));
1680 frame
1681 .shared_hashes
1682 .push((name.to_string(), Arc::clone(&arc)));
1683 arc
1684 }
1685
1686 fn find_shared_array(&self, name: &str) -> Option<Arc<parking_lot::RwLock<Vec<PerlValue>>>> {
1688 for frame in self.frames.iter().rev() {
1689 if let Some(entry) = frame.shared_arrays.iter().find(|(k, _)| k == name) {
1690 return Some(Arc::clone(&entry.1));
1691 }
1692 if frame.arrays.iter().any(|(k, _)| k == name) {
1694 return None;
1695 }
1696 }
1697 None
1698 }
1699
1700 fn find_shared_hash(
1702 &self,
1703 name: &str,
1704 ) -> Option<Arc<parking_lot::RwLock<IndexMap<String, PerlValue>>>> {
1705 for frame in self.frames.iter().rev() {
1706 if let Some(entry) = frame.shared_hashes.iter().find(|(k, _)| k == name) {
1707 return Some(Arc::clone(&entry.1));
1708 }
1709 if frame.hashes.iter().any(|(k, _)| k == name) {
1710 return None;
1711 }
1712 }
1713 None
1714 }
1715
1716 pub fn get_array_mut(&mut self, name: &str) -> Result<&mut Vec<PerlValue>, PerlError> {
1717 if self.find_atomic_array(name).is_some() {
1720 return Err(PerlError::runtime(
1721 "get_array_mut: use atomic path for mysync arrays",
1722 0,
1723 ));
1724 }
1725 self.check_parallel_array_write(name)?;
1726 let idx = self.resolve_array_frame_idx(name).unwrap_or_default();
1727 let frame = &mut self.frames[idx];
1728 if frame.get_array_mut(name).is_none() {
1729 frame.arrays.push((name.to_string(), Vec::new()));
1730 }
1731 Ok(frame.get_array_mut(name).unwrap())
1732 }
1733
1734 pub fn push_to_array(&mut self, name: &str, val: PerlValue) -> Result<(), PerlError> {
1736 let val = self.resolve_container_binding_ref(val);
1737 if let Some(aa) = self.find_atomic_array(name) {
1738 aa.0.lock().push(val);
1739 return Ok(());
1740 }
1741 if let Some(arc) = self.find_shared_array(name) {
1742 arc.write().push(val);
1743 return Ok(());
1744 }
1745 self.get_array_mut(name)?.push(val);
1746 Ok(())
1747 }
1748
1749 pub fn push_int_range_to_array(
1753 &mut self,
1754 name: &str,
1755 start: i64,
1756 end: i64,
1757 ) -> Result<(), PerlError> {
1758 if end <= start {
1759 return Ok(());
1760 }
1761 let count = (end - start) as usize;
1762 if let Some(aa) = self.find_atomic_array(name) {
1763 let mut g = aa.0.lock();
1764 g.reserve(count);
1765 for i in start..end {
1766 g.push(PerlValue::integer(i));
1767 }
1768 return Ok(());
1769 }
1770 let arr = self.get_array_mut(name)?;
1771 arr.reserve(count);
1772 for i in start..end {
1773 arr.push(PerlValue::integer(i));
1774 }
1775 Ok(())
1776 }
1777
1778 pub fn pop_from_array(&mut self, name: &str) -> Result<PerlValue, PerlError> {
1780 if let Some(aa) = self.find_atomic_array(name) {
1781 return Ok(aa.0.lock().pop().unwrap_or(PerlValue::UNDEF));
1782 }
1783 if let Some(arc) = self.find_shared_array(name) {
1784 return Ok(arc.write().pop().unwrap_or(PerlValue::UNDEF));
1785 }
1786 Ok(self.get_array_mut(name)?.pop().unwrap_or(PerlValue::UNDEF))
1787 }
1788
1789 pub fn shift_from_array(&mut self, name: &str) -> Result<PerlValue, PerlError> {
1791 if let Some(aa) = self.find_atomic_array(name) {
1792 let mut guard = aa.0.lock();
1793 return Ok(if guard.is_empty() {
1794 PerlValue::UNDEF
1795 } else {
1796 guard.remove(0)
1797 });
1798 }
1799 if let Some(arc) = self.find_shared_array(name) {
1800 let mut arr = arc.write();
1801 return Ok(if arr.is_empty() {
1802 PerlValue::UNDEF
1803 } else {
1804 arr.remove(0)
1805 });
1806 }
1807 let arr = self.get_array_mut(name)?;
1808 Ok(if arr.is_empty() {
1809 PerlValue::UNDEF
1810 } else {
1811 arr.remove(0)
1812 })
1813 }
1814
1815 pub fn splice_in_place(
1819 &mut self,
1820 name: &str,
1821 off: usize,
1822 end: usize,
1823 rep_vals: Vec<PerlValue>,
1824 ) -> Result<Vec<PerlValue>, PerlError> {
1825 if let Some(aa) = self.find_atomic_array(name) {
1826 let mut g = aa.0.lock();
1827 let removed: Vec<PerlValue> = g.drain(off..end).collect();
1828 for (i, v) in rep_vals.into_iter().enumerate() {
1829 g.insert(off + i, v);
1830 }
1831 return Ok(removed);
1832 }
1833 if let Some(arc) = self.find_shared_array(name) {
1834 let mut g = arc.write();
1835 let removed: Vec<PerlValue> = g.drain(off..end).collect();
1836 for (i, v) in rep_vals.into_iter().enumerate() {
1837 g.insert(off + i, v);
1838 }
1839 return Ok(removed);
1840 }
1841 let arr = self.get_array_mut(name)?;
1842 let removed: Vec<PerlValue> = arr.drain(off..end).collect();
1843 for (i, v) in rep_vals.into_iter().enumerate() {
1844 arr.insert(off + i, v);
1845 }
1846 Ok(removed)
1847 }
1848
1849 pub fn array_len(&self, name: &str) -> usize {
1851 if let Some(aa) = self.find_atomic_array(name) {
1852 return aa.0.lock().len();
1853 }
1854 if let Some(arc) = self.find_shared_array(name) {
1855 return arc.read().len();
1856 }
1857 if name.contains("::") {
1858 return self
1859 .frames
1860 .first()
1861 .and_then(|f| f.get_array(name))
1862 .map(|a| a.len())
1863 .unwrap_or(0);
1864 }
1865 for frame in self.frames.iter().rev() {
1866 if let Some(arr) = frame.get_array(name) {
1867 return arr.len();
1868 }
1869 }
1870 0
1871 }
1872
1873 pub fn set_array(&mut self, name: &str, val: Vec<PerlValue>) -> Result<(), PerlError> {
1874 if let Some(aa) = self.find_atomic_array(name) {
1875 *aa.0.lock() = val;
1876 return Ok(());
1877 }
1878 if let Some(arc) = self.find_shared_array(name) {
1879 *arc.write() = val;
1880 return Ok(());
1881 }
1882 self.check_parallel_array_write(name)?;
1883 for frame in self.frames.iter_mut().rev() {
1884 if frame.has_array(name) {
1885 frame.set_array(name, val);
1886 return Ok(());
1887 }
1888 }
1889 self.frames[0].set_array(name, val);
1890 Ok(())
1891 }
1892
1893 #[inline]
1895 pub fn get_array_element(&self, name: &str, index: i64) -> PerlValue {
1896 if let Some(aa) = self.find_atomic_array(name) {
1897 let arr = aa.0.lock();
1898 let idx = if index < 0 {
1899 (arr.len() as i64 + index) as usize
1900 } else {
1901 index as usize
1902 };
1903 return arr.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
1904 }
1905 if let Some(arc) = self.find_shared_array(name) {
1906 let arr = arc.read();
1907 let idx = if index < 0 {
1908 (arr.len() as i64 + index) as usize
1909 } else {
1910 index as usize
1911 };
1912 return arr.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
1913 }
1914 for frame in self.frames.iter().rev() {
1915 if let Some(arr) = frame.get_array(name) {
1916 let idx = if index < 0 {
1917 (arr.len() as i64 + index) as usize
1918 } else {
1919 index as usize
1920 };
1921 return arr.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
1922 }
1923 }
1924 PerlValue::UNDEF
1925 }
1926
1927 pub fn set_array_element(
1928 &mut self,
1929 name: &str,
1930 index: i64,
1931 val: PerlValue,
1932 ) -> Result<(), PerlError> {
1933 let val = self.resolve_container_binding_ref(val);
1934 if let Some(aa) = self.find_atomic_array(name) {
1935 let mut arr = aa.0.lock();
1936 let idx = if index < 0 {
1937 (arr.len() as i64 + index).max(0) as usize
1938 } else {
1939 index as usize
1940 };
1941 if idx >= arr.len() {
1942 arr.resize(idx + 1, PerlValue::UNDEF);
1943 }
1944 arr[idx] = val;
1945 return Ok(());
1946 }
1947 if let Some(arc) = self.find_shared_array(name) {
1948 let mut arr = arc.write();
1949 let idx = if index < 0 {
1950 (arr.len() as i64 + index).max(0) as usize
1951 } else {
1952 index as usize
1953 };
1954 if idx >= arr.len() {
1955 arr.resize(idx + 1, PerlValue::UNDEF);
1956 }
1957 arr[idx] = val;
1958 return Ok(());
1959 }
1960 let arr = self.get_array_mut(name)?;
1961 let idx = if index < 0 {
1962 let len = arr.len() as i64;
1963 (len + index).max(0) as usize
1964 } else {
1965 index as usize
1966 };
1967 if idx >= arr.len() {
1968 arr.resize(idx + 1, PerlValue::UNDEF);
1969 }
1970 arr[idx] = val;
1971 Ok(())
1972 }
1973
1974 pub fn exists_array_element(&self, name: &str, index: i64) -> bool {
1976 if let Some(aa) = self.find_atomic_array(name) {
1977 let arr = aa.0.lock();
1978 let idx = if index < 0 {
1979 (arr.len() as i64 + index) as usize
1980 } else {
1981 index as usize
1982 };
1983 return idx < arr.len();
1984 }
1985 for frame in self.frames.iter().rev() {
1986 if let Some(arr) = frame.get_array(name) {
1987 let idx = if index < 0 {
1988 (arr.len() as i64 + index) as usize
1989 } else {
1990 index as usize
1991 };
1992 return idx < arr.len();
1993 }
1994 }
1995 false
1996 }
1997
1998 pub fn delete_array_element(&mut self, name: &str, index: i64) -> Result<PerlValue, PerlError> {
2000 if let Some(aa) = self.find_atomic_array(name) {
2001 let mut arr = aa.0.lock();
2002 let idx = if index < 0 {
2003 (arr.len() as i64 + index) as usize
2004 } else {
2005 index as usize
2006 };
2007 if idx >= arr.len() {
2008 return Ok(PerlValue::UNDEF);
2009 }
2010 let old = arr.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
2011 arr[idx] = PerlValue::UNDEF;
2012 return Ok(old);
2013 }
2014 let arr = self.get_array_mut(name)?;
2015 let idx = if index < 0 {
2016 (arr.len() as i64 + index) as usize
2017 } else {
2018 index as usize
2019 };
2020 if idx >= arr.len() {
2021 return Ok(PerlValue::UNDEF);
2022 }
2023 let old = arr.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
2024 arr[idx] = PerlValue::UNDEF;
2025 Ok(old)
2026 }
2027
2028 #[inline]
2031 pub fn declare_hash(&mut self, name: &str, val: IndexMap<String, PerlValue>) {
2032 self.declare_hash_frozen(name, val, false);
2033 }
2034
2035 pub fn declare_hash_frozen(
2036 &mut self,
2037 name: &str,
2038 val: IndexMap<String, PerlValue>,
2039 frozen: bool,
2040 ) {
2041 if let Some(frame) = self.frames.last_mut() {
2042 frame.shared_hashes.retain(|(k, _)| k != name);
2044 frame.set_hash(name, val);
2045 if frozen {
2046 frame.frozen_hashes.insert(name.to_string());
2047 }
2048 }
2049 }
2050
2051 pub fn declare_hash_global(&mut self, name: &str, val: IndexMap<String, PerlValue>) {
2053 if let Some(frame) = self.frames.first_mut() {
2054 frame.set_hash(name, val);
2055 }
2056 }
2057
2058 pub fn declare_hash_global_frozen(&mut self, name: &str, val: IndexMap<String, PerlValue>) {
2060 if let Some(frame) = self.frames.first_mut() {
2061 frame.set_hash(name, val);
2062 frame.frozen_hashes.insert(name.to_string());
2063 }
2064 }
2065
2066 pub fn has_lexical_hash(&self, name: &str) -> bool {
2068 self.frames.iter().skip(1).any(|f| f.has_hash(name))
2069 }
2070
2071 pub fn any_frame_has_hash(&self, name: &str) -> bool {
2073 self.frames.iter().any(|f| f.has_hash(name))
2074 }
2075
2076 pub fn get_hash(&self, name: &str) -> IndexMap<String, PerlValue> {
2077 if let Some(ah) = self.find_atomic_hash(name) {
2078 return ah.0.lock().clone();
2079 }
2080 if let Some(arc) = self.find_shared_hash(name) {
2081 return arc.read().clone();
2082 }
2083 for frame in self.frames.iter().rev() {
2084 if let Some(val) = frame.get_hash(name) {
2085 return val.clone();
2086 }
2087 }
2088 IndexMap::new()
2089 }
2090
2091 fn resolve_hash_frame_idx(&self, name: &str) -> Option<usize> {
2092 if name.contains("::") {
2093 return Some(0);
2094 }
2095 (0..self.frames.len())
2096 .rev()
2097 .find(|&i| self.frames[i].has_hash(name))
2098 }
2099
2100 fn check_parallel_hash_write(&self, name: &str) -> Result<(), PerlError> {
2101 if !self.parallel_guard
2102 || Self::parallel_skip_special_name(name)
2103 || Self::parallel_allowed_internal_hash(name)
2104 {
2105 return Ok(());
2106 }
2107 let inner = self.frames.len().saturating_sub(1);
2108 match self.resolve_hash_frame_idx(name) {
2109 None => Err(PerlError::runtime(
2110 format!(
2111 "cannot modify undeclared hash `%{}` in a parallel block",
2112 name
2113 ),
2114 0,
2115 )),
2116 Some(idx) if idx != inner => Err(PerlError::runtime(
2117 format!(
2118 "cannot modify captured non-mysync hash `%{}` in a parallel block",
2119 name
2120 ),
2121 0,
2122 )),
2123 Some(_) => Ok(()),
2124 }
2125 }
2126
2127 pub fn get_hash_mut(
2128 &mut self,
2129 name: &str,
2130 ) -> Result<&mut IndexMap<String, PerlValue>, PerlError> {
2131 if self.find_atomic_hash(name).is_some() {
2132 return Err(PerlError::runtime(
2133 "get_hash_mut: use atomic path for mysync hashes",
2134 0,
2135 ));
2136 }
2137 self.check_parallel_hash_write(name)?;
2138 let idx = self.resolve_hash_frame_idx(name).unwrap_or_default();
2139 let frame = &mut self.frames[idx];
2140 if frame.get_hash_mut(name).is_none() {
2141 frame.hashes.push((name.to_string(), IndexMap::new()));
2142 }
2143 Ok(frame.get_hash_mut(name).unwrap())
2144 }
2145
2146 pub fn set_hash(
2147 &mut self,
2148 name: &str,
2149 val: IndexMap<String, PerlValue>,
2150 ) -> Result<(), PerlError> {
2151 if let Some(ah) = self.find_atomic_hash(name) {
2152 *ah.0.lock() = val;
2153 return Ok(());
2154 }
2155 self.check_parallel_hash_write(name)?;
2156 for frame in self.frames.iter_mut().rev() {
2157 if frame.has_hash(name) {
2158 frame.set_hash(name, val);
2159 return Ok(());
2160 }
2161 }
2162 self.frames[0].set_hash(name, val);
2163 Ok(())
2164 }
2165
2166 #[inline]
2167 pub fn get_hash_element(&self, name: &str, key: &str) -> PerlValue {
2168 if let Some(ah) = self.find_atomic_hash(name) {
2169 return ah.0.lock().get(key).cloned().unwrap_or(PerlValue::UNDEF);
2170 }
2171 if let Some(arc) = self.find_shared_hash(name) {
2172 return arc.read().get(key).cloned().unwrap_or(PerlValue::UNDEF);
2173 }
2174 for frame in self.frames.iter().rev() {
2175 if let Some(hash) = frame.get_hash(name) {
2176 return hash.get(key).cloned().unwrap_or(PerlValue::UNDEF);
2177 }
2178 }
2179 PerlValue::UNDEF
2180 }
2181
2182 pub fn atomic_hash_mutate(
2185 &mut self,
2186 name: &str,
2187 key: &str,
2188 f: impl FnOnce(&PerlValue) -> PerlValue,
2189 ) -> Result<PerlValue, PerlError> {
2190 if let Some(ah) = self.find_atomic_hash(name) {
2191 let mut guard = ah.0.lock();
2192 let old = guard.get(key).cloned().unwrap_or(PerlValue::UNDEF);
2193 let new_val = f(&old);
2194 guard.insert(key.to_string(), new_val.clone());
2195 return Ok(new_val);
2196 }
2197 let old = self.get_hash_element(name, key);
2199 let new_val = f(&old);
2200 self.set_hash_element(name, key, new_val.clone())?;
2201 Ok(new_val)
2202 }
2203
2204 pub fn atomic_array_mutate(
2206 &mut self,
2207 name: &str,
2208 index: i64,
2209 f: impl FnOnce(&PerlValue) -> PerlValue,
2210 ) -> Result<PerlValue, PerlError> {
2211 if let Some(aa) = self.find_atomic_array(name) {
2212 let mut guard = aa.0.lock();
2213 let idx = if index < 0 {
2214 (guard.len() as i64 + index).max(0) as usize
2215 } else {
2216 index as usize
2217 };
2218 if idx >= guard.len() {
2219 guard.resize(idx + 1, PerlValue::UNDEF);
2220 }
2221 let old = guard[idx].clone();
2222 let new_val = f(&old);
2223 guard[idx] = new_val.clone();
2224 return Ok(new_val);
2225 }
2226 let old = self.get_array_element(name, index);
2228 let new_val = f(&old);
2229 self.set_array_element(name, index, new_val.clone())?;
2230 Ok(new_val)
2231 }
2232
2233 pub fn set_hash_element(
2234 &mut self,
2235 name: &str,
2236 key: &str,
2237 val: PerlValue,
2238 ) -> Result<(), PerlError> {
2239 let val = self.resolve_container_binding_ref(val);
2240 if name == "SIG" {
2243 crate::perl_signal::install(key);
2244 }
2245 if let Some(ah) = self.find_atomic_hash(name) {
2246 ah.0.lock().insert(key.to_string(), val);
2247 return Ok(());
2248 }
2249 if let Some(arc) = self.find_shared_hash(name) {
2250 arc.write().insert(key.to_string(), val);
2251 return Ok(());
2252 }
2253 let hash = self.get_hash_mut(name)?;
2254 hash.insert(key.to_string(), val);
2255 Ok(())
2256 }
2257
2258 pub fn set_hash_int_times_range(
2262 &mut self,
2263 name: &str,
2264 start: i64,
2265 end: i64,
2266 k: i64,
2267 ) -> Result<(), PerlError> {
2268 if end <= start {
2269 return Ok(());
2270 }
2271 let count = (end - start) as usize;
2272 if let Some(ah) = self.find_atomic_hash(name) {
2273 let mut g = ah.0.lock();
2274 g.reserve(count);
2275 let mut buf = itoa::Buffer::new();
2276 for i in start..end {
2277 let key = buf.format(i).to_owned();
2278 g.insert(key, PerlValue::integer(i.wrapping_mul(k)));
2279 }
2280 return Ok(());
2281 }
2282 let hash = self.get_hash_mut(name)?;
2283 hash.reserve(count);
2284 let mut buf = itoa::Buffer::new();
2285 for i in start..end {
2286 let key = buf.format(i).to_owned();
2287 hash.insert(key, PerlValue::integer(i.wrapping_mul(k)));
2288 }
2289 Ok(())
2290 }
2291
2292 pub fn delete_hash_element(&mut self, name: &str, key: &str) -> Result<PerlValue, PerlError> {
2293 if let Some(ah) = self.find_atomic_hash(name) {
2294 return Ok(ah.0.lock().shift_remove(key).unwrap_or(PerlValue::UNDEF));
2295 }
2296 let hash = self.get_hash_mut(name)?;
2297 Ok(hash.shift_remove(key).unwrap_or(PerlValue::UNDEF))
2298 }
2299
2300 #[inline]
2301 pub fn exists_hash_element(&self, name: &str, key: &str) -> bool {
2302 if let Some(ah) = self.find_atomic_hash(name) {
2303 return ah.0.lock().contains_key(key);
2304 }
2305 for frame in self.frames.iter().rev() {
2306 if let Some(hash) = frame.get_hash(name) {
2307 return hash.contains_key(key);
2308 }
2309 }
2310 false
2311 }
2312
2313 #[inline]
2318 pub fn for_each_hash_value(&self, name: &str, mut visit: impl FnMut(&PerlValue)) {
2319 if let Some(ah) = self.find_atomic_hash(name) {
2320 let g = ah.0.lock();
2321 for v in g.values() {
2322 visit(v);
2323 }
2324 return;
2325 }
2326 for frame in self.frames.iter().rev() {
2327 if let Some(hash) = frame.get_hash(name) {
2328 for v in hash.values() {
2329 visit(v);
2330 }
2331 return;
2332 }
2333 }
2334 }
2335
2336 pub fn frames_for_introspection(&self) -> Vec<(Vec<&str>, Vec<&str>, Vec<&str>)> {
2342 self.frames
2343 .iter()
2344 .map(|f| {
2345 let mut scalars: Vec<&str> = f.scalars.iter().map(|(n, _)| n.as_str()).collect();
2346 scalars.extend(f.scalar_slot_names.iter().filter_map(|opt| match opt {
2348 Some(n) if !n.is_empty() => Some(n.as_str()),
2349 _ => None,
2350 }));
2351 let mut arrays: Vec<&str> = f.arrays.iter().map(|(n, _)| n.as_str()).collect();
2352 arrays.extend(f.atomic_arrays.iter().map(|(n, _)| n.as_str()));
2353 arrays.extend(f.shared_arrays.iter().map(|(n, _)| n.as_str()));
2354 let mut hashes: Vec<&str> = f.hashes.iter().map(|(n, _)| n.as_str()).collect();
2355 hashes.extend(f.atomic_hashes.iter().map(|(n, _)| n.as_str()));
2356 hashes.extend(f.shared_hashes.iter().map(|(n, _)| n.as_str()));
2357 scalars.sort_unstable();
2358 arrays.sort_unstable();
2359 hashes.sort_unstable();
2360 (scalars, arrays, hashes)
2361 })
2362 .collect()
2363 }
2364
2365 pub fn parameters_pairs(&self) -> Vec<(String, &'static str)> {
2371 let mut seen: HashSet<String> = HashSet::new();
2372 let mut out: Vec<(String, &'static str)> = Vec::new();
2373 for frame in self.frames.iter().rev() {
2376 for n in frame.scalar_slot_names.iter().flatten() {
2380 if !n.is_empty() {
2381 let s = format!("${}", n);
2382 if seen.insert(s.clone()) {
2383 out.push((s, "scalar"));
2384 }
2385 }
2386 }
2387 for (name, _) in &frame.scalars {
2388 let s = format!("${}", name);
2389 if seen.insert(s.clone()) {
2390 out.push((s, "scalar"));
2391 }
2392 }
2393 for (name, _) in &frame.arrays {
2394 let s = format!("@{}", name);
2395 if seen.insert(s.clone()) {
2396 out.push((s, "array"));
2397 }
2398 }
2399 for (name, _) in &frame.hashes {
2400 let s = format!("%{}", name);
2401 if seen.insert(s.clone()) {
2402 out.push((s, "hash"));
2403 }
2404 }
2405 for (name, _) in &frame.atomic_arrays {
2406 let s = format!("@{}", name);
2407 if seen.insert(s.clone()) {
2408 out.push((s, "atomic_array"));
2409 }
2410 }
2411 for (name, _) in &frame.atomic_hashes {
2412 let s = format!("%{}", name);
2413 if seen.insert(s.clone()) {
2414 out.push((s, "atomic_hash"));
2415 }
2416 }
2417 for (name, _) in &frame.shared_arrays {
2418 let s = format!("@{}", name);
2419 if seen.insert(s.clone()) {
2420 out.push((s, "shared_array"));
2421 }
2422 }
2423 for (name, _) in &frame.shared_hashes {
2424 let s = format!("%{}", name);
2425 if seen.insert(s.clone()) {
2426 out.push((s, "shared_hash"));
2427 }
2428 }
2429 }
2430 out.sort_by(|a, b| a.0.cmp(&b.0));
2431 out
2432 }
2433
2434 pub fn repl_binding_names(&self) -> Vec<String> {
2436 let mut seen = HashSet::new();
2437 let mut out = Vec::new();
2438 for frame in &self.frames {
2439 for (name, _) in &frame.scalars {
2440 let s = format!("${}", name);
2441 if seen.insert(s.clone()) {
2442 out.push(s);
2443 }
2444 }
2445 for (name, _) in &frame.arrays {
2446 let s = format!("@{}", name);
2447 if seen.insert(s.clone()) {
2448 out.push(s);
2449 }
2450 }
2451 for (name, _) in &frame.hashes {
2452 let s = format!("%{}", name);
2453 if seen.insert(s.clone()) {
2454 out.push(s);
2455 }
2456 }
2457 for (name, _) in &frame.atomic_arrays {
2458 let s = format!("@{}", name);
2459 if seen.insert(s.clone()) {
2460 out.push(s);
2461 }
2462 }
2463 for (name, _) in &frame.atomic_hashes {
2464 let s = format!("%{}", name);
2465 if seen.insert(s.clone()) {
2466 out.push(s);
2467 }
2468 }
2469 }
2470 out.sort();
2471 out
2472 }
2473
2474 pub fn capture(&mut self) -> Vec<(String, PerlValue)> {
2475 let by_ref = crate::compat_mode();
2488 let mut captured = Vec::new();
2489 let mut seen_hash_scalars: HashSet<String> = HashSet::new();
2504 for frame in self.frames.iter_mut().rev() {
2505 for (k, v) in &mut frame.scalars {
2506 if !seen_hash_scalars.insert(k.clone()) {
2507 continue;
2508 }
2509 if v.as_capture_cell().is_some() || v.as_scalar_ref().is_some() {
2510 captured.push((format!("${}", k), v.clone()));
2511 } else if v.is_simple_scalar() {
2512 let wrapped = PerlValue::capture_cell(Arc::new(RwLock::new(v.clone())));
2513 *v = wrapped.clone();
2514 captured.push((format!("${}", k), wrapped));
2515 } else {
2516 captured.push((format!("${}", k), v.clone()));
2517 }
2518 }
2519 }
2520 for frame in &mut self.frames {
2521 for (i, v) in frame.scalar_slots.iter_mut().enumerate() {
2529 if let Some(Some(name)) = frame.scalar_slot_names.get(i) {
2530 if !name.is_empty() && seen_hash_scalars.contains(name) {
2540 continue;
2541 }
2542 let cap_val = if v.as_capture_cell().is_some() || v.as_scalar_ref().is_some() {
2543 v.clone()
2544 } else {
2545 let wrapped = PerlValue::capture_cell(Arc::new(RwLock::new(v.clone())));
2546 if by_ref {
2547 *v = wrapped.clone();
2548 }
2549 wrapped
2550 };
2551 captured.push((format!("$slot:{}:{}", i, name), cap_val));
2552 }
2553 }
2554 for (k, v) in &frame.arrays {
2555 if capture_skip_bootstrap_array(k) {
2556 continue;
2557 }
2558 if frame.frozen_arrays.contains(k) {
2559 captured.push((format!("@frozen:{}", k), PerlValue::array(v.clone())));
2560 } else {
2561 captured.push((format!("@{}", k), PerlValue::array(v.clone())));
2562 }
2563 }
2564 for (k, v) in &frame.hashes {
2565 if capture_skip_bootstrap_hash(k) {
2566 continue;
2567 }
2568 if frame.frozen_hashes.contains(k) {
2569 captured.push((format!("%frozen:{}", k), PerlValue::hash(v.clone())));
2570 } else {
2571 captured.push((format!("%{}", k), PerlValue::hash(v.clone())));
2572 }
2573 }
2574 for (k, _aa) in &frame.atomic_arrays {
2575 captured.push((
2576 format!("@sync_{}", k),
2577 PerlValue::atomic(Arc::new(Mutex::new(PerlValue::string(String::new())))),
2578 ));
2579 }
2580 for (k, _ah) in &frame.atomic_hashes {
2581 captured.push((
2582 format!("%sync_{}", k),
2583 PerlValue::atomic(Arc::new(Mutex::new(PerlValue::string(String::new())))),
2584 ));
2585 }
2586 }
2587 captured
2588 }
2589
2590 pub fn capture_with_atomics(&self) -> ScopeCaptureWithAtomics {
2592 let mut scalars = Vec::new();
2593 let mut arrays = Vec::new();
2594 let mut hashes = Vec::new();
2595 for frame in &self.frames {
2596 for (k, v) in &frame.scalars {
2597 scalars.push((format!("${}", k), v.clone()));
2598 }
2599 for (i, v) in frame.scalar_slots.iter().enumerate() {
2600 if let Some(Some(name)) = frame.scalar_slot_names.get(i) {
2601 scalars.push((format!("$slot:{}:{}", i, name), v.clone()));
2602 }
2603 }
2604 for (k, v) in &frame.arrays {
2605 if capture_skip_bootstrap_array(k) {
2606 continue;
2607 }
2608 if frame.frozen_arrays.contains(k) {
2609 scalars.push((format!("@frozen:{}", k), PerlValue::array(v.clone())));
2610 } else {
2611 scalars.push((format!("@{}", k), PerlValue::array(v.clone())));
2612 }
2613 }
2614 for (k, v) in &frame.hashes {
2615 if capture_skip_bootstrap_hash(k) {
2616 continue;
2617 }
2618 if frame.frozen_hashes.contains(k) {
2619 scalars.push((format!("%frozen:{}", k), PerlValue::hash(v.clone())));
2620 } else {
2621 scalars.push((format!("%{}", k), PerlValue::hash(v.clone())));
2622 }
2623 }
2624 for (k, aa) in &frame.atomic_arrays {
2625 arrays.push((k.clone(), aa.clone()));
2626 }
2627 for (k, ah) in &frame.atomic_hashes {
2628 hashes.push((k.clone(), ah.clone()));
2629 }
2630 }
2631 (scalars, arrays, hashes)
2632 }
2633
2634 pub fn restore_capture(&mut self, captured: &[(String, PerlValue)]) {
2635 for (name, val) in captured {
2636 if let Some(rest) = name.strip_prefix("$slot:") {
2637 if let Some(colon) = rest.find(':') {
2643 let idx: usize = rest[..colon].parse().unwrap_or(0);
2644 let sname = &rest[colon + 1..];
2645 self.declare_scalar_slot(idx as u8, val.clone(), Some(sname));
2646 }
2647 } else if let Some(stripped) = name.strip_prefix('$') {
2648 self.declare_scalar(stripped, val.clone());
2649 if let Some(slot) = parse_positional_topic_slot(stripped) {
2656 if slot > self.max_active_slot {
2657 self.max_active_slot = slot;
2658 }
2659 }
2660 } else if let Some(rest) = name.strip_prefix("@frozen:") {
2661 let arr = val.as_array_vec().unwrap_or_else(|| val.to_list());
2662 self.declare_array_frozen(rest, arr, true);
2663 } else if let Some(rest) = name.strip_prefix("%frozen:") {
2664 if let Some(h) = val.as_hash_map() {
2665 self.declare_hash_frozen(rest, h.clone(), true);
2666 }
2667 } else if let Some(rest) = name.strip_prefix('@') {
2668 if rest.starts_with("sync_") {
2669 continue;
2670 }
2671 let arr = val.as_array_vec().unwrap_or_else(|| val.to_list());
2672 self.declare_array(rest, arr);
2673 } else if let Some(rest) = name.strip_prefix('%') {
2674 if rest.starts_with("sync_") {
2675 continue;
2676 }
2677 if let Some(h) = val.as_hash_map() {
2678 self.declare_hash(rest, h.clone());
2679 }
2680 }
2681 }
2682 }
2683
2684 pub fn restore_atomics(
2686 &mut self,
2687 arrays: &[(String, AtomicArray)],
2688 hashes: &[(String, AtomicHash)],
2689 ) {
2690 if let Some(frame) = self.frames.last_mut() {
2691 for (name, aa) in arrays {
2692 frame.atomic_arrays.push((name.clone(), aa.clone()));
2693 }
2694 for (name, ah) in hashes {
2695 frame.atomic_hashes.push((name.clone(), ah.clone()));
2696 }
2697 }
2698 }
2699}
2700
2701#[cfg(test)]
2702mod tests {
2703 use super::*;
2704 use crate::value::PerlValue;
2705
2706 #[test]
2707 fn missing_scalar_is_undef() {
2708 let s = Scope::new();
2709 assert!(s.get_scalar("not_declared").is_undef());
2710 }
2711
2712 #[test]
2713 fn inner_frame_shadows_outer_scalar() {
2714 let mut s = Scope::new();
2715 s.declare_scalar("a", PerlValue::integer(1));
2716 s.push_frame();
2717 s.declare_scalar("a", PerlValue::integer(2));
2718 assert_eq!(s.get_scalar("a").to_int(), 2);
2719 s.pop_frame();
2720 assert_eq!(s.get_scalar("a").to_int(), 1);
2721 }
2722
2723 #[test]
2724 fn set_scalar_updates_innermost_binding() {
2725 let mut s = Scope::new();
2726 s.declare_scalar("a", PerlValue::integer(1));
2727 s.push_frame();
2728 s.declare_scalar("a", PerlValue::integer(2));
2729 let _ = s.set_scalar("a", PerlValue::integer(99));
2730 assert_eq!(s.get_scalar("a").to_int(), 99);
2731 s.pop_frame();
2732 assert_eq!(s.get_scalar("a").to_int(), 1);
2733 }
2734
2735 #[test]
2736 fn array_negative_index_reads_from_end() {
2737 let mut s = Scope::new();
2738 s.declare_array(
2739 "a",
2740 vec![
2741 PerlValue::integer(10),
2742 PerlValue::integer(20),
2743 PerlValue::integer(30),
2744 ],
2745 );
2746 assert_eq!(s.get_array_element("a", -1).to_int(), 30);
2747 }
2748
2749 #[test]
2750 fn set_array_element_extends_array_with_undef_gaps() {
2751 let mut s = Scope::new();
2752 s.declare_array("a", vec![]);
2753 s.set_array_element("a", 2, PerlValue::integer(7)).unwrap();
2754 assert_eq!(s.get_array_element("a", 2).to_int(), 7);
2755 assert!(s.get_array_element("a", 0).is_undef());
2756 }
2757
2758 #[test]
2759 fn capture_restore_roundtrip_scalar() {
2760 let mut s = Scope::new();
2761 s.declare_scalar("n", PerlValue::integer(42));
2762 let cap = s.capture();
2763 let mut t = Scope::new();
2764 t.restore_capture(&cap);
2765 assert_eq!(t.get_scalar("n").to_int(), 42);
2766 }
2767
2768 #[test]
2769 fn capture_restore_roundtrip_lexical_array_and_hash() {
2770 let mut s = Scope::new();
2771 s.declare_array("a", vec![PerlValue::integer(1), PerlValue::integer(2)]);
2772 let mut m = IndexMap::new();
2773 m.insert("k".to_string(), PerlValue::integer(99));
2774 s.declare_hash("h", m);
2775 let cap = s.capture();
2776 let mut t = Scope::new();
2777 t.restore_capture(&cap);
2778 assert_eq!(t.get_array_element("a", 1).to_int(), 2);
2779 assert_eq!(t.get_hash_element("h", "k").to_int(), 99);
2780 }
2781
2782 #[test]
2783 fn hash_get_set_delete_exists() {
2784 let mut s = Scope::new();
2785 let mut m = IndexMap::new();
2786 m.insert("k".to_string(), PerlValue::integer(1));
2787 s.declare_hash("h", m);
2788 assert_eq!(s.get_hash_element("h", "k").to_int(), 1);
2789 assert!(s.exists_hash_element("h", "k"));
2790 s.set_hash_element("h", "k", PerlValue::integer(99))
2791 .unwrap();
2792 assert_eq!(s.get_hash_element("h", "k").to_int(), 99);
2793 let del = s.delete_hash_element("h", "k").unwrap();
2794 assert_eq!(del.to_int(), 99);
2795 assert!(!s.exists_hash_element("h", "k"));
2796 }
2797
2798 #[test]
2799 fn inner_frame_shadows_outer_hash_name() {
2800 let mut s = Scope::new();
2801 let mut outer = IndexMap::new();
2802 outer.insert("k".to_string(), PerlValue::integer(1));
2803 s.declare_hash("h", outer);
2804 s.push_frame();
2805 let mut inner = IndexMap::new();
2806 inner.insert("k".to_string(), PerlValue::integer(2));
2807 s.declare_hash("h", inner);
2808 assert_eq!(s.get_hash_element("h", "k").to_int(), 2);
2809 s.pop_frame();
2810 assert_eq!(s.get_hash_element("h", "k").to_int(), 1);
2811 }
2812
2813 #[test]
2814 fn inner_frame_shadows_outer_array_name() {
2815 let mut s = Scope::new();
2816 s.declare_array("a", vec![PerlValue::integer(1)]);
2817 s.push_frame();
2818 s.declare_array("a", vec![PerlValue::integer(2), PerlValue::integer(3)]);
2819 assert_eq!(s.get_array_element("a", 1).to_int(), 3);
2820 s.pop_frame();
2821 assert_eq!(s.get_array_element("a", 0).to_int(), 1);
2822 }
2823
2824 #[test]
2825 fn pop_frame_never_removes_global_frame() {
2826 let mut s = Scope::new();
2827 s.declare_scalar("x", PerlValue::integer(1));
2828 s.pop_frame();
2829 s.pop_frame();
2830 assert_eq!(s.get_scalar("x").to_int(), 1);
2831 }
2832
2833 #[test]
2834 fn empty_array_declared_has_zero_length() {
2835 let mut s = Scope::new();
2836 s.declare_array("a", vec![]);
2837 assert_eq!(s.get_array("a").len(), 0);
2838 }
2839
2840 #[test]
2841 fn depth_increments_with_push_frame() {
2842 let mut s = Scope::new();
2843 let d0 = s.depth();
2844 s.push_frame();
2845 assert_eq!(s.depth(), d0 + 1);
2846 s.pop_frame();
2847 assert_eq!(s.depth(), d0);
2848 }
2849
2850 #[test]
2851 fn pop_to_depth_unwinds_to_target() {
2852 let mut s = Scope::new();
2853 s.push_frame();
2854 s.push_frame();
2855 let target = s.depth() - 1;
2856 s.pop_to_depth(target);
2857 assert_eq!(s.depth(), target);
2858 }
2859
2860 #[test]
2861 fn array_len_and_push_pop_roundtrip() {
2862 let mut s = Scope::new();
2863 s.declare_array("a", vec![]);
2864 assert_eq!(s.array_len("a"), 0);
2865 s.push_to_array("a", PerlValue::integer(1)).unwrap();
2866 s.push_to_array("a", PerlValue::integer(2)).unwrap();
2867 assert_eq!(s.array_len("a"), 2);
2868 assert_eq!(s.pop_from_array("a").unwrap().to_int(), 2);
2869 assert_eq!(s.pop_from_array("a").unwrap().to_int(), 1);
2870 assert!(s.pop_from_array("a").unwrap().is_undef());
2871 }
2872
2873 #[test]
2874 fn shift_from_array_drops_front() {
2875 let mut s = Scope::new();
2876 s.declare_array("a", vec![PerlValue::integer(1), PerlValue::integer(2)]);
2877 assert_eq!(s.shift_from_array("a").unwrap().to_int(), 1);
2878 assert_eq!(s.array_len("a"), 1);
2879 }
2880
2881 #[test]
2882 fn atomic_mutate_increments_wrapped_scalar() {
2883 use parking_lot::Mutex;
2884 use std::sync::Arc;
2885 let mut s = Scope::new();
2886 s.declare_scalar(
2887 "n",
2888 PerlValue::atomic(Arc::new(Mutex::new(PerlValue::integer(10)))),
2889 );
2890 let v = s
2891 .atomic_mutate("n", |old| PerlValue::integer(old.to_int() + 5))
2892 .expect("atomic_mutate on atomic-backed scalar must not fail");
2893 assert_eq!(v.to_int(), 15);
2894 assert_eq!(s.get_scalar("n").to_int(), 15);
2895 }
2896
2897 #[test]
2898 fn atomic_mutate_post_returns_old_value() {
2899 use parking_lot::Mutex;
2900 use std::sync::Arc;
2901 let mut s = Scope::new();
2902 s.declare_scalar(
2903 "n",
2904 PerlValue::atomic(Arc::new(Mutex::new(PerlValue::integer(7)))),
2905 );
2906 let old = s
2907 .atomic_mutate_post("n", |v| PerlValue::integer(v.to_int() + 1))
2908 .expect("atomic_mutate_post on atomic-backed scalar must not fail");
2909 assert_eq!(old.to_int(), 7);
2910 assert_eq!(s.get_scalar("n").to_int(), 8);
2911 }
2912
2913 #[test]
2914 fn get_scalar_raw_keeps_atomic_wrapper() {
2915 use parking_lot::Mutex;
2916 use std::sync::Arc;
2917 let mut s = Scope::new();
2918 s.declare_scalar(
2919 "n",
2920 PerlValue::atomic(Arc::new(Mutex::new(PerlValue::integer(3)))),
2921 );
2922 assert!(s.get_scalar_raw("n").is_atomic());
2923 assert!(!s.get_scalar("n").is_atomic());
2924 }
2925
2926 #[test]
2927 fn missing_array_element_is_undef() {
2928 let mut s = Scope::new();
2929 s.declare_array("a", vec![PerlValue::integer(1)]);
2930 assert!(s.get_array_element("a", 99).is_undef());
2931 }
2932
2933 #[test]
2934 fn restore_atomics_puts_atomic_containers_in_frame() {
2935 use indexmap::IndexMap;
2936 use parking_lot::Mutex;
2937 use std::sync::Arc;
2938 let mut s = Scope::new();
2939 let aa = AtomicArray(Arc::new(Mutex::new(vec![PerlValue::integer(1)])));
2940 let ah = AtomicHash(Arc::new(Mutex::new(IndexMap::new())));
2941 s.restore_atomics(&[("ax".into(), aa.clone())], &[("hx".into(), ah.clone())]);
2942 assert_eq!(s.get_array_element("ax", 0).to_int(), 1);
2943 assert_eq!(s.array_len("ax"), 1);
2944 s.set_hash_element("hx", "k", PerlValue::integer(2))
2945 .unwrap();
2946 assert_eq!(s.get_hash_element("hx", "k").to_int(), 2);
2947 }
2948}