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
25#[inline]
29fn capture_skip_bootstrap_array(name: &str) -> bool {
30 matches!(
31 name,
32 "INC" | "ARGV" | "_" | "-" | "+" | "^CAPTURE" | "^CAPTURE_ALL"
33 )
34}
35
36#[inline]
38fn capture_skip_bootstrap_hash(name: &str) -> bool {
39 matches!(name, "INC" | "ENV" | "SIG" | "^HOOK")
40}
41
42#[derive(Clone, Debug)]
44enum LocalRestore {
45 Scalar(String, PerlValue),
46 Array(String, Vec<PerlValue>),
47 Hash(String, IndexMap<String, PerlValue>),
48 HashElement(String, String, Option<PerlValue>),
50 ArrayElement(String, i64, PerlValue),
52}
53
54#[derive(Debug, Clone)]
59struct Frame {
60 scalars: Vec<(String, PerlValue)>,
61 arrays: Vec<(String, Vec<PerlValue>)>,
62 sub_underscore: Option<Vec<PerlValue>>,
65 hashes: Vec<(String, IndexMap<String, PerlValue>)>,
66 scalar_slots: Vec<PerlValue>,
70 scalar_slot_names: Vec<Option<String>>,
73 local_restores: Vec<LocalRestore>,
75 frozen_scalars: HashSet<String>,
77 frozen_arrays: HashSet<String>,
78 frozen_hashes: HashSet<String>,
79 typed_scalars: HashMap<String, PerlTypeName>,
81 atomic_arrays: Vec<(String, AtomicArray)>,
83 atomic_hashes: Vec<(String, AtomicHash)>,
85 defers: Vec<PerlValue>,
87}
88
89impl Frame {
90 #[inline]
93 fn clear_all_bindings(&mut self) {
94 self.scalars.clear();
95 self.arrays.clear();
96 self.sub_underscore = None;
97 self.hashes.clear();
98 self.scalar_slots.clear();
99 self.scalar_slot_names.clear();
100 self.local_restores.clear();
101 self.frozen_scalars.clear();
102 self.frozen_arrays.clear();
103 self.frozen_hashes.clear();
104 self.typed_scalars.clear();
105 self.atomic_arrays.clear();
106 self.defers.clear();
107 self.atomic_hashes.clear();
108 }
109
110 #[inline]
114 fn owns_scalar_slot_index(&self, idx: usize) -> bool {
115 self.scalar_slot_names.get(idx).is_some_and(|n| n.is_some())
116 }
117
118 #[inline]
119 fn new() -> Self {
120 Self {
121 scalars: Vec::new(),
122 arrays: Vec::new(),
123 sub_underscore: None,
124 hashes: Vec::new(),
125 scalar_slots: Vec::new(),
126 scalar_slot_names: Vec::new(),
127 frozen_scalars: HashSet::new(),
128 frozen_arrays: HashSet::new(),
129 frozen_hashes: HashSet::new(),
130 typed_scalars: HashMap::new(),
131 atomic_arrays: Vec::new(),
132 atomic_hashes: Vec::new(),
133 local_restores: Vec::new(),
134 defers: Vec::new(),
135 }
136 }
137
138 #[inline]
139 fn get_scalar(&self, name: &str) -> Option<&PerlValue> {
140 if let Some(v) = self.get_scalar_from_slot(name) {
141 return Some(v);
142 }
143 self.scalars.iter().find(|(k, _)| k == name).map(|(_, v)| v)
144 }
145
146 #[inline]
149 fn get_scalar_from_slot(&self, name: &str) -> Option<&PerlValue> {
150 for (i, sn) in self.scalar_slot_names.iter().enumerate() {
151 if let Some(ref n) = sn {
152 if n == name {
153 return self.scalar_slots.get(i);
154 }
155 }
156 }
157 None
158 }
159
160 #[inline]
161 fn has_scalar(&self, name: &str) -> bool {
162 if self
163 .scalar_slot_names
164 .iter()
165 .any(|sn| sn.as_deref() == Some(name))
166 {
167 return true;
168 }
169 self.scalars.iter().any(|(k, _)| k == name)
170 }
171
172 #[inline]
173 fn set_scalar(&mut self, name: &str, val: PerlValue) {
174 for (i, sn) in self.scalar_slot_names.iter().enumerate() {
175 if let Some(ref n) = sn {
176 if n == name {
177 if i < self.scalar_slots.len() {
178 self.scalar_slots[i] = val;
179 }
180 return;
181 }
182 }
183 }
184 if let Some(entry) = self.scalars.iter_mut().find(|(k, _)| k == name) {
185 entry.1 = val;
186 } else {
187 self.scalars.push((name.to_string(), val));
188 }
189 }
190
191 #[inline]
192 fn get_array(&self, name: &str) -> Option<&Vec<PerlValue>> {
193 if name == "_" {
194 if let Some(ref v) = self.sub_underscore {
195 return Some(v);
196 }
197 }
198 self.arrays.iter().find(|(k, _)| k == name).map(|(_, v)| v)
199 }
200
201 #[inline]
202 fn has_array(&self, name: &str) -> bool {
203 if name == "_" && self.sub_underscore.is_some() {
204 return true;
205 }
206 self.arrays.iter().any(|(k, _)| k == name)
207 }
208
209 #[inline]
210 fn get_array_mut(&mut self, name: &str) -> Option<&mut Vec<PerlValue>> {
211 if name == "_" {
212 return self.sub_underscore.as_mut();
213 }
214 self.arrays
215 .iter_mut()
216 .find(|(k, _)| k == name)
217 .map(|(_, v)| v)
218 }
219
220 #[inline]
221 fn set_array(&mut self, name: &str, val: Vec<PerlValue>) {
222 if name == "_" {
223 if let Some(pos) = self.arrays.iter().position(|(k, _)| k == name) {
224 self.arrays.swap_remove(pos);
225 }
226 self.sub_underscore = Some(val);
227 return;
228 }
229 if let Some(entry) = self.arrays.iter_mut().find(|(k, _)| k == name) {
230 entry.1 = val;
231 } else {
232 self.arrays.push((name.to_string(), val));
233 }
234 }
235
236 #[inline]
237 fn get_hash(&self, name: &str) -> Option<&IndexMap<String, PerlValue>> {
238 self.hashes.iter().find(|(k, _)| k == name).map(|(_, v)| v)
239 }
240
241 #[inline]
242 fn has_hash(&self, name: &str) -> bool {
243 self.hashes.iter().any(|(k, _)| k == name)
244 }
245
246 #[inline]
247 fn get_hash_mut(&mut self, name: &str) -> Option<&mut IndexMap<String, PerlValue>> {
248 self.hashes
249 .iter_mut()
250 .find(|(k, _)| k == name)
251 .map(|(_, v)| v)
252 }
253
254 #[inline]
255 fn set_hash(&mut self, name: &str, val: IndexMap<String, PerlValue>) {
256 if let Some(entry) = self.hashes.iter_mut().find(|(k, _)| k == name) {
257 entry.1 = val;
258 } else {
259 self.hashes.push((name.to_string(), val));
260 }
261 }
262}
263
264#[derive(Debug, Clone)]
267pub struct Scope {
268 frames: Vec<Frame>,
269 frame_pool: Vec<Frame>,
271 parallel_guard: bool,
276}
277
278impl Default for Scope {
279 fn default() -> Self {
280 Self::new()
281 }
282}
283
284impl Scope {
285 pub fn new() -> Self {
286 let mut s = Self {
287 frames: Vec::with_capacity(32),
288 frame_pool: Vec::with_capacity(32),
289 parallel_guard: false,
290 };
291 s.frames.push(Frame::new());
292 s
293 }
294
295 #[inline]
297 pub fn set_parallel_guard(&mut self, enabled: bool) {
298 self.parallel_guard = enabled;
299 }
300
301 #[inline]
302 pub fn parallel_guard(&self) -> bool {
303 self.parallel_guard
304 }
305
306 #[inline]
307 fn parallel_skip_special_name(name: &str) -> bool {
308 name.contains("::")
309 }
310
311 #[inline]
313 fn parallel_allowed_topic_scalar(name: &str) -> bool {
314 matches!(name, "_" | "a" | "b")
315 }
316
317 #[inline]
319 fn parallel_allowed_internal_array(name: &str) -> bool {
320 matches!(name, "-" | "+" | "^CAPTURE" | "^CAPTURE_ALL")
321 }
322
323 #[inline]
325 fn parallel_allowed_internal_hash(name: &str) -> bool {
326 matches!(name, "+" | "-" | "ENV" | "INC")
327 }
328
329 fn check_parallel_scalar_write(&self, name: &str) -> Result<(), PerlError> {
330 if !self.parallel_guard || Self::parallel_skip_special_name(name) {
331 return Ok(());
332 }
333 if Self::parallel_allowed_topic_scalar(name) {
334 return Ok(());
335 }
336 if crate::special_vars::is_regex_match_scalar_name(name) {
337 return Ok(());
338 }
339 let inner = self.frames.len().saturating_sub(1);
340 for (i, frame) in self.frames.iter().enumerate().rev() {
341 if frame.has_scalar(name) {
342 if let Some(v) = frame.get_scalar(name) {
343 if v.as_atomic_arc().is_some() {
344 return Ok(());
345 }
346 }
347 if i != inner {
348 return Err(PerlError::runtime(
349 format!(
350 "cannot assign to captured non-mysync variable `${}` in a parallel block",
351 name
352 ),
353 0,
354 ));
355 }
356 return Ok(());
357 }
358 }
359 Err(PerlError::runtime(
360 format!(
361 "cannot assign to undeclared variable `${}` in a parallel block",
362 name
363 ),
364 0,
365 ))
366 }
367
368 #[inline]
369 pub fn depth(&self) -> usize {
370 self.frames.len()
371 }
372
373 #[inline]
376 pub fn pop_to_depth(&mut self, target_depth: usize) {
377 while self.frames.len() > target_depth && self.frames.len() > 1 {
378 self.pop_frame();
379 }
380 }
381
382 #[inline]
383 pub fn push_frame(&mut self) {
384 if let Some(mut frame) = self.frame_pool.pop() {
385 frame.clear_all_bindings();
386 self.frames.push(frame);
387 } else {
388 self.frames.push(Frame::new());
389 }
390 }
391
392 #[inline]
397 pub fn get_scalar_slot(&self, slot: u8) -> PerlValue {
398 let idx = slot as usize;
399 for frame in self.frames.iter().rev() {
400 if idx < frame.scalar_slots.len() && frame.owns_scalar_slot_index(idx) {
401 return frame.scalar_slots[idx].clone();
402 }
403 }
404 PerlValue::UNDEF
405 }
406
407 #[inline]
409 pub fn set_scalar_slot(&mut self, slot: u8, val: PerlValue) {
410 let idx = slot as usize;
411 let len = self.frames.len();
412 for i in (0..len).rev() {
413 if idx < self.frames[i].scalar_slots.len() && self.frames[i].owns_scalar_slot_index(idx)
414 {
415 self.frames[i].scalar_slots[idx] = val;
416 return;
417 }
418 }
419 let top = self.frames.last_mut().unwrap();
420 top.scalar_slots.resize(idx + 1, PerlValue::UNDEF);
421 if idx >= top.scalar_slot_names.len() {
422 top.scalar_slot_names.resize(idx + 1, None);
423 }
424 top.scalar_slot_names[idx] = Some(String::new());
425 top.scalar_slots[idx] = val;
426 }
427
428 #[inline]
432 pub fn set_scalar_slot_checked(
433 &mut self,
434 slot: u8,
435 val: PerlValue,
436 slot_name: Option<&str>,
437 ) -> Result<(), PerlError> {
438 if self.parallel_guard {
439 let idx = slot as usize;
440 let len = self.frames.len();
441 let top_has = idx < self.frames[len - 1].scalar_slots.len()
442 && self.frames[len - 1].owns_scalar_slot_index(idx);
443 if !top_has {
444 let name_owned: String = {
445 let mut found = String::new();
446 for i in (0..len).rev() {
447 if let Some(Some(n)) = self.frames[i].scalar_slot_names.get(idx) {
448 found = n.clone();
449 break;
450 }
451 }
452 if found.is_empty() {
453 if let Some(sn) = slot_name {
454 found = sn.to_string();
455 }
456 }
457 found
458 };
459 let name = name_owned.as_str();
460 if !name.is_empty() && !Self::parallel_allowed_topic_scalar(name) {
461 let inner = len.saturating_sub(1);
462 for (fi, frame) in self.frames.iter().enumerate().rev() {
463 if frame.has_scalar(name)
464 || (idx < frame.scalar_slots.len() && frame.owns_scalar_slot_index(idx))
465 {
466 if fi != inner {
467 return Err(PerlError::runtime(
468 format!(
469 "cannot assign to captured outer lexical `${}` inside a parallel block (use `mysync`)",
470 name
471 ),
472 0,
473 ));
474 }
475 break;
476 }
477 }
478 }
479 }
480 }
481 self.set_scalar_slot(slot, val);
482 Ok(())
483 }
484
485 #[inline]
489 pub fn declare_scalar_slot(&mut self, slot: u8, val: PerlValue, name: Option<&str>) {
490 let idx = slot as usize;
491 let frame = self.frames.last_mut().unwrap();
492 if idx >= frame.scalar_slots.len() {
493 frame.scalar_slots.resize(idx + 1, PerlValue::UNDEF);
494 }
495 frame.scalar_slots[idx] = val;
496 if idx >= frame.scalar_slot_names.len() {
497 frame.scalar_slot_names.resize(idx + 1, None);
498 }
499 match name {
500 Some(n) => frame.scalar_slot_names[idx] = Some(n.to_string()),
501 None => frame.scalar_slot_names[idx] = Some(String::new()),
503 }
504 }
505
506 #[inline]
517 pub fn scalar_slot_concat_repeat_inplace(&mut self, slot: u8, rhs: &str, n: usize) -> bool {
518 let idx = slot as usize;
519 let len = self.frames.len();
520 let fi = {
521 let mut found = len - 1;
522 if idx >= self.frames[found].scalar_slots.len()
523 || !self.frames[found].owns_scalar_slot_index(idx)
524 {
525 for i in (0..len - 1).rev() {
526 if idx < self.frames[i].scalar_slots.len()
527 && self.frames[i].owns_scalar_slot_index(idx)
528 {
529 found = i;
530 break;
531 }
532 }
533 }
534 found
535 };
536 let frame = &mut self.frames[fi];
537 if idx >= frame.scalar_slots.len() {
538 frame.scalar_slots.resize(idx + 1, PerlValue::UNDEF);
539 }
540 frame.scalar_slots[idx].try_concat_repeat_inplace(rhs, n)
541 }
542
543 #[inline]
548 pub fn scalar_slot_concat_repeat_slow(&mut self, slot: u8, rhs: &str, n: usize) {
549 let pv = PerlValue::string(rhs.to_owned());
550 for _ in 0..n {
551 let _ = self.scalar_slot_concat_inplace(slot, &pv);
552 }
553 }
554
555 #[inline]
556 pub fn scalar_slot_concat_inplace(&mut self, slot: u8, rhs: &PerlValue) -> PerlValue {
557 let idx = slot as usize;
558 let len = self.frames.len();
559 let fi = {
560 let mut found = len - 1;
561 if idx >= self.frames[found].scalar_slots.len()
562 || !self.frames[found].owns_scalar_slot_index(idx)
563 {
564 for i in (0..len - 1).rev() {
565 if idx < self.frames[i].scalar_slots.len()
566 && self.frames[i].owns_scalar_slot_index(idx)
567 {
568 found = i;
569 break;
570 }
571 }
572 }
573 found
574 };
575 let frame = &mut self.frames[fi];
576 if idx >= frame.scalar_slots.len() {
577 frame.scalar_slots.resize(idx + 1, PerlValue::UNDEF);
578 }
579 if frame.scalar_slots[idx].try_concat_append_inplace(rhs) {
587 return frame.scalar_slots[idx].shallow_clone();
588 }
589 let new_val = std::mem::replace(&mut frame.scalar_slots[idx], PerlValue::UNDEF)
590 .concat_append_owned(rhs);
591 let handle = new_val.shallow_clone();
592 frame.scalar_slots[idx] = new_val;
593 handle
594 }
595
596 #[inline]
597 pub(crate) fn can_pop_frame(&self) -> bool {
598 self.frames.len() > 1
599 }
600
601 #[inline]
602 pub fn pop_frame(&mut self) {
603 if self.frames.len() > 1 {
604 let mut frame = self.frames.pop().expect("pop_frame");
605 let saved_guard = self.parallel_guard;
608 self.parallel_guard = false;
609 for entry in frame.local_restores.drain(..).rev() {
610 match entry {
611 LocalRestore::Scalar(name, old) => {
612 let _ = self.set_scalar(&name, old);
613 }
614 LocalRestore::Array(name, old) => {
615 let _ = self.set_array(&name, old);
616 }
617 LocalRestore::Hash(name, old) => {
618 let _ = self.set_hash(&name, old);
619 }
620 LocalRestore::HashElement(name, key, old) => match old {
621 Some(v) => {
622 let _ = self.set_hash_element(&name, &key, v);
623 }
624 None => {
625 let _ = self.delete_hash_element(&name, &key);
626 }
627 },
628 LocalRestore::ArrayElement(name, index, old) => {
629 let _ = self.set_array_element(&name, index, old);
630 }
631 }
632 }
633 self.parallel_guard = saved_guard;
634 frame.clear_all_bindings();
635 if self.frame_pool.len() < 64 {
637 self.frame_pool.push(frame);
638 }
639 }
640 }
641
642 pub fn local_set_scalar(&mut self, name: &str, val: PerlValue) -> Result<(), PerlError> {
644 let old = self.get_scalar(name);
645 if let Some(frame) = self.frames.last_mut() {
646 frame
647 .local_restores
648 .push(LocalRestore::Scalar(name.to_string(), old));
649 }
650 self.set_scalar(name, val)
651 }
652
653 pub fn local_set_array(&mut self, name: &str, val: Vec<PerlValue>) -> Result<(), PerlError> {
655 if self.find_atomic_array(name).is_some() {
656 return Err(PerlError::runtime(
657 "local cannot be used on mysync arrays",
658 0,
659 ));
660 }
661 let old = self.get_array(name);
662 if let Some(frame) = self.frames.last_mut() {
663 frame
664 .local_restores
665 .push(LocalRestore::Array(name.to_string(), old));
666 }
667 self.set_array(name, val)?;
668 Ok(())
669 }
670
671 pub fn local_set_hash(
673 &mut self,
674 name: &str,
675 val: IndexMap<String, PerlValue>,
676 ) -> Result<(), PerlError> {
677 if self.find_atomic_hash(name).is_some() {
678 return Err(PerlError::runtime(
679 "local cannot be used on mysync hashes",
680 0,
681 ));
682 }
683 let old = self.get_hash(name);
684 if let Some(frame) = self.frames.last_mut() {
685 frame
686 .local_restores
687 .push(LocalRestore::Hash(name.to_string(), old));
688 }
689 self.set_hash(name, val)?;
690 Ok(())
691 }
692
693 pub fn local_set_hash_element(
695 &mut self,
696 name: &str,
697 key: &str,
698 val: PerlValue,
699 ) -> Result<(), PerlError> {
700 if self.find_atomic_hash(name).is_some() {
701 return Err(PerlError::runtime(
702 "local cannot be used on mysync hash elements",
703 0,
704 ));
705 }
706 let old = if self.exists_hash_element(name, key) {
707 Some(self.get_hash_element(name, key))
708 } else {
709 None
710 };
711 if let Some(frame) = self.frames.last_mut() {
712 frame.local_restores.push(LocalRestore::HashElement(
713 name.to_string(),
714 key.to_string(),
715 old,
716 ));
717 }
718 self.set_hash_element(name, key, val)?;
719 Ok(())
720 }
721
722 pub fn local_set_array_element(
725 &mut self,
726 name: &str,
727 index: i64,
728 val: PerlValue,
729 ) -> Result<(), PerlError> {
730 if self.find_atomic_array(name).is_some() {
731 return Err(PerlError::runtime(
732 "local cannot be used on mysync array elements",
733 0,
734 ));
735 }
736 let old = self.get_array_element(name, index);
737 if let Some(frame) = self.frames.last_mut() {
738 frame
739 .local_restores
740 .push(LocalRestore::ArrayElement(name.to_string(), index, old));
741 }
742 self.set_array_element(name, index, val)?;
743 Ok(())
744 }
745
746 #[inline]
749 pub fn declare_scalar(&mut self, name: &str, val: PerlValue) {
750 let _ = self.declare_scalar_frozen(name, val, false, None);
751 }
752
753 pub fn declare_scalar_frozen(
756 &mut self,
757 name: &str,
758 val: PerlValue,
759 frozen: bool,
760 ty: Option<PerlTypeName>,
761 ) -> Result<(), PerlError> {
762 if let Some(ref t) = ty {
763 t.check_value(&val)
764 .map_err(|msg| PerlError::type_error(format!("`${}`: {}", name, msg), 0))?;
765 }
766 if let Some(frame) = self.frames.last_mut() {
767 frame.set_scalar(name, val);
768 if frozen {
769 frame.frozen_scalars.insert(name.to_string());
770 }
771 if let Some(t) = ty {
772 frame.typed_scalars.insert(name.to_string(), t);
773 }
774 }
775 Ok(())
776 }
777
778 pub fn is_scalar_frozen(&self, name: &str) -> bool {
780 for frame in self.frames.iter().rev() {
781 if frame.has_scalar(name) {
782 return frame.frozen_scalars.contains(name);
783 }
784 }
785 false
786 }
787
788 pub fn is_array_frozen(&self, name: &str) -> bool {
790 for frame in self.frames.iter().rev() {
791 if frame.has_array(name) {
792 return frame.frozen_arrays.contains(name);
793 }
794 }
795 false
796 }
797
798 pub fn is_hash_frozen(&self, name: &str) -> bool {
800 for frame in self.frames.iter().rev() {
801 if frame.has_hash(name) {
802 return frame.frozen_hashes.contains(name);
803 }
804 }
805 false
806 }
807
808 pub fn check_frozen(&self, sigil: &str, name: &str) -> Option<&'static str> {
810 match sigil {
811 "$" => {
812 if self.is_scalar_frozen(name) {
813 Some("scalar")
814 } else {
815 None
816 }
817 }
818 "@" => {
819 if self.is_array_frozen(name) {
820 Some("array")
821 } else {
822 None
823 }
824 }
825 "%" => {
826 if self.is_hash_frozen(name) {
827 Some("hash")
828 } else {
829 None
830 }
831 }
832 _ => None,
833 }
834 }
835
836 #[inline]
837 pub fn get_scalar(&self, name: &str) -> PerlValue {
838 for frame in self.frames.iter().rev() {
839 if let Some(val) = frame.get_scalar(name) {
840 if let Some(arc) = val.as_atomic_arc() {
842 return arc.lock().clone();
843 }
844 if let Some(arc) = val.as_scalar_ref() {
846 return arc.read().clone();
847 }
848 return val.clone();
849 }
850 }
851 PerlValue::UNDEF
852 }
853
854 #[inline]
856 pub fn scalar_binding_exists(&self, name: &str) -> bool {
857 for frame in self.frames.iter().rev() {
858 if frame.has_scalar(name) {
859 return true;
860 }
861 }
862 false
863 }
864
865 pub fn all_scalar_names(&self) -> Vec<String> {
867 let mut names = Vec::new();
868 for frame in &self.frames {
869 for (name, _) in &frame.scalars {
870 if !names.contains(name) {
871 names.push(name.clone());
872 }
873 }
874 for name in frame.scalar_slot_names.iter().flatten() {
875 if !names.contains(name) {
876 names.push(name.clone());
877 }
878 }
879 }
880 names
881 }
882
883 #[inline]
885 pub fn array_binding_exists(&self, name: &str) -> bool {
886 if self.find_atomic_array(name).is_some() {
887 return true;
888 }
889 for frame in self.frames.iter().rev() {
890 if frame.has_array(name) {
891 return true;
892 }
893 }
894 false
895 }
896
897 #[inline]
899 pub fn hash_binding_exists(&self, name: &str) -> bool {
900 if self.find_atomic_hash(name).is_some() {
901 return true;
902 }
903 for frame in self.frames.iter().rev() {
904 if frame.has_hash(name) {
905 return true;
906 }
907 }
908 false
909 }
910
911 #[inline]
914 pub fn get_scalar_raw(&self, name: &str) -> PerlValue {
915 for frame in self.frames.iter().rev() {
916 if let Some(val) = frame.get_scalar(name) {
917 return val.clone();
918 }
919 }
920 PerlValue::UNDEF
921 }
922
923 pub fn atomic_mutate(
927 &mut self,
928 name: &str,
929 f: impl FnOnce(&PerlValue) -> PerlValue,
930 ) -> PerlValue {
931 for frame in self.frames.iter().rev() {
932 if let Some(v) = frame.get_scalar(name) {
933 if let Some(arc) = v.as_atomic_arc() {
934 let mut guard = arc.lock();
935 let old = guard.clone();
936 let new_val = f(&guard);
937 *guard = new_val.clone();
938 crate::parallel_trace::emit_scalar_mutation(name, &old, &new_val);
939 return new_val;
940 }
941 }
942 }
943 let old = self.get_scalar(name);
945 let new_val = f(&old);
946 let _ = self.set_scalar(name, new_val.clone());
947 new_val
948 }
949
950 pub fn atomic_mutate_post(
952 &mut self,
953 name: &str,
954 f: impl FnOnce(&PerlValue) -> PerlValue,
955 ) -> PerlValue {
956 for frame in self.frames.iter().rev() {
957 if let Some(v) = frame.get_scalar(name) {
958 if let Some(arc) = v.as_atomic_arc() {
959 let mut guard = arc.lock();
960 let old = guard.clone();
961 let new_val = f(&old);
962 *guard = new_val.clone();
963 crate::parallel_trace::emit_scalar_mutation(name, &old, &new_val);
964 return old;
965 }
966 }
967 }
968 let old = self.get_scalar(name);
970 let _ = self.set_scalar(name, f(&old));
971 old
972 }
973
974 #[inline]
981 pub fn scalar_concat_inplace(
982 &mut self,
983 name: &str,
984 rhs: &PerlValue,
985 ) -> Result<PerlValue, PerlError> {
986 self.check_parallel_scalar_write(name)?;
987 for frame in self.frames.iter_mut().rev() {
988 if let Some(entry) = frame.scalars.iter_mut().find(|(k, _)| k == name) {
989 if let Some(atomic_arc) = entry.1.as_atomic_arc() {
992 let mut guard = atomic_arc.lock();
993 let inner = std::mem::replace(&mut *guard, PerlValue::UNDEF);
994 let new_val = inner.concat_append_owned(rhs);
995 *guard = new_val.shallow_clone();
996 return Ok(new_val);
997 }
998 if entry.1.try_concat_append_inplace(rhs) {
1001 return Ok(entry.1.shallow_clone());
1002 }
1003 let new_val =
1006 std::mem::replace(&mut entry.1, PerlValue::UNDEF).concat_append_owned(rhs);
1007 entry.1 = new_val.shallow_clone();
1008 return Ok(new_val);
1009 }
1010 }
1011 let val = PerlValue::UNDEF.concat_append_owned(rhs);
1013 self.frames[0].set_scalar(name, val.shallow_clone());
1014 Ok(val)
1015 }
1016
1017 #[inline]
1018 pub fn set_scalar(&mut self, name: &str, val: PerlValue) -> Result<(), PerlError> {
1019 self.check_parallel_scalar_write(name)?;
1020 for frame in self.frames.iter_mut().rev() {
1021 if let Some(v) = frame.get_scalar(name) {
1023 if let Some(arc) = v.as_atomic_arc() {
1024 let mut guard = arc.lock();
1025 let old = guard.clone();
1026 *guard = val.clone();
1027 crate::parallel_trace::emit_scalar_mutation(name, &old, &val);
1028 return Ok(());
1029 }
1030 if let Some(arc) = v.as_scalar_ref() {
1032 *arc.write() = val;
1033 return Ok(());
1034 }
1035 }
1036 if frame.has_scalar(name) {
1037 if let Some(ty) = frame.typed_scalars.get(name) {
1038 ty.check_value(&val)
1039 .map_err(|msg| PerlError::type_error(format!("`${}`: {}", name, msg), 0))?;
1040 }
1041 frame.set_scalar(name, val);
1042 return Ok(());
1043 }
1044 }
1045 self.frames[0].set_scalar(name, val);
1046 Ok(())
1047 }
1048
1049 #[inline]
1056 pub fn set_topic(&mut self, val: PerlValue) {
1057 let old_3lt = self.get_scalar("_<<<");
1061 let old_2lt = self.get_scalar("_<<");
1062 let old_1lt = self.get_scalar("_<");
1063 let old_topic = self.get_scalar("_");
1064
1065 self.declare_scalar("_", val.clone());
1067 self.declare_scalar("_0", val);
1068 if !old_topic.is_undef() {
1070 self.declare_scalar("_<", old_topic);
1071 }
1072 if !old_1lt.is_undef() {
1073 self.declare_scalar("_<<", old_1lt);
1074 }
1075 if !old_2lt.is_undef() {
1076 self.declare_scalar("_<<<", old_2lt);
1077 }
1078 if !old_3lt.is_undef() {
1079 self.declare_scalar("_<<<<", old_3lt);
1080 }
1081 }
1082
1083 #[inline]
1086 pub fn set_closure_args(&mut self, args: &[PerlValue]) {
1087 if let Some(first) = args.first() {
1088 self.set_topic(first.clone());
1090 }
1091 for (i, val) in args.iter().enumerate() {
1092 self.declare_scalar(&format!("_{}", i), val.clone());
1093 }
1094 }
1095
1096 #[inline]
1098 pub fn push_defer(&mut self, coderef: PerlValue) {
1099 if let Some(frame) = self.frames.last_mut() {
1100 frame.defers.push(coderef);
1101 }
1102 }
1103
1104 #[inline]
1107 pub fn take_defers(&mut self) -> Vec<PerlValue> {
1108 if let Some(frame) = self.frames.last_mut() {
1109 let mut defers = std::mem::take(&mut frame.defers);
1110 defers.reverse();
1111 defers
1112 } else {
1113 Vec::new()
1114 }
1115 }
1116
1117 pub fn declare_atomic_array(&mut self, name: &str, val: Vec<PerlValue>) {
1120 if let Some(frame) = self.frames.last_mut() {
1121 frame
1122 .atomic_arrays
1123 .push((name.to_string(), AtomicArray(Arc::new(Mutex::new(val)))));
1124 }
1125 }
1126
1127 pub fn declare_atomic_hash(&mut self, name: &str, val: IndexMap<String, PerlValue>) {
1128 if let Some(frame) = self.frames.last_mut() {
1129 frame
1130 .atomic_hashes
1131 .push((name.to_string(), AtomicHash(Arc::new(Mutex::new(val)))));
1132 }
1133 }
1134
1135 fn find_atomic_array(&self, name: &str) -> Option<&AtomicArray> {
1137 for frame in self.frames.iter().rev() {
1138 if let Some(aa) = frame.atomic_arrays.iter().find(|(k, _)| k == name) {
1139 return Some(&aa.1);
1140 }
1141 }
1142 None
1143 }
1144
1145 fn find_atomic_hash(&self, name: &str) -> Option<&AtomicHash> {
1147 for frame in self.frames.iter().rev() {
1148 if let Some(ah) = frame.atomic_hashes.iter().find(|(k, _)| k == name) {
1149 return Some(&ah.1);
1150 }
1151 }
1152 None
1153 }
1154
1155 #[inline]
1160 pub fn take_sub_underscore(&mut self) -> Option<Vec<PerlValue>> {
1161 self.frames.last_mut()?.sub_underscore.take()
1162 }
1163
1164 pub fn declare_array(&mut self, name: &str, val: Vec<PerlValue>) {
1165 self.declare_array_frozen(name, val, false);
1166 }
1167
1168 pub fn declare_array_frozen(&mut self, name: &str, val: Vec<PerlValue>, frozen: bool) {
1169 let idx = if name.contains("::") {
1172 0
1173 } else {
1174 self.frames.len().saturating_sub(1)
1175 };
1176 if let Some(frame) = self.frames.get_mut(idx) {
1177 frame.set_array(name, val);
1178 if frozen {
1179 frame.frozen_arrays.insert(name.to_string());
1180 }
1181 }
1182 }
1183
1184 pub fn get_array(&self, name: &str) -> Vec<PerlValue> {
1185 if let Some(aa) = self.find_atomic_array(name) {
1187 return aa.0.lock().clone();
1188 }
1189 if name.contains("::") {
1190 if let Some(f) = self.frames.first() {
1191 if let Some(val) = f.get_array(name) {
1192 return val.clone();
1193 }
1194 }
1195 return Vec::new();
1196 }
1197 for frame in self.frames.iter().rev() {
1198 if let Some(val) = frame.get_array(name) {
1199 return val.clone();
1200 }
1201 }
1202 Vec::new()
1203 }
1204
1205 #[inline]
1208 pub fn get_array_borrow(&self, name: &str) -> Option<&[PerlValue]> {
1209 if self.find_atomic_array(name).is_some() {
1210 return None;
1211 }
1212 if name.contains("::") {
1213 return self
1214 .frames
1215 .first()
1216 .and_then(|f| f.get_array(name))
1217 .map(|v| v.as_slice());
1218 }
1219 for frame in self.frames.iter().rev() {
1220 if let Some(val) = frame.get_array(name) {
1221 return Some(val.as_slice());
1222 }
1223 }
1224 None
1225 }
1226
1227 fn resolve_array_frame_idx(&self, name: &str) -> Option<usize> {
1228 if name.contains("::") {
1229 return Some(0);
1230 }
1231 (0..self.frames.len())
1232 .rev()
1233 .find(|&i| self.frames[i].has_array(name))
1234 }
1235
1236 fn check_parallel_array_write(&self, name: &str) -> Result<(), PerlError> {
1237 if !self.parallel_guard
1238 || Self::parallel_skip_special_name(name)
1239 || Self::parallel_allowed_internal_array(name)
1240 {
1241 return Ok(());
1242 }
1243 let inner = self.frames.len().saturating_sub(1);
1244 match self.resolve_array_frame_idx(name) {
1245 None => Err(PerlError::runtime(
1246 format!(
1247 "cannot modify undeclared array `@{}` in a parallel block",
1248 name
1249 ),
1250 0,
1251 )),
1252 Some(idx) if idx != inner => Err(PerlError::runtime(
1253 format!(
1254 "cannot modify captured non-mysync array `@{}` in a parallel block",
1255 name
1256 ),
1257 0,
1258 )),
1259 Some(_) => Ok(()),
1260 }
1261 }
1262
1263 pub fn get_array_mut(&mut self, name: &str) -> Result<&mut Vec<PerlValue>, PerlError> {
1264 if self.find_atomic_array(name).is_some() {
1267 return Err(PerlError::runtime(
1268 "get_array_mut: use atomic path for mysync arrays",
1269 0,
1270 ));
1271 }
1272 self.check_parallel_array_write(name)?;
1273 let idx = self.resolve_array_frame_idx(name).unwrap_or_default();
1274 let frame = &mut self.frames[idx];
1275 if frame.get_array_mut(name).is_none() {
1276 frame.arrays.push((name.to_string(), Vec::new()));
1277 }
1278 Ok(frame.get_array_mut(name).unwrap())
1279 }
1280
1281 pub fn push_to_array(&mut self, name: &str, val: PerlValue) -> Result<(), PerlError> {
1283 if let Some(aa) = self.find_atomic_array(name) {
1284 aa.0.lock().push(val);
1285 return Ok(());
1286 }
1287 self.get_array_mut(name)?.push(val);
1288 Ok(())
1289 }
1290
1291 pub fn push_int_range_to_array(
1295 &mut self,
1296 name: &str,
1297 start: i64,
1298 end: i64,
1299 ) -> Result<(), PerlError> {
1300 if end <= start {
1301 return Ok(());
1302 }
1303 let count = (end - start) as usize;
1304 if let Some(aa) = self.find_atomic_array(name) {
1305 let mut g = aa.0.lock();
1306 g.reserve(count);
1307 for i in start..end {
1308 g.push(PerlValue::integer(i));
1309 }
1310 return Ok(());
1311 }
1312 let arr = self.get_array_mut(name)?;
1313 arr.reserve(count);
1314 for i in start..end {
1315 arr.push(PerlValue::integer(i));
1316 }
1317 Ok(())
1318 }
1319
1320 pub fn pop_from_array(&mut self, name: &str) -> Result<PerlValue, PerlError> {
1322 if let Some(aa) = self.find_atomic_array(name) {
1323 return Ok(aa.0.lock().pop().unwrap_or(PerlValue::UNDEF));
1324 }
1325 Ok(self.get_array_mut(name)?.pop().unwrap_or(PerlValue::UNDEF))
1326 }
1327
1328 pub fn shift_from_array(&mut self, name: &str) -> Result<PerlValue, PerlError> {
1330 if let Some(aa) = self.find_atomic_array(name) {
1331 let mut guard = aa.0.lock();
1332 return Ok(if guard.is_empty() {
1333 PerlValue::UNDEF
1334 } else {
1335 guard.remove(0)
1336 });
1337 }
1338 let arr = self.get_array_mut(name)?;
1339 Ok(if arr.is_empty() {
1340 PerlValue::UNDEF
1341 } else {
1342 arr.remove(0)
1343 })
1344 }
1345
1346 pub fn array_len(&self, name: &str) -> usize {
1348 if let Some(aa) = self.find_atomic_array(name) {
1349 return aa.0.lock().len();
1350 }
1351 if name.contains("::") {
1352 return self
1353 .frames
1354 .first()
1355 .and_then(|f| f.get_array(name))
1356 .map(|a| a.len())
1357 .unwrap_or(0);
1358 }
1359 for frame in self.frames.iter().rev() {
1360 if let Some(arr) = frame.get_array(name) {
1361 return arr.len();
1362 }
1363 }
1364 0
1365 }
1366
1367 pub fn set_array(&mut self, name: &str, val: Vec<PerlValue>) -> Result<(), PerlError> {
1368 if let Some(aa) = self.find_atomic_array(name) {
1369 *aa.0.lock() = val;
1370 return Ok(());
1371 }
1372 self.check_parallel_array_write(name)?;
1373 for frame in self.frames.iter_mut().rev() {
1374 if frame.has_array(name) {
1375 frame.set_array(name, val);
1376 return Ok(());
1377 }
1378 }
1379 self.frames[0].set_array(name, val);
1380 Ok(())
1381 }
1382
1383 #[inline]
1385 pub fn get_array_element(&self, name: &str, index: i64) -> PerlValue {
1386 if let Some(aa) = self.find_atomic_array(name) {
1387 let arr = aa.0.lock();
1388 let idx = if index < 0 {
1389 (arr.len() as i64 + index) as usize
1390 } else {
1391 index as usize
1392 };
1393 return arr.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
1394 }
1395 for frame in self.frames.iter().rev() {
1396 if let Some(arr) = frame.get_array(name) {
1397 let idx = if index < 0 {
1398 (arr.len() as i64 + index) as usize
1399 } else {
1400 index as usize
1401 };
1402 return arr.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
1403 }
1404 }
1405 PerlValue::UNDEF
1406 }
1407
1408 pub fn set_array_element(
1409 &mut self,
1410 name: &str,
1411 index: i64,
1412 val: PerlValue,
1413 ) -> Result<(), PerlError> {
1414 if let Some(aa) = self.find_atomic_array(name) {
1415 let mut arr = aa.0.lock();
1416 let idx = if index < 0 {
1417 (arr.len() as i64 + index).max(0) as usize
1418 } else {
1419 index as usize
1420 };
1421 if idx >= arr.len() {
1422 arr.resize(idx + 1, PerlValue::UNDEF);
1423 }
1424 arr[idx] = val;
1425 return Ok(());
1426 }
1427 let arr = self.get_array_mut(name)?;
1428 let idx = if index < 0 {
1429 let len = arr.len() as i64;
1430 (len + index).max(0) as usize
1431 } else {
1432 index as usize
1433 };
1434 if idx >= arr.len() {
1435 arr.resize(idx + 1, PerlValue::UNDEF);
1436 }
1437 arr[idx] = val;
1438 Ok(())
1439 }
1440
1441 pub fn exists_array_element(&self, name: &str, index: i64) -> bool {
1443 if let Some(aa) = self.find_atomic_array(name) {
1444 let arr = aa.0.lock();
1445 let idx = if index < 0 {
1446 (arr.len() as i64 + index) as usize
1447 } else {
1448 index as usize
1449 };
1450 return idx < arr.len();
1451 }
1452 for frame in self.frames.iter().rev() {
1453 if let Some(arr) = frame.get_array(name) {
1454 let idx = if index < 0 {
1455 (arr.len() as i64 + index) as usize
1456 } else {
1457 index as usize
1458 };
1459 return idx < arr.len();
1460 }
1461 }
1462 false
1463 }
1464
1465 pub fn delete_array_element(&mut self, name: &str, index: i64) -> Result<PerlValue, PerlError> {
1467 if let Some(aa) = self.find_atomic_array(name) {
1468 let mut arr = aa.0.lock();
1469 let idx = if index < 0 {
1470 (arr.len() as i64 + index) as usize
1471 } else {
1472 index as usize
1473 };
1474 if idx >= arr.len() {
1475 return Ok(PerlValue::UNDEF);
1476 }
1477 let old = arr.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
1478 arr[idx] = PerlValue::UNDEF;
1479 return Ok(old);
1480 }
1481 let arr = self.get_array_mut(name)?;
1482 let idx = if index < 0 {
1483 (arr.len() as i64 + index) as usize
1484 } else {
1485 index as usize
1486 };
1487 if idx >= arr.len() {
1488 return Ok(PerlValue::UNDEF);
1489 }
1490 let old = arr.get(idx).cloned().unwrap_or(PerlValue::UNDEF);
1491 arr[idx] = PerlValue::UNDEF;
1492 Ok(old)
1493 }
1494
1495 #[inline]
1498 pub fn declare_hash(&mut self, name: &str, val: IndexMap<String, PerlValue>) {
1499 self.declare_hash_frozen(name, val, false);
1500 }
1501
1502 pub fn declare_hash_frozen(
1503 &mut self,
1504 name: &str,
1505 val: IndexMap<String, PerlValue>,
1506 frozen: bool,
1507 ) {
1508 if let Some(frame) = self.frames.last_mut() {
1509 frame.set_hash(name, val);
1510 if frozen {
1511 frame.frozen_hashes.insert(name.to_string());
1512 }
1513 }
1514 }
1515
1516 pub fn get_hash(&self, name: &str) -> IndexMap<String, PerlValue> {
1517 if let Some(ah) = self.find_atomic_hash(name) {
1518 return ah.0.lock().clone();
1519 }
1520 for frame in self.frames.iter().rev() {
1521 if let Some(val) = frame.get_hash(name) {
1522 return val.clone();
1523 }
1524 }
1525 IndexMap::new()
1526 }
1527
1528 fn resolve_hash_frame_idx(&self, name: &str) -> Option<usize> {
1529 if name.contains("::") {
1530 return Some(0);
1531 }
1532 (0..self.frames.len())
1533 .rev()
1534 .find(|&i| self.frames[i].has_hash(name))
1535 }
1536
1537 fn check_parallel_hash_write(&self, name: &str) -> Result<(), PerlError> {
1538 if !self.parallel_guard
1539 || Self::parallel_skip_special_name(name)
1540 || Self::parallel_allowed_internal_hash(name)
1541 {
1542 return Ok(());
1543 }
1544 let inner = self.frames.len().saturating_sub(1);
1545 match self.resolve_hash_frame_idx(name) {
1546 None => Err(PerlError::runtime(
1547 format!(
1548 "cannot modify undeclared hash `%{}` in a parallel block",
1549 name
1550 ),
1551 0,
1552 )),
1553 Some(idx) if idx != inner => Err(PerlError::runtime(
1554 format!(
1555 "cannot modify captured non-mysync hash `%{}` in a parallel block",
1556 name
1557 ),
1558 0,
1559 )),
1560 Some(_) => Ok(()),
1561 }
1562 }
1563
1564 pub fn get_hash_mut(
1565 &mut self,
1566 name: &str,
1567 ) -> Result<&mut IndexMap<String, PerlValue>, PerlError> {
1568 if self.find_atomic_hash(name).is_some() {
1569 return Err(PerlError::runtime(
1570 "get_hash_mut: use atomic path for mysync hashes",
1571 0,
1572 ));
1573 }
1574 self.check_parallel_hash_write(name)?;
1575 let idx = self.resolve_hash_frame_idx(name).unwrap_or_default();
1576 let frame = &mut self.frames[idx];
1577 if frame.get_hash_mut(name).is_none() {
1578 frame.hashes.push((name.to_string(), IndexMap::new()));
1579 }
1580 Ok(frame.get_hash_mut(name).unwrap())
1581 }
1582
1583 pub fn set_hash(
1584 &mut self,
1585 name: &str,
1586 val: IndexMap<String, PerlValue>,
1587 ) -> Result<(), PerlError> {
1588 if let Some(ah) = self.find_atomic_hash(name) {
1589 *ah.0.lock() = val;
1590 return Ok(());
1591 }
1592 self.check_parallel_hash_write(name)?;
1593 for frame in self.frames.iter_mut().rev() {
1594 if frame.has_hash(name) {
1595 frame.set_hash(name, val);
1596 return Ok(());
1597 }
1598 }
1599 self.frames[0].set_hash(name, val);
1600 Ok(())
1601 }
1602
1603 #[inline]
1604 pub fn get_hash_element(&self, name: &str, key: &str) -> PerlValue {
1605 if let Some(ah) = self.find_atomic_hash(name) {
1606 return ah.0.lock().get(key).cloned().unwrap_or(PerlValue::UNDEF);
1607 }
1608 for frame in self.frames.iter().rev() {
1609 if let Some(hash) = frame.get_hash(name) {
1610 return hash.get(key).cloned().unwrap_or(PerlValue::UNDEF);
1611 }
1612 }
1613 PerlValue::UNDEF
1614 }
1615
1616 pub fn atomic_hash_mutate(
1619 &mut self,
1620 name: &str,
1621 key: &str,
1622 f: impl FnOnce(&PerlValue) -> PerlValue,
1623 ) -> Result<PerlValue, PerlError> {
1624 if let Some(ah) = self.find_atomic_hash(name) {
1625 let mut guard = ah.0.lock();
1626 let old = guard.get(key).cloned().unwrap_or(PerlValue::UNDEF);
1627 let new_val = f(&old);
1628 guard.insert(key.to_string(), new_val.clone());
1629 return Ok(new_val);
1630 }
1631 let old = self.get_hash_element(name, key);
1633 let new_val = f(&old);
1634 self.set_hash_element(name, key, new_val.clone())?;
1635 Ok(new_val)
1636 }
1637
1638 pub fn atomic_array_mutate(
1640 &mut self,
1641 name: &str,
1642 index: i64,
1643 f: impl FnOnce(&PerlValue) -> PerlValue,
1644 ) -> Result<PerlValue, PerlError> {
1645 if let Some(aa) = self.find_atomic_array(name) {
1646 let mut guard = aa.0.lock();
1647 let idx = if index < 0 {
1648 (guard.len() as i64 + index).max(0) as usize
1649 } else {
1650 index as usize
1651 };
1652 if idx >= guard.len() {
1653 guard.resize(idx + 1, PerlValue::UNDEF);
1654 }
1655 let old = guard[idx].clone();
1656 let new_val = f(&old);
1657 guard[idx] = new_val.clone();
1658 return Ok(new_val);
1659 }
1660 let old = self.get_array_element(name, index);
1662 let new_val = f(&old);
1663 self.set_array_element(name, index, new_val.clone())?;
1664 Ok(new_val)
1665 }
1666
1667 pub fn set_hash_element(
1668 &mut self,
1669 name: &str,
1670 key: &str,
1671 val: PerlValue,
1672 ) -> Result<(), PerlError> {
1673 if name == "SIG" {
1676 crate::perl_signal::install(key);
1677 }
1678 if let Some(ah) = self.find_atomic_hash(name) {
1679 ah.0.lock().insert(key.to_string(), val);
1680 return Ok(());
1681 }
1682 let hash = self.get_hash_mut(name)?;
1683 hash.insert(key.to_string(), val);
1684 Ok(())
1685 }
1686
1687 pub fn set_hash_int_times_range(
1691 &mut self,
1692 name: &str,
1693 start: i64,
1694 end: i64,
1695 k: i64,
1696 ) -> Result<(), PerlError> {
1697 if end <= start {
1698 return Ok(());
1699 }
1700 let count = (end - start) as usize;
1701 if let Some(ah) = self.find_atomic_hash(name) {
1702 let mut g = ah.0.lock();
1703 g.reserve(count);
1704 let mut buf = itoa::Buffer::new();
1705 for i in start..end {
1706 let key = buf.format(i).to_owned();
1707 g.insert(key, PerlValue::integer(i.wrapping_mul(k)));
1708 }
1709 return Ok(());
1710 }
1711 let hash = self.get_hash_mut(name)?;
1712 hash.reserve(count);
1713 let mut buf = itoa::Buffer::new();
1714 for i in start..end {
1715 let key = buf.format(i).to_owned();
1716 hash.insert(key, PerlValue::integer(i.wrapping_mul(k)));
1717 }
1718 Ok(())
1719 }
1720
1721 pub fn delete_hash_element(&mut self, name: &str, key: &str) -> Result<PerlValue, PerlError> {
1722 if let Some(ah) = self.find_atomic_hash(name) {
1723 return Ok(ah.0.lock().shift_remove(key).unwrap_or(PerlValue::UNDEF));
1724 }
1725 let hash = self.get_hash_mut(name)?;
1726 Ok(hash.shift_remove(key).unwrap_or(PerlValue::UNDEF))
1727 }
1728
1729 #[inline]
1730 pub fn exists_hash_element(&self, name: &str, key: &str) -> bool {
1731 if let Some(ah) = self.find_atomic_hash(name) {
1732 return ah.0.lock().contains_key(key);
1733 }
1734 for frame in self.frames.iter().rev() {
1735 if let Some(hash) = frame.get_hash(name) {
1736 return hash.contains_key(key);
1737 }
1738 }
1739 false
1740 }
1741
1742 #[inline]
1747 pub fn for_each_hash_value(&self, name: &str, mut visit: impl FnMut(&PerlValue)) {
1748 if let Some(ah) = self.find_atomic_hash(name) {
1749 let g = ah.0.lock();
1750 for v in g.values() {
1751 visit(v);
1752 }
1753 return;
1754 }
1755 for frame in self.frames.iter().rev() {
1756 if let Some(hash) = frame.get_hash(name) {
1757 for v in hash.values() {
1758 visit(v);
1759 }
1760 return;
1761 }
1762 }
1763 }
1764
1765 pub fn repl_binding_names(&self) -> Vec<String> {
1767 let mut seen = HashSet::new();
1768 let mut out = Vec::new();
1769 for frame in &self.frames {
1770 for (name, _) in &frame.scalars {
1771 let s = format!("${}", name);
1772 if seen.insert(s.clone()) {
1773 out.push(s);
1774 }
1775 }
1776 for (name, _) in &frame.arrays {
1777 let s = format!("@{}", name);
1778 if seen.insert(s.clone()) {
1779 out.push(s);
1780 }
1781 }
1782 for (name, _) in &frame.hashes {
1783 let s = format!("%{}", name);
1784 if seen.insert(s.clone()) {
1785 out.push(s);
1786 }
1787 }
1788 for (name, _) in &frame.atomic_arrays {
1789 let s = format!("@{}", name);
1790 if seen.insert(s.clone()) {
1791 out.push(s);
1792 }
1793 }
1794 for (name, _) in &frame.atomic_hashes {
1795 let s = format!("%{}", name);
1796 if seen.insert(s.clone()) {
1797 out.push(s);
1798 }
1799 }
1800 }
1801 out.sort();
1802 out
1803 }
1804
1805 pub fn capture(&mut self) -> Vec<(String, PerlValue)> {
1806 let mut captured = Vec::new();
1807 for frame in &mut self.frames {
1808 for (k, v) in &mut frame.scalars {
1809 if v.as_scalar_ref().is_some() {
1815 captured.push((format!("${}", k), v.clone()));
1816 } else if v.is_simple_scalar() {
1817 let wrapped = PerlValue::scalar_ref(Arc::new(RwLock::new(v.clone())));
1818 *v = wrapped.clone();
1821 captured.push((format!("${}", k), wrapped));
1822 } else {
1823 captured.push((format!("${}", k), v.clone()));
1824 }
1825 }
1826 for (i, v) in frame.scalar_slots.iter().enumerate() {
1827 if let Some(Some(name)) = frame.scalar_slot_names.get(i) {
1828 let wrapped = if v.as_scalar_ref().is_some() {
1831 v.clone()
1832 } else {
1833 PerlValue::scalar_ref(Arc::new(RwLock::new(v.clone())))
1834 };
1835 captured.push((format!("$slot:{}:{}", i, name), wrapped));
1836 }
1837 }
1838 for (k, v) in &frame.arrays {
1839 if capture_skip_bootstrap_array(k) {
1840 continue;
1841 }
1842 if frame.frozen_arrays.contains(k) {
1843 captured.push((format!("@frozen:{}", k), PerlValue::array(v.clone())));
1844 } else {
1845 captured.push((format!("@{}", k), PerlValue::array(v.clone())));
1846 }
1847 }
1848 for (k, v) in &frame.hashes {
1849 if capture_skip_bootstrap_hash(k) {
1850 continue;
1851 }
1852 if frame.frozen_hashes.contains(k) {
1853 captured.push((format!("%frozen:{}", k), PerlValue::hash(v.clone())));
1854 } else {
1855 captured.push((format!("%{}", k), PerlValue::hash(v.clone())));
1856 }
1857 }
1858 for (k, _aa) in &frame.atomic_arrays {
1859 captured.push((
1860 format!("@sync_{}", k),
1861 PerlValue::atomic(Arc::new(Mutex::new(PerlValue::string(String::new())))),
1862 ));
1863 }
1864 for (k, _ah) in &frame.atomic_hashes {
1865 captured.push((
1866 format!("%sync_{}", k),
1867 PerlValue::atomic(Arc::new(Mutex::new(PerlValue::string(String::new())))),
1868 ));
1869 }
1870 }
1871 captured
1872 }
1873
1874 pub fn capture_with_atomics(&self) -> ScopeCaptureWithAtomics {
1876 let mut scalars = Vec::new();
1877 let mut arrays = Vec::new();
1878 let mut hashes = Vec::new();
1879 for frame in &self.frames {
1880 for (k, v) in &frame.scalars {
1881 scalars.push((format!("${}", k), v.clone()));
1882 }
1883 for (i, v) in frame.scalar_slots.iter().enumerate() {
1884 if let Some(Some(name)) = frame.scalar_slot_names.get(i) {
1885 scalars.push((format!("$slot:{}:{}", i, name), v.clone()));
1886 }
1887 }
1888 for (k, v) in &frame.arrays {
1889 if capture_skip_bootstrap_array(k) {
1890 continue;
1891 }
1892 if frame.frozen_arrays.contains(k) {
1893 scalars.push((format!("@frozen:{}", k), PerlValue::array(v.clone())));
1894 } else {
1895 scalars.push((format!("@{}", k), PerlValue::array(v.clone())));
1896 }
1897 }
1898 for (k, v) in &frame.hashes {
1899 if capture_skip_bootstrap_hash(k) {
1900 continue;
1901 }
1902 if frame.frozen_hashes.contains(k) {
1903 scalars.push((format!("%frozen:{}", k), PerlValue::hash(v.clone())));
1904 } else {
1905 scalars.push((format!("%{}", k), PerlValue::hash(v.clone())));
1906 }
1907 }
1908 for (k, aa) in &frame.atomic_arrays {
1909 arrays.push((k.clone(), aa.clone()));
1910 }
1911 for (k, ah) in &frame.atomic_hashes {
1912 hashes.push((k.clone(), ah.clone()));
1913 }
1914 }
1915 (scalars, arrays, hashes)
1916 }
1917
1918 pub fn restore_capture(&mut self, captured: &[(String, PerlValue)]) {
1919 for (name, val) in captured {
1920 if let Some(rest) = name.strip_prefix("$slot:") {
1921 if let Some(colon) = rest.find(':') {
1923 let idx: usize = rest[..colon].parse().unwrap_or(0);
1924 let sname = &rest[colon + 1..];
1925 self.declare_scalar_slot(idx as u8, val.clone(), Some(sname));
1926 self.declare_scalar(sname, val.clone());
1927 }
1928 } else if let Some(stripped) = name.strip_prefix('$') {
1929 self.declare_scalar(stripped, val.clone());
1930 } else if let Some(rest) = name.strip_prefix("@frozen:") {
1931 let arr = val.as_array_vec().unwrap_or_else(|| val.to_list());
1932 self.declare_array_frozen(rest, arr, true);
1933 } else if let Some(rest) = name.strip_prefix("%frozen:") {
1934 if let Some(h) = val.as_hash_map() {
1935 self.declare_hash_frozen(rest, h.clone(), true);
1936 }
1937 } else if let Some(rest) = name.strip_prefix('@') {
1938 if rest.starts_with("sync_") {
1939 continue;
1940 }
1941 let arr = val.as_array_vec().unwrap_or_else(|| val.to_list());
1942 self.declare_array(rest, arr);
1943 } else if let Some(rest) = name.strip_prefix('%') {
1944 if rest.starts_with("sync_") {
1945 continue;
1946 }
1947 if let Some(h) = val.as_hash_map() {
1948 self.declare_hash(rest, h.clone());
1949 }
1950 }
1951 }
1952 }
1953
1954 pub fn restore_atomics(
1956 &mut self,
1957 arrays: &[(String, AtomicArray)],
1958 hashes: &[(String, AtomicHash)],
1959 ) {
1960 if let Some(frame) = self.frames.last_mut() {
1961 for (name, aa) in arrays {
1962 frame.atomic_arrays.push((name.clone(), aa.clone()));
1963 }
1964 for (name, ah) in hashes {
1965 frame.atomic_hashes.push((name.clone(), ah.clone()));
1966 }
1967 }
1968 }
1969}
1970
1971#[cfg(test)]
1972mod tests {
1973 use super::*;
1974 use crate::value::PerlValue;
1975
1976 #[test]
1977 fn missing_scalar_is_undef() {
1978 let s = Scope::new();
1979 assert!(s.get_scalar("not_declared").is_undef());
1980 }
1981
1982 #[test]
1983 fn inner_frame_shadows_outer_scalar() {
1984 let mut s = Scope::new();
1985 s.declare_scalar("a", PerlValue::integer(1));
1986 s.push_frame();
1987 s.declare_scalar("a", PerlValue::integer(2));
1988 assert_eq!(s.get_scalar("a").to_int(), 2);
1989 s.pop_frame();
1990 assert_eq!(s.get_scalar("a").to_int(), 1);
1991 }
1992
1993 #[test]
1994 fn set_scalar_updates_innermost_binding() {
1995 let mut s = Scope::new();
1996 s.declare_scalar("a", PerlValue::integer(1));
1997 s.push_frame();
1998 s.declare_scalar("a", PerlValue::integer(2));
1999 let _ = s.set_scalar("a", PerlValue::integer(99));
2000 assert_eq!(s.get_scalar("a").to_int(), 99);
2001 s.pop_frame();
2002 assert_eq!(s.get_scalar("a").to_int(), 1);
2003 }
2004
2005 #[test]
2006 fn array_negative_index_reads_from_end() {
2007 let mut s = Scope::new();
2008 s.declare_array(
2009 "a",
2010 vec![
2011 PerlValue::integer(10),
2012 PerlValue::integer(20),
2013 PerlValue::integer(30),
2014 ],
2015 );
2016 assert_eq!(s.get_array_element("a", -1).to_int(), 30);
2017 }
2018
2019 #[test]
2020 fn set_array_element_extends_array_with_undef_gaps() {
2021 let mut s = Scope::new();
2022 s.declare_array("a", vec![]);
2023 s.set_array_element("a", 2, PerlValue::integer(7)).unwrap();
2024 assert_eq!(s.get_array_element("a", 2).to_int(), 7);
2025 assert!(s.get_array_element("a", 0).is_undef());
2026 }
2027
2028 #[test]
2029 fn capture_restore_roundtrip_scalar() {
2030 let mut s = Scope::new();
2031 s.declare_scalar("n", PerlValue::integer(42));
2032 let cap = s.capture();
2033 let mut t = Scope::new();
2034 t.restore_capture(&cap);
2035 assert_eq!(t.get_scalar("n").to_int(), 42);
2036 }
2037
2038 #[test]
2039 fn capture_restore_roundtrip_lexical_array_and_hash() {
2040 let mut s = Scope::new();
2041 s.declare_array("a", vec![PerlValue::integer(1), PerlValue::integer(2)]);
2042 let mut m = IndexMap::new();
2043 m.insert("k".to_string(), PerlValue::integer(99));
2044 s.declare_hash("h", m);
2045 let cap = s.capture();
2046 let mut t = Scope::new();
2047 t.restore_capture(&cap);
2048 assert_eq!(t.get_array_element("a", 1).to_int(), 2);
2049 assert_eq!(t.get_hash_element("h", "k").to_int(), 99);
2050 }
2051
2052 #[test]
2053 fn hash_get_set_delete_exists() {
2054 let mut s = Scope::new();
2055 let mut m = IndexMap::new();
2056 m.insert("k".to_string(), PerlValue::integer(1));
2057 s.declare_hash("h", m);
2058 assert_eq!(s.get_hash_element("h", "k").to_int(), 1);
2059 assert!(s.exists_hash_element("h", "k"));
2060 s.set_hash_element("h", "k", PerlValue::integer(99))
2061 .unwrap();
2062 assert_eq!(s.get_hash_element("h", "k").to_int(), 99);
2063 let del = s.delete_hash_element("h", "k").unwrap();
2064 assert_eq!(del.to_int(), 99);
2065 assert!(!s.exists_hash_element("h", "k"));
2066 }
2067
2068 #[test]
2069 fn inner_frame_shadows_outer_hash_name() {
2070 let mut s = Scope::new();
2071 let mut outer = IndexMap::new();
2072 outer.insert("k".to_string(), PerlValue::integer(1));
2073 s.declare_hash("h", outer);
2074 s.push_frame();
2075 let mut inner = IndexMap::new();
2076 inner.insert("k".to_string(), PerlValue::integer(2));
2077 s.declare_hash("h", inner);
2078 assert_eq!(s.get_hash_element("h", "k").to_int(), 2);
2079 s.pop_frame();
2080 assert_eq!(s.get_hash_element("h", "k").to_int(), 1);
2081 }
2082
2083 #[test]
2084 fn inner_frame_shadows_outer_array_name() {
2085 let mut s = Scope::new();
2086 s.declare_array("a", vec![PerlValue::integer(1)]);
2087 s.push_frame();
2088 s.declare_array("a", vec![PerlValue::integer(2), PerlValue::integer(3)]);
2089 assert_eq!(s.get_array_element("a", 1).to_int(), 3);
2090 s.pop_frame();
2091 assert_eq!(s.get_array_element("a", 0).to_int(), 1);
2092 }
2093
2094 #[test]
2095 fn pop_frame_never_removes_global_frame() {
2096 let mut s = Scope::new();
2097 s.declare_scalar("x", PerlValue::integer(1));
2098 s.pop_frame();
2099 s.pop_frame();
2100 assert_eq!(s.get_scalar("x").to_int(), 1);
2101 }
2102
2103 #[test]
2104 fn empty_array_declared_has_zero_length() {
2105 let mut s = Scope::new();
2106 s.declare_array("a", vec![]);
2107 assert_eq!(s.get_array("a").len(), 0);
2108 }
2109
2110 #[test]
2111 fn depth_increments_with_push_frame() {
2112 let mut s = Scope::new();
2113 let d0 = s.depth();
2114 s.push_frame();
2115 assert_eq!(s.depth(), d0 + 1);
2116 s.pop_frame();
2117 assert_eq!(s.depth(), d0);
2118 }
2119
2120 #[test]
2121 fn pop_to_depth_unwinds_to_target() {
2122 let mut s = Scope::new();
2123 s.push_frame();
2124 s.push_frame();
2125 let target = s.depth() - 1;
2126 s.pop_to_depth(target);
2127 assert_eq!(s.depth(), target);
2128 }
2129
2130 #[test]
2131 fn array_len_and_push_pop_roundtrip() {
2132 let mut s = Scope::new();
2133 s.declare_array("a", vec![]);
2134 assert_eq!(s.array_len("a"), 0);
2135 s.push_to_array("a", PerlValue::integer(1)).unwrap();
2136 s.push_to_array("a", PerlValue::integer(2)).unwrap();
2137 assert_eq!(s.array_len("a"), 2);
2138 assert_eq!(s.pop_from_array("a").unwrap().to_int(), 2);
2139 assert_eq!(s.pop_from_array("a").unwrap().to_int(), 1);
2140 assert!(s.pop_from_array("a").unwrap().is_undef());
2141 }
2142
2143 #[test]
2144 fn shift_from_array_drops_front() {
2145 let mut s = Scope::new();
2146 s.declare_array("a", vec![PerlValue::integer(1), PerlValue::integer(2)]);
2147 assert_eq!(s.shift_from_array("a").unwrap().to_int(), 1);
2148 assert_eq!(s.array_len("a"), 1);
2149 }
2150
2151 #[test]
2152 fn atomic_mutate_increments_wrapped_scalar() {
2153 use parking_lot::Mutex;
2154 use std::sync::Arc;
2155 let mut s = Scope::new();
2156 s.declare_scalar(
2157 "n",
2158 PerlValue::atomic(Arc::new(Mutex::new(PerlValue::integer(10)))),
2159 );
2160 let v = s.atomic_mutate("n", |old| PerlValue::integer(old.to_int() + 5));
2161 assert_eq!(v.to_int(), 15);
2162 assert_eq!(s.get_scalar("n").to_int(), 15);
2163 }
2164
2165 #[test]
2166 fn atomic_mutate_post_returns_old_value() {
2167 use parking_lot::Mutex;
2168 use std::sync::Arc;
2169 let mut s = Scope::new();
2170 s.declare_scalar(
2171 "n",
2172 PerlValue::atomic(Arc::new(Mutex::new(PerlValue::integer(7)))),
2173 );
2174 let old = s.atomic_mutate_post("n", |v| PerlValue::integer(v.to_int() + 1));
2175 assert_eq!(old.to_int(), 7);
2176 assert_eq!(s.get_scalar("n").to_int(), 8);
2177 }
2178
2179 #[test]
2180 fn get_scalar_raw_keeps_atomic_wrapper() {
2181 use parking_lot::Mutex;
2182 use std::sync::Arc;
2183 let mut s = Scope::new();
2184 s.declare_scalar(
2185 "n",
2186 PerlValue::atomic(Arc::new(Mutex::new(PerlValue::integer(3)))),
2187 );
2188 assert!(s.get_scalar_raw("n").is_atomic());
2189 assert!(!s.get_scalar("n").is_atomic());
2190 }
2191
2192 #[test]
2193 fn missing_array_element_is_undef() {
2194 let mut s = Scope::new();
2195 s.declare_array("a", vec![PerlValue::integer(1)]);
2196 assert!(s.get_array_element("a", 99).is_undef());
2197 }
2198
2199 #[test]
2200 fn restore_atomics_puts_atomic_containers_in_frame() {
2201 use indexmap::IndexMap;
2202 use parking_lot::Mutex;
2203 use std::sync::Arc;
2204 let mut s = Scope::new();
2205 let aa = AtomicArray(Arc::new(Mutex::new(vec![PerlValue::integer(1)])));
2206 let ah = AtomicHash(Arc::new(Mutex::new(IndexMap::new())));
2207 s.restore_atomics(&[("ax".into(), aa.clone())], &[("hx".into(), ah.clone())]);
2208 assert_eq!(s.get_array_element("ax", 0).to_int(), 1);
2209 assert_eq!(s.array_len("ax"), 1);
2210 s.set_hash_element("hx", "k", PerlValue::integer(2))
2211 .unwrap();
2212 assert_eq!(s.get_hash_element("hx", "k").to_int(), 2);
2213 }
2214}