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]
252 fn get_array(&self, name: &str) -> Option<&Vec<PerlValue>> {
253 if name == "_" {
254 if let Some(ref v) = self.sub_underscore {
255 return Some(v);
256 }
257 }
258 self.arrays.iter().find(|(k, _)| k == name).map(|(_, v)| v)
259 }
260
261 #[inline]
262 fn has_array(&self, name: &str) -> bool {
263 if name == "_" && self.sub_underscore.is_some() {
264 return true;
265 }
266 self.arrays.iter().any(|(k, _)| k == name)
267 || self.shared_arrays.iter().any(|(k, _)| k == name)
268 }
269
270 #[inline]
271 fn get_array_mut(&mut self, name: &str) -> Option<&mut Vec<PerlValue>> {
272 if name == "_" {
273 return self.sub_underscore.as_mut();
274 }
275 self.arrays
276 .iter_mut()
277 .find(|(k, _)| k == name)
278 .map(|(_, v)| v)
279 }
280
281 #[inline]
282 fn set_array(&mut self, name: &str, val: Vec<PerlValue>) {
283 if name == "_" {
284 if let Some(pos) = self.arrays.iter().position(|(k, _)| k == name) {
285 self.arrays.swap_remove(pos);
286 }
287 self.sub_underscore = Some(val);
288 return;
289 }
290 if let Some(entry) = self.arrays.iter_mut().find(|(k, _)| k == name) {
291 entry.1 = val;
292 } else {
293 self.arrays.push((name.to_string(), val));
294 }
295 }
296
297 #[inline]
298 fn get_hash(&self, name: &str) -> Option<&IndexMap<String, PerlValue>> {
299 self.hashes.iter().find(|(k, _)| k == name).map(|(_, v)| v)
300 }
301
302 #[inline]
303 fn has_hash(&self, name: &str) -> bool {
304 self.hashes.iter().any(|(k, _)| k == name)
305 || self.shared_hashes.iter().any(|(k, _)| k == name)
306 }
307
308 #[inline]
309 fn get_hash_mut(&mut self, name: &str) -> Option<&mut IndexMap<String, PerlValue>> {
310 self.hashes
311 .iter_mut()
312 .find(|(k, _)| k == name)
313 .map(|(_, v)| v)
314 }
315
316 #[inline]
317 fn set_hash(&mut self, name: &str, val: IndexMap<String, PerlValue>) {
318 if let Some(entry) = self.hashes.iter_mut().find(|(k, _)| k == name) {
319 entry.1 = val;
320 } else {
321 self.hashes.push((name.to_string(), val));
322 }
323 }
324}
325
326#[derive(Debug, Clone)]
329pub struct Scope {
330 frames: Vec<Frame>,
331 frame_pool: Vec<Frame>,
333 parallel_guard: bool,
338 max_active_slot: usize,
345}
346
347impl Default for Scope {
348 fn default() -> Self {
349 Self::new()
350 }
351}
352
353impl Scope {
354 pub fn new() -> Self {
355 let mut s = Self {
356 frames: Vec::with_capacity(32),
357 frame_pool: Vec::with_capacity(32),
358 parallel_guard: false,
359 max_active_slot: 0,
360 };
361 s.frames.push(Frame::new());
362 s
363 }
364
365 #[inline]
367 pub fn set_parallel_guard(&mut self, enabled: bool) {
368 self.parallel_guard = enabled;
369 }
370
371 #[inline]
372 pub fn parallel_guard(&self) -> bool {
373 self.parallel_guard
374 }
375
376 #[inline]
377 fn parallel_skip_special_name(name: &str) -> bool {
378 name.contains("::")
379 }
380
381 #[inline]
383 fn parallel_allowed_topic_scalar(name: &str) -> bool {
384 matches!(name, "_" | "a" | "b")
385 }
386
387 #[inline]
389 fn parallel_allowed_internal_array(name: &str) -> bool {
390 matches!(name, "-" | "+" | "^CAPTURE" | "^CAPTURE_ALL")
391 }
392
393 #[inline]
395 fn parallel_allowed_internal_hash(name: &str) -> bool {
396 matches!(name, "+" | "-" | "ENV" | "INC")
397 }
398
399 fn check_parallel_scalar_write(&self, name: &str) -> Result<(), PerlError> {
400 if !self.parallel_guard || Self::parallel_skip_special_name(name) {
401 return Ok(());
402 }
403 if Self::parallel_allowed_topic_scalar(name) {
404 return Ok(());
405 }
406 if crate::special_vars::is_regex_match_scalar_name(name) {
407 return Ok(());
408 }
409 let inner = self.frames.len().saturating_sub(1);
410 for (i, frame) in self.frames.iter().enumerate().rev() {
411 if frame.has_scalar(name) {
412 if let Some(v) = frame.get_scalar(name) {
413 if v.as_atomic_arc().is_some() {
414 return Ok(());
415 }
416 }
417 if i != inner {
418 return Err(PerlError::runtime(
419 format!(
420 "cannot assign to captured non-mysync variable `${}` in a parallel block",
421 name
422 ),
423 0,
424 ));
425 }
426 return Ok(());
427 }
428 }
429 Err(PerlError::runtime(
430 format!(
431 "cannot assign to undeclared variable `${}` in a parallel block",
432 name
433 ),
434 0,
435 ))
436 }
437
438 #[inline]
439 pub fn depth(&self) -> usize {
440 self.frames.len()
441 }
442
443 #[inline]
446 pub fn pop_to_depth(&mut self, target_depth: usize) {
447 while self.frames.len() > target_depth && self.frames.len() > 1 {
448 self.pop_frame();
449 }
450 }
451
452 #[inline]
453 pub fn push_frame(&mut self) {
454 if let Some(mut frame) = self.frame_pool.pop() {
455 frame.clear_all_bindings();
456 self.frames.push(frame);
457 } else {
458 self.frames.push(Frame::new());
459 }
460 }
461
462 #[inline]
467 pub fn get_scalar_slot(&self, slot: u8) -> PerlValue {
468 let idx = slot as usize;
469 for frame in self.frames.iter().rev() {
470 if idx < frame.scalar_slots.len() && frame.owns_scalar_slot_index(idx) {
471 let val = &frame.scalar_slots[idx];
472 if let Some(arc) = val.as_capture_cell() {
475 return arc.read().clone();
476 }
477 return val.clone();
478 }
479 }
480 PerlValue::UNDEF
481 }
482
483 #[inline]
485 pub fn set_scalar_slot(&mut self, slot: u8, val: PerlValue) {
486 let idx = slot as usize;
487 let len = self.frames.len();
488 for i in (0..len).rev() {
489 if idx < self.frames[i].scalar_slots.len() && self.frames[i].owns_scalar_slot_index(idx)
490 {
491 if let Some(r) = self.frames[i].scalar_slots[idx].as_capture_cell() {
493 *r.write() = val;
494 } else {
495 self.frames[i].scalar_slots[idx] = val;
496 }
497 return;
498 }
499 }
500 let top = self.frames.last_mut().unwrap();
501 top.scalar_slots.resize(idx + 1, PerlValue::UNDEF);
502 if idx >= top.scalar_slot_names.len() {
503 top.scalar_slot_names.resize(idx + 1, None);
504 }
505 top.scalar_slot_names[idx] = Some(String::new());
506 top.scalar_slots[idx] = val;
507 }
508
509 #[inline]
513 pub fn set_scalar_slot_checked(
514 &mut self,
515 slot: u8,
516 val: PerlValue,
517 slot_name: Option<&str>,
518 ) -> Result<(), PerlError> {
519 if self.parallel_guard {
520 let idx = slot as usize;
521 let len = self.frames.len();
522 let top_has = idx < self.frames[len - 1].scalar_slots.len()
523 && self.frames[len - 1].owns_scalar_slot_index(idx);
524 if !top_has {
525 let name_owned: String = {
526 let mut found = String::new();
527 for i in (0..len).rev() {
528 if let Some(Some(n)) = self.frames[i].scalar_slot_names.get(idx) {
529 found = n.clone();
530 break;
531 }
532 }
533 if found.is_empty() {
534 if let Some(sn) = slot_name {
535 found = sn.to_string();
536 }
537 }
538 found
539 };
540 let name = name_owned.as_str();
541 if !name.is_empty() && !Self::parallel_allowed_topic_scalar(name) {
542 let inner = len.saturating_sub(1);
543 for (fi, frame) in self.frames.iter().enumerate().rev() {
544 if frame.has_scalar(name)
545 || (idx < frame.scalar_slots.len() && frame.owns_scalar_slot_index(idx))
546 {
547 if fi != inner {
548 return Err(PerlError::runtime(
549 format!(
550 "cannot assign to captured outer lexical `${}` inside a parallel block (use `mysync`)",
551 name
552 ),
553 0,
554 ));
555 }
556 break;
557 }
558 }
559 }
560 }
561 }
562 self.set_scalar_slot(slot, val);
563 Ok(())
564 }
565
566 #[inline]
570 pub fn declare_scalar_slot(&mut self, slot: u8, val: PerlValue, name: Option<&str>) {
571 let idx = slot as usize;
572 let frame = self.frames.last_mut().unwrap();
573 if idx >= frame.scalar_slots.len() {
574 frame.scalar_slots.resize(idx + 1, PerlValue::UNDEF);
575 }
576 frame.scalar_slots[idx] = val;
577 if idx >= frame.scalar_slot_names.len() {
578 frame.scalar_slot_names.resize(idx + 1, None);
579 }
580 match name {
581 Some(n) => frame.scalar_slot_names[idx] = Some(n.to_string()),
582 None => frame.scalar_slot_names[idx] = Some(String::new()),
584 }
585 }
586
587 #[inline]
598 pub fn scalar_slot_concat_repeat_inplace(&mut self, slot: u8, rhs: &str, n: usize) -> bool {
599 let idx = slot as usize;
600 let len = self.frames.len();
601 let fi = {
602 let mut found = len - 1;
603 if idx >= self.frames[found].scalar_slots.len()
604 || !self.frames[found].owns_scalar_slot_index(idx)
605 {
606 for i in (0..len - 1).rev() {
607 if idx < self.frames[i].scalar_slots.len()
608 && self.frames[i].owns_scalar_slot_index(idx)
609 {
610 found = i;
611 break;
612 }
613 }
614 }
615 found
616 };
617 let frame = &mut self.frames[fi];
618 if idx >= frame.scalar_slots.len() {
619 frame.scalar_slots.resize(idx + 1, PerlValue::UNDEF);
620 }
621 frame.scalar_slots[idx].try_concat_repeat_inplace(rhs, n)
622 }
623
624 #[inline]
629 pub fn scalar_slot_concat_repeat_slow(&mut self, slot: u8, rhs: &str, n: usize) {
630 let pv = PerlValue::string(rhs.to_owned());
631 for _ in 0..n {
632 let _ = self.scalar_slot_concat_inplace(slot, &pv);
633 }
634 }
635
636 #[inline]
637 pub fn scalar_slot_concat_inplace(&mut self, slot: u8, rhs: &PerlValue) -> PerlValue {
638 let idx = slot as usize;
639 let len = self.frames.len();
640 let fi = {
641 let mut found = len - 1;
642 if idx >= self.frames[found].scalar_slots.len()
643 || !self.frames[found].owns_scalar_slot_index(idx)
644 {
645 for i in (0..len - 1).rev() {
646 if idx < self.frames[i].scalar_slots.len()
647 && self.frames[i].owns_scalar_slot_index(idx)
648 {
649 found = i;
650 break;
651 }
652 }
653 }
654 found
655 };
656 let frame = &mut self.frames[fi];
657 if idx >= frame.scalar_slots.len() {
658 frame.scalar_slots.resize(idx + 1, PerlValue::UNDEF);
659 }
660 if frame.scalar_slots[idx].try_concat_append_inplace(rhs) {
668 return frame.scalar_slots[idx].shallow_clone();
669 }
670 let new_val = std::mem::replace(&mut frame.scalar_slots[idx], PerlValue::UNDEF)
671 .concat_append_owned(rhs);
672 let handle = new_val.shallow_clone();
673 frame.scalar_slots[idx] = new_val;
674 handle
675 }
676
677 #[inline]
678 pub(crate) fn can_pop_frame(&self) -> bool {
679 self.frames.len() > 1
680 }
681
682 #[inline]
683 pub fn pop_frame(&mut self) {
684 if self.frames.len() > 1 {
685 let mut frame = self.frames.pop().expect("pop_frame");
686 let saved_guard = self.parallel_guard;
689 self.parallel_guard = false;
690 for entry in frame.local_restores.drain(..).rev() {
691 match entry {
692 LocalRestore::Scalar(name, old) => {
693 let _ = self.set_scalar(&name, old);
694 }
695 LocalRestore::Array(name, old) => {
696 let _ = self.set_array(&name, old);
697 }
698 LocalRestore::Hash(name, old) => {
699 let _ = self.set_hash(&name, old);
700 }
701 LocalRestore::HashElement(name, key, old) => match old {
702 Some(v) => {
703 let _ = self.set_hash_element(&name, &key, v);
704 }
705 None => {
706 let _ = self.delete_hash_element(&name, &key);
707 }
708 },
709 LocalRestore::ArrayElement(name, index, old) => {
710 let _ = self.set_array_element(&name, index, old);
711 }
712 }
713 }
714 self.parallel_guard = saved_guard;
715 frame.clear_all_bindings();
716 if self.frame_pool.len() < 64 {
718 self.frame_pool.push(frame);
719 }
720 }
721 }
722
723 pub fn local_set_scalar(&mut self, name: &str, val: PerlValue) -> Result<(), PerlError> {
725 let old = self.get_scalar(name);
726 if let Some(frame) = self.frames.last_mut() {
727 frame
728 .local_restores
729 .push(LocalRestore::Scalar(name.to_string(), old));
730 }
731 self.set_scalar(name, val)
732 }
733
734 pub fn local_set_array(&mut self, name: &str, val: Vec<PerlValue>) -> Result<(), PerlError> {
736 if self.find_atomic_array(name).is_some() {
737 return Err(PerlError::runtime(
738 "local cannot be used on mysync arrays",
739 0,
740 ));
741 }
742 let old = self.get_array(name);
743 if let Some(frame) = self.frames.last_mut() {
744 frame
745 .local_restores
746 .push(LocalRestore::Array(name.to_string(), old));
747 }
748 self.set_array(name, val)?;
749 Ok(())
750 }
751
752 pub fn local_set_hash(
754 &mut self,
755 name: &str,
756 val: IndexMap<String, PerlValue>,
757 ) -> Result<(), PerlError> {
758 if self.find_atomic_hash(name).is_some() {
759 return Err(PerlError::runtime(
760 "local cannot be used on mysync hashes",
761 0,
762 ));
763 }
764 let old = self.get_hash(name);
765 if let Some(frame) = self.frames.last_mut() {
766 frame
767 .local_restores
768 .push(LocalRestore::Hash(name.to_string(), old));
769 }
770 self.set_hash(name, val)?;
771 Ok(())
772 }
773
774 pub fn local_set_hash_element(
776 &mut self,
777 name: &str,
778 key: &str,
779 val: PerlValue,
780 ) -> Result<(), PerlError> {
781 if self.find_atomic_hash(name).is_some() {
782 return Err(PerlError::runtime(
783 "local cannot be used on mysync hash elements",
784 0,
785 ));
786 }
787 let old = if self.exists_hash_element(name, key) {
788 Some(self.get_hash_element(name, key))
789 } else {
790 None
791 };
792 if let Some(frame) = self.frames.last_mut() {
793 frame.local_restores.push(LocalRestore::HashElement(
794 name.to_string(),
795 key.to_string(),
796 old,
797 ));
798 }
799 self.set_hash_element(name, key, val)?;
800 Ok(())
801 }
802
803 pub fn local_set_array_element(
806 &mut self,
807 name: &str,
808 index: i64,
809 val: PerlValue,
810 ) -> Result<(), PerlError> {
811 if self.find_atomic_array(name).is_some() {
812 return Err(PerlError::runtime(
813 "local cannot be used on mysync array elements",
814 0,
815 ));
816 }
817 let old = self.get_array_element(name, index);
818 if let Some(frame) = self.frames.last_mut() {
819 frame
820 .local_restores
821 .push(LocalRestore::ArrayElement(name.to_string(), index, old));
822 }
823 self.set_array_element(name, index, val)?;
824 Ok(())
825 }
826
827 #[inline]
830 pub fn declare_scalar(&mut self, name: &str, val: PerlValue) {
831 let _ = self.declare_scalar_frozen(name, val, false, None);
832 }
833
834 pub fn declare_scalar_frozen(
837 &mut self,
838 name: &str,
839 val: PerlValue,
840 frozen: bool,
841 ty: Option<PerlTypeName>,
842 ) -> Result<(), PerlError> {
843 if let Some(ref t) = ty {
844 t.check_value(&val)
845 .map_err(|msg| PerlError::type_error(format!("`${}`: {}", name, msg), 0))?;
846 }
847 if let Some(frame) = self.frames.last_mut() {
848 frame.set_scalar(name, val);
849 if frozen {
850 frame.frozen_scalars.insert(name.to_string());
851 }
852 if let Some(t) = ty {
853 frame.typed_scalars.insert(name.to_string(), t);
854 }
855 }
856 Ok(())
857 }
858
859 pub fn is_scalar_frozen(&self, name: &str) -> bool {
861 for frame in self.frames.iter().rev() {
862 if frame.has_scalar(name) {
863 return frame.frozen_scalars.contains(name);
864 }
865 }
866 false
867 }
868
869 pub fn is_array_frozen(&self, name: &str) -> bool {
871 for frame in self.frames.iter().rev() {
872 if frame.has_array(name) {
873 return frame.frozen_arrays.contains(name);
874 }
875 }
876 false
877 }
878
879 pub fn is_hash_frozen(&self, name: &str) -> bool {
881 for frame in self.frames.iter().rev() {
882 if frame.has_hash(name) {
883 return frame.frozen_hashes.contains(name);
884 }
885 }
886 false
887 }
888
889 pub fn check_frozen(&self, sigil: &str, name: &str) -> Option<&'static str> {
891 match sigil {
892 "$" => {
893 if self.is_scalar_frozen(name) {
894 Some("scalar")
895 } else {
896 None
897 }
898 }
899 "@" => {
900 if self.is_array_frozen(name) {
901 Some("array")
902 } else {
903 None
904 }
905 }
906 "%" => {
907 if self.is_hash_frozen(name) {
908 Some("hash")
909 } else {
910 None
911 }
912 }
913 _ => None,
914 }
915 }
916
917 #[inline]
918 pub fn get_scalar(&self, name: &str) -> PerlValue {
919 for frame in self.frames.iter().rev() {
920 if let Some(val) = frame.get_scalar(name) {
921 if let Some(arc) = val.as_atomic_arc() {
923 return arc.lock().clone();
924 }
925 if let Some(arc) = val.as_capture_cell() {
928 return arc.read().clone();
929 }
930 if val.is_undef() && Self::is_topic_chain_name(name) {
940 return self.get_scalar("_");
941 }
942 return val.clone();
943 }
944 }
945 PerlValue::UNDEF
946 }
947
948 #[inline]
951 fn is_topic_chain_name(name: &str) -> bool {
952 let bytes = name.as_bytes();
953 if bytes.is_empty() || bytes[0] != b'_' {
954 return false;
955 }
956 let mut i = 1;
957 while i < bytes.len() && bytes[i].is_ascii_digit() {
958 i += 1;
959 }
960 if i >= bytes.len() || bytes[i] != b'<' {
961 return false;
962 }
963 while i < bytes.len() && bytes[i] == b'<' {
964 i += 1;
965 }
966 i == bytes.len()
967 }
968
969 #[inline]
971 pub fn scalar_binding_exists(&self, name: &str) -> bool {
972 for frame in self.frames.iter().rev() {
973 if frame.has_scalar(name) {
974 return true;
975 }
976 }
977 false
978 }
979
980 pub fn all_scalar_names(&self) -> Vec<String> {
982 let mut names = Vec::new();
983 for frame in &self.frames {
984 for (name, _) in &frame.scalars {
985 if !names.contains(name) {
986 names.push(name.clone());
987 }
988 }
989 for name in frame.scalar_slot_names.iter().flatten() {
990 if !names.contains(name) {
991 names.push(name.clone());
992 }
993 }
994 }
995 names
996 }
997
998 #[inline]
1000 pub fn array_binding_exists(&self, name: &str) -> bool {
1001 if self.find_atomic_array(name).is_some() {
1002 return true;
1003 }
1004 for frame in self.frames.iter().rev() {
1005 if frame.has_array(name) {
1006 return true;
1007 }
1008 }
1009 false
1010 }
1011
1012 #[inline]
1014 pub fn hash_binding_exists(&self, name: &str) -> bool {
1015 if self.find_atomic_hash(name).is_some() {
1016 return true;
1017 }
1018 for frame in self.frames.iter().rev() {
1019 if frame.has_hash(name) {
1020 return true;
1021 }
1022 }
1023 false
1024 }
1025
1026 #[inline]
1029 pub fn get_scalar_raw(&self, name: &str) -> PerlValue {
1030 for frame in self.frames.iter().rev() {
1031 if let Some(val) = frame.get_scalar(name) {
1032 return val.clone();
1033 }
1034 }
1035 PerlValue::UNDEF
1036 }
1037
1038 pub fn atomic_mutate(
1042 &mut self,
1043 name: &str,
1044 f: impl FnOnce(&PerlValue) -> PerlValue,
1045 ) -> PerlValue {
1046 for frame in self.frames.iter().rev() {
1047 if let Some(v) = frame.get_scalar(name) {
1048 if let Some(arc) = v.as_atomic_arc() {
1049 let mut guard = arc.lock();
1050 let old = guard.clone();
1051 let new_val = f(&guard);
1052 *guard = new_val.clone();
1053 crate::parallel_trace::emit_scalar_mutation(name, &old, &new_val);
1054 return new_val;
1055 }
1056 }
1057 }
1058 let old = self.get_scalar(name);
1060 let new_val = f(&old);
1061 let _ = self.set_scalar(name, new_val.clone());
1062 new_val
1063 }
1064
1065 pub fn atomic_mutate_post(
1067 &mut self,
1068 name: &str,
1069 f: impl FnOnce(&PerlValue) -> PerlValue,
1070 ) -> PerlValue {
1071 for frame in self.frames.iter().rev() {
1072 if let Some(v) = frame.get_scalar(name) {
1073 if let Some(arc) = v.as_atomic_arc() {
1074 let mut guard = arc.lock();
1075 let old = guard.clone();
1076 let new_val = f(&old);
1077 *guard = new_val.clone();
1078 crate::parallel_trace::emit_scalar_mutation(name, &old, &new_val);
1079 return old;
1080 }
1081 }
1082 }
1083 let old = self.get_scalar(name);
1085 let _ = self.set_scalar(name, f(&old));
1086 old
1087 }
1088
1089 #[inline]
1096 pub fn scalar_concat_inplace(
1097 &mut self,
1098 name: &str,
1099 rhs: &PerlValue,
1100 ) -> Result<PerlValue, PerlError> {
1101 self.check_parallel_scalar_write(name)?;
1102 for frame in self.frames.iter_mut().rev() {
1103 if let Some(entry) = frame.scalars.iter_mut().find(|(k, _)| k == name) {
1104 if let Some(atomic_arc) = entry.1.as_atomic_arc() {
1107 let mut guard = atomic_arc.lock();
1108 let inner = std::mem::replace(&mut *guard, PerlValue::UNDEF);
1109 let new_val = inner.concat_append_owned(rhs);
1110 *guard = new_val.shallow_clone();
1111 return Ok(new_val);
1112 }
1113 if entry.1.try_concat_append_inplace(rhs) {
1116 return Ok(entry.1.shallow_clone());
1117 }
1118 let new_val =
1121 std::mem::replace(&mut entry.1, PerlValue::UNDEF).concat_append_owned(rhs);
1122 entry.1 = new_val.shallow_clone();
1123 return Ok(new_val);
1124 }
1125 }
1126 let val = PerlValue::UNDEF.concat_append_owned(rhs);
1128 self.frames[0].set_scalar(name, val.shallow_clone());
1129 Ok(val)
1130 }
1131
1132 #[inline]
1133 pub fn set_scalar(&mut self, name: &str, val: PerlValue) -> Result<(), PerlError> {
1134 self.check_parallel_scalar_write(name)?;
1135 for frame in self.frames.iter_mut().rev() {
1136 if let Some(v) = frame.get_scalar(name) {
1138 if let Some(arc) = v.as_atomic_arc() {
1139 let mut guard = arc.lock();
1140 let old = guard.clone();
1141 *guard = val.clone();
1142 crate::parallel_trace::emit_scalar_mutation(name, &old, &val);
1143 return Ok(());
1144 }
1145 if let Some(arc) = v.as_capture_cell() {
1147 *arc.write() = val;
1148 return Ok(());
1149 }
1150 }
1151 if frame.has_scalar(name) {
1152 if let Some(ty) = frame.typed_scalars.get(name) {
1153 ty.check_value(&val)
1154 .map_err(|msg| PerlError::type_error(format!("`${}`: {}", name, msg), 0))?;
1155 }
1156 frame.set_scalar(name, val);
1157 return Ok(());
1158 }
1159 }
1160 self.frames[0].set_scalar(name, val);
1161 Ok(())
1162 }
1163
1164 #[inline]
1170 fn topic_slot_key(slot: usize, level: usize) -> String {
1171 debug_assert!(level <= 4);
1172 if slot == 0 {
1173 if level == 0 {
1174 "_".to_string()
1175 } else {
1176 format!("_{}", "<".repeat(level))
1177 }
1178 } else if level == 0 {
1179 format!("_{}", slot)
1180 } else {
1181 format!("_{}{}", slot, "<".repeat(level))
1182 }
1183 }
1184
1185 #[inline]
1188 fn topic_slot_alias_key(slot: usize, level: usize) -> Option<String> {
1189 if slot != 0 {
1190 return None;
1191 }
1192 Some(if level == 0 {
1193 "_0".to_string()
1194 } else {
1195 format!("_0{}", "<".repeat(level))
1196 })
1197 }
1198
1199 #[inline]
1205 fn declare_topic_slot(&mut self, slot: usize, level: usize, val: PerlValue) {
1206 self.declare_scalar(&Self::topic_slot_key(slot, level), val.clone());
1207 if let Some(alias) = Self::topic_slot_alias_key(slot, level) {
1208 self.declare_scalar(&alias, val);
1209 }
1210 }
1211
1212 #[inline]
1227 pub fn set_topic(&mut self, val: PerlValue) {
1228 let already_shifted = self
1236 .frames
1237 .last()
1238 .map(|f| f.set_topic_called)
1239 .unwrap_or(false);
1240 if already_shifted {
1241 self.declare_topic_slot(0, 0, val);
1242 for slot in 1..=self.max_active_slot {
1243 self.declare_topic_slot(slot, 0, PerlValue::UNDEF);
1244 }
1245 return;
1246 }
1247 if let Some(frame) = self.frames.last_mut() {
1248 frame.set_topic_called = true;
1249 }
1250 self.shift_slot_chain(0, val);
1251 for slot in 1..=self.max_active_slot {
1252 self.shift_slot_chain(slot, PerlValue::UNDEF);
1253 }
1254 }
1255
1256 #[inline]
1268 pub fn set_closure_args(&mut self, args: &[PerlValue]) {
1269 let n = args.len();
1270 if n == 0 {
1271 return;
1272 }
1273 let high = n.saturating_sub(1).max(self.max_active_slot);
1274 for slot in 0..=high {
1275 let val = args.get(slot).cloned().unwrap_or(PerlValue::UNDEF);
1276 self.shift_slot_chain(slot, val);
1277 }
1278 if n > 0 && n - 1 > self.max_active_slot {
1279 self.max_active_slot = n - 1;
1280 }
1281 }
1282
1283 #[inline]
1292 fn shift_slot_chain(&mut self, slot: usize, val: PerlValue) {
1293 let l3 = self.get_scalar(&Self::topic_slot_key(slot, 3));
1294 let l2 = self.get_scalar(&Self::topic_slot_key(slot, 2));
1295 let l1 = self.get_scalar(&Self::topic_slot_key(slot, 1));
1296 let cur = self.get_scalar(&Self::topic_slot_key(slot, 0));
1297
1298 self.declare_topic_slot(slot, 0, val);
1299 self.declare_topic_slot(slot, 1, cur);
1300 self.declare_topic_slot(slot, 2, l1);
1301 self.declare_topic_slot(slot, 3, l2);
1302 self.declare_topic_slot(slot, 4, l3);
1303 }
1304
1305 #[inline]
1313 pub fn set_sort_pair(&mut self, a: PerlValue, b: PerlValue) {
1314 let _ = self.set_scalar("a", a.clone());
1315 let _ = self.set_scalar("b", b.clone());
1316 let _ = self.set_scalar("_0", a);
1317 let _ = self.set_scalar("_1", b);
1318 }
1319
1320 #[inline]
1322 pub fn push_defer(&mut self, coderef: PerlValue) {
1323 if let Some(frame) = self.frames.last_mut() {
1324 frame.defers.push(coderef);
1325 }
1326 }
1327
1328 #[inline]
1331 pub fn take_defers(&mut self) -> Vec<PerlValue> {
1332 if let Some(frame) = self.frames.last_mut() {
1333 let mut defers = std::mem::take(&mut frame.defers);
1334 defers.reverse();
1335 defers
1336 } else {
1337 Vec::new()
1338 }
1339 }
1340
1341 pub fn declare_atomic_array(&mut self, name: &str, val: Vec<PerlValue>) {
1344 if let Some(frame) = self.frames.last_mut() {
1345 frame
1346 .atomic_arrays
1347 .push((name.to_string(), AtomicArray(Arc::new(Mutex::new(val)))));
1348 }
1349 }
1350
1351 pub fn declare_atomic_hash(&mut self, name: &str, val: IndexMap<String, PerlValue>) {
1352 if let Some(frame) = self.frames.last_mut() {
1353 frame
1354 .atomic_hashes
1355 .push((name.to_string(), AtomicHash(Arc::new(Mutex::new(val)))));
1356 }
1357 }
1358
1359 fn find_atomic_array(&self, name: &str) -> Option<&AtomicArray> {
1361 for frame in self.frames.iter().rev() {
1362 if let Some(aa) = frame.atomic_arrays.iter().find(|(k, _)| k == name) {
1363 return Some(&aa.1);
1364 }
1365 }
1366 None
1367 }
1368
1369 fn find_atomic_hash(&self, name: &str) -> Option<&AtomicHash> {
1371 for frame in self.frames.iter().rev() {
1372 if let Some(ah) = frame.atomic_hashes.iter().find(|(k, _)| k == name) {
1373 return Some(&ah.1);
1374 }
1375 }
1376 None
1377 }
1378
1379 #[inline]
1384 pub fn take_sub_underscore(&mut self) -> Option<Vec<PerlValue>> {
1385 self.frames.last_mut()?.sub_underscore.take()
1386 }
1387
1388 pub fn declare_array(&mut self, name: &str, val: Vec<PerlValue>) {
1389 self.declare_array_frozen(name, val, false);
1390 }
1391
1392 pub fn declare_array_frozen(&mut self, name: &str, val: Vec<PerlValue>, frozen: bool) {
1393 let idx = if name.contains("::") {
1396 0
1397 } else {
1398 self.frames.len().saturating_sub(1)
1399 };
1400 if let Some(frame) = self.frames.get_mut(idx) {
1401 frame.shared_arrays.retain(|(k, _)| k != name);
1403 frame.set_array(name, val);
1404 if frozen {
1405 frame.frozen_arrays.insert(name.to_string());
1406 } else {
1407 frame.frozen_arrays.remove(name);
1409 }
1410 }
1411 }
1412
1413 pub fn get_array(&self, name: &str) -> Vec<PerlValue> {
1414 if let Some(aa) = self.find_atomic_array(name) {
1416 return aa.0.lock().clone();
1417 }
1418 if let Some(arc) = self.find_shared_array(name) {
1420 return arc.read().clone();
1421 }
1422 if name.contains("::") {
1423 if let Some(f) = self.frames.first() {
1424 if let Some(val) = f.get_array(name) {
1425 return val.clone();
1426 }
1427 }
1428 return Vec::new();
1429 }
1430 for frame in self.frames.iter().rev() {
1431 if let Some(val) = frame.get_array(name) {
1432 return val.clone();
1433 }
1434 }
1435 Vec::new()
1436 }
1437
1438 #[inline]
1441 pub fn get_array_borrow(&self, name: &str) -> Option<&[PerlValue]> {
1442 if self.find_atomic_array(name).is_some() {
1443 return None;
1444 }
1445 if name.contains("::") {
1446 return self
1447 .frames
1448 .first()
1449 .and_then(|f| f.get_array(name))
1450 .map(|v| v.as_slice());
1451 }
1452 for frame in self.frames.iter().rev() {
1453 if let Some(val) = frame.get_array(name) {
1454 return Some(val.as_slice());
1455 }
1456 }
1457 None
1458 }
1459
1460 fn resolve_array_frame_idx(&self, name: &str) -> Option<usize> {
1461 if name.contains("::") {
1462 return Some(0);
1463 }
1464 (0..self.frames.len())
1465 .rev()
1466 .find(|&i| self.frames[i].has_array(name))
1467 }
1468
1469 fn check_parallel_array_write(&self, name: &str) -> Result<(), PerlError> {
1470 if !self.parallel_guard
1471 || Self::parallel_skip_special_name(name)
1472 || Self::parallel_allowed_internal_array(name)
1473 {
1474 return Ok(());
1475 }
1476 let inner = self.frames.len().saturating_sub(1);
1477 match self.resolve_array_frame_idx(name) {
1478 None => Err(PerlError::runtime(
1479 format!(
1480 "cannot modify undeclared array `@{}` in a parallel block",
1481 name
1482 ),
1483 0,
1484 )),
1485 Some(idx) if idx != inner => Err(PerlError::runtime(
1486 format!(
1487 "cannot modify captured non-mysync array `@{}` in a parallel block",
1488 name
1489 ),
1490 0,
1491 )),
1492 Some(_) => Ok(()),
1493 }
1494 }
1495
1496 #[inline]
1501 pub fn resolve_container_binding_ref(&self, val: PerlValue) -> PerlValue {
1502 if let Some(name) = val.as_array_binding_name() {
1503 let data = self.get_array(&name);
1504 return PerlValue::array_ref(Arc::new(parking_lot::RwLock::new(data)));
1505 }
1506 if let Some(name) = val.as_hash_binding_name() {
1507 let data = self.get_hash(&name);
1508 return PerlValue::hash_ref(Arc::new(parking_lot::RwLock::new(data)));
1509 }
1510 val
1511 }
1512
1513 pub fn promote_array_to_shared(
1517 &mut self,
1518 name: &str,
1519 ) -> Arc<parking_lot::RwLock<Vec<PerlValue>>> {
1520 if let Some(aa) = self.find_atomic_array(name) {
1523 let data = aa.0.lock().clone();
1524 return Arc::new(parking_lot::RwLock::new(data));
1525 }
1526 let idx = self.resolve_array_frame_idx(name).unwrap_or_default();
1528 let frame = &mut self.frames[idx];
1529 if let Some(entry) = frame.shared_arrays.iter().find(|(k, _)| k == name) {
1530 return Arc::clone(&entry.1);
1531 }
1532 let data = if let Some(pos) = frame.arrays.iter().position(|(k, _)| k == name) {
1534 frame.arrays.swap_remove(pos).1
1535 } else if name == "_" {
1536 frame.sub_underscore.take().unwrap_or_default()
1537 } else {
1538 Vec::new()
1539 };
1540 let arc = Arc::new(parking_lot::RwLock::new(data));
1541 frame
1542 .shared_arrays
1543 .push((name.to_string(), Arc::clone(&arc)));
1544 arc
1545 }
1546
1547 pub fn promote_hash_to_shared(
1550 &mut self,
1551 name: &str,
1552 ) -> Arc<parking_lot::RwLock<IndexMap<String, PerlValue>>> {
1553 let idx = self.resolve_hash_frame_idx(name).unwrap_or_default();
1554 let frame = &mut self.frames[idx];
1555 if let Some(entry) = frame.shared_hashes.iter().find(|(k, _)| k == name) {
1556 return Arc::clone(&entry.1);
1557 }
1558 let data = if let Some(pos) = frame.hashes.iter().position(|(k, _)| k == name) {
1559 frame.hashes.swap_remove(pos).1
1560 } else {
1561 IndexMap::new()
1562 };
1563 let arc = Arc::new(parking_lot::RwLock::new(data));
1564 frame
1565 .shared_hashes
1566 .push((name.to_string(), Arc::clone(&arc)));
1567 arc
1568 }
1569
1570 fn find_shared_array(&self, name: &str) -> Option<Arc<parking_lot::RwLock<Vec<PerlValue>>>> {
1572 for frame in self.frames.iter().rev() {
1573 if let Some(entry) = frame.shared_arrays.iter().find(|(k, _)| k == name) {
1574 return Some(Arc::clone(&entry.1));
1575 }
1576 if frame.arrays.iter().any(|(k, _)| k == name) {
1578 return None;
1579 }
1580 }
1581 None
1582 }
1583
1584 fn find_shared_hash(
1586 &self,
1587 name: &str,
1588 ) -> Option<Arc<parking_lot::RwLock<IndexMap<String, PerlValue>>>> {
1589 for frame in self.frames.iter().rev() {
1590 if let Some(entry) = frame.shared_hashes.iter().find(|(k, _)| k == name) {
1591 return Some(Arc::clone(&entry.1));
1592 }
1593 if frame.hashes.iter().any(|(k, _)| k == name) {
1594 return None;
1595 }
1596 }
1597 None
1598 }
1599
1600 pub fn get_array_mut(&mut self, name: &str) -> Result<&mut Vec<PerlValue>, PerlError> {
1601 if self.find_atomic_array(name).is_some() {
1604 return Err(PerlError::runtime(
1605 "get_array_mut: use atomic path for mysync arrays",
1606 0,
1607 ));
1608 }
1609 self.check_parallel_array_write(name)?;
1610 let idx = self.resolve_array_frame_idx(name).unwrap_or_default();
1611 let frame = &mut self.frames[idx];
1612 if frame.get_array_mut(name).is_none() {
1613 frame.arrays.push((name.to_string(), Vec::new()));
1614 }
1615 Ok(frame.get_array_mut(name).unwrap())
1616 }
1617
1618 pub fn push_to_array(&mut self, name: &str, val: PerlValue) -> Result<(), PerlError> {
1620 let val = self.resolve_container_binding_ref(val);
1621 if let Some(aa) = self.find_atomic_array(name) {
1622 aa.0.lock().push(val);
1623 return Ok(());
1624 }
1625 if let Some(arc) = self.find_shared_array(name) {
1626 arc.write().push(val);
1627 return Ok(());
1628 }
1629 self.get_array_mut(name)?.push(val);
1630 Ok(())
1631 }
1632
1633 pub fn push_int_range_to_array(
1637 &mut self,
1638 name: &str,
1639 start: i64,
1640 end: i64,
1641 ) -> Result<(), PerlError> {
1642 if end <= start {
1643 return Ok(());
1644 }
1645 let count = (end - start) as usize;
1646 if let Some(aa) = self.find_atomic_array(name) {
1647 let mut g = aa.0.lock();
1648 g.reserve(count);
1649 for i in start..end {
1650 g.push(PerlValue::integer(i));
1651 }
1652 return Ok(());
1653 }
1654 let arr = self.get_array_mut(name)?;
1655 arr.reserve(count);
1656 for i in start..end {
1657 arr.push(PerlValue::integer(i));
1658 }
1659 Ok(())
1660 }
1661
1662 pub fn pop_from_array(&mut self, name: &str) -> Result<PerlValue, PerlError> {
1664 if let Some(aa) = self.find_atomic_array(name) {
1665 return Ok(aa.0.lock().pop().unwrap_or(PerlValue::UNDEF));
1666 }
1667 if let Some(arc) = self.find_shared_array(name) {
1668 return Ok(arc.write().pop().unwrap_or(PerlValue::UNDEF));
1669 }
1670 Ok(self.get_array_mut(name)?.pop().unwrap_or(PerlValue::UNDEF))
1671 }
1672
1673 pub fn shift_from_array(&mut self, name: &str) -> Result<PerlValue, PerlError> {
1675 if let Some(aa) = self.find_atomic_array(name) {
1676 let mut guard = aa.0.lock();
1677 return Ok(if guard.is_empty() {
1678 PerlValue::UNDEF
1679 } else {
1680 guard.remove(0)
1681 });
1682 }
1683 if let Some(arc) = self.find_shared_array(name) {
1684 let mut arr = arc.write();
1685 return Ok(if arr.is_empty() {
1686 PerlValue::UNDEF
1687 } else {
1688 arr.remove(0)
1689 });
1690 }
1691 let arr = self.get_array_mut(name)?;
1692 Ok(if arr.is_empty() {
1693 PerlValue::UNDEF
1694 } else {
1695 arr.remove(0)
1696 })
1697 }
1698
1699 pub fn splice_in_place(
1703 &mut self,
1704 name: &str,
1705 off: usize,
1706 end: usize,
1707 rep_vals: Vec<PerlValue>,
1708 ) -> Result<Vec<PerlValue>, PerlError> {
1709 if let Some(aa) = self.find_atomic_array(name) {
1710 let mut g = aa.0.lock();
1711 let removed: Vec<PerlValue> = g.drain(off..end).collect();
1712 for (i, v) in rep_vals.into_iter().enumerate() {
1713 g.insert(off + i, v);
1714 }
1715 return Ok(removed);
1716 }
1717 if let Some(arc) = self.find_shared_array(name) {
1718 let mut g = arc.write();
1719 let removed: Vec<PerlValue> = g.drain(off..end).collect();
1720 for (i, v) in rep_vals.into_iter().enumerate() {
1721 g.insert(off + i, v);
1722 }
1723 return Ok(removed);
1724 }
1725 let arr = self.get_array_mut(name)?;
1726 let removed: Vec<PerlValue> = arr.drain(off..end).collect();
1727 for (i, v) in rep_vals.into_iter().enumerate() {
1728 arr.insert(off + i, v);
1729 }
1730 Ok(removed)
1731 }
1732
1733 pub fn array_len(&self, name: &str) -> usize {
1735 if let Some(aa) = self.find_atomic_array(name) {
1736 return aa.0.lock().len();
1737 }
1738 if let Some(arc) = self.find_shared_array(name) {
1739 return arc.read().len();
1740 }
1741 if name.contains("::") {
1742 return self
1743 .frames
1744 .first()
1745 .and_then(|f| f.get_array(name))
1746 .map(|a| a.len())
1747 .unwrap_or(0);
1748 }
1749 for frame in self.frames.iter().rev() {
1750 if let Some(arr) = frame.get_array(name) {
1751 return arr.len();
1752 }
1753 }
1754 0
1755 }
1756
1757 pub fn set_array(&mut self, name: &str, val: Vec<PerlValue>) -> Result<(), PerlError> {
1758 if let Some(aa) = self.find_atomic_array(name) {
1759 *aa.0.lock() = val;
1760 return Ok(());
1761 }
1762 if let Some(arc) = self.find_shared_array(name) {
1763 *arc.write() = val;
1764 return Ok(());
1765 }
1766 self.check_parallel_array_write(name)?;
1767 for frame in self.frames.iter_mut().rev() {
1768 if frame.has_array(name) {
1769 frame.set_array(name, val);
1770 return Ok(());
1771 }
1772 }
1773 self.frames[0].set_array(name, val);
1774 Ok(())
1775 }
1776
1777 #[inline]
1779 pub fn get_array_element(&self, name: &str, index: i64) -> PerlValue {
1780 if let Some(aa) = self.find_atomic_array(name) {
1781 let arr = aa.0.lock();
1782 let idx = if index < 0 {
1783 (arr.len() as i64 + index) as usize
1784 } else {
1785 index as usize
1786 };
1787 return arr.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
1788 }
1789 if let Some(arc) = self.find_shared_array(name) {
1790 let arr = arc.read();
1791 let idx = if index < 0 {
1792 (arr.len() as i64 + index) as usize
1793 } else {
1794 index as usize
1795 };
1796 return arr.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
1797 }
1798 for frame in self.frames.iter().rev() {
1799 if let Some(arr) = frame.get_array(name) {
1800 let idx = if index < 0 {
1801 (arr.len() as i64 + index) as usize
1802 } else {
1803 index as usize
1804 };
1805 return arr.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
1806 }
1807 }
1808 PerlValue::UNDEF
1809 }
1810
1811 pub fn set_array_element(
1812 &mut self,
1813 name: &str,
1814 index: i64,
1815 val: PerlValue,
1816 ) -> Result<(), PerlError> {
1817 let val = self.resolve_container_binding_ref(val);
1818 if let Some(aa) = self.find_atomic_array(name) {
1819 let mut arr = aa.0.lock();
1820 let idx = if index < 0 {
1821 (arr.len() as i64 + index).max(0) as usize
1822 } else {
1823 index as usize
1824 };
1825 if idx >= arr.len() {
1826 arr.resize(idx + 1, PerlValue::UNDEF);
1827 }
1828 arr[idx] = val;
1829 return Ok(());
1830 }
1831 if let Some(arc) = self.find_shared_array(name) {
1832 let mut arr = arc.write();
1833 let idx = if index < 0 {
1834 (arr.len() as i64 + index).max(0) as usize
1835 } else {
1836 index as usize
1837 };
1838 if idx >= arr.len() {
1839 arr.resize(idx + 1, PerlValue::UNDEF);
1840 }
1841 arr[idx] = val;
1842 return Ok(());
1843 }
1844 let arr = self.get_array_mut(name)?;
1845 let idx = if index < 0 {
1846 let len = arr.len() as i64;
1847 (len + index).max(0) as usize
1848 } else {
1849 index as usize
1850 };
1851 if idx >= arr.len() {
1852 arr.resize(idx + 1, PerlValue::UNDEF);
1853 }
1854 arr[idx] = val;
1855 Ok(())
1856 }
1857
1858 pub fn exists_array_element(&self, name: &str, index: i64) -> bool {
1860 if let Some(aa) = self.find_atomic_array(name) {
1861 let arr = aa.0.lock();
1862 let idx = if index < 0 {
1863 (arr.len() as i64 + index) as usize
1864 } else {
1865 index as usize
1866 };
1867 return idx < arr.len();
1868 }
1869 for frame in self.frames.iter().rev() {
1870 if let Some(arr) = frame.get_array(name) {
1871 let idx = if index < 0 {
1872 (arr.len() as i64 + index) as usize
1873 } else {
1874 index as usize
1875 };
1876 return idx < arr.len();
1877 }
1878 }
1879 false
1880 }
1881
1882 pub fn delete_array_element(&mut self, name: &str, index: i64) -> Result<PerlValue, PerlError> {
1884 if let Some(aa) = self.find_atomic_array(name) {
1885 let mut arr = aa.0.lock();
1886 let idx = if index < 0 {
1887 (arr.len() as i64 + index) as usize
1888 } else {
1889 index as usize
1890 };
1891 if idx >= arr.len() {
1892 return Ok(PerlValue::UNDEF);
1893 }
1894 let old = arr.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
1895 arr[idx] = PerlValue::UNDEF;
1896 return Ok(old);
1897 }
1898 let arr = self.get_array_mut(name)?;
1899 let idx = if index < 0 {
1900 (arr.len() as i64 + index) as usize
1901 } else {
1902 index as usize
1903 };
1904 if idx >= arr.len() {
1905 return Ok(PerlValue::UNDEF);
1906 }
1907 let old = arr.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
1908 arr[idx] = PerlValue::UNDEF;
1909 Ok(old)
1910 }
1911
1912 #[inline]
1915 pub fn declare_hash(&mut self, name: &str, val: IndexMap<String, PerlValue>) {
1916 self.declare_hash_frozen(name, val, false);
1917 }
1918
1919 pub fn declare_hash_frozen(
1920 &mut self,
1921 name: &str,
1922 val: IndexMap<String, PerlValue>,
1923 frozen: bool,
1924 ) {
1925 if let Some(frame) = self.frames.last_mut() {
1926 frame.shared_hashes.retain(|(k, _)| k != name);
1928 frame.set_hash(name, val);
1929 if frozen {
1930 frame.frozen_hashes.insert(name.to_string());
1931 }
1932 }
1933 }
1934
1935 pub fn declare_hash_global(&mut self, name: &str, val: IndexMap<String, PerlValue>) {
1937 if let Some(frame) = self.frames.first_mut() {
1938 frame.set_hash(name, val);
1939 }
1940 }
1941
1942 pub fn declare_hash_global_frozen(&mut self, name: &str, val: IndexMap<String, PerlValue>) {
1944 if let Some(frame) = self.frames.first_mut() {
1945 frame.set_hash(name, val);
1946 frame.frozen_hashes.insert(name.to_string());
1947 }
1948 }
1949
1950 pub fn has_lexical_hash(&self, name: &str) -> bool {
1952 self.frames.iter().skip(1).any(|f| f.has_hash(name))
1953 }
1954
1955 pub fn any_frame_has_hash(&self, name: &str) -> bool {
1957 self.frames.iter().any(|f| f.has_hash(name))
1958 }
1959
1960 pub fn get_hash(&self, name: &str) -> IndexMap<String, PerlValue> {
1961 if let Some(ah) = self.find_atomic_hash(name) {
1962 return ah.0.lock().clone();
1963 }
1964 if let Some(arc) = self.find_shared_hash(name) {
1965 return arc.read().clone();
1966 }
1967 for frame in self.frames.iter().rev() {
1968 if let Some(val) = frame.get_hash(name) {
1969 return val.clone();
1970 }
1971 }
1972 IndexMap::new()
1973 }
1974
1975 fn resolve_hash_frame_idx(&self, name: &str) -> Option<usize> {
1976 if name.contains("::") {
1977 return Some(0);
1978 }
1979 (0..self.frames.len())
1980 .rev()
1981 .find(|&i| self.frames[i].has_hash(name))
1982 }
1983
1984 fn check_parallel_hash_write(&self, name: &str) -> Result<(), PerlError> {
1985 if !self.parallel_guard
1986 || Self::parallel_skip_special_name(name)
1987 || Self::parallel_allowed_internal_hash(name)
1988 {
1989 return Ok(());
1990 }
1991 let inner = self.frames.len().saturating_sub(1);
1992 match self.resolve_hash_frame_idx(name) {
1993 None => Err(PerlError::runtime(
1994 format!(
1995 "cannot modify undeclared hash `%{}` in a parallel block",
1996 name
1997 ),
1998 0,
1999 )),
2000 Some(idx) if idx != inner => Err(PerlError::runtime(
2001 format!(
2002 "cannot modify captured non-mysync hash `%{}` in a parallel block",
2003 name
2004 ),
2005 0,
2006 )),
2007 Some(_) => Ok(()),
2008 }
2009 }
2010
2011 pub fn get_hash_mut(
2012 &mut self,
2013 name: &str,
2014 ) -> Result<&mut IndexMap<String, PerlValue>, PerlError> {
2015 if self.find_atomic_hash(name).is_some() {
2016 return Err(PerlError::runtime(
2017 "get_hash_mut: use atomic path for mysync hashes",
2018 0,
2019 ));
2020 }
2021 self.check_parallel_hash_write(name)?;
2022 let idx = self.resolve_hash_frame_idx(name).unwrap_or_default();
2023 let frame = &mut self.frames[idx];
2024 if frame.get_hash_mut(name).is_none() {
2025 frame.hashes.push((name.to_string(), IndexMap::new()));
2026 }
2027 Ok(frame.get_hash_mut(name).unwrap())
2028 }
2029
2030 pub fn set_hash(
2031 &mut self,
2032 name: &str,
2033 val: IndexMap<String, PerlValue>,
2034 ) -> Result<(), PerlError> {
2035 if let Some(ah) = self.find_atomic_hash(name) {
2036 *ah.0.lock() = val;
2037 return Ok(());
2038 }
2039 self.check_parallel_hash_write(name)?;
2040 for frame in self.frames.iter_mut().rev() {
2041 if frame.has_hash(name) {
2042 frame.set_hash(name, val);
2043 return Ok(());
2044 }
2045 }
2046 self.frames[0].set_hash(name, val);
2047 Ok(())
2048 }
2049
2050 #[inline]
2051 pub fn get_hash_element(&self, name: &str, key: &str) -> PerlValue {
2052 if let Some(ah) = self.find_atomic_hash(name) {
2053 return ah.0.lock().get(key).cloned().unwrap_or(PerlValue::UNDEF);
2054 }
2055 if let Some(arc) = self.find_shared_hash(name) {
2056 return arc.read().get(key).cloned().unwrap_or(PerlValue::UNDEF);
2057 }
2058 for frame in self.frames.iter().rev() {
2059 if let Some(hash) = frame.get_hash(name) {
2060 return hash.get(key).cloned().unwrap_or(PerlValue::UNDEF);
2061 }
2062 }
2063 PerlValue::UNDEF
2064 }
2065
2066 pub fn atomic_hash_mutate(
2069 &mut self,
2070 name: &str,
2071 key: &str,
2072 f: impl FnOnce(&PerlValue) -> PerlValue,
2073 ) -> Result<PerlValue, PerlError> {
2074 if let Some(ah) = self.find_atomic_hash(name) {
2075 let mut guard = ah.0.lock();
2076 let old = guard.get(key).cloned().unwrap_or(PerlValue::UNDEF);
2077 let new_val = f(&old);
2078 guard.insert(key.to_string(), new_val.clone());
2079 return Ok(new_val);
2080 }
2081 let old = self.get_hash_element(name, key);
2083 let new_val = f(&old);
2084 self.set_hash_element(name, key, new_val.clone())?;
2085 Ok(new_val)
2086 }
2087
2088 pub fn atomic_array_mutate(
2090 &mut self,
2091 name: &str,
2092 index: i64,
2093 f: impl FnOnce(&PerlValue) -> PerlValue,
2094 ) -> Result<PerlValue, PerlError> {
2095 if let Some(aa) = self.find_atomic_array(name) {
2096 let mut guard = aa.0.lock();
2097 let idx = if index < 0 {
2098 (guard.len() as i64 + index).max(0) as usize
2099 } else {
2100 index as usize
2101 };
2102 if idx >= guard.len() {
2103 guard.resize(idx + 1, PerlValue::UNDEF);
2104 }
2105 let old = guard[idx].clone();
2106 let new_val = f(&old);
2107 guard[idx] = new_val.clone();
2108 return Ok(new_val);
2109 }
2110 let old = self.get_array_element(name, index);
2112 let new_val = f(&old);
2113 self.set_array_element(name, index, new_val.clone())?;
2114 Ok(new_val)
2115 }
2116
2117 pub fn set_hash_element(
2118 &mut self,
2119 name: &str,
2120 key: &str,
2121 val: PerlValue,
2122 ) -> Result<(), PerlError> {
2123 let val = self.resolve_container_binding_ref(val);
2124 if name == "SIG" {
2127 crate::perl_signal::install(key);
2128 }
2129 if let Some(ah) = self.find_atomic_hash(name) {
2130 ah.0.lock().insert(key.to_string(), val);
2131 return Ok(());
2132 }
2133 if let Some(arc) = self.find_shared_hash(name) {
2134 arc.write().insert(key.to_string(), val);
2135 return Ok(());
2136 }
2137 let hash = self.get_hash_mut(name)?;
2138 hash.insert(key.to_string(), val);
2139 Ok(())
2140 }
2141
2142 pub fn set_hash_int_times_range(
2146 &mut self,
2147 name: &str,
2148 start: i64,
2149 end: i64,
2150 k: i64,
2151 ) -> Result<(), PerlError> {
2152 if end <= start {
2153 return Ok(());
2154 }
2155 let count = (end - start) as usize;
2156 if let Some(ah) = self.find_atomic_hash(name) {
2157 let mut g = ah.0.lock();
2158 g.reserve(count);
2159 let mut buf = itoa::Buffer::new();
2160 for i in start..end {
2161 let key = buf.format(i).to_owned();
2162 g.insert(key, PerlValue::integer(i.wrapping_mul(k)));
2163 }
2164 return Ok(());
2165 }
2166 let hash = self.get_hash_mut(name)?;
2167 hash.reserve(count);
2168 let mut buf = itoa::Buffer::new();
2169 for i in start..end {
2170 let key = buf.format(i).to_owned();
2171 hash.insert(key, PerlValue::integer(i.wrapping_mul(k)));
2172 }
2173 Ok(())
2174 }
2175
2176 pub fn delete_hash_element(&mut self, name: &str, key: &str) -> Result<PerlValue, PerlError> {
2177 if let Some(ah) = self.find_atomic_hash(name) {
2178 return Ok(ah.0.lock().shift_remove(key).unwrap_or(PerlValue::UNDEF));
2179 }
2180 let hash = self.get_hash_mut(name)?;
2181 Ok(hash.shift_remove(key).unwrap_or(PerlValue::UNDEF))
2182 }
2183
2184 #[inline]
2185 pub fn exists_hash_element(&self, name: &str, key: &str) -> bool {
2186 if let Some(ah) = self.find_atomic_hash(name) {
2187 return ah.0.lock().contains_key(key);
2188 }
2189 for frame in self.frames.iter().rev() {
2190 if let Some(hash) = frame.get_hash(name) {
2191 return hash.contains_key(key);
2192 }
2193 }
2194 false
2195 }
2196
2197 #[inline]
2202 pub fn for_each_hash_value(&self, name: &str, mut visit: impl FnMut(&PerlValue)) {
2203 if let Some(ah) = self.find_atomic_hash(name) {
2204 let g = ah.0.lock();
2205 for v in g.values() {
2206 visit(v);
2207 }
2208 return;
2209 }
2210 for frame in self.frames.iter().rev() {
2211 if let Some(hash) = frame.get_hash(name) {
2212 for v in hash.values() {
2213 visit(v);
2214 }
2215 return;
2216 }
2217 }
2218 }
2219
2220 pub fn frames_for_introspection(&self) -> Vec<(Vec<&str>, Vec<&str>, Vec<&str>)> {
2226 self.frames
2227 .iter()
2228 .map(|f| {
2229 let mut scalars: Vec<&str> = f.scalars.iter().map(|(n, _)| n.as_str()).collect();
2230 scalars.extend(f.scalar_slot_names.iter().filter_map(|opt| match opt {
2232 Some(n) if !n.is_empty() => Some(n.as_str()),
2233 _ => None,
2234 }));
2235 let mut arrays: Vec<&str> = f.arrays.iter().map(|(n, _)| n.as_str()).collect();
2236 arrays.extend(f.atomic_arrays.iter().map(|(n, _)| n.as_str()));
2237 arrays.extend(f.shared_arrays.iter().map(|(n, _)| n.as_str()));
2238 let mut hashes: Vec<&str> = f.hashes.iter().map(|(n, _)| n.as_str()).collect();
2239 hashes.extend(f.atomic_hashes.iter().map(|(n, _)| n.as_str()));
2240 hashes.extend(f.shared_hashes.iter().map(|(n, _)| n.as_str()));
2241 scalars.sort_unstable();
2242 arrays.sort_unstable();
2243 hashes.sort_unstable();
2244 (scalars, arrays, hashes)
2245 })
2246 .collect()
2247 }
2248
2249 pub fn parameters_pairs(&self) -> Vec<(String, &'static str)> {
2255 let mut seen: HashSet<String> = HashSet::new();
2256 let mut out: Vec<(String, &'static str)> = Vec::new();
2257 for frame in self.frames.iter().rev() {
2260 for n in frame.scalar_slot_names.iter().flatten() {
2264 if !n.is_empty() {
2265 let s = format!("${}", n);
2266 if seen.insert(s.clone()) {
2267 out.push((s, "scalar"));
2268 }
2269 }
2270 }
2271 for (name, _) in &frame.scalars {
2272 let s = format!("${}", name);
2273 if seen.insert(s.clone()) {
2274 out.push((s, "scalar"));
2275 }
2276 }
2277 for (name, _) in &frame.arrays {
2278 let s = format!("@{}", name);
2279 if seen.insert(s.clone()) {
2280 out.push((s, "array"));
2281 }
2282 }
2283 for (name, _) in &frame.hashes {
2284 let s = format!("%{}", name);
2285 if seen.insert(s.clone()) {
2286 out.push((s, "hash"));
2287 }
2288 }
2289 for (name, _) in &frame.atomic_arrays {
2290 let s = format!("@{}", name);
2291 if seen.insert(s.clone()) {
2292 out.push((s, "atomic_array"));
2293 }
2294 }
2295 for (name, _) in &frame.atomic_hashes {
2296 let s = format!("%{}", name);
2297 if seen.insert(s.clone()) {
2298 out.push((s, "atomic_hash"));
2299 }
2300 }
2301 for (name, _) in &frame.shared_arrays {
2302 let s = format!("@{}", name);
2303 if seen.insert(s.clone()) {
2304 out.push((s, "shared_array"));
2305 }
2306 }
2307 for (name, _) in &frame.shared_hashes {
2308 let s = format!("%{}", name);
2309 if seen.insert(s.clone()) {
2310 out.push((s, "shared_hash"));
2311 }
2312 }
2313 }
2314 out.sort_by(|a, b| a.0.cmp(&b.0));
2315 out
2316 }
2317
2318 pub fn repl_binding_names(&self) -> Vec<String> {
2320 let mut seen = HashSet::new();
2321 let mut out = Vec::new();
2322 for frame in &self.frames {
2323 for (name, _) in &frame.scalars {
2324 let s = format!("${}", name);
2325 if seen.insert(s.clone()) {
2326 out.push(s);
2327 }
2328 }
2329 for (name, _) in &frame.arrays {
2330 let s = format!("@{}", name);
2331 if seen.insert(s.clone()) {
2332 out.push(s);
2333 }
2334 }
2335 for (name, _) in &frame.hashes {
2336 let s = format!("%{}", name);
2337 if seen.insert(s.clone()) {
2338 out.push(s);
2339 }
2340 }
2341 for (name, _) in &frame.atomic_arrays {
2342 let s = format!("@{}", name);
2343 if seen.insert(s.clone()) {
2344 out.push(s);
2345 }
2346 }
2347 for (name, _) in &frame.atomic_hashes {
2348 let s = format!("%{}", name);
2349 if seen.insert(s.clone()) {
2350 out.push(s);
2351 }
2352 }
2353 }
2354 out.sort();
2355 out
2356 }
2357
2358 pub fn capture(&mut self) -> Vec<(String, PerlValue)> {
2359 let by_ref = crate::compat_mode();
2372 let mut captured = Vec::new();
2373 for frame in &mut self.frames {
2374 for (k, v) in &mut frame.scalars {
2381 if v.as_capture_cell().is_some() || v.as_scalar_ref().is_some() {
2382 captured.push((format!("${}", k), v.clone()));
2383 } else if v.is_simple_scalar() {
2384 let wrapped = PerlValue::capture_cell(Arc::new(RwLock::new(v.clone())));
2385 *v = wrapped.clone();
2386 captured.push((format!("${}", k), wrapped));
2387 } else {
2388 captured.push((format!("${}", k), v.clone()));
2389 }
2390 }
2391 for (i, v) in frame.scalar_slots.iter_mut().enumerate() {
2399 if let Some(Some(name)) = frame.scalar_slot_names.get(i) {
2400 let cap_val = if v.as_capture_cell().is_some() || v.as_scalar_ref().is_some() {
2401 v.clone()
2402 } else {
2403 let wrapped = PerlValue::capture_cell(Arc::new(RwLock::new(v.clone())));
2404 if by_ref {
2405 *v = wrapped.clone();
2406 }
2407 wrapped
2408 };
2409 captured.push((format!("$slot:{}:{}", i, name), cap_val));
2410 }
2411 }
2412 for (k, v) in &frame.arrays {
2413 if capture_skip_bootstrap_array(k) {
2414 continue;
2415 }
2416 if frame.frozen_arrays.contains(k) {
2417 captured.push((format!("@frozen:{}", k), PerlValue::array(v.clone())));
2418 } else {
2419 captured.push((format!("@{}", k), PerlValue::array(v.clone())));
2420 }
2421 }
2422 for (k, v) in &frame.hashes {
2423 if capture_skip_bootstrap_hash(k) {
2424 continue;
2425 }
2426 if frame.frozen_hashes.contains(k) {
2427 captured.push((format!("%frozen:{}", k), PerlValue::hash(v.clone())));
2428 } else {
2429 captured.push((format!("%{}", k), PerlValue::hash(v.clone())));
2430 }
2431 }
2432 for (k, _aa) in &frame.atomic_arrays {
2433 captured.push((
2434 format!("@sync_{}", k),
2435 PerlValue::atomic(Arc::new(Mutex::new(PerlValue::string(String::new())))),
2436 ));
2437 }
2438 for (k, _ah) in &frame.atomic_hashes {
2439 captured.push((
2440 format!("%sync_{}", k),
2441 PerlValue::atomic(Arc::new(Mutex::new(PerlValue::string(String::new())))),
2442 ));
2443 }
2444 }
2445 captured
2446 }
2447
2448 pub fn capture_with_atomics(&self) -> ScopeCaptureWithAtomics {
2450 let mut scalars = Vec::new();
2451 let mut arrays = Vec::new();
2452 let mut hashes = Vec::new();
2453 for frame in &self.frames {
2454 for (k, v) in &frame.scalars {
2455 scalars.push((format!("${}", k), v.clone()));
2456 }
2457 for (i, v) in frame.scalar_slots.iter().enumerate() {
2458 if let Some(Some(name)) = frame.scalar_slot_names.get(i) {
2459 scalars.push((format!("$slot:{}:{}", i, name), v.clone()));
2460 }
2461 }
2462 for (k, v) in &frame.arrays {
2463 if capture_skip_bootstrap_array(k) {
2464 continue;
2465 }
2466 if frame.frozen_arrays.contains(k) {
2467 scalars.push((format!("@frozen:{}", k), PerlValue::array(v.clone())));
2468 } else {
2469 scalars.push((format!("@{}", k), PerlValue::array(v.clone())));
2470 }
2471 }
2472 for (k, v) in &frame.hashes {
2473 if capture_skip_bootstrap_hash(k) {
2474 continue;
2475 }
2476 if frame.frozen_hashes.contains(k) {
2477 scalars.push((format!("%frozen:{}", k), PerlValue::hash(v.clone())));
2478 } else {
2479 scalars.push((format!("%{}", k), PerlValue::hash(v.clone())));
2480 }
2481 }
2482 for (k, aa) in &frame.atomic_arrays {
2483 arrays.push((k.clone(), aa.clone()));
2484 }
2485 for (k, ah) in &frame.atomic_hashes {
2486 hashes.push((k.clone(), ah.clone()));
2487 }
2488 }
2489 (scalars, arrays, hashes)
2490 }
2491
2492 pub fn restore_capture(&mut self, captured: &[(String, PerlValue)]) {
2493 for (name, val) in captured {
2494 if let Some(rest) = name.strip_prefix("$slot:") {
2495 if let Some(colon) = rest.find(':') {
2501 let idx: usize = rest[..colon].parse().unwrap_or(0);
2502 let sname = &rest[colon + 1..];
2503 self.declare_scalar_slot(idx as u8, val.clone(), Some(sname));
2504 }
2505 } else if let Some(stripped) = name.strip_prefix('$') {
2506 self.declare_scalar(stripped, val.clone());
2507 if let Some(slot) = parse_positional_topic_slot(stripped) {
2514 if slot > self.max_active_slot {
2515 self.max_active_slot = slot;
2516 }
2517 }
2518 } else if let Some(rest) = name.strip_prefix("@frozen:") {
2519 let arr = val.as_array_vec().unwrap_or_else(|| val.to_list());
2520 self.declare_array_frozen(rest, arr, true);
2521 } else if let Some(rest) = name.strip_prefix("%frozen:") {
2522 if let Some(h) = val.as_hash_map() {
2523 self.declare_hash_frozen(rest, h.clone(), true);
2524 }
2525 } else if let Some(rest) = name.strip_prefix('@') {
2526 if rest.starts_with("sync_") {
2527 continue;
2528 }
2529 let arr = val.as_array_vec().unwrap_or_else(|| val.to_list());
2530 self.declare_array(rest, arr);
2531 } else if let Some(rest) = name.strip_prefix('%') {
2532 if rest.starts_with("sync_") {
2533 continue;
2534 }
2535 if let Some(h) = val.as_hash_map() {
2536 self.declare_hash(rest, h.clone());
2537 }
2538 }
2539 }
2540 }
2541
2542 pub fn restore_atomics(
2544 &mut self,
2545 arrays: &[(String, AtomicArray)],
2546 hashes: &[(String, AtomicHash)],
2547 ) {
2548 if let Some(frame) = self.frames.last_mut() {
2549 for (name, aa) in arrays {
2550 frame.atomic_arrays.push((name.clone(), aa.clone()));
2551 }
2552 for (name, ah) in hashes {
2553 frame.atomic_hashes.push((name.clone(), ah.clone()));
2554 }
2555 }
2556 }
2557}
2558
2559#[cfg(test)]
2560mod tests {
2561 use super::*;
2562 use crate::value::PerlValue;
2563
2564 #[test]
2565 fn missing_scalar_is_undef() {
2566 let s = Scope::new();
2567 assert!(s.get_scalar("not_declared").is_undef());
2568 }
2569
2570 #[test]
2571 fn inner_frame_shadows_outer_scalar() {
2572 let mut s = Scope::new();
2573 s.declare_scalar("a", PerlValue::integer(1));
2574 s.push_frame();
2575 s.declare_scalar("a", PerlValue::integer(2));
2576 assert_eq!(s.get_scalar("a").to_int(), 2);
2577 s.pop_frame();
2578 assert_eq!(s.get_scalar("a").to_int(), 1);
2579 }
2580
2581 #[test]
2582 fn set_scalar_updates_innermost_binding() {
2583 let mut s = Scope::new();
2584 s.declare_scalar("a", PerlValue::integer(1));
2585 s.push_frame();
2586 s.declare_scalar("a", PerlValue::integer(2));
2587 let _ = s.set_scalar("a", PerlValue::integer(99));
2588 assert_eq!(s.get_scalar("a").to_int(), 99);
2589 s.pop_frame();
2590 assert_eq!(s.get_scalar("a").to_int(), 1);
2591 }
2592
2593 #[test]
2594 fn array_negative_index_reads_from_end() {
2595 let mut s = Scope::new();
2596 s.declare_array(
2597 "a",
2598 vec![
2599 PerlValue::integer(10),
2600 PerlValue::integer(20),
2601 PerlValue::integer(30),
2602 ],
2603 );
2604 assert_eq!(s.get_array_element("a", -1).to_int(), 30);
2605 }
2606
2607 #[test]
2608 fn set_array_element_extends_array_with_undef_gaps() {
2609 let mut s = Scope::new();
2610 s.declare_array("a", vec![]);
2611 s.set_array_element("a", 2, PerlValue::integer(7)).unwrap();
2612 assert_eq!(s.get_array_element("a", 2).to_int(), 7);
2613 assert!(s.get_array_element("a", 0).is_undef());
2614 }
2615
2616 #[test]
2617 fn capture_restore_roundtrip_scalar() {
2618 let mut s = Scope::new();
2619 s.declare_scalar("n", PerlValue::integer(42));
2620 let cap = s.capture();
2621 let mut t = Scope::new();
2622 t.restore_capture(&cap);
2623 assert_eq!(t.get_scalar("n").to_int(), 42);
2624 }
2625
2626 #[test]
2627 fn capture_restore_roundtrip_lexical_array_and_hash() {
2628 let mut s = Scope::new();
2629 s.declare_array("a", vec![PerlValue::integer(1), PerlValue::integer(2)]);
2630 let mut m = IndexMap::new();
2631 m.insert("k".to_string(), PerlValue::integer(99));
2632 s.declare_hash("h", m);
2633 let cap = s.capture();
2634 let mut t = Scope::new();
2635 t.restore_capture(&cap);
2636 assert_eq!(t.get_array_element("a", 1).to_int(), 2);
2637 assert_eq!(t.get_hash_element("h", "k").to_int(), 99);
2638 }
2639
2640 #[test]
2641 fn hash_get_set_delete_exists() {
2642 let mut s = Scope::new();
2643 let mut m = IndexMap::new();
2644 m.insert("k".to_string(), PerlValue::integer(1));
2645 s.declare_hash("h", m);
2646 assert_eq!(s.get_hash_element("h", "k").to_int(), 1);
2647 assert!(s.exists_hash_element("h", "k"));
2648 s.set_hash_element("h", "k", PerlValue::integer(99))
2649 .unwrap();
2650 assert_eq!(s.get_hash_element("h", "k").to_int(), 99);
2651 let del = s.delete_hash_element("h", "k").unwrap();
2652 assert_eq!(del.to_int(), 99);
2653 assert!(!s.exists_hash_element("h", "k"));
2654 }
2655
2656 #[test]
2657 fn inner_frame_shadows_outer_hash_name() {
2658 let mut s = Scope::new();
2659 let mut outer = IndexMap::new();
2660 outer.insert("k".to_string(), PerlValue::integer(1));
2661 s.declare_hash("h", outer);
2662 s.push_frame();
2663 let mut inner = IndexMap::new();
2664 inner.insert("k".to_string(), PerlValue::integer(2));
2665 s.declare_hash("h", inner);
2666 assert_eq!(s.get_hash_element("h", "k").to_int(), 2);
2667 s.pop_frame();
2668 assert_eq!(s.get_hash_element("h", "k").to_int(), 1);
2669 }
2670
2671 #[test]
2672 fn inner_frame_shadows_outer_array_name() {
2673 let mut s = Scope::new();
2674 s.declare_array("a", vec![PerlValue::integer(1)]);
2675 s.push_frame();
2676 s.declare_array("a", vec![PerlValue::integer(2), PerlValue::integer(3)]);
2677 assert_eq!(s.get_array_element("a", 1).to_int(), 3);
2678 s.pop_frame();
2679 assert_eq!(s.get_array_element("a", 0).to_int(), 1);
2680 }
2681
2682 #[test]
2683 fn pop_frame_never_removes_global_frame() {
2684 let mut s = Scope::new();
2685 s.declare_scalar("x", PerlValue::integer(1));
2686 s.pop_frame();
2687 s.pop_frame();
2688 assert_eq!(s.get_scalar("x").to_int(), 1);
2689 }
2690
2691 #[test]
2692 fn empty_array_declared_has_zero_length() {
2693 let mut s = Scope::new();
2694 s.declare_array("a", vec![]);
2695 assert_eq!(s.get_array("a").len(), 0);
2696 }
2697
2698 #[test]
2699 fn depth_increments_with_push_frame() {
2700 let mut s = Scope::new();
2701 let d0 = s.depth();
2702 s.push_frame();
2703 assert_eq!(s.depth(), d0 + 1);
2704 s.pop_frame();
2705 assert_eq!(s.depth(), d0);
2706 }
2707
2708 #[test]
2709 fn pop_to_depth_unwinds_to_target() {
2710 let mut s = Scope::new();
2711 s.push_frame();
2712 s.push_frame();
2713 let target = s.depth() - 1;
2714 s.pop_to_depth(target);
2715 assert_eq!(s.depth(), target);
2716 }
2717
2718 #[test]
2719 fn array_len_and_push_pop_roundtrip() {
2720 let mut s = Scope::new();
2721 s.declare_array("a", vec![]);
2722 assert_eq!(s.array_len("a"), 0);
2723 s.push_to_array("a", PerlValue::integer(1)).unwrap();
2724 s.push_to_array("a", PerlValue::integer(2)).unwrap();
2725 assert_eq!(s.array_len("a"), 2);
2726 assert_eq!(s.pop_from_array("a").unwrap().to_int(), 2);
2727 assert_eq!(s.pop_from_array("a").unwrap().to_int(), 1);
2728 assert!(s.pop_from_array("a").unwrap().is_undef());
2729 }
2730
2731 #[test]
2732 fn shift_from_array_drops_front() {
2733 let mut s = Scope::new();
2734 s.declare_array("a", vec![PerlValue::integer(1), PerlValue::integer(2)]);
2735 assert_eq!(s.shift_from_array("a").unwrap().to_int(), 1);
2736 assert_eq!(s.array_len("a"), 1);
2737 }
2738
2739 #[test]
2740 fn atomic_mutate_increments_wrapped_scalar() {
2741 use parking_lot::Mutex;
2742 use std::sync::Arc;
2743 let mut s = Scope::new();
2744 s.declare_scalar(
2745 "n",
2746 PerlValue::atomic(Arc::new(Mutex::new(PerlValue::integer(10)))),
2747 );
2748 let v = s.atomic_mutate("n", |old| PerlValue::integer(old.to_int() + 5));
2749 assert_eq!(v.to_int(), 15);
2750 assert_eq!(s.get_scalar("n").to_int(), 15);
2751 }
2752
2753 #[test]
2754 fn atomic_mutate_post_returns_old_value() {
2755 use parking_lot::Mutex;
2756 use std::sync::Arc;
2757 let mut s = Scope::new();
2758 s.declare_scalar(
2759 "n",
2760 PerlValue::atomic(Arc::new(Mutex::new(PerlValue::integer(7)))),
2761 );
2762 let old = s.atomic_mutate_post("n", |v| PerlValue::integer(v.to_int() + 1));
2763 assert_eq!(old.to_int(), 7);
2764 assert_eq!(s.get_scalar("n").to_int(), 8);
2765 }
2766
2767 #[test]
2768 fn get_scalar_raw_keeps_atomic_wrapper() {
2769 use parking_lot::Mutex;
2770 use std::sync::Arc;
2771 let mut s = Scope::new();
2772 s.declare_scalar(
2773 "n",
2774 PerlValue::atomic(Arc::new(Mutex::new(PerlValue::integer(3)))),
2775 );
2776 assert!(s.get_scalar_raw("n").is_atomic());
2777 assert!(!s.get_scalar("n").is_atomic());
2778 }
2779
2780 #[test]
2781 fn missing_array_element_is_undef() {
2782 let mut s = Scope::new();
2783 s.declare_array("a", vec![PerlValue::integer(1)]);
2784 assert!(s.get_array_element("a", 99).is_undef());
2785 }
2786
2787 #[test]
2788 fn restore_atomics_puts_atomic_containers_in_frame() {
2789 use indexmap::IndexMap;
2790 use parking_lot::Mutex;
2791 use std::sync::Arc;
2792 let mut s = Scope::new();
2793 let aa = AtomicArray(Arc::new(Mutex::new(vec![PerlValue::integer(1)])));
2794 let ah = AtomicHash(Arc::new(Mutex::new(IndexMap::new())));
2795 s.restore_atomics(&[("ax".into(), aa.clone())], &[("hx".into(), ah.clone())]);
2796 assert_eq!(s.get_array_element("ax", 0).to_int(), 1);
2797 assert_eq!(s.array_len("ax"), 1);
2798 s.set_hash_element("hx", "k", PerlValue::integer(2))
2799 .unwrap();
2800 assert_eq!(s.get_hash_element("hx", "k").to_int(), 2);
2801 }
2802}