1use crossbeam::channel::{Receiver, Sender};
2use indexmap::IndexMap;
3use parking_lot::{Mutex, RwLock};
4use std::cmp::Ordering;
5use std::collections::VecDeque;
6use std::fmt;
7use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
8use std::sync::Arc;
9use std::sync::Barrier;
10
11use crate::ast::{Block, ClassDef, EnumDef, StructDef, SubSigParam};
12use crate::error::PerlResult;
13use crate::nanbox;
14use crate::perl_decode::decode_utf8_or_latin1;
15use crate::perl_regex::PerlCompiledRegex;
16
17#[derive(Debug)]
19pub struct PerlAsyncTask {
20 pub(crate) result: Arc<Mutex<Option<PerlResult<PerlValue>>>>,
21 pub(crate) join: Arc<Mutex<Option<std::thread::JoinHandle<()>>>>,
22}
23
24impl Clone for PerlAsyncTask {
25 fn clone(&self) -> Self {
26 Self {
27 result: self.result.clone(),
28 join: self.join.clone(),
29 }
30 }
31}
32
33impl PerlAsyncTask {
34 pub fn await_result(&self) -> PerlResult<PerlValue> {
36 if let Some(h) = self.join.lock().take() {
37 let _ = h.join();
38 }
39 self.result
40 .lock()
41 .clone()
42 .unwrap_or_else(|| Ok(PerlValue::UNDEF))
43 }
44}
45
46pub trait PerlIterator: Send + Sync {
51 fn next_item(&self) -> Option<PerlValue>;
53
54 fn collect_all(&self) -> Vec<PerlValue> {
56 let mut out = Vec::new();
57 while let Some(v) = self.next_item() {
58 out.push(v);
59 }
60 out
61 }
62}
63
64impl fmt::Debug for dyn PerlIterator {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 f.write_str("PerlIterator")
67 }
68}
69
70pub struct FsWalkIterator {
72 stack: Mutex<Vec<(std::path::PathBuf, String)>>,
74 buf: Mutex<Vec<(String, bool)>>, pending_dirs: Mutex<Vec<(std::path::PathBuf, String)>>,
78 files_only: bool,
79}
80
81impl FsWalkIterator {
82 pub fn new(dir: &str, files_only: bool) -> Self {
83 Self {
84 stack: Mutex::new(vec![(std::path::PathBuf::from(dir), String::new())]),
85 buf: Mutex::new(Vec::new()),
86 pending_dirs: Mutex::new(Vec::new()),
87 files_only,
88 }
89 }
90
91 fn refill(&self) -> bool {
94 loop {
95 let mut stack = self.stack.lock();
96 let mut pending = self.pending_dirs.lock();
98 while let Some(d) = pending.pop() {
99 stack.push(d);
100 }
101 drop(pending);
102
103 let (base, rel) = match stack.pop() {
104 Some(v) => v,
105 None => return false,
106 };
107 drop(stack);
108
109 let entries = match std::fs::read_dir(&base) {
110 Ok(e) => e,
111 Err(_) => continue, };
113 let mut children: Vec<(std::ffi::OsString, String, bool, bool)> = Vec::new();
114 for entry in entries.flatten() {
115 let ft = match entry.file_type() {
116 Ok(ft) => ft,
117 Err(_) => continue,
118 };
119 let os_name = entry.file_name();
120 let name = match os_name.to_str() {
121 Some(n) => n.to_string(),
122 None => continue,
123 };
124 let child_rel = if rel.is_empty() {
125 name.clone()
126 } else {
127 format!("{rel}/{name}")
128 };
129 children.push((os_name, child_rel, ft.is_file(), ft.is_dir()));
130 }
131 children.sort_by(|a, b| a.0.cmp(&b.0));
132
133 let mut buf = self.buf.lock();
134 let mut pending = self.pending_dirs.lock();
135 let mut subdirs = Vec::new();
136 for (os_name, child_rel, is_file, is_dir) in children {
137 if is_dir {
138 if !self.files_only {
139 buf.push((child_rel.clone(), true));
140 }
141 subdirs.push((base.join(os_name), child_rel));
142 } else if is_file && self.files_only {
143 buf.push((child_rel, false));
144 }
145 }
146 for s in subdirs.into_iter().rev() {
147 pending.push(s);
148 }
149 buf.reverse();
150 if !buf.is_empty() {
151 return true;
152 }
153 }
155 }
156}
157
158impl PerlIterator for FsWalkIterator {
159 fn next_item(&self) -> Option<PerlValue> {
160 loop {
161 {
162 let mut buf = self.buf.lock();
163 if let Some((path, _)) = buf.pop() {
164 return Some(PerlValue::string(path));
165 }
166 }
167 if !self.refill() {
168 return None;
169 }
170 }
171 }
172}
173
174pub struct RevIterator {
176 source: Arc<dyn PerlIterator>,
177}
178
179impl RevIterator {
180 pub fn new(source: Arc<dyn PerlIterator>) -> Self {
181 Self { source }
182 }
183}
184
185impl PerlIterator for RevIterator {
186 fn next_item(&self) -> Option<PerlValue> {
187 let item = self.source.next_item()?;
188 let s = item.to_string();
189 Some(PerlValue::string(s.chars().rev().collect()))
190 }
191}
192
193#[derive(Debug)]
195pub struct PerlGenerator {
196 pub(crate) block: Block,
197 pub(crate) pc: Mutex<usize>,
198 pub(crate) scope_started: Mutex<bool>,
199 pub(crate) exhausted: Mutex<bool>,
200}
201
202pub type PerlSet = IndexMap<String, PerlValue>;
204
205#[derive(Debug, Clone)]
207pub struct PerlHeap {
208 pub items: Vec<PerlValue>,
209 pub cmp: Arc<PerlSub>,
210}
211
212#[derive(Debug, Clone)]
220pub struct RemoteSlot {
221 pub host: String,
223 pub pe_path: String,
225}
226
227#[cfg(test)]
228mod cluster_parsing_tests {
229 use super::*;
230
231 fn s(v: &str) -> PerlValue {
232 PerlValue::string(v.to_string())
233 }
234
235 #[test]
236 fn parses_simple_host() {
237 let c = RemoteCluster::from_list_args(&[s("host1")]).expect("parse");
238 assert_eq!(c.slots.len(), 1);
239 assert_eq!(c.slots[0].host, "host1");
240 assert_eq!(c.slots[0].pe_path, "stryke");
241 }
242
243 #[test]
244 fn parses_host_with_slot_count() {
245 let c = RemoteCluster::from_list_args(&[s("host1:4")]).expect("parse");
246 assert_eq!(c.slots.len(), 4);
247 assert!(c.slots.iter().all(|s| s.host == "host1"));
248 }
249
250 #[test]
251 fn parses_user_at_host_with_slots() {
252 let c = RemoteCluster::from_list_args(&[s("alice@build1:2")]).expect("parse");
253 assert_eq!(c.slots.len(), 2);
254 assert_eq!(c.slots[0].host, "alice@build1");
255 }
256
257 #[test]
258 fn parses_host_slots_stryke_path_triple() {
259 let c =
260 RemoteCluster::from_list_args(&[s("build1:3:/usr/local/bin/stryke")]).expect("parse");
261 assert_eq!(c.slots.len(), 3);
262 assert!(c.slots.iter().all(|sl| sl.host == "build1"));
263 assert!(c
264 .slots
265 .iter()
266 .all(|sl| sl.pe_path == "/usr/local/bin/stryke"));
267 }
268
269 #[test]
270 fn parses_multiple_hosts_in_one_call() {
271 let c = RemoteCluster::from_list_args(&[s("host1:2"), s("host2:1")]).expect("parse");
272 assert_eq!(c.slots.len(), 3);
273 assert_eq!(c.slots[0].host, "host1");
274 assert_eq!(c.slots[1].host, "host1");
275 assert_eq!(c.slots[2].host, "host2");
276 }
277
278 #[test]
279 fn parses_hashref_slot_form() {
280 let mut h = indexmap::IndexMap::new();
281 h.insert("host".to_string(), s("data1"));
282 h.insert("slots".to_string(), PerlValue::integer(2));
283 h.insert("stryke".to_string(), s("/opt/stryke"));
284 let c = RemoteCluster::from_list_args(&[PerlValue::hash(h)]).expect("parse");
285 assert_eq!(c.slots.len(), 2);
286 assert_eq!(c.slots[0].host, "data1");
287 assert_eq!(c.slots[0].pe_path, "/opt/stryke");
288 }
289
290 #[test]
291 fn parses_trailing_tunables_hashref() {
292 let mut tun = indexmap::IndexMap::new();
293 tun.insert("timeout".to_string(), PerlValue::integer(30));
294 tun.insert("retries".to_string(), PerlValue::integer(2));
295 tun.insert("connect_timeout".to_string(), PerlValue::integer(5));
296 let c = RemoteCluster::from_list_args(&[s("h1:1"), PerlValue::hash(tun)]).expect("parse");
297 assert_eq!(c.slots.len(), 1);
299 assert_eq!(c.job_timeout_ms, 30_000);
300 assert_eq!(c.max_attempts, 3); assert_eq!(c.connect_timeout_ms, 5_000);
302 }
303
304 #[test]
305 fn defaults_when_no_tunables() {
306 let c = RemoteCluster::from_list_args(&[s("h1")]).expect("parse");
307 assert_eq!(c.job_timeout_ms, RemoteCluster::DEFAULT_JOB_TIMEOUT_MS);
308 assert_eq!(c.max_attempts, RemoteCluster::DEFAULT_MAX_ATTEMPTS);
309 assert_eq!(
310 c.connect_timeout_ms,
311 RemoteCluster::DEFAULT_CONNECT_TIMEOUT_MS
312 );
313 }
314
315 #[test]
316 fn rejects_empty_cluster() {
317 assert!(RemoteCluster::from_list_args(&[]).is_err());
318 }
319
320 #[test]
321 fn slot_count_minimum_one() {
322 let c = RemoteCluster::from_list_args(&[s("h1:0")]).expect("parse");
323 assert_eq!(c.slots.len(), 1);
326 }
327}
328
329#[derive(Debug, Clone)]
338pub struct RemoteCluster {
339 pub slots: Vec<RemoteSlot>,
340 pub job_timeout_ms: u64,
341 pub max_attempts: u32,
342 pub connect_timeout_ms: u64,
343}
344
345impl RemoteCluster {
346 pub const DEFAULT_JOB_TIMEOUT_MS: u64 = 60_000;
347 pub const DEFAULT_MAX_ATTEMPTS: u32 = 3;
348 pub const DEFAULT_CONNECT_TIMEOUT_MS: u64 = 10_000;
349
350 pub fn from_list_args(items: &[PerlValue]) -> Result<Self, String> {
364 let mut slots: Vec<RemoteSlot> = Vec::new();
365 let mut job_timeout_ms = Self::DEFAULT_JOB_TIMEOUT_MS;
366 let mut max_attempts = Self::DEFAULT_MAX_ATTEMPTS;
367 let mut connect_timeout_ms = Self::DEFAULT_CONNECT_TIMEOUT_MS;
368
369 let (slot_items, tunables) = if let Some(last) = items.last() {
371 let h = last
372 .as_hash_map()
373 .or_else(|| last.as_hash_ref().map(|r| r.read().clone()));
374 if let Some(map) = h {
375 let known = |k: &str| {
376 matches!(k, "timeout" | "retries" | "connect_timeout" | "job_timeout")
377 };
378 if !map.is_empty() && map.keys().all(|k| known(k.as_str())) {
379 (&items[..items.len() - 1], Some(map))
380 } else {
381 (items, None)
382 }
383 } else {
384 (items, None)
385 }
386 } else {
387 (items, None)
388 };
389
390 if let Some(map) = tunables {
391 if let Some(v) = map.get("timeout").or_else(|| map.get("job_timeout")) {
392 job_timeout_ms = (v.to_number() * 1000.0) as u64;
393 }
394 if let Some(v) = map.get("retries") {
395 max_attempts = v.to_int().max(0) as u32 + 1;
397 }
398 if let Some(v) = map.get("connect_timeout") {
399 connect_timeout_ms = (v.to_number() * 1000.0) as u64;
400 }
401 }
402
403 for it in slot_items {
404 if let Some(map) = it
406 .as_hash_map()
407 .or_else(|| it.as_hash_ref().map(|r| r.read().clone()))
408 {
409 let host = map
410 .get("host")
411 .map(|v| v.to_string())
412 .ok_or_else(|| "cluster: hashref slot needs `host`".to_string())?;
413 let n = map.get("slots").map(|v| v.to_int().max(1)).unwrap_or(1) as usize;
414 let stryke = map
415 .get("stryke")
416 .or_else(|| map.get("pe_path"))
417 .map(|v| v.to_string())
418 .unwrap_or_else(|| "stryke".to_string());
419 for _ in 0..n {
420 slots.push(RemoteSlot {
421 host: host.clone(),
422 pe_path: stryke.clone(),
423 });
424 }
425 continue;
426 }
427
428 let s = it.to_string();
434 let (left, pe_path) = if let Some(idx) = s.find(':') {
436 let rest = &s[idx + 1..];
438 if let Some(jdx) = rest.find(':') {
439 let count_seg = &rest[..jdx];
441 if count_seg.parse::<usize>().is_ok() {
442 (
443 format!("{}:{}", &s[..idx], count_seg),
444 Some(rest[jdx + 1..].to_string()),
445 )
446 } else {
447 (s.clone(), None)
448 }
449 } else {
450 (s.clone(), None)
451 }
452 } else {
453 (s.clone(), None)
454 };
455 let pe_path = pe_path.unwrap_or_else(|| "stryke".to_string());
456
457 let (host, n) = if let Some((h, nstr)) = left.rsplit_once(':') {
460 if let Ok(n) = nstr.parse::<usize>() {
461 (h.to_string(), n.max(1))
462 } else {
463 (left.clone(), 1)
464 }
465 } else {
466 (left.clone(), 1)
467 };
468 for _ in 0..n {
469 slots.push(RemoteSlot {
470 host: host.clone(),
471 pe_path: pe_path.clone(),
472 });
473 }
474 }
475
476 if slots.is_empty() {
477 return Err("cluster: need at least one host".into());
478 }
479 Ok(RemoteCluster {
480 slots,
481 job_timeout_ms,
482 max_attempts,
483 connect_timeout_ms,
484 })
485 }
486}
487
488#[derive(Clone)]
490pub struct PerlBarrier(pub Arc<Barrier>);
491
492impl fmt::Debug for PerlBarrier {
493 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
494 f.write_str("Barrier")
495 }
496}
497
498#[derive(Debug, Clone)]
500pub struct CaptureResult {
501 pub stdout: String,
502 pub stderr: String,
503 pub exitcode: i64,
504}
505
506#[derive(Debug, Clone)]
508pub struct PerlDataFrame {
509 pub columns: Vec<String>,
510 pub cols: Vec<Vec<PerlValue>>,
511 pub group_by: Option<String>,
513}
514
515impl PerlDataFrame {
516 #[inline]
517 pub fn nrows(&self) -> usize {
518 self.cols.first().map(|c| c.len()).unwrap_or(0)
519 }
520
521 #[inline]
522 pub fn ncols(&self) -> usize {
523 self.columns.len()
524 }
525
526 #[inline]
527 pub fn col_index(&self, name: &str) -> Option<usize> {
528 self.columns.iter().position(|c| c == name)
529 }
530}
531
532#[derive(Debug, Clone)]
534pub(crate) enum HeapObject {
535 Integer(i64),
536 Float(f64),
537 String(String),
538 Bytes(Arc<Vec<u8>>),
539 Array(Vec<PerlValue>),
540 Hash(IndexMap<String, PerlValue>),
541 ArrayRef(Arc<RwLock<Vec<PerlValue>>>),
542 HashRef(Arc<RwLock<IndexMap<String, PerlValue>>>),
543 ScalarRef(Arc<RwLock<PerlValue>>),
544 CaptureCell(Arc<RwLock<PerlValue>>),
548 ScalarBindingRef(String),
550 ArrayBindingRef(String),
552 HashBindingRef(String),
554 CodeRef(Arc<PerlSub>),
555 Regex(Arc<PerlCompiledRegex>, String, String),
557 Blessed(Arc<BlessedRef>),
558 IOHandle(String),
559 Atomic(Arc<Mutex<PerlValue>>),
560 Set(Arc<PerlSet>),
561 ChannelTx(Arc<Sender<PerlValue>>),
562 ChannelRx(Arc<Receiver<PerlValue>>),
563 AsyncTask(Arc<PerlAsyncTask>),
564 Generator(Arc<PerlGenerator>),
565 Deque(Arc<Mutex<VecDeque<PerlValue>>>),
566 Heap(Arc<Mutex<PerlHeap>>),
567 Pipeline(Arc<Mutex<PipelineInner>>),
568 Capture(Arc<CaptureResult>),
569 Ppool(PerlPpool),
570 RemoteCluster(Arc<RemoteCluster>),
571 Barrier(PerlBarrier),
572 SqliteConn(Arc<Mutex<rusqlite::Connection>>),
573 StructInst(Arc<StructInstance>),
574 DataFrame(Arc<Mutex<PerlDataFrame>>),
575 EnumInst(Arc<EnumInstance>),
576 ClassInst(Arc<ClassInstance>),
577 Iterator(Arc<dyn PerlIterator>),
579 ErrnoDual {
581 code: i32,
582 msg: String,
583 },
584}
585
586#[repr(transparent)]
588pub struct PerlValue(pub(crate) u64);
589
590impl Default for PerlValue {
591 fn default() -> Self {
592 Self::UNDEF
593 }
594}
595
596impl Clone for PerlValue {
597 fn clone(&self) -> Self {
598 if nanbox::is_heap(self.0) {
599 let arc = self.heap_arc();
600 match &*arc {
601 HeapObject::Array(v) => {
602 PerlValue::from_heap(Arc::new(HeapObject::Array(v.clone())))
603 }
604 HeapObject::Hash(h) => PerlValue::from_heap(Arc::new(HeapObject::Hash(h.clone()))),
605 HeapObject::String(s) => {
606 PerlValue::from_heap(Arc::new(HeapObject::String(s.clone())))
607 }
608 HeapObject::Integer(n) => PerlValue::integer(*n),
609 HeapObject::Float(f) => PerlValue::float(*f),
610 _ => PerlValue::from_heap(Arc::clone(&arc)),
611 }
612 } else {
613 PerlValue(self.0)
614 }
615 }
616}
617
618impl PerlValue {
619 #[inline]
622 pub fn dup_stack(&self) -> Self {
623 if nanbox::is_heap(self.0) {
624 let arc = self.heap_arc();
625 match &*arc {
626 HeapObject::Array(_) | HeapObject::Hash(_) => {
627 PerlValue::from_heap(Arc::clone(&arc))
628 }
629 _ => self.clone(),
630 }
631 } else {
632 PerlValue(self.0)
633 }
634 }
635
636 #[inline]
647 pub fn shallow_clone(&self) -> Self {
648 if nanbox::is_heap(self.0) {
649 PerlValue::from_heap(self.heap_arc())
650 } else {
651 PerlValue(self.0)
652 }
653 }
654}
655
656impl Drop for PerlValue {
657 fn drop(&mut self) {
658 if nanbox::is_heap(self.0) {
659 unsafe {
660 let p = nanbox::decode_heap_ptr::<HeapObject>(self.0) as *mut HeapObject;
661 drop(Arc::from_raw(p));
662 }
663 }
664 }
665}
666
667impl fmt::Debug for PerlValue {
668 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
669 write!(f, "{self}")
670 }
671}
672
673#[derive(Clone)]
676pub struct PerlPpool(pub(crate) Arc<crate::ppool::PpoolInner>);
677
678impl fmt::Debug for PerlPpool {
679 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
680 f.write_str("PerlPpool")
681 }
682}
683
684#[derive(Debug, Clone, PartialEq, Eq)]
687pub struct FibLikeRecAddPattern {
688 pub param: String,
690 pub base_k: i64,
692 pub left_k: i64,
694 pub right_k: i64,
696}
697
698#[derive(Debug, Clone)]
699pub struct PerlSub {
700 pub name: String,
701 pub params: Vec<SubSigParam>,
702 pub body: Block,
703 pub closure_env: Option<Vec<(String, PerlValue)>>,
705 pub prototype: Option<String>,
707 pub fib_like: Option<FibLikeRecAddPattern>,
710}
711
712#[derive(Debug, Clone)]
714pub enum PipelineOp {
715 Filter(Arc<PerlSub>),
716 Map(Arc<PerlSub>),
717 Tap(Arc<PerlSub>),
719 Take(i64),
720 PMap {
722 sub: Arc<PerlSub>,
723 progress: bool,
724 },
725 PGrep {
727 sub: Arc<PerlSub>,
728 progress: bool,
729 },
730 PFor {
732 sub: Arc<PerlSub>,
733 progress: bool,
734 },
735 PMapChunked {
737 chunk: i64,
738 sub: Arc<PerlSub>,
739 progress: bool,
740 },
741 PSort {
743 cmp: Option<Arc<PerlSub>>,
744 progress: bool,
745 },
746 PCache {
748 sub: Arc<PerlSub>,
749 progress: bool,
750 },
751 PReduce {
753 sub: Arc<PerlSub>,
754 progress: bool,
755 },
756 PReduceInit {
758 init: PerlValue,
759 sub: Arc<PerlSub>,
760 progress: bool,
761 },
762 PMapReduce {
764 map: Arc<PerlSub>,
765 reduce: Arc<PerlSub>,
766 progress: bool,
767 },
768}
769
770#[derive(Debug)]
771pub struct PipelineInner {
772 pub source: Vec<PerlValue>,
773 pub ops: Vec<PipelineOp>,
774 pub has_scalar_terminal: bool,
776 pub par_stream: bool,
778 pub streaming: bool,
781 pub streaming_workers: usize,
783 pub streaming_buffer: usize,
785}
786
787#[derive(Debug)]
788pub struct BlessedRef {
789 pub class: String,
790 pub data: RwLock<PerlValue>,
791 pub(crate) suppress_destroy_queue: AtomicBool,
793}
794
795impl BlessedRef {
796 pub(crate) fn new_blessed(class: String, data: PerlValue) -> Self {
797 Self {
798 class,
799 data: RwLock::new(data),
800 suppress_destroy_queue: AtomicBool::new(false),
801 }
802 }
803
804 pub(crate) fn new_for_destroy_invocant(class: String, data: PerlValue) -> Self {
806 Self {
807 class,
808 data: RwLock::new(data),
809 suppress_destroy_queue: AtomicBool::new(true),
810 }
811 }
812}
813
814impl Clone for BlessedRef {
815 fn clone(&self) -> Self {
816 Self {
817 class: self.class.clone(),
818 data: RwLock::new(self.data.read().clone()),
819 suppress_destroy_queue: AtomicBool::new(false),
820 }
821 }
822}
823
824impl Drop for BlessedRef {
825 fn drop(&mut self) {
826 if self.suppress_destroy_queue.load(AtomicOrdering::Acquire) {
827 return;
828 }
829 let inner = {
830 let mut g = self.data.write();
831 std::mem::take(&mut *g)
832 };
833 crate::pending_destroy::enqueue(self.class.clone(), inner);
834 }
835}
836
837#[derive(Debug)]
839pub struct StructInstance {
840 pub def: Arc<StructDef>,
841 pub values: RwLock<Vec<PerlValue>>,
842}
843
844impl StructInstance {
845 pub fn new(def: Arc<StructDef>, values: Vec<PerlValue>) -> Self {
847 Self {
848 def,
849 values: RwLock::new(values),
850 }
851 }
852
853 #[inline]
855 pub fn get_field(&self, idx: usize) -> Option<PerlValue> {
856 self.values.read().get(idx).cloned()
857 }
858
859 #[inline]
861 pub fn set_field(&self, idx: usize, val: PerlValue) {
862 if let Some(slot) = self.values.write().get_mut(idx) {
863 *slot = val;
864 }
865 }
866
867 #[inline]
869 pub fn get_values(&self) -> Vec<PerlValue> {
870 self.values.read().clone()
871 }
872}
873
874impl Clone for StructInstance {
875 fn clone(&self) -> Self {
876 Self {
877 def: Arc::clone(&self.def),
878 values: RwLock::new(self.values.read().clone()),
879 }
880 }
881}
882
883#[derive(Debug)]
885pub struct EnumInstance {
886 pub def: Arc<EnumDef>,
887 pub variant_idx: usize,
888 pub data: PerlValue,
890}
891
892impl EnumInstance {
893 pub fn new(def: Arc<EnumDef>, variant_idx: usize, data: PerlValue) -> Self {
894 Self {
895 def,
896 variant_idx,
897 data,
898 }
899 }
900
901 pub fn variant_name(&self) -> &str {
902 &self.def.variants[self.variant_idx].name
903 }
904}
905
906impl Clone for EnumInstance {
907 fn clone(&self) -> Self {
908 Self {
909 def: Arc::clone(&self.def),
910 variant_idx: self.variant_idx,
911 data: self.data.clone(),
912 }
913 }
914}
915
916#[derive(Debug)]
918pub struct ClassInstance {
919 pub def: Arc<ClassDef>,
920 pub values: RwLock<Vec<PerlValue>>,
921 pub isa_chain: Vec<String>,
923}
924
925impl ClassInstance {
926 pub fn new(def: Arc<ClassDef>, values: Vec<PerlValue>) -> Self {
927 Self {
928 def,
929 values: RwLock::new(values),
930 isa_chain: Vec::new(),
931 }
932 }
933
934 pub fn new_with_isa(
935 def: Arc<ClassDef>,
936 values: Vec<PerlValue>,
937 isa_chain: Vec<String>,
938 ) -> Self {
939 Self {
940 def,
941 values: RwLock::new(values),
942 isa_chain,
943 }
944 }
945
946 #[inline]
948 pub fn isa(&self, name: &str) -> bool {
949 self.def.name == name || self.isa_chain.contains(&name.to_string())
950 }
951
952 #[inline]
953 pub fn get_field(&self, idx: usize) -> Option<PerlValue> {
954 self.values.read().get(idx).cloned()
955 }
956
957 #[inline]
958 pub fn set_field(&self, idx: usize, val: PerlValue) {
959 if let Some(slot) = self.values.write().get_mut(idx) {
960 *slot = val;
961 }
962 }
963
964 #[inline]
965 pub fn get_values(&self) -> Vec<PerlValue> {
966 self.values.read().clone()
967 }
968
969 pub fn get_field_by_name(&self, name: &str) -> Option<PerlValue> {
971 self.def
972 .field_index(name)
973 .and_then(|idx| self.get_field(idx))
974 }
975
976 pub fn set_field_by_name(&self, name: &str, val: PerlValue) -> bool {
978 if let Some(idx) = self.def.field_index(name) {
979 self.set_field(idx, val);
980 true
981 } else {
982 false
983 }
984 }
985}
986
987impl Clone for ClassInstance {
988 fn clone(&self) -> Self {
989 Self {
990 def: Arc::clone(&self.def),
991 values: RwLock::new(self.values.read().clone()),
992 isa_chain: self.isa_chain.clone(),
993 }
994 }
995}
996
997impl PerlValue {
998 pub const UNDEF: PerlValue = PerlValue(nanbox::encode_imm_undef());
999
1000 #[inline]
1001 fn from_heap(arc: Arc<HeapObject>) -> PerlValue {
1002 let ptr = Arc::into_raw(arc);
1003 PerlValue(nanbox::encode_heap_ptr(ptr))
1004 }
1005
1006 #[inline]
1007 pub(crate) fn heap_arc(&self) -> Arc<HeapObject> {
1008 debug_assert!(nanbox::is_heap(self.0));
1009 unsafe {
1010 let p = nanbox::decode_heap_ptr::<HeapObject>(self.0);
1011 Arc::increment_strong_count(p);
1012 Arc::from_raw(p as *mut HeapObject)
1013 }
1014 }
1015
1016 #[inline]
1021 pub(crate) unsafe fn heap_ref(&self) -> &HeapObject {
1022 &*nanbox::decode_heap_ptr::<HeapObject>(self.0)
1023 }
1024
1025 #[inline]
1026 pub(crate) fn with_heap<R>(&self, f: impl FnOnce(&HeapObject) -> R) -> Option<R> {
1027 if !nanbox::is_heap(self.0) {
1028 return None;
1029 }
1030 Some(f(unsafe { self.heap_ref() }))
1032 }
1033
1034 #[inline]
1036 pub(crate) fn raw_bits(&self) -> u64 {
1037 self.0
1038 }
1039
1040 #[inline]
1042 pub(crate) fn from_raw_bits(bits: u64) -> Self {
1043 Self(bits)
1044 }
1045
1046 #[inline]
1048 pub fn is_integer_like(&self) -> bool {
1049 nanbox::as_imm_int32(self.0).is_some()
1050 || matches!(
1051 self.with_heap(|h| matches!(h, HeapObject::Integer(_))),
1052 Some(true)
1053 )
1054 }
1055
1056 #[inline]
1058 pub fn is_float_like(&self) -> bool {
1059 nanbox::is_raw_float_bits(self.0)
1060 || matches!(
1061 self.with_heap(|h| matches!(h, HeapObject::Float(_))),
1062 Some(true)
1063 )
1064 }
1065
1066 #[inline]
1068 pub fn is_string_like(&self) -> bool {
1069 matches!(
1070 self.with_heap(|h| matches!(h, HeapObject::String(_))),
1071 Some(true)
1072 )
1073 }
1074
1075 #[inline]
1076 pub fn integer(n: i64) -> Self {
1077 if n >= i32::MIN as i64 && n <= i32::MAX as i64 {
1078 PerlValue(nanbox::encode_imm_int32(n as i32))
1079 } else {
1080 Self::from_heap(Arc::new(HeapObject::Integer(n)))
1081 }
1082 }
1083
1084 #[inline]
1085 pub fn float(f: f64) -> Self {
1086 if nanbox::float_needs_box(f) {
1087 Self::from_heap(Arc::new(HeapObject::Float(f)))
1088 } else {
1089 PerlValue(f.to_bits())
1090 }
1091 }
1092
1093 #[inline]
1094 pub fn string(s: String) -> Self {
1095 Self::from_heap(Arc::new(HeapObject::String(s)))
1096 }
1097
1098 #[inline]
1099 pub fn bytes(b: Arc<Vec<u8>>) -> Self {
1100 Self::from_heap(Arc::new(HeapObject::Bytes(b)))
1101 }
1102
1103 #[inline]
1104 pub fn array(v: Vec<PerlValue>) -> Self {
1105 Self::from_heap(Arc::new(HeapObject::Array(v)))
1106 }
1107
1108 #[inline]
1110 pub fn iterator(it: Arc<dyn PerlIterator>) -> Self {
1111 Self::from_heap(Arc::new(HeapObject::Iterator(it)))
1112 }
1113
1114 #[inline]
1116 pub fn is_iterator(&self) -> bool {
1117 if !nanbox::is_heap(self.0) {
1118 return false;
1119 }
1120 matches!(unsafe { self.heap_ref() }, HeapObject::Iterator(_))
1121 }
1122
1123 pub fn into_iterator(&self) -> Arc<dyn PerlIterator> {
1125 if nanbox::is_heap(self.0) {
1126 if let HeapObject::Iterator(it) = &*self.heap_arc() {
1127 return Arc::clone(it);
1128 }
1129 }
1130 panic!("into_iterator on non-iterator value");
1131 }
1132
1133 #[inline]
1134 pub fn hash(h: IndexMap<String, PerlValue>) -> Self {
1135 Self::from_heap(Arc::new(HeapObject::Hash(h)))
1136 }
1137
1138 #[inline]
1139 pub fn array_ref(a: Arc<RwLock<Vec<PerlValue>>>) -> Self {
1140 Self::from_heap(Arc::new(HeapObject::ArrayRef(a)))
1141 }
1142
1143 #[inline]
1144 pub fn hash_ref(h: Arc<RwLock<IndexMap<String, PerlValue>>>) -> Self {
1145 Self::from_heap(Arc::new(HeapObject::HashRef(h)))
1146 }
1147
1148 #[inline]
1149 pub fn scalar_ref(r: Arc<RwLock<PerlValue>>) -> Self {
1150 Self::from_heap(Arc::new(HeapObject::ScalarRef(r)))
1151 }
1152
1153 #[inline]
1154 pub fn capture_cell(r: Arc<RwLock<PerlValue>>) -> Self {
1155 Self::from_heap(Arc::new(HeapObject::CaptureCell(r)))
1156 }
1157
1158 #[inline]
1159 pub fn scalar_binding_ref(name: String) -> Self {
1160 Self::from_heap(Arc::new(HeapObject::ScalarBindingRef(name)))
1161 }
1162
1163 #[inline]
1164 pub fn array_binding_ref(name: String) -> Self {
1165 Self::from_heap(Arc::new(HeapObject::ArrayBindingRef(name)))
1166 }
1167
1168 #[inline]
1169 pub fn hash_binding_ref(name: String) -> Self {
1170 Self::from_heap(Arc::new(HeapObject::HashBindingRef(name)))
1171 }
1172
1173 #[inline]
1174 pub fn code_ref(c: Arc<PerlSub>) -> Self {
1175 Self::from_heap(Arc::new(HeapObject::CodeRef(c)))
1176 }
1177
1178 #[inline]
1179 pub fn as_code_ref(&self) -> Option<Arc<PerlSub>> {
1180 self.with_heap(|h| match h {
1181 HeapObject::CodeRef(sub) => Some(Arc::clone(sub)),
1182 _ => None,
1183 })
1184 .flatten()
1185 }
1186
1187 #[inline]
1188 pub fn as_regex(&self) -> Option<Arc<PerlCompiledRegex>> {
1189 self.with_heap(|h| match h {
1190 HeapObject::Regex(re, _, _) => Some(Arc::clone(re)),
1191 _ => None,
1192 })
1193 .flatten()
1194 }
1195
1196 #[inline]
1197 pub fn as_blessed_ref(&self) -> Option<Arc<BlessedRef>> {
1198 self.with_heap(|h| match h {
1199 HeapObject::Blessed(b) => Some(Arc::clone(b)),
1200 _ => None,
1201 })
1202 .flatten()
1203 }
1204
1205 #[inline]
1207 pub fn hash_get(&self, key: &str) -> Option<PerlValue> {
1208 self.with_heap(|h| match h {
1209 HeapObject::Hash(h) => h.get(key).cloned(),
1210 _ => None,
1211 })
1212 .flatten()
1213 }
1214
1215 #[inline]
1216 pub fn is_undef(&self) -> bool {
1217 nanbox::is_imm_undef(self.0)
1218 }
1219
1220 pub fn is_simple_scalar(&self) -> bool {
1225 if self.is_undef() {
1226 return true;
1227 }
1228 if !nanbox::is_heap(self.0) {
1229 return true; }
1231 matches!(
1232 unsafe { self.heap_ref() },
1233 HeapObject::Integer(_)
1234 | HeapObject::Float(_)
1235 | HeapObject::String(_)
1236 | HeapObject::Bytes(_)
1237 )
1238 }
1239
1240 #[inline]
1242 pub fn as_integer(&self) -> Option<i64> {
1243 if let Some(n) = nanbox::as_imm_int32(self.0) {
1244 return Some(n as i64);
1245 }
1246 if nanbox::is_raw_float_bits(self.0) {
1247 return None;
1248 }
1249 self.with_heap(|h| match h {
1250 HeapObject::Integer(n) => Some(*n),
1251 _ => None,
1252 })
1253 .flatten()
1254 }
1255
1256 #[inline]
1257 pub fn as_float(&self) -> Option<f64> {
1258 if nanbox::is_raw_float_bits(self.0) {
1259 return Some(f64::from_bits(self.0));
1260 }
1261 self.with_heap(|h| match h {
1262 HeapObject::Float(f) => Some(*f),
1263 _ => None,
1264 })
1265 .flatten()
1266 }
1267
1268 #[inline]
1269 pub fn as_array_vec(&self) -> Option<Vec<PerlValue>> {
1270 self.with_heap(|h| match h {
1271 HeapObject::Array(v) => Some(v.clone()),
1272 _ => None,
1273 })
1274 .flatten()
1275 }
1276
1277 pub fn map_flatten_outputs(&self, peel_array_ref: bool) -> Vec<PerlValue> {
1281 if let Some(a) = self.as_array_vec() {
1282 return a;
1283 }
1284 if peel_array_ref {
1285 if let Some(r) = self.as_array_ref() {
1286 return r.read().clone();
1287 }
1288 }
1289 if self.is_iterator() {
1290 return self.into_iterator().collect_all();
1291 }
1292 vec![self.clone()]
1293 }
1294
1295 #[inline]
1296 pub fn as_hash_map(&self) -> Option<IndexMap<String, PerlValue>> {
1297 self.with_heap(|h| match h {
1298 HeapObject::Hash(h) => Some(h.clone()),
1299 _ => None,
1300 })
1301 .flatten()
1302 }
1303
1304 #[inline]
1305 pub fn as_bytes_arc(&self) -> Option<Arc<Vec<u8>>> {
1306 self.with_heap(|h| match h {
1307 HeapObject::Bytes(b) => Some(Arc::clone(b)),
1308 _ => None,
1309 })
1310 .flatten()
1311 }
1312
1313 #[inline]
1314 pub fn as_async_task(&self) -> Option<Arc<PerlAsyncTask>> {
1315 self.with_heap(|h| match h {
1316 HeapObject::AsyncTask(t) => Some(Arc::clone(t)),
1317 _ => None,
1318 })
1319 .flatten()
1320 }
1321
1322 #[inline]
1323 pub fn as_generator(&self) -> Option<Arc<PerlGenerator>> {
1324 self.with_heap(|h| match h {
1325 HeapObject::Generator(g) => Some(Arc::clone(g)),
1326 _ => None,
1327 })
1328 .flatten()
1329 }
1330
1331 #[inline]
1332 pub fn as_atomic_arc(&self) -> Option<Arc<Mutex<PerlValue>>> {
1333 self.with_heap(|h| match h {
1334 HeapObject::Atomic(a) => Some(Arc::clone(a)),
1335 _ => None,
1336 })
1337 .flatten()
1338 }
1339
1340 #[inline]
1341 pub fn as_io_handle_name(&self) -> Option<String> {
1342 self.with_heap(|h| match h {
1343 HeapObject::IOHandle(n) => Some(n.clone()),
1344 _ => None,
1345 })
1346 .flatten()
1347 }
1348
1349 #[inline]
1350 pub fn as_sqlite_conn(&self) -> Option<Arc<Mutex<rusqlite::Connection>>> {
1351 self.with_heap(|h| match h {
1352 HeapObject::SqliteConn(c) => Some(Arc::clone(c)),
1353 _ => None,
1354 })
1355 .flatten()
1356 }
1357
1358 #[inline]
1359 pub fn as_struct_inst(&self) -> Option<Arc<StructInstance>> {
1360 self.with_heap(|h| match h {
1361 HeapObject::StructInst(s) => Some(Arc::clone(s)),
1362 _ => None,
1363 })
1364 .flatten()
1365 }
1366
1367 #[inline]
1368 pub fn as_enum_inst(&self) -> Option<Arc<EnumInstance>> {
1369 self.with_heap(|h| match h {
1370 HeapObject::EnumInst(e) => Some(Arc::clone(e)),
1371 _ => None,
1372 })
1373 .flatten()
1374 }
1375
1376 #[inline]
1377 pub fn as_class_inst(&self) -> Option<Arc<ClassInstance>> {
1378 self.with_heap(|h| match h {
1379 HeapObject::ClassInst(c) => Some(Arc::clone(c)),
1380 _ => None,
1381 })
1382 .flatten()
1383 }
1384
1385 #[inline]
1386 pub fn as_dataframe(&self) -> Option<Arc<Mutex<PerlDataFrame>>> {
1387 self.with_heap(|h| match h {
1388 HeapObject::DataFrame(d) => Some(Arc::clone(d)),
1389 _ => None,
1390 })
1391 .flatten()
1392 }
1393
1394 #[inline]
1395 pub fn as_deque(&self) -> Option<Arc<Mutex<VecDeque<PerlValue>>>> {
1396 self.with_heap(|h| match h {
1397 HeapObject::Deque(d) => Some(Arc::clone(d)),
1398 _ => None,
1399 })
1400 .flatten()
1401 }
1402
1403 #[inline]
1404 pub fn as_heap_pq(&self) -> Option<Arc<Mutex<PerlHeap>>> {
1405 self.with_heap(|h| match h {
1406 HeapObject::Heap(h) => Some(Arc::clone(h)),
1407 _ => None,
1408 })
1409 .flatten()
1410 }
1411
1412 #[inline]
1413 pub fn as_pipeline(&self) -> Option<Arc<Mutex<PipelineInner>>> {
1414 self.with_heap(|h| match h {
1415 HeapObject::Pipeline(p) => Some(Arc::clone(p)),
1416 _ => None,
1417 })
1418 .flatten()
1419 }
1420
1421 #[inline]
1422 pub fn as_capture(&self) -> Option<Arc<CaptureResult>> {
1423 self.with_heap(|h| match h {
1424 HeapObject::Capture(c) => Some(Arc::clone(c)),
1425 _ => None,
1426 })
1427 .flatten()
1428 }
1429
1430 #[inline]
1431 pub fn as_ppool(&self) -> Option<PerlPpool> {
1432 self.with_heap(|h| match h {
1433 HeapObject::Ppool(p) => Some(p.clone()),
1434 _ => None,
1435 })
1436 .flatten()
1437 }
1438
1439 #[inline]
1440 pub fn as_remote_cluster(&self) -> Option<Arc<RemoteCluster>> {
1441 self.with_heap(|h| match h {
1442 HeapObject::RemoteCluster(c) => Some(Arc::clone(c)),
1443 _ => None,
1444 })
1445 .flatten()
1446 }
1447
1448 #[inline]
1449 pub fn as_barrier(&self) -> Option<PerlBarrier> {
1450 self.with_heap(|h| match h {
1451 HeapObject::Barrier(b) => Some(b.clone()),
1452 _ => None,
1453 })
1454 .flatten()
1455 }
1456
1457 #[inline]
1458 pub fn as_channel_tx(&self) -> Option<Arc<Sender<PerlValue>>> {
1459 self.with_heap(|h| match h {
1460 HeapObject::ChannelTx(t) => Some(Arc::clone(t)),
1461 _ => None,
1462 })
1463 .flatten()
1464 }
1465
1466 #[inline]
1467 pub fn as_channel_rx(&self) -> Option<Arc<Receiver<PerlValue>>> {
1468 self.with_heap(|h| match h {
1469 HeapObject::ChannelRx(r) => Some(Arc::clone(r)),
1470 _ => None,
1471 })
1472 .flatten()
1473 }
1474
1475 #[inline]
1476 pub fn as_scalar_ref(&self) -> Option<Arc<RwLock<PerlValue>>> {
1477 self.with_heap(|h| match h {
1478 HeapObject::ScalarRef(r) => Some(Arc::clone(r)),
1479 _ => None,
1480 })
1481 .flatten()
1482 }
1483
1484 #[inline]
1486 pub fn as_capture_cell(&self) -> Option<Arc<RwLock<PerlValue>>> {
1487 self.with_heap(|h| match h {
1488 HeapObject::CaptureCell(r) => Some(Arc::clone(r)),
1489 _ => None,
1490 })
1491 .flatten()
1492 }
1493
1494 #[inline]
1496 pub fn as_scalar_binding_name(&self) -> Option<String> {
1497 self.with_heap(|h| match h {
1498 HeapObject::ScalarBindingRef(s) => Some(s.clone()),
1499 _ => None,
1500 })
1501 .flatten()
1502 }
1503
1504 #[inline]
1506 pub fn as_array_binding_name(&self) -> Option<String> {
1507 self.with_heap(|h| match h {
1508 HeapObject::ArrayBindingRef(s) => Some(s.clone()),
1509 _ => None,
1510 })
1511 .flatten()
1512 }
1513
1514 #[inline]
1516 pub fn as_hash_binding_name(&self) -> Option<String> {
1517 self.with_heap(|h| match h {
1518 HeapObject::HashBindingRef(s) => Some(s.clone()),
1519 _ => None,
1520 })
1521 .flatten()
1522 }
1523
1524 #[inline]
1525 pub fn as_array_ref(&self) -> Option<Arc<RwLock<Vec<PerlValue>>>> {
1526 self.with_heap(|h| match h {
1527 HeapObject::ArrayRef(r) => Some(Arc::clone(r)),
1528 _ => None,
1529 })
1530 .flatten()
1531 }
1532
1533 #[inline]
1534 pub fn as_hash_ref(&self) -> Option<Arc<RwLock<IndexMap<String, PerlValue>>>> {
1535 self.with_heap(|h| match h {
1536 HeapObject::HashRef(r) => Some(Arc::clone(r)),
1537 _ => None,
1538 })
1539 .flatten()
1540 }
1541
1542 #[inline]
1544 pub fn is_mysync_deque_or_heap(&self) -> bool {
1545 matches!(
1546 self.with_heap(|h| matches!(h, HeapObject::Deque(_) | HeapObject::Heap(_))),
1547 Some(true)
1548 )
1549 }
1550
1551 #[inline]
1552 pub fn regex(rx: Arc<PerlCompiledRegex>, pattern_src: String, flags: String) -> Self {
1553 Self::from_heap(Arc::new(HeapObject::Regex(rx, pattern_src, flags)))
1554 }
1555
1556 #[inline]
1558 pub fn regex_src_and_flags(&self) -> Option<(String, String)> {
1559 self.with_heap(|h| match h {
1560 HeapObject::Regex(_, pat, fl) => Some((pat.clone(), fl.clone())),
1561 _ => None,
1562 })
1563 .flatten()
1564 }
1565
1566 #[inline]
1567 pub fn blessed(b: Arc<BlessedRef>) -> Self {
1568 Self::from_heap(Arc::new(HeapObject::Blessed(b)))
1569 }
1570
1571 #[inline]
1572 pub fn io_handle(name: String) -> Self {
1573 Self::from_heap(Arc::new(HeapObject::IOHandle(name)))
1574 }
1575
1576 #[inline]
1577 pub fn atomic(a: Arc<Mutex<PerlValue>>) -> Self {
1578 Self::from_heap(Arc::new(HeapObject::Atomic(a)))
1579 }
1580
1581 #[inline]
1582 pub fn set(s: Arc<PerlSet>) -> Self {
1583 Self::from_heap(Arc::new(HeapObject::Set(s)))
1584 }
1585
1586 #[inline]
1587 pub fn channel_tx(tx: Arc<Sender<PerlValue>>) -> Self {
1588 Self::from_heap(Arc::new(HeapObject::ChannelTx(tx)))
1589 }
1590
1591 #[inline]
1592 pub fn channel_rx(rx: Arc<Receiver<PerlValue>>) -> Self {
1593 Self::from_heap(Arc::new(HeapObject::ChannelRx(rx)))
1594 }
1595
1596 #[inline]
1597 pub fn async_task(t: Arc<PerlAsyncTask>) -> Self {
1598 Self::from_heap(Arc::new(HeapObject::AsyncTask(t)))
1599 }
1600
1601 #[inline]
1602 pub fn generator(g: Arc<PerlGenerator>) -> Self {
1603 Self::from_heap(Arc::new(HeapObject::Generator(g)))
1604 }
1605
1606 #[inline]
1607 pub fn deque(d: Arc<Mutex<VecDeque<PerlValue>>>) -> Self {
1608 Self::from_heap(Arc::new(HeapObject::Deque(d)))
1609 }
1610
1611 #[inline]
1612 pub fn heap(h: Arc<Mutex<PerlHeap>>) -> Self {
1613 Self::from_heap(Arc::new(HeapObject::Heap(h)))
1614 }
1615
1616 #[inline]
1617 pub fn pipeline(p: Arc<Mutex<PipelineInner>>) -> Self {
1618 Self::from_heap(Arc::new(HeapObject::Pipeline(p)))
1619 }
1620
1621 #[inline]
1622 pub fn capture(c: Arc<CaptureResult>) -> Self {
1623 Self::from_heap(Arc::new(HeapObject::Capture(c)))
1624 }
1625
1626 #[inline]
1627 pub fn ppool(p: PerlPpool) -> Self {
1628 Self::from_heap(Arc::new(HeapObject::Ppool(p)))
1629 }
1630
1631 #[inline]
1632 pub fn remote_cluster(c: Arc<RemoteCluster>) -> Self {
1633 Self::from_heap(Arc::new(HeapObject::RemoteCluster(c)))
1634 }
1635
1636 #[inline]
1637 pub fn barrier(b: PerlBarrier) -> Self {
1638 Self::from_heap(Arc::new(HeapObject::Barrier(b)))
1639 }
1640
1641 #[inline]
1642 pub fn sqlite_conn(c: Arc<Mutex<rusqlite::Connection>>) -> Self {
1643 Self::from_heap(Arc::new(HeapObject::SqliteConn(c)))
1644 }
1645
1646 #[inline]
1647 pub fn struct_inst(s: Arc<StructInstance>) -> Self {
1648 Self::from_heap(Arc::new(HeapObject::StructInst(s)))
1649 }
1650
1651 #[inline]
1652 pub fn enum_inst(e: Arc<EnumInstance>) -> Self {
1653 Self::from_heap(Arc::new(HeapObject::EnumInst(e)))
1654 }
1655
1656 #[inline]
1657 pub fn class_inst(c: Arc<ClassInstance>) -> Self {
1658 Self::from_heap(Arc::new(HeapObject::ClassInst(c)))
1659 }
1660
1661 #[inline]
1662 pub fn dataframe(df: Arc<Mutex<PerlDataFrame>>) -> Self {
1663 Self::from_heap(Arc::new(HeapObject::DataFrame(df)))
1664 }
1665
1666 #[inline]
1668 pub fn errno_dual(code: i32, msg: String) -> Self {
1669 Self::from_heap(Arc::new(HeapObject::ErrnoDual { code, msg }))
1670 }
1671
1672 #[inline]
1674 pub(crate) fn errno_dual_parts(&self) -> Option<(i32, String)> {
1675 if !nanbox::is_heap(self.0) {
1676 return None;
1677 }
1678 match unsafe { self.heap_ref() } {
1679 HeapObject::ErrnoDual { code, msg } => Some((*code, msg.clone())),
1680 _ => None,
1681 }
1682 }
1683
1684 #[inline]
1686 pub fn as_str(&self) -> Option<String> {
1687 if !nanbox::is_heap(self.0) {
1688 return None;
1689 }
1690 match unsafe { self.heap_ref() } {
1691 HeapObject::String(s) => Some(s.clone()),
1692 _ => None,
1693 }
1694 }
1695
1696 #[inline]
1697 pub fn append_to(&self, buf: &mut String) {
1698 if nanbox::is_imm_undef(self.0) {
1699 return;
1700 }
1701 if let Some(n) = nanbox::as_imm_int32(self.0) {
1702 let mut b = itoa::Buffer::new();
1703 buf.push_str(b.format(n));
1704 return;
1705 }
1706 if nanbox::is_raw_float_bits(self.0) {
1707 buf.push_str(&format_float(f64::from_bits(self.0)));
1708 return;
1709 }
1710 match unsafe { self.heap_ref() } {
1711 HeapObject::String(s) => buf.push_str(s),
1712 HeapObject::ErrnoDual { msg, .. } => buf.push_str(msg),
1713 HeapObject::Bytes(b) => buf.push_str(&decode_utf8_or_latin1(b)),
1714 HeapObject::Atomic(arc) => arc.lock().append_to(buf),
1715 HeapObject::Set(s) => {
1716 buf.push('{');
1717 let mut first = true;
1718 for v in s.values() {
1719 if !first {
1720 buf.push(',');
1721 }
1722 first = false;
1723 v.append_to(buf);
1724 }
1725 buf.push('}');
1726 }
1727 HeapObject::ChannelTx(_) => buf.push_str("PCHANNEL::Tx"),
1728 HeapObject::ChannelRx(_) => buf.push_str("PCHANNEL::Rx"),
1729 HeapObject::AsyncTask(_) => buf.push_str("AsyncTask"),
1730 HeapObject::Generator(_) => buf.push_str("Generator"),
1731 HeapObject::Pipeline(_) => buf.push_str("Pipeline"),
1732 HeapObject::DataFrame(d) => {
1733 let g = d.lock();
1734 buf.push_str(&format!("DataFrame({}x{})", g.nrows(), g.ncols()));
1735 }
1736 HeapObject::Capture(_) => buf.push_str("Capture"),
1737 HeapObject::Ppool(_) => buf.push_str("Ppool"),
1738 HeapObject::RemoteCluster(_) => buf.push_str("Cluster"),
1739 HeapObject::Barrier(_) => buf.push_str("Barrier"),
1740 HeapObject::SqliteConn(_) => buf.push_str("SqliteConn"),
1741 HeapObject::StructInst(s) => buf.push_str(&s.def.name),
1742 _ => buf.push_str(&self.to_string()),
1743 }
1744 }
1745
1746 #[inline]
1747 pub fn unwrap_atomic(&self) -> PerlValue {
1748 if !nanbox::is_heap(self.0) {
1749 return self.clone();
1750 }
1751 match unsafe { self.heap_ref() } {
1752 HeapObject::Atomic(a) => a.lock().clone(),
1753 _ => self.clone(),
1754 }
1755 }
1756
1757 #[inline]
1758 pub fn is_atomic(&self) -> bool {
1759 if !nanbox::is_heap(self.0) {
1760 return false;
1761 }
1762 matches!(unsafe { self.heap_ref() }, HeapObject::Atomic(_))
1763 }
1764
1765 #[inline]
1766 pub fn is_true(&self) -> bool {
1767 if nanbox::is_imm_undef(self.0) {
1768 return false;
1769 }
1770 if let Some(n) = nanbox::as_imm_int32(self.0) {
1771 return n != 0;
1772 }
1773 if nanbox::is_raw_float_bits(self.0) {
1774 return f64::from_bits(self.0) != 0.0;
1775 }
1776 match unsafe { self.heap_ref() } {
1777 HeapObject::ErrnoDual { code, msg } => *code != 0 || !msg.is_empty(),
1778 HeapObject::String(s) => !s.is_empty() && s != "0",
1779 HeapObject::Bytes(b) => !b.is_empty(),
1780 HeapObject::Array(a) => !a.is_empty(),
1781 HeapObject::Hash(h) => !h.is_empty(),
1782 HeapObject::Atomic(arc) => arc.lock().is_true(),
1783 HeapObject::Set(s) => !s.is_empty(),
1784 HeapObject::Deque(d) => !d.lock().is_empty(),
1785 HeapObject::Heap(h) => !h.lock().items.is_empty(),
1786 HeapObject::DataFrame(d) => d.lock().nrows() > 0,
1787 HeapObject::Pipeline(_) | HeapObject::Capture(_) => true,
1788 _ => true,
1789 }
1790 }
1791
1792 #[inline]
1795 pub(crate) fn concat_append_owned(self, rhs: &PerlValue) -> PerlValue {
1796 let mut s = self.into_string();
1797 rhs.append_to(&mut s);
1798 PerlValue::string(s)
1799 }
1800
1801 #[inline]
1807 pub(crate) fn try_concat_repeat_inplace(&mut self, rhs: &str, n: usize) -> bool {
1808 if !nanbox::is_heap(self.0) || n == 0 {
1809 return n == 0 && nanbox::is_heap(self.0);
1811 }
1812 unsafe {
1813 if !matches!(self.heap_ref(), HeapObject::String(_)) {
1814 return false;
1815 }
1816 let raw = nanbox::decode_heap_ptr::<HeapObject>(self.0) as *mut HeapObject
1817 as *const HeapObject;
1818 let mut arc: Arc<HeapObject> = Arc::from_raw(raw);
1819 let did = if let Some(HeapObject::String(s)) = Arc::get_mut(&mut arc) {
1820 if !rhs.is_empty() {
1821 s.reserve(rhs.len().saturating_mul(n));
1822 for _ in 0..n {
1823 s.push_str(rhs);
1824 }
1825 }
1826 true
1827 } else {
1828 false
1829 };
1830 let restored = Arc::into_raw(arc);
1831 self.0 = nanbox::encode_heap_ptr(restored);
1832 did
1833 }
1834 }
1835
1836 #[inline]
1846 pub(crate) fn try_concat_append_inplace(&mut self, rhs: &PerlValue) -> bool {
1847 if !nanbox::is_heap(self.0) {
1848 return false;
1849 }
1850 unsafe {
1854 if !matches!(self.heap_ref(), HeapObject::String(_)) {
1855 return false;
1856 }
1857 let raw = nanbox::decode_heap_ptr::<HeapObject>(self.0) as *mut HeapObject
1860 as *const HeapObject;
1861 let mut arc: Arc<HeapObject> = Arc::from_raw(raw);
1862 let did_append = if let Some(HeapObject::String(s)) = Arc::get_mut(&mut arc) {
1863 rhs.append_to(s);
1864 true
1865 } else {
1866 false
1867 };
1868 let restored = Arc::into_raw(arc);
1871 self.0 = nanbox::encode_heap_ptr(restored);
1872 did_append
1873 }
1874 }
1875
1876 #[inline]
1877 pub fn into_string(self) -> String {
1878 let bits = self.0;
1879 std::mem::forget(self);
1880 if nanbox::is_imm_undef(bits) {
1881 return String::new();
1882 }
1883 if let Some(n) = nanbox::as_imm_int32(bits) {
1884 let mut buf = itoa::Buffer::new();
1885 return buf.format(n).to_owned();
1886 }
1887 if nanbox::is_raw_float_bits(bits) {
1888 return format_float(f64::from_bits(bits));
1889 }
1890 if nanbox::is_heap(bits) {
1891 unsafe {
1892 let arc =
1893 Arc::from_raw(nanbox::decode_heap_ptr::<HeapObject>(bits) as *mut HeapObject);
1894 match Arc::try_unwrap(arc) {
1895 Ok(HeapObject::String(s)) => return s,
1896 Ok(o) => return PerlValue::from_heap(Arc::new(o)).to_string(),
1897 Err(arc) => {
1898 return match &*arc {
1899 HeapObject::String(s) => s.clone(),
1900 _ => PerlValue::from_heap(Arc::clone(&arc)).to_string(),
1901 };
1902 }
1903 }
1904 }
1905 }
1906 String::new()
1907 }
1908
1909 #[inline]
1910 pub fn as_str_or_empty(&self) -> String {
1911 if !nanbox::is_heap(self.0) {
1912 return String::new();
1913 }
1914 match unsafe { self.heap_ref() } {
1915 HeapObject::String(s) => s.clone(),
1916 HeapObject::ErrnoDual { msg, .. } => msg.clone(),
1917 _ => String::new(),
1918 }
1919 }
1920
1921 #[inline]
1922 pub fn to_number(&self) -> f64 {
1923 if nanbox::is_imm_undef(self.0) {
1924 return 0.0;
1925 }
1926 if let Some(n) = nanbox::as_imm_int32(self.0) {
1927 return n as f64;
1928 }
1929 if nanbox::is_raw_float_bits(self.0) {
1930 return f64::from_bits(self.0);
1931 }
1932 match unsafe { self.heap_ref() } {
1933 HeapObject::Integer(n) => *n as f64,
1934 HeapObject::Float(f) => *f,
1935 HeapObject::ErrnoDual { code, .. } => *code as f64,
1936 HeapObject::String(s) => parse_number(s),
1937 HeapObject::Bytes(b) => b.len() as f64,
1938 HeapObject::Array(a) => a.len() as f64,
1939 HeapObject::Atomic(arc) => arc.lock().to_number(),
1940 HeapObject::Set(s) => s.len() as f64,
1941 HeapObject::ChannelTx(_)
1942 | HeapObject::ChannelRx(_)
1943 | HeapObject::AsyncTask(_)
1944 | HeapObject::Generator(_) => 1.0,
1945 HeapObject::Deque(d) => d.lock().len() as f64,
1946 HeapObject::Heap(h) => h.lock().items.len() as f64,
1947 HeapObject::Pipeline(p) => p.lock().source.len() as f64,
1948 HeapObject::DataFrame(d) => d.lock().nrows() as f64,
1949 HeapObject::Capture(_)
1950 | HeapObject::Ppool(_)
1951 | HeapObject::RemoteCluster(_)
1952 | HeapObject::Barrier(_)
1953 | HeapObject::SqliteConn(_)
1954 | HeapObject::StructInst(_)
1955 | HeapObject::IOHandle(_) => 1.0,
1956 _ => 0.0,
1957 }
1958 }
1959
1960 #[inline]
1961 pub fn to_int(&self) -> i64 {
1962 if nanbox::is_imm_undef(self.0) {
1963 return 0;
1964 }
1965 if let Some(n) = nanbox::as_imm_int32(self.0) {
1966 return n as i64;
1967 }
1968 if nanbox::is_raw_float_bits(self.0) {
1969 return f64::from_bits(self.0) as i64;
1970 }
1971 match unsafe { self.heap_ref() } {
1972 HeapObject::Integer(n) => *n,
1973 HeapObject::Float(f) => *f as i64,
1974 HeapObject::ErrnoDual { code, .. } => *code as i64,
1975 HeapObject::String(s) => parse_number(s) as i64,
1976 HeapObject::Bytes(b) => b.len() as i64,
1977 HeapObject::Array(a) => a.len() as i64,
1978 HeapObject::Atomic(arc) => arc.lock().to_int(),
1979 HeapObject::Set(s) => s.len() as i64,
1980 HeapObject::ChannelTx(_)
1981 | HeapObject::ChannelRx(_)
1982 | HeapObject::AsyncTask(_)
1983 | HeapObject::Generator(_) => 1,
1984 HeapObject::Deque(d) => d.lock().len() as i64,
1985 HeapObject::Heap(h) => h.lock().items.len() as i64,
1986 HeapObject::Pipeline(p) => p.lock().source.len() as i64,
1987 HeapObject::DataFrame(d) => d.lock().nrows() as i64,
1988 HeapObject::Capture(_)
1989 | HeapObject::Ppool(_)
1990 | HeapObject::RemoteCluster(_)
1991 | HeapObject::Barrier(_)
1992 | HeapObject::SqliteConn(_)
1993 | HeapObject::StructInst(_)
1994 | HeapObject::IOHandle(_) => 1,
1995 _ => 0,
1996 }
1997 }
1998
1999 pub fn type_name(&self) -> String {
2000 if nanbox::is_imm_undef(self.0) {
2001 return "undef".to_string();
2002 }
2003 if nanbox::as_imm_int32(self.0).is_some() {
2004 return "INTEGER".to_string();
2005 }
2006 if nanbox::is_raw_float_bits(self.0) {
2007 return "FLOAT".to_string();
2008 }
2009 match unsafe { self.heap_ref() } {
2010 HeapObject::String(_) => "STRING".to_string(),
2011 HeapObject::Bytes(_) => "BYTES".to_string(),
2012 HeapObject::Array(_) => "ARRAY".to_string(),
2013 HeapObject::Hash(_) => "HASH".to_string(),
2014 HeapObject::ArrayRef(_) | HeapObject::ArrayBindingRef(_) => "ARRAY".to_string(),
2015 HeapObject::HashRef(_) | HeapObject::HashBindingRef(_) => "HASH".to_string(),
2016 HeapObject::ScalarRef(_)
2017 | HeapObject::ScalarBindingRef(_)
2018 | HeapObject::CaptureCell(_) => "SCALAR".to_string(),
2019 HeapObject::CodeRef(_) => "CODE".to_string(),
2020 HeapObject::Regex(_, _, _) => "Regexp".to_string(),
2021 HeapObject::Blessed(b) => b.class.clone(),
2022 HeapObject::IOHandle(_) => "GLOB".to_string(),
2023 HeapObject::Atomic(_) => "ATOMIC".to_string(),
2024 HeapObject::Set(_) => "Set".to_string(),
2025 HeapObject::ChannelTx(_) => "PCHANNEL::Tx".to_string(),
2026 HeapObject::ChannelRx(_) => "PCHANNEL::Rx".to_string(),
2027 HeapObject::AsyncTask(_) => "ASYNCTASK".to_string(),
2028 HeapObject::Generator(_) => "Generator".to_string(),
2029 HeapObject::Deque(_) => "Deque".to_string(),
2030 HeapObject::Heap(_) => "Heap".to_string(),
2031 HeapObject::Pipeline(_) => "Pipeline".to_string(),
2032 HeapObject::DataFrame(_) => "DataFrame".to_string(),
2033 HeapObject::Capture(_) => "Capture".to_string(),
2034 HeapObject::Ppool(_) => "Ppool".to_string(),
2035 HeapObject::RemoteCluster(_) => "Cluster".to_string(),
2036 HeapObject::Barrier(_) => "Barrier".to_string(),
2037 HeapObject::SqliteConn(_) => "SqliteConn".to_string(),
2038 HeapObject::StructInst(s) => s.def.name.to_string(),
2039 HeapObject::EnumInst(e) => e.def.name.to_string(),
2040 HeapObject::ClassInst(c) => c.def.name.to_string(),
2041 HeapObject::Iterator(_) => "Iterator".to_string(),
2042 HeapObject::ErrnoDual { .. } => "Errno".to_string(),
2043 HeapObject::Integer(_) => "INTEGER".to_string(),
2044 HeapObject::Float(_) => "FLOAT".to_string(),
2045 }
2046 }
2047
2048 pub fn ref_type(&self) -> PerlValue {
2049 if !nanbox::is_heap(self.0) {
2050 return PerlValue::string(String::new());
2051 }
2052 match unsafe { self.heap_ref() } {
2053 HeapObject::ArrayRef(_) | HeapObject::ArrayBindingRef(_) => {
2054 PerlValue::string("ARRAY".into())
2055 }
2056 HeapObject::HashRef(_) | HeapObject::HashBindingRef(_) => {
2057 PerlValue::string("HASH".into())
2058 }
2059 HeapObject::ScalarRef(_) | HeapObject::ScalarBindingRef(_) => {
2060 PerlValue::string("SCALAR".into())
2061 }
2062 HeapObject::CodeRef(_) => PerlValue::string("CODE".into()),
2063 HeapObject::Regex(_, _, _) => PerlValue::string("Regexp".into()),
2064 HeapObject::Atomic(_) => PerlValue::string("ATOMIC".into()),
2065 HeapObject::Set(_) => PerlValue::string("Set".into()),
2066 HeapObject::ChannelTx(_) => PerlValue::string("PCHANNEL::Tx".into()),
2067 HeapObject::ChannelRx(_) => PerlValue::string("PCHANNEL::Rx".into()),
2068 HeapObject::AsyncTask(_) => PerlValue::string("ASYNCTASK".into()),
2069 HeapObject::Generator(_) => PerlValue::string("Generator".into()),
2070 HeapObject::Deque(_) => PerlValue::string("Deque".into()),
2071 HeapObject::Heap(_) => PerlValue::string("Heap".into()),
2072 HeapObject::Pipeline(_) => PerlValue::string("Pipeline".into()),
2073 HeapObject::DataFrame(_) => PerlValue::string("DataFrame".into()),
2074 HeapObject::Capture(_) => PerlValue::string("Capture".into()),
2075 HeapObject::Ppool(_) => PerlValue::string("Ppool".into()),
2076 HeapObject::RemoteCluster(_) => PerlValue::string("Cluster".into()),
2077 HeapObject::Barrier(_) => PerlValue::string("Barrier".into()),
2078 HeapObject::SqliteConn(_) => PerlValue::string("SqliteConn".into()),
2079 HeapObject::StructInst(s) => PerlValue::string(s.def.name.clone()),
2080 HeapObject::EnumInst(e) => PerlValue::string(e.def.name.clone()),
2081 HeapObject::Bytes(_) => PerlValue::string("BYTES".into()),
2082 HeapObject::Blessed(b) => PerlValue::string(b.class.clone()),
2083 _ => PerlValue::string(String::new()),
2084 }
2085 }
2086
2087 pub fn num_cmp(&self, other: &PerlValue) -> Ordering {
2088 let a = self.to_number();
2089 let b = other.to_number();
2090 a.partial_cmp(&b).unwrap_or(Ordering::Equal)
2091 }
2092
2093 #[inline]
2095 pub fn str_eq(&self, other: &PerlValue) -> bool {
2096 if nanbox::is_heap(self.0) && nanbox::is_heap(other.0) {
2097 if let (HeapObject::String(a), HeapObject::String(b)) =
2098 unsafe { (self.heap_ref(), other.heap_ref()) }
2099 {
2100 return a == b;
2101 }
2102 }
2103 self.to_string() == other.to_string()
2104 }
2105
2106 pub fn str_cmp(&self, other: &PerlValue) -> Ordering {
2107 if nanbox::is_heap(self.0) && nanbox::is_heap(other.0) {
2108 if let (HeapObject::String(a), HeapObject::String(b)) =
2109 unsafe { (self.heap_ref(), other.heap_ref()) }
2110 {
2111 return a.cmp(b);
2112 }
2113 }
2114 self.to_string().cmp(&other.to_string())
2115 }
2116
2117 pub fn struct_field_eq(&self, other: &PerlValue) -> bool {
2119 if nanbox::is_imm_undef(self.0) && nanbox::is_imm_undef(other.0) {
2120 return true;
2121 }
2122 if let (Some(a), Some(b)) = (nanbox::as_imm_int32(self.0), nanbox::as_imm_int32(other.0)) {
2123 return a == b;
2124 }
2125 if nanbox::is_raw_float_bits(self.0) && nanbox::is_raw_float_bits(other.0) {
2126 return f64::from_bits(self.0) == f64::from_bits(other.0);
2127 }
2128 if !nanbox::is_heap(self.0) || !nanbox::is_heap(other.0) {
2129 return self.to_number() == other.to_number();
2130 }
2131 match (unsafe { self.heap_ref() }, unsafe { other.heap_ref() }) {
2132 (HeapObject::String(a), HeapObject::String(b)) => a == b,
2133 (HeapObject::Integer(a), HeapObject::Integer(b)) => a == b,
2134 (HeapObject::Float(a), HeapObject::Float(b)) => a == b,
2135 (HeapObject::Array(a), HeapObject::Array(b)) => {
2136 a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| x.struct_field_eq(y))
2137 }
2138 (HeapObject::ArrayRef(a), HeapObject::ArrayRef(b)) => {
2139 let ag = a.read();
2140 let bg = b.read();
2141 ag.len() == bg.len() && ag.iter().zip(bg.iter()).all(|(x, y)| x.struct_field_eq(y))
2142 }
2143 (HeapObject::Hash(a), HeapObject::Hash(b)) => {
2144 a.len() == b.len()
2145 && a.iter()
2146 .all(|(k, v)| b.get(k).is_some_and(|bv| v.struct_field_eq(bv)))
2147 }
2148 (HeapObject::HashRef(a), HeapObject::HashRef(b)) => {
2149 let ag = a.read();
2150 let bg = b.read();
2151 ag.len() == bg.len()
2152 && ag
2153 .iter()
2154 .all(|(k, v)| bg.get(k).is_some_and(|bv| v.struct_field_eq(bv)))
2155 }
2156 (HeapObject::StructInst(a), HeapObject::StructInst(b)) => {
2157 if a.def.name != b.def.name {
2158 false
2159 } else {
2160 let av = a.get_values();
2161 let bv = b.get_values();
2162 av.len() == bv.len()
2163 && av.iter().zip(bv.iter()).all(|(x, y)| x.struct_field_eq(y))
2164 }
2165 }
2166 _ => self.to_string() == other.to_string(),
2167 }
2168 }
2169
2170 pub fn deep_clone(&self) -> PerlValue {
2172 if !nanbox::is_heap(self.0) {
2173 return self.clone();
2174 }
2175 match unsafe { self.heap_ref() } {
2176 HeapObject::Array(a) => PerlValue::array(a.iter().map(|v| v.deep_clone()).collect()),
2177 HeapObject::ArrayRef(a) => {
2178 let cloned: Vec<PerlValue> = a.read().iter().map(|v| v.deep_clone()).collect();
2179 PerlValue::array_ref(Arc::new(RwLock::new(cloned)))
2180 }
2181 HeapObject::Hash(h) => {
2182 let mut cloned = IndexMap::new();
2183 for (k, v) in h.iter() {
2184 cloned.insert(k.clone(), v.deep_clone());
2185 }
2186 PerlValue::hash(cloned)
2187 }
2188 HeapObject::HashRef(h) => {
2189 let mut cloned = IndexMap::new();
2190 for (k, v) in h.read().iter() {
2191 cloned.insert(k.clone(), v.deep_clone());
2192 }
2193 PerlValue::hash_ref(Arc::new(RwLock::new(cloned)))
2194 }
2195 HeapObject::StructInst(s) => {
2196 let new_values = s.get_values().iter().map(|v| v.deep_clone()).collect();
2197 PerlValue::struct_inst(Arc::new(StructInstance::new(
2198 Arc::clone(&s.def),
2199 new_values,
2200 )))
2201 }
2202 _ => self.clone(),
2203 }
2204 }
2205
2206 pub fn to_list(&self) -> Vec<PerlValue> {
2207 if nanbox::is_imm_undef(self.0) {
2208 return vec![];
2209 }
2210 if !nanbox::is_heap(self.0) {
2211 return vec![self.clone()];
2212 }
2213 match unsafe { self.heap_ref() } {
2214 HeapObject::Array(a) => a.clone(),
2215 HeapObject::Hash(h) => h
2216 .iter()
2217 .flat_map(|(k, v)| vec![PerlValue::string(k.clone()), v.clone()])
2218 .collect(),
2219 HeapObject::Atomic(arc) => arc.lock().to_list(),
2220 HeapObject::Set(s) => s.values().cloned().collect(),
2221 HeapObject::Deque(d) => d.lock().iter().cloned().collect(),
2222 HeapObject::Iterator(it) => {
2223 let mut out = Vec::new();
2224 while let Some(v) = it.next_item() {
2225 out.push(v);
2226 }
2227 out
2228 }
2229 _ => vec![self.clone()],
2230 }
2231 }
2232
2233 pub fn scalar_context(&self) -> PerlValue {
2234 if !nanbox::is_heap(self.0) {
2235 return self.clone();
2236 }
2237 if let Some(arc) = self.as_atomic_arc() {
2238 return arc.lock().scalar_context();
2239 }
2240 match unsafe { self.heap_ref() } {
2241 HeapObject::Array(a) => PerlValue::integer(a.len() as i64),
2242 HeapObject::Hash(h) => {
2243 if h.is_empty() {
2244 PerlValue::integer(0)
2245 } else {
2246 PerlValue::string(format!("{}/{}", h.len(), h.capacity()))
2247 }
2248 }
2249 HeapObject::Set(s) => PerlValue::integer(s.len() as i64),
2250 HeapObject::Deque(d) => PerlValue::integer(d.lock().len() as i64),
2251 HeapObject::Heap(h) => PerlValue::integer(h.lock().items.len() as i64),
2252 HeapObject::Pipeline(p) => PerlValue::integer(p.lock().source.len() as i64),
2253 HeapObject::Capture(_)
2254 | HeapObject::Ppool(_)
2255 | HeapObject::RemoteCluster(_)
2256 | HeapObject::Barrier(_) => PerlValue::integer(1),
2257 HeapObject::Generator(_) => PerlValue::integer(1),
2258 _ => self.clone(),
2259 }
2260 }
2261}
2262
2263impl fmt::Display for PerlValue {
2264 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2265 if nanbox::is_imm_undef(self.0) {
2266 return Ok(());
2267 }
2268 if let Some(n) = nanbox::as_imm_int32(self.0) {
2269 return write!(f, "{n}");
2270 }
2271 if nanbox::is_raw_float_bits(self.0) {
2272 return write!(f, "{}", format_float(f64::from_bits(self.0)));
2273 }
2274 match unsafe { self.heap_ref() } {
2275 HeapObject::Integer(n) => write!(f, "{n}"),
2276 HeapObject::Float(val) => write!(f, "{}", format_float(*val)),
2277 HeapObject::ErrnoDual { msg, .. } => f.write_str(msg),
2278 HeapObject::String(s) => f.write_str(s),
2279 HeapObject::Bytes(b) => f.write_str(&decode_utf8_or_latin1(b)),
2280 HeapObject::Array(a) => {
2281 for v in a {
2282 write!(f, "{v}")?;
2283 }
2284 Ok(())
2285 }
2286 HeapObject::Hash(h) => write!(f, "{}/{}", h.len(), h.capacity()),
2287 HeapObject::ArrayRef(_) | HeapObject::ArrayBindingRef(_) => f.write_str("ARRAY(0x...)"),
2288 HeapObject::HashRef(_) | HeapObject::HashBindingRef(_) => f.write_str("HASH(0x...)"),
2289 HeapObject::ScalarRef(_)
2290 | HeapObject::ScalarBindingRef(_)
2291 | HeapObject::CaptureCell(_) => f.write_str("SCALAR(0x...)"),
2292 HeapObject::CodeRef(sub) => write!(f, "CODE({})", sub.name),
2293 HeapObject::Regex(_, src, _) => write!(f, "(?:{src})"),
2294 HeapObject::Blessed(b) => write!(f, "{}=HASH(0x...)", b.class),
2295 HeapObject::IOHandle(name) => f.write_str(name),
2296 HeapObject::Atomic(arc) => write!(f, "{}", arc.lock()),
2297 HeapObject::Set(s) => {
2298 f.write_str("{")?;
2299 if !s.is_empty() {
2300 let mut iter = s.values();
2301 if let Some(v) = iter.next() {
2302 write!(f, "{v}")?;
2303 }
2304 for v in iter {
2305 write!(f, ",{v}")?;
2306 }
2307 }
2308 f.write_str("}")
2309 }
2310 HeapObject::ChannelTx(_) => f.write_str("PCHANNEL::Tx"),
2311 HeapObject::ChannelRx(_) => f.write_str("PCHANNEL::Rx"),
2312 HeapObject::AsyncTask(_) => f.write_str("AsyncTask"),
2313 HeapObject::Generator(g) => write!(f, "Generator({} stmts)", g.block.len()),
2314 HeapObject::Deque(d) => write!(f, "Deque({})", d.lock().len()),
2315 HeapObject::Heap(h) => write!(f, "Heap({})", h.lock().items.len()),
2316 HeapObject::Pipeline(p) => {
2317 let g = p.lock();
2318 write!(f, "Pipeline({} ops)", g.ops.len())
2319 }
2320 HeapObject::Capture(c) => write!(f, "Capture(exit={})", c.exitcode),
2321 HeapObject::Ppool(_) => f.write_str("Ppool"),
2322 HeapObject::RemoteCluster(c) => write!(f, "Cluster({} slots)", c.slots.len()),
2323 HeapObject::Barrier(_) => f.write_str("Barrier"),
2324 HeapObject::SqliteConn(_) => f.write_str("SqliteConn"),
2325 HeapObject::StructInst(s) => {
2326 write!(f, "{}(", s.def.name)?;
2328 let values = s.values.read();
2329 for (i, field) in s.def.fields.iter().enumerate() {
2330 if i > 0 {
2331 f.write_str(", ")?;
2332 }
2333 write!(
2334 f,
2335 "{} => {}",
2336 field.name,
2337 values.get(i).cloned().unwrap_or(PerlValue::UNDEF)
2338 )?;
2339 }
2340 f.write_str(")")
2341 }
2342 HeapObject::EnumInst(e) => {
2343 write!(f, "{}::{}", e.def.name, e.variant_name())?;
2345 if e.def.variants[e.variant_idx].ty.is_some() {
2346 write!(f, "({})", e.data)?;
2347 }
2348 Ok(())
2349 }
2350 HeapObject::ClassInst(c) => {
2351 write!(f, "{}(", c.def.name)?;
2353 let values = c.values.read();
2354 for (i, field) in c.def.fields.iter().enumerate() {
2355 if i > 0 {
2356 f.write_str(", ")?;
2357 }
2358 write!(
2359 f,
2360 "{} => {}",
2361 field.name,
2362 values.get(i).cloned().unwrap_or(PerlValue::UNDEF)
2363 )?;
2364 }
2365 f.write_str(")")
2366 }
2367 HeapObject::DataFrame(d) => {
2368 let g = d.lock();
2369 write!(f, "DataFrame({} rows)", g.nrows())
2370 }
2371 HeapObject::Iterator(_) => f.write_str("Iterator"),
2372 }
2373 }
2374}
2375
2376pub fn set_member_key(v: &PerlValue) -> String {
2378 if nanbox::is_imm_undef(v.0) {
2379 return "u:".to_string();
2380 }
2381 if let Some(n) = nanbox::as_imm_int32(v.0) {
2382 return format!("i:{n}");
2383 }
2384 if nanbox::is_raw_float_bits(v.0) {
2385 return format!("f:{}", f64::from_bits(v.0).to_bits());
2386 }
2387 match unsafe { v.heap_ref() } {
2388 HeapObject::String(s) => format!("s:{s}"),
2389 HeapObject::Bytes(b) => {
2390 use std::fmt::Write as _;
2391 let mut h = String::with_capacity(b.len() * 2);
2392 for &x in b.iter() {
2393 let _ = write!(&mut h, "{:02x}", x);
2394 }
2395 format!("by:{h}")
2396 }
2397 HeapObject::Array(a) => {
2398 let parts: Vec<_> = a.iter().map(set_member_key).collect();
2399 format!("a:{}", parts.join(","))
2400 }
2401 HeapObject::Hash(h) => {
2402 let mut keys: Vec<_> = h.keys().cloned().collect();
2403 keys.sort();
2404 let parts: Vec<_> = keys
2405 .iter()
2406 .map(|k| format!("{}={}", k, set_member_key(h.get(k).unwrap())))
2407 .collect();
2408 format!("h:{}", parts.join(","))
2409 }
2410 HeapObject::Set(inner) => {
2411 let mut keys: Vec<_> = inner.keys().cloned().collect();
2412 keys.sort();
2413 format!("S:{}", keys.join(","))
2414 }
2415 HeapObject::ArrayRef(a) => {
2416 let g = a.read();
2417 let parts: Vec<_> = g.iter().map(set_member_key).collect();
2418 format!("ar:{}", parts.join(","))
2419 }
2420 HeapObject::HashRef(h) => {
2421 let g = h.read();
2422 let mut keys: Vec<_> = g.keys().cloned().collect();
2423 keys.sort();
2424 let parts: Vec<_> = keys
2425 .iter()
2426 .map(|k| format!("{}={}", k, set_member_key(g.get(k).unwrap())))
2427 .collect();
2428 format!("hr:{}", parts.join(","))
2429 }
2430 HeapObject::Blessed(b) => {
2431 let d = b.data.read();
2432 format!("b:{}:{}", b.class, set_member_key(&d))
2433 }
2434 HeapObject::ScalarRef(_) | HeapObject::ScalarBindingRef(_) | HeapObject::CaptureCell(_) => {
2435 format!("sr:{v}")
2436 }
2437 HeapObject::ArrayBindingRef(n) => format!("abind:{n}"),
2438 HeapObject::HashBindingRef(n) => format!("hbind:{n}"),
2439 HeapObject::CodeRef(_) => format!("c:{v}"),
2440 HeapObject::Regex(_, src, _) => format!("r:{src}"),
2441 HeapObject::IOHandle(s) => format!("io:{s}"),
2442 HeapObject::Atomic(arc) => format!("at:{}", set_member_key(&arc.lock())),
2443 HeapObject::ChannelTx(tx) => format!("chtx:{:p}", Arc::as_ptr(tx)),
2444 HeapObject::ChannelRx(rx) => format!("chrx:{:p}", Arc::as_ptr(rx)),
2445 HeapObject::AsyncTask(t) => format!("async:{:p}", Arc::as_ptr(t)),
2446 HeapObject::Generator(g) => format!("gen:{:p}", Arc::as_ptr(g)),
2447 HeapObject::Deque(d) => format!("dq:{:p}", Arc::as_ptr(d)),
2448 HeapObject::Heap(h) => format!("hp:{:p}", Arc::as_ptr(h)),
2449 HeapObject::Pipeline(p) => format!("pl:{:p}", Arc::as_ptr(p)),
2450 HeapObject::Capture(c) => format!("cap:{:p}", Arc::as_ptr(c)),
2451 HeapObject::Ppool(p) => format!("pp:{:p}", Arc::as_ptr(&p.0)),
2452 HeapObject::RemoteCluster(c) => format!("rcl:{:p}", Arc::as_ptr(c)),
2453 HeapObject::Barrier(b) => format!("br:{:p}", Arc::as_ptr(&b.0)),
2454 HeapObject::SqliteConn(c) => format!("sql:{:p}", Arc::as_ptr(c)),
2455 HeapObject::StructInst(s) => format!("st:{}:{:?}", s.def.name, s.values),
2456 HeapObject::EnumInst(e) => {
2457 format!("en:{}::{}:{}", e.def.name, e.variant_name(), e.data)
2458 }
2459 HeapObject::ClassInst(c) => format!("cl:{}:{:?}", c.def.name, c.values),
2460 HeapObject::DataFrame(d) => format!("df:{:p}", Arc::as_ptr(d)),
2461 HeapObject::Iterator(_) => "iter".to_string(),
2462 HeapObject::ErrnoDual { code, msg } => format!("e:{code}:{msg}"),
2463 HeapObject::Integer(n) => format!("i:{n}"),
2464 HeapObject::Float(fl) => format!("f:{}", fl.to_bits()),
2465 }
2466}
2467
2468pub fn set_from_elements<I: IntoIterator<Item = PerlValue>>(items: I) -> PerlValue {
2469 let mut map = PerlSet::new();
2470 for v in items {
2471 let k = set_member_key(&v);
2472 map.insert(k, v);
2473 }
2474 PerlValue::set(Arc::new(map))
2475}
2476
2477#[inline]
2479pub fn set_payload(v: &PerlValue) -> Option<Arc<PerlSet>> {
2480 if !nanbox::is_heap(v.0) {
2481 return None;
2482 }
2483 match unsafe { v.heap_ref() } {
2484 HeapObject::Set(s) => Some(Arc::clone(s)),
2485 HeapObject::Atomic(a) => set_payload(&a.lock()),
2486 _ => None,
2487 }
2488}
2489
2490pub fn set_union(a: &PerlValue, b: &PerlValue) -> Option<PerlValue> {
2491 let ia = set_payload(a)?;
2492 let ib = set_payload(b)?;
2493 let mut m = (*ia).clone();
2494 for (k, v) in ib.iter() {
2495 m.entry(k.clone()).or_insert_with(|| v.clone());
2496 }
2497 Some(PerlValue::set(Arc::new(m)))
2498}
2499
2500pub fn set_intersection(a: &PerlValue, b: &PerlValue) -> Option<PerlValue> {
2501 let ia = set_payload(a)?;
2502 let ib = set_payload(b)?;
2503 let mut m = PerlSet::new();
2504 for (k, v) in ia.iter() {
2505 if ib.contains_key(k) {
2506 m.insert(k.clone(), v.clone());
2507 }
2508 }
2509 Some(PerlValue::set(Arc::new(m)))
2510}
2511fn parse_number(s: &str) -> f64 {
2512 let s = s.trim();
2513 if s.is_empty() {
2514 return 0.0;
2515 }
2516 let mut end = 0;
2518 let bytes = s.as_bytes();
2519 if end < bytes.len() && (bytes[end] == b'+' || bytes[end] == b'-') {
2520 end += 1;
2521 }
2522 while end < bytes.len() && bytes[end].is_ascii_digit() {
2523 end += 1;
2524 }
2525 if end < bytes.len() && bytes[end] == b'.' {
2526 end += 1;
2527 while end < bytes.len() && bytes[end].is_ascii_digit() {
2528 end += 1;
2529 }
2530 }
2531 if end < bytes.len() && (bytes[end] == b'e' || bytes[end] == b'E') {
2532 end += 1;
2533 if end < bytes.len() && (bytes[end] == b'+' || bytes[end] == b'-') {
2534 end += 1;
2535 }
2536 while end < bytes.len() && bytes[end].is_ascii_digit() {
2537 end += 1;
2538 }
2539 }
2540 if end == 0 {
2541 return 0.0;
2542 }
2543 s[..end].parse::<f64>().unwrap_or(0.0)
2544}
2545
2546fn format_float(f: f64) -> String {
2547 if f.fract() == 0.0 && f.abs() < 1e16 {
2548 format!("{}", f as i64)
2549 } else {
2550 let mut buf = [0u8; 64];
2552 unsafe {
2553 libc::snprintf(
2554 buf.as_mut_ptr() as *mut libc::c_char,
2555 buf.len(),
2556 c"%.15g".as_ptr(),
2557 f,
2558 );
2559 std::ffi::CStr::from_ptr(buf.as_ptr() as *const libc::c_char)
2560 .to_string_lossy()
2561 .into_owned()
2562 }
2563 }
2564}
2565
2566#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2568pub(crate) enum PerlListRangeIncOutcome {
2569 Continue,
2570 BecameNumeric,
2572}
2573
2574fn perl_str_looks_like_number_for_range(s: &str) -> bool {
2577 let t = s.trim();
2578 if t.is_empty() {
2579 return s.is_empty();
2580 }
2581 let b = t.as_bytes();
2582 let mut i = 0usize;
2583 if i < b.len() && (b[i] == b'+' || b[i] == b'-') {
2584 i += 1;
2585 }
2586 if i >= b.len() {
2587 return false;
2588 }
2589 let mut saw_digit = false;
2590 while i < b.len() && b[i].is_ascii_digit() {
2591 saw_digit = true;
2592 i += 1;
2593 }
2594 if i < b.len() && b[i] == b'.' {
2595 i += 1;
2596 while i < b.len() && b[i].is_ascii_digit() {
2597 saw_digit = true;
2598 i += 1;
2599 }
2600 }
2601 if !saw_digit {
2602 return false;
2603 }
2604 if i < b.len() && (b[i] == b'e' || b[i] == b'E') {
2605 i += 1;
2606 if i < b.len() && (b[i] == b'+' || b[i] == b'-') {
2607 i += 1;
2608 }
2609 let exp0 = i;
2610 while i < b.len() && b[i].is_ascii_digit() {
2611 i += 1;
2612 }
2613 if i == exp0 {
2614 return false;
2615 }
2616 }
2617 i == b.len()
2618}
2619
2620pub(crate) fn perl_list_range_pair_is_numeric(left: &PerlValue, right: &PerlValue) -> bool {
2622 if left.is_integer_like() || left.is_float_like() {
2623 return true;
2624 }
2625 if !left.is_undef() && !left.is_string_like() {
2626 return true;
2627 }
2628 if right.is_integer_like() || right.is_float_like() {
2629 return true;
2630 }
2631 if !right.is_undef() && !right.is_string_like() {
2632 return true;
2633 }
2634
2635 let left_ok = !left.is_undef();
2636 let right_ok = !right.is_undef();
2637 let left_pok = left.is_string_like();
2638 let left_pv = left.as_str_or_empty();
2639 let right_pv = right.as_str_or_empty();
2640
2641 let left_n = perl_str_looks_like_number_for_range(&left_pv);
2642 let right_n = perl_str_looks_like_number_for_range(&right_pv);
2643
2644 let left_zero_prefix =
2645 left_pok && left_pv.len() > 1 && left_pv.as_bytes().first() == Some(&b'0');
2646
2647 let clause5_left =
2648 (!left_ok && right_ok) || ((!left_ok || left_n) && left_pok && !left_zero_prefix);
2649 clause5_left && (!right_ok || right_n)
2650}
2651
2652pub(crate) fn perl_magic_string_increment_for_range(s: &mut String) -> PerlListRangeIncOutcome {
2654 if s.is_empty() {
2655 return PerlListRangeIncOutcome::BecameNumeric;
2656 }
2657 let b = s.as_bytes();
2658 let mut i = 0usize;
2659 while i < b.len() && b[i].is_ascii_alphabetic() {
2660 i += 1;
2661 }
2662 while i < b.len() && b[i].is_ascii_digit() {
2663 i += 1;
2664 }
2665 if i < b.len() {
2666 let n = parse_number(s) + 1.0;
2667 *s = format_float(n);
2668 return PerlListRangeIncOutcome::BecameNumeric;
2669 }
2670
2671 let bytes = unsafe { s.as_mut_vec() };
2672 let mut idx = bytes.len() - 1;
2673 loop {
2674 if bytes[idx].is_ascii_digit() {
2675 bytes[idx] += 1;
2676 if bytes[idx] <= b'9' {
2677 return PerlListRangeIncOutcome::Continue;
2678 }
2679 bytes[idx] = b'0';
2680 if idx == 0 {
2681 bytes.insert(0, b'1');
2682 return PerlListRangeIncOutcome::Continue;
2683 }
2684 idx -= 1;
2685 } else {
2686 bytes[idx] = bytes[idx].wrapping_add(1);
2687 if bytes[idx].is_ascii_alphabetic() {
2688 return PerlListRangeIncOutcome::Continue;
2689 }
2690 bytes[idx] = bytes[idx].wrapping_sub(b'z' - b'a' + 1);
2691 if idx == 0 {
2692 let c = bytes[0];
2693 bytes.insert(0, if c.is_ascii_digit() { b'1' } else { c });
2694 return PerlListRangeIncOutcome::Continue;
2695 }
2696 idx -= 1;
2697 }
2698 }
2699}
2700
2701pub(crate) fn perl_magic_string_decrement_for_range(s: &mut String) -> Option<()> {
2704 if s.is_empty() {
2705 return None;
2706 }
2707 let b = s.as_bytes();
2709 let mut i = 0usize;
2710 while i < b.len() && b[i].is_ascii_alphabetic() {
2711 i += 1;
2712 }
2713 while i < b.len() && b[i].is_ascii_digit() {
2714 i += 1;
2715 }
2716 if i < b.len() {
2717 return None; }
2719
2720 let bytes = unsafe { s.as_mut_vec() };
2721 let mut idx = bytes.len() - 1;
2722 loop {
2723 if bytes[idx].is_ascii_digit() {
2724 if bytes[idx] > b'0' {
2725 bytes[idx] -= 1;
2726 return Some(());
2727 }
2728 bytes[idx] = b'9';
2730 if idx == 0 {
2731 if bytes.len() == 1 {
2733 bytes[0] = b'0'; return None;
2735 }
2736 bytes.remove(0);
2737 return Some(());
2738 }
2739 idx -= 1;
2740 } else if bytes[idx].is_ascii_lowercase() {
2741 if bytes[idx] > b'a' {
2742 bytes[idx] -= 1;
2743 return Some(());
2744 }
2745 bytes[idx] = b'z';
2747 if idx == 0 {
2748 if bytes.len() == 1 {
2750 bytes[0] = b'a'; return None;
2752 }
2753 bytes.remove(0);
2754 return Some(());
2755 }
2756 idx -= 1;
2757 } else if bytes[idx].is_ascii_uppercase() {
2758 if bytes[idx] > b'A' {
2759 bytes[idx] -= 1;
2760 return Some(());
2761 }
2762 bytes[idx] = b'Z';
2764 if idx == 0 {
2765 if bytes.len() == 1 {
2766 bytes[0] = b'A'; return None;
2768 }
2769 bytes.remove(0);
2770 return Some(());
2771 }
2772 idx -= 1;
2773 } else {
2774 return None;
2775 }
2776 }
2777}
2778
2779fn perl_list_range_max_bound(right: &str) -> usize {
2780 if right.is_ascii() {
2781 right.len()
2782 } else {
2783 right.chars().count()
2784 }
2785}
2786
2787fn perl_list_range_cur_bound(cur: &str, right_is_ascii: bool) -> usize {
2788 if right_is_ascii {
2789 cur.len()
2790 } else {
2791 cur.chars().count()
2792 }
2793}
2794
2795fn perl_list_range_expand_string_magic(from: PerlValue, to: PerlValue) -> Vec<PerlValue> {
2796 let mut cur = from.into_string();
2797 let right = to.into_string();
2798 let right_ascii = right.is_ascii();
2799 let max_bound = perl_list_range_max_bound(&right);
2800 let mut out = Vec::new();
2801 let mut guard = 0usize;
2802 loop {
2803 guard += 1;
2804 if guard > 50_000_000 {
2805 break;
2806 }
2807 let cur_bound = perl_list_range_cur_bound(&cur, right_ascii);
2808 if cur_bound > max_bound {
2809 break;
2810 }
2811 out.push(PerlValue::string(cur.clone()));
2812 if cur == right {
2813 break;
2814 }
2815 match perl_magic_string_increment_for_range(&mut cur) {
2816 PerlListRangeIncOutcome::Continue => {}
2817 PerlListRangeIncOutcome::BecameNumeric => break,
2818 }
2819 }
2820 out
2821}
2822
2823pub(crate) fn perl_list_range_expand(from: PerlValue, to: PerlValue) -> Vec<PerlValue> {
2825 if perl_list_range_pair_is_numeric(&from, &to) {
2826 let i = from.to_int();
2827 let j = to.to_int();
2828 if j >= i {
2829 (i..=j).map(PerlValue::integer).collect()
2830 } else {
2831 Vec::new()
2832 }
2833 } else {
2834 perl_list_range_expand_string_magic(from, to)
2835 }
2836}
2837
2838fn is_roman_numeral(s: &str) -> bool {
2844 if s.is_empty() {
2845 return false;
2846 }
2847 let upper = s.to_ascii_uppercase();
2848 upper
2849 .chars()
2850 .all(|c| matches!(c, 'I' | 'V' | 'X' | 'L' | 'C' | 'D' | 'M'))
2851}
2852
2853fn is_ipv4(s: &str) -> bool {
2855 let parts: Vec<&str> = s.split('.').collect();
2856 parts.len() == 4 && parts.iter().all(|p| p.parse::<u8>().is_ok())
2857}
2858
2859fn ipv4_to_u32(s: &str) -> Option<u32> {
2861 let parts: Vec<u8> = s.split('.').filter_map(|p| p.parse().ok()).collect();
2862 if parts.len() != 4 {
2863 return None;
2864 }
2865 Some(
2866 ((parts[0] as u32) << 24)
2867 | ((parts[1] as u32) << 16)
2868 | ((parts[2] as u32) << 8)
2869 | (parts[3] as u32),
2870 )
2871}
2872
2873fn u32_to_ipv4(n: u32) -> String {
2875 format!(
2876 "{}.{}.{}.{}",
2877 (n >> 24) & 0xFF,
2878 (n >> 16) & 0xFF,
2879 (n >> 8) & 0xFF,
2880 n & 0xFF
2881 )
2882}
2883
2884fn ipv4_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
2886 let Some(start) = ipv4_to_u32(from) else {
2887 return vec![];
2888 };
2889 let Some(end) = ipv4_to_u32(to) else {
2890 return vec![];
2891 };
2892 let mut out = Vec::new();
2893 if step > 0 {
2894 let mut cur = start as i64;
2895 while cur <= end as i64 {
2896 out.push(PerlValue::string(u32_to_ipv4(cur as u32)));
2897 cur += step;
2898 }
2899 } else {
2900 let mut cur = start as i64;
2901 while cur >= end as i64 {
2902 out.push(PerlValue::string(u32_to_ipv4(cur as u32)));
2903 cur += step;
2904 }
2905 }
2906 out
2907}
2908
2909fn is_iso_date(s: &str) -> bool {
2911 if s.len() != 10 {
2912 return false;
2913 }
2914 let parts: Vec<&str> = s.split('-').collect();
2915 parts.len() == 3
2916 && parts[0].len() == 4
2917 && parts[0].parse::<u16>().is_ok()
2918 && parts[1].len() == 2
2919 && parts[1]
2920 .parse::<u8>()
2921 .map(|m| m >= 1 && m <= 12)
2922 .unwrap_or(false)
2923 && parts[2].len() == 2
2924 && parts[2]
2925 .parse::<u8>()
2926 .map(|d| d >= 1 && d <= 31)
2927 .unwrap_or(false)
2928}
2929
2930fn is_year_month(s: &str) -> bool {
2932 if s.len() != 7 {
2933 return false;
2934 }
2935 let parts: Vec<&str> = s.split('-').collect();
2936 parts.len() == 2
2937 && parts[0].len() == 4
2938 && parts[0].parse::<u16>().is_ok()
2939 && parts[1].len() == 2
2940 && parts[1]
2941 .parse::<u8>()
2942 .map(|m| m >= 1 && m <= 12)
2943 .unwrap_or(false)
2944}
2945
2946fn parse_iso_date(s: &str) -> Option<(i32, u32, u32)> {
2948 let parts: Vec<&str> = s.split('-').collect();
2949 if parts.len() != 3 {
2950 return None;
2951 }
2952 Some((
2953 parts[0].parse().ok()?,
2954 parts[1].parse().ok()?,
2955 parts[2].parse().ok()?,
2956 ))
2957}
2958
2959fn parse_year_month(s: &str) -> Option<(i32, u32)> {
2961 let parts: Vec<&str> = s.split('-').collect();
2962 if parts.len() != 2 {
2963 return None;
2964 }
2965 Some((parts[0].parse().ok()?, parts[1].parse().ok()?))
2966}
2967
2968fn days_in_month(year: i32, month: u32) -> u32 {
2970 match month {
2971 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
2972 4 | 6 | 9 | 11 => 30,
2973 2 => {
2974 if (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 {
2975 29
2976 } else {
2977 28
2978 }
2979 }
2980 _ => 30,
2981 }
2982}
2983
2984fn add_days(mut year: i32, mut month: u32, mut day: u32, mut delta: i64) -> (i32, u32, u32) {
2986 if delta > 0 {
2987 while delta > 0 {
2988 let dim = days_in_month(year, month);
2989 let remaining = dim - day;
2990 if delta <= remaining as i64 {
2991 day += delta as u32;
2992 break;
2993 }
2994 delta -= (remaining + 1) as i64;
2995 day = 1;
2996 month += 1;
2997 if month > 12 {
2998 month = 1;
2999 year += 1;
3000 }
3001 }
3002 } else {
3003 while delta < 0 {
3004 if (-delta) < day as i64 {
3005 day = (day as i64 + delta) as u32;
3006 break;
3007 }
3008 delta += day as i64;
3009 month -= 1;
3010 if month == 0 {
3011 month = 12;
3012 year -= 1;
3013 }
3014 day = days_in_month(year, month);
3015 }
3016 }
3017 (year, month, day)
3018}
3019
3020fn iso_date_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
3022 let Some((mut y, mut m, mut d)) = parse_iso_date(from) else {
3023 return vec![];
3024 };
3025 let Some((ey, em, ed)) = parse_iso_date(to) else {
3026 return vec![];
3027 };
3028 let mut out = Vec::new();
3029 let mut guard = 0;
3030 if step > 0 {
3031 while (y, m, d) <= (ey, em, ed) && guard < 50_000 {
3032 out.push(PerlValue::string(format!("{:04}-{:02}-{:02}", y, m, d)));
3033 (y, m, d) = add_days(y, m, d, step);
3034 guard += 1;
3035 }
3036 } else {
3037 while (y, m, d) >= (ey, em, ed) && guard < 50_000 {
3038 out.push(PerlValue::string(format!("{:04}-{:02}-{:02}", y, m, d)));
3039 (y, m, d) = add_days(y, m, d, step);
3040 guard += 1;
3041 }
3042 }
3043 out
3044}
3045
3046fn add_months(mut year: i32, mut month: u32, delta: i64) -> (i32, u32) {
3048 let total = (year as i64 * 12 + month as i64 - 1) + delta;
3049 year = (total / 12) as i32;
3050 month = ((total % 12) + 1) as u32;
3051 if month == 0 {
3052 month = 12;
3053 year -= 1;
3054 }
3055 (year, month)
3056}
3057
3058fn year_month_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
3060 let Some((mut y, mut m)) = parse_year_month(from) else {
3061 return vec![];
3062 };
3063 let Some((ey, em)) = parse_year_month(to) else {
3064 return vec![];
3065 };
3066 let mut out = Vec::new();
3067 let mut guard = 0;
3068 if step > 0 {
3069 while (y, m) <= (ey, em) && guard < 50_000 {
3070 out.push(PerlValue::string(format!("{:04}-{:02}", y, m)));
3071 (y, m) = add_months(y, m, step);
3072 guard += 1;
3073 }
3074 } else {
3075 while (y, m) >= (ey, em) && guard < 50_000 {
3076 out.push(PerlValue::string(format!("{:04}-{:02}", y, m)));
3077 (y, m) = add_months(y, m, step);
3078 guard += 1;
3079 }
3080 }
3081 out
3082}
3083
3084fn is_time_hhmm(s: &str) -> bool {
3086 if s.len() != 5 {
3087 return false;
3088 }
3089 let parts: Vec<&str> = s.split(':').collect();
3090 parts.len() == 2
3091 && parts[0].len() == 2
3092 && parts[0].parse::<u8>().map(|h| h < 24).unwrap_or(false)
3093 && parts[1].len() == 2
3094 && parts[1].parse::<u8>().map(|m| m < 60).unwrap_or(false)
3095}
3096
3097fn parse_time_hhmm(s: &str) -> Option<i32> {
3099 let parts: Vec<&str> = s.split(':').collect();
3100 if parts.len() != 2 {
3101 return None;
3102 }
3103 let h: i32 = parts[0].parse().ok()?;
3104 let m: i32 = parts[1].parse().ok()?;
3105 Some(h * 60 + m)
3106}
3107
3108fn minutes_to_hhmm(mins: i32) -> String {
3110 let h = (mins / 60) % 24;
3111 let m = mins % 60;
3112 format!("{:02}:{:02}", h, m)
3113}
3114
3115fn time_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
3117 let Some(start) = parse_time_hhmm(from) else {
3118 return vec![];
3119 };
3120 let Some(end) = parse_time_hhmm(to) else {
3121 return vec![];
3122 };
3123 let mut out = Vec::new();
3124 let mut guard = 0;
3125 if step > 0 {
3126 let mut cur = start;
3127 while cur <= end && guard < 50_000 {
3128 out.push(PerlValue::string(minutes_to_hhmm(cur)));
3129 cur += step as i32;
3130 guard += 1;
3131 }
3132 } else {
3133 let mut cur = start;
3134 while cur >= end && guard < 50_000 {
3135 out.push(PerlValue::string(minutes_to_hhmm(cur)));
3136 cur += step as i32;
3137 guard += 1;
3138 }
3139 }
3140 out
3141}
3142
3143const WEEKDAYS: [&str; 7] = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
3144const WEEKDAYS_FULL: [&str; 7] = [
3145 "Monday",
3146 "Tuesday",
3147 "Wednesday",
3148 "Thursday",
3149 "Friday",
3150 "Saturday",
3151 "Sunday",
3152];
3153
3154fn weekday_index(s: &str) -> Option<usize> {
3156 let lower = s.to_ascii_lowercase();
3157 for (i, &d) in WEEKDAYS.iter().enumerate() {
3158 if d.to_ascii_lowercase() == lower {
3159 return Some(i);
3160 }
3161 }
3162 for (i, &d) in WEEKDAYS_FULL.iter().enumerate() {
3163 if d.to_ascii_lowercase() == lower {
3164 return Some(i);
3165 }
3166 }
3167 None
3168}
3169
3170fn weekday_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
3172 let Some(start) = weekday_index(from) else {
3173 return vec![];
3174 };
3175 let Some(end) = weekday_index(to) else {
3176 return vec![];
3177 };
3178 let full = from.len() > 3;
3179 let names = if full { &WEEKDAYS_FULL } else { &WEEKDAYS };
3180 let mut out = Vec::new();
3181 if step > 0 {
3182 let mut cur = start as i64;
3183 let target = if end >= start {
3184 end as i64
3185 } else {
3186 end as i64 + 7
3187 };
3188 while cur <= target {
3189 out.push(PerlValue::string(names[(cur % 7) as usize].to_string()));
3190 cur += step;
3191 }
3192 } else {
3193 let mut cur = start as i64;
3194 let target = if end <= start {
3195 end as i64
3196 } else {
3197 end as i64 - 7
3198 };
3199 while cur >= target {
3200 out.push(PerlValue::string(
3201 names[((cur % 7 + 7) % 7) as usize].to_string(),
3202 ));
3203 cur += step;
3204 }
3205 }
3206 out
3207}
3208
3209const MONTHS: [&str; 12] = [
3210 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
3211];
3212const MONTHS_FULL: [&str; 12] = [
3213 "January",
3214 "February",
3215 "March",
3216 "April",
3217 "May",
3218 "June",
3219 "July",
3220 "August",
3221 "September",
3222 "October",
3223 "November",
3224 "December",
3225];
3226
3227fn month_name_index(s: &str) -> Option<usize> {
3229 let lower = s.to_ascii_lowercase();
3230 for (i, &m) in MONTHS.iter().enumerate() {
3231 if m.to_ascii_lowercase() == lower {
3232 return Some(i);
3233 }
3234 }
3235 for (i, &m) in MONTHS_FULL.iter().enumerate() {
3236 if m.to_ascii_lowercase() == lower {
3237 return Some(i);
3238 }
3239 }
3240 None
3241}
3242
3243fn month_name_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
3245 let Some(start) = month_name_index(from) else {
3246 return vec![];
3247 };
3248 let Some(end) = month_name_index(to) else {
3249 return vec![];
3250 };
3251 let full = from.len() > 3;
3252 let names = if full { &MONTHS_FULL } else { &MONTHS };
3253 let mut out = Vec::new();
3254 if step > 0 {
3255 let mut cur = start as i64;
3256 let target = if end >= start {
3257 end as i64
3258 } else {
3259 end as i64 + 12
3260 };
3261 while cur <= target {
3262 out.push(PerlValue::string(names[(cur % 12) as usize].to_string()));
3263 cur += step;
3264 }
3265 } else {
3266 let mut cur = start as i64;
3267 let target = if end <= start {
3268 end as i64
3269 } else {
3270 end as i64 - 12
3271 };
3272 while cur >= target {
3273 out.push(PerlValue::string(
3274 names[((cur % 12 + 12) % 12) as usize].to_string(),
3275 ));
3276 cur += step;
3277 }
3278 }
3279 out
3280}
3281
3282fn is_float_pair(from: &str, to: &str) -> bool {
3284 fn is_float(s: &str) -> bool {
3285 s.contains('.')
3286 && !s.contains(':')
3287 && s.matches('.').count() == 1
3288 && s.parse::<f64>().is_ok()
3289 }
3290 is_float(from) && is_float(to)
3291}
3292
3293fn float_range_stepped(from: &str, to: &str, step: f64) -> Vec<PerlValue> {
3295 let Ok(start) = from.parse::<f64>() else {
3296 return vec![];
3297 };
3298 let Ok(end) = to.parse::<f64>() else {
3299 return vec![];
3300 };
3301 let mut out = Vec::new();
3302 let mut guard = 0;
3303 if step > 0.0 {
3305 let mut i = 0i64;
3306 loop {
3307 let cur = start + (i as f64) * step;
3308 if cur > end + step.abs() * f64::EPSILON * 10.0 || guard >= 50_000 {
3309 break;
3310 }
3311 let rounded = (cur * 1e12).round() / 1e12;
3313 out.push(PerlValue::float(rounded));
3314 i += 1;
3315 guard += 1;
3316 }
3317 } else if step < 0.0 {
3318 let mut i = 0i64;
3319 loop {
3320 let cur = start + (i as f64) * step;
3321 if cur < end - step.abs() * f64::EPSILON * 10.0 || guard >= 50_000 {
3322 break;
3323 }
3324 let rounded = (cur * 1e12).round() / 1e12;
3325 out.push(PerlValue::float(rounded));
3326 i += 1;
3327 guard += 1;
3328 }
3329 }
3330 out
3331}
3332
3333fn roman_to_int(s: &str) -> Option<i64> {
3335 let upper = s.to_ascii_uppercase();
3336 let mut result = 0i64;
3337 let mut prev = 0i64;
3338 for c in upper.chars().rev() {
3339 let val = match c {
3340 'I' => 1,
3341 'V' => 5,
3342 'X' => 10,
3343 'L' => 50,
3344 'C' => 100,
3345 'D' => 500,
3346 'M' => 1000,
3347 _ => return None,
3348 };
3349 if val < prev {
3350 result -= val;
3351 } else {
3352 result += val;
3353 }
3354 prev = val;
3355 }
3356 if result > 0 {
3357 Some(result)
3358 } else {
3359 None
3360 }
3361}
3362
3363fn int_to_roman(mut n: i64, lowercase: bool) -> Option<String> {
3365 if n <= 0 || n > 3999 {
3366 return None;
3367 }
3368 let numerals = [
3369 (1000, "M"),
3370 (900, "CM"),
3371 (500, "D"),
3372 (400, "CD"),
3373 (100, "C"),
3374 (90, "XC"),
3375 (50, "L"),
3376 (40, "XL"),
3377 (10, "X"),
3378 (9, "IX"),
3379 (5, "V"),
3380 (4, "IV"),
3381 (1, "I"),
3382 ];
3383 let mut result = String::new();
3384 for (val, sym) in numerals {
3385 while n >= val {
3386 result.push_str(sym);
3387 n -= val;
3388 }
3389 }
3390 if lowercase {
3391 Some(result.to_ascii_lowercase())
3392 } else {
3393 Some(result)
3394 }
3395}
3396
3397fn roman_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
3399 let Some(start) = roman_to_int(from) else {
3400 return vec![];
3401 };
3402 let Some(end) = roman_to_int(to) else {
3403 return vec![];
3404 };
3405 let lowercase = from
3406 .chars()
3407 .next()
3408 .map(|c| c.is_ascii_lowercase())
3409 .unwrap_or(false);
3410
3411 let mut out = Vec::new();
3412 if step > 0 {
3413 let mut cur = start;
3414 while cur <= end {
3415 if let Some(r) = int_to_roman(cur, lowercase) {
3416 out.push(PerlValue::string(r));
3417 }
3418 cur += step;
3419 }
3420 } else {
3421 let mut cur = start;
3422 while cur >= end {
3423 if let Some(r) = int_to_roman(cur, lowercase) {
3424 out.push(PerlValue::string(r));
3425 }
3426 cur += step; }
3428 }
3429 out
3430}
3431
3432pub(crate) fn perl_list_range_expand_stepped(
3435 from: PerlValue,
3436 to: PerlValue,
3437 step_val: PerlValue,
3438) -> Vec<PerlValue> {
3439 let from_str = from.to_string();
3440 let to_str = to.to_string();
3441
3442 let is_float_range = is_float_pair(&from_str, &to_str);
3444
3445 let step_float = step_val.as_float().unwrap_or(step_val.to_int() as f64);
3447 let step_int = step_val.to_int();
3448
3449 if step_int == 0 && step_float == 0.0 {
3450 return vec![];
3451 }
3452
3453 if is_float_range {
3455 return float_range_stepped(&from_str, &to_str, step_float);
3456 }
3457
3458 if perl_list_range_pair_is_numeric(&from, &to) {
3460 let i = from.to_int();
3461 let j = to.to_int();
3462 if step_int > 0 {
3463 (i..=j)
3464 .step_by(step_int as usize)
3465 .map(PerlValue::integer)
3466 .collect()
3467 } else {
3468 std::iter::successors(Some(i), |&x| {
3469 let next = x + step_int;
3470 if next >= j {
3471 Some(next)
3472 } else {
3473 None
3474 }
3475 })
3476 .map(PerlValue::integer)
3477 .collect()
3478 }
3479 } else {
3480 if is_ipv4(&from_str) && is_ipv4(&to_str) {
3484 return ipv4_range_stepped(&from_str, &to_str, step_int);
3485 }
3486
3487 if is_iso_date(&from_str) && is_iso_date(&to_str) {
3489 return iso_date_range_stepped(&from_str, &to_str, step_int);
3490 }
3491
3492 if is_year_month(&from_str) && is_year_month(&to_str) {
3494 return year_month_range_stepped(&from_str, &to_str, step_int);
3495 }
3496
3497 if is_time_hhmm(&from_str) && is_time_hhmm(&to_str) {
3499 return time_range_stepped(&from_str, &to_str, step_int);
3500 }
3501
3502 if weekday_index(&from_str).is_some() && weekday_index(&to_str).is_some() {
3504 return weekday_range_stepped(&from_str, &to_str, step_int);
3505 }
3506
3507 if month_name_index(&from_str).is_some() && month_name_index(&to_str).is_some() {
3509 return month_name_range_stepped(&from_str, &to_str, step_int);
3510 }
3511
3512 if is_roman_numeral(&from_str) && is_roman_numeral(&to_str) {
3514 return roman_range_stepped(&from_str, &to_str, step_int);
3515 }
3516
3517 perl_list_range_expand_string_magic_stepped(from, to, step_int)
3519 }
3520}
3521
3522pub(crate) fn perl_slice_endpoint_to_strict_int(
3526 v: &PerlValue,
3527 where_: &str,
3528) -> Result<i64, String> {
3529 if let Some(n) = v.as_integer() {
3530 return Ok(n);
3531 }
3532 if let Some(f) = v.as_float() {
3533 if f.is_finite() && f.fract() == 0.0 && f >= i64::MIN as f64 && f <= i64::MAX as f64 {
3534 return Ok(f as i64);
3535 }
3536 return Err(format!(
3537 "array slice {}: non-integer float endpoint {}",
3538 where_, f
3539 ));
3540 }
3541 let s = v.as_str_or_empty();
3542 if !s.is_empty() {
3543 if let Ok(n) = s.trim().parse::<i64>() {
3544 return Ok(n);
3545 }
3546 return Err(format!(
3547 "array slice {}: non-integer string endpoint {:?}",
3548 where_, s
3549 ));
3550 }
3551 Err(format!(
3552 "array slice {}: endpoint must be an integer (got non-numeric value)",
3553 where_
3554 ))
3555}
3556
3557pub(crate) fn compute_array_slice_indices(
3567 arr_len: i64,
3568 from: &PerlValue,
3569 to: &PerlValue,
3570 step: &PerlValue,
3571) -> Result<Vec<i64>, String> {
3572 let step_i = if step.is_undef() {
3573 1i64
3574 } else {
3575 perl_slice_endpoint_to_strict_int(step, "step")?
3576 };
3577 if step_i == 0 {
3578 return Err("array slice step cannot be 0".into());
3579 }
3580
3581 let normalize = |i: i64| -> i64 {
3582 if i < 0 {
3583 i + arr_len
3584 } else {
3585 i
3586 }
3587 };
3588
3589 let any_undef = from.is_undef() || to.is_undef();
3595
3596 let from_raw = if from.is_undef() {
3597 if step_i > 0 {
3598 0
3599 } else {
3600 arr_len - 1
3601 }
3602 } else {
3603 perl_slice_endpoint_to_strict_int(from, "start")?
3604 };
3605
3606 let to_raw = if to.is_undef() {
3607 if step_i > 0 {
3608 arr_len - 1
3609 } else {
3610 0
3611 }
3612 } else {
3613 perl_slice_endpoint_to_strict_int(to, "stop")?
3614 };
3615
3616 let mut out = Vec::new();
3617 if arr_len == 0 {
3618 return Ok(out);
3619 }
3620
3621 let (from_i, to_i) = if any_undef {
3622 (normalize(from_raw), normalize(to_raw))
3623 } else {
3624 (from_raw, to_raw)
3625 };
3626
3627 if step_i > 0 {
3628 let mut i = from_i;
3629 while i <= to_i {
3630 out.push(if any_undef { i } else { normalize(i) });
3631 i += step_i;
3632 }
3633 } else {
3634 let mut i = from_i;
3635 while i >= to_i {
3636 out.push(if any_undef { i } else { normalize(i) });
3637 i += step_i; }
3639 }
3640 Ok(out)
3641}
3642
3643pub(crate) fn compute_hash_slice_keys(
3648 from: &PerlValue,
3649 to: &PerlValue,
3650 step: &PerlValue,
3651) -> Result<Vec<String>, String> {
3652 if from.is_undef() || to.is_undef() {
3653 return Err(
3654 "hash slice range requires both endpoints (open-ended forms not allowed)".into(),
3655 );
3656 }
3657 let step_val = if step.is_undef() {
3658 PerlValue::integer(1)
3659 } else {
3660 step.clone()
3661 };
3662 let expanded = perl_list_range_expand_stepped(from.clone(), to.clone(), step_val);
3663 Ok(expanded.into_iter().map(|v| v.to_string()).collect())
3664}
3665
3666fn perl_list_range_expand_string_magic_stepped(
3667 from: PerlValue,
3668 to: PerlValue,
3669 step: i64,
3670) -> Vec<PerlValue> {
3671 if step == 0 {
3672 return vec![];
3673 }
3674 let mut cur = from.into_string();
3675 let right = to.into_string();
3676
3677 if step > 0 {
3678 let step = step as usize;
3680 let right_ascii = right.is_ascii();
3681 let max_bound = perl_list_range_max_bound(&right);
3682 let mut out = Vec::new();
3683 let mut guard = 0usize;
3684 let mut idx = 0usize;
3685 loop {
3686 guard += 1;
3687 if guard > 50_000_000 {
3688 break;
3689 }
3690 let cur_bound = perl_list_range_cur_bound(&cur, right_ascii);
3691 if cur_bound > max_bound {
3692 break;
3693 }
3694 if idx % step == 0 {
3695 out.push(PerlValue::string(cur.clone()));
3696 }
3697 if cur == right {
3698 break;
3699 }
3700 match perl_magic_string_increment_for_range(&mut cur) {
3701 PerlListRangeIncOutcome::Continue => {}
3702 PerlListRangeIncOutcome::BecameNumeric => break,
3703 }
3704 idx += 1;
3705 }
3706 out
3707 } else {
3708 let step = (-step) as usize;
3710 let mut out = Vec::new();
3711 let mut guard = 0usize;
3712 let mut idx = 0usize;
3713 loop {
3714 guard += 1;
3715 if guard > 50_000_000 {
3716 break;
3717 }
3718 if idx % step == 0 {
3719 out.push(PerlValue::string(cur.clone()));
3720 }
3721 if cur == right {
3722 break;
3723 }
3724 if cur < right {
3726 break;
3727 }
3728 match perl_magic_string_decrement_for_range(&mut cur) {
3729 Some(()) => {}
3730 None => break, }
3732 idx += 1;
3733 }
3734 out
3735 }
3736}
3737
3738impl PerlDataFrame {
3739 pub fn row_hashref(&self, row: usize) -> PerlValue {
3741 let mut m = IndexMap::new();
3742 for (i, col) in self.columns.iter().enumerate() {
3743 m.insert(
3744 col.clone(),
3745 self.cols[i].get(row).cloned().unwrap_or(PerlValue::UNDEF),
3746 );
3747 }
3748 PerlValue::hash_ref(Arc::new(RwLock::new(m)))
3749 }
3750}
3751
3752#[cfg(test)]
3753mod tests {
3754 use super::PerlValue;
3755 use crate::perl_regex::PerlCompiledRegex;
3756 use indexmap::IndexMap;
3757 use parking_lot::RwLock;
3758 use std::cmp::Ordering;
3759 use std::sync::Arc;
3760
3761 #[test]
3762 fn undef_is_false() {
3763 assert!(!PerlValue::UNDEF.is_true());
3764 }
3765
3766 #[test]
3767 fn string_zero_is_false() {
3768 assert!(!PerlValue::string("0".into()).is_true());
3769 assert!(PerlValue::string("00".into()).is_true());
3770 }
3771
3772 #[test]
3773 fn empty_string_is_false() {
3774 assert!(!PerlValue::string(String::new()).is_true());
3775 }
3776
3777 #[test]
3778 fn integer_zero_is_false_nonzero_true() {
3779 assert!(!PerlValue::integer(0).is_true());
3780 assert!(PerlValue::integer(-1).is_true());
3781 }
3782
3783 #[test]
3784 fn float_zero_is_false_nonzero_true() {
3785 assert!(!PerlValue::float(0.0).is_true());
3786 assert!(PerlValue::float(0.1).is_true());
3787 }
3788
3789 #[test]
3790 fn num_cmp_orders_float_against_integer() {
3791 assert_eq!(
3792 PerlValue::float(2.5).num_cmp(&PerlValue::integer(3)),
3793 Ordering::Less
3794 );
3795 }
3796
3797 #[test]
3798 fn to_int_parses_leading_number_from_string() {
3799 assert_eq!(PerlValue::string("42xyz".into()).to_int(), 42);
3800 assert_eq!(PerlValue::string(" -3.7foo".into()).to_int(), -3);
3801 }
3802
3803 #[test]
3804 fn num_cmp_orders_as_numeric() {
3805 assert_eq!(
3806 PerlValue::integer(2).num_cmp(&PerlValue::integer(11)),
3807 Ordering::Less
3808 );
3809 assert_eq!(
3810 PerlValue::string("2foo".into()).num_cmp(&PerlValue::string("11".into())),
3811 Ordering::Less
3812 );
3813 }
3814
3815 #[test]
3816 fn str_cmp_orders_as_strings() {
3817 assert_eq!(
3818 PerlValue::string("2".into()).str_cmp(&PerlValue::string("11".into())),
3819 Ordering::Greater
3820 );
3821 }
3822
3823 #[test]
3824 fn str_eq_heap_strings_fast_path() {
3825 let a = PerlValue::string("hello".into());
3826 let b = PerlValue::string("hello".into());
3827 assert!(a.str_eq(&b));
3828 assert!(!a.str_eq(&PerlValue::string("hell".into())));
3829 }
3830
3831 #[test]
3832 fn str_eq_fallback_matches_stringified_equality() {
3833 let n = PerlValue::integer(42);
3834 let s = PerlValue::string("42".into());
3835 assert!(n.str_eq(&s));
3836 assert!(!PerlValue::integer(1).str_eq(&PerlValue::string("2".into())));
3837 }
3838
3839 #[test]
3840 fn str_cmp_heap_strings_fast_path() {
3841 assert_eq!(
3842 PerlValue::string("a".into()).str_cmp(&PerlValue::string("b".into())),
3843 Ordering::Less
3844 );
3845 }
3846
3847 #[test]
3848 fn scalar_context_array_and_hash() {
3849 let a =
3850 PerlValue::array(vec![PerlValue::integer(1), PerlValue::integer(2)]).scalar_context();
3851 assert_eq!(a.to_int(), 2);
3852 let mut h = IndexMap::new();
3853 h.insert("a".into(), PerlValue::integer(1));
3854 let sc = PerlValue::hash(h).scalar_context();
3855 assert!(sc.is_string_like());
3856 }
3857
3858 #[test]
3859 fn to_list_array_hash_and_scalar() {
3860 assert_eq!(
3861 PerlValue::array(vec![PerlValue::integer(7)])
3862 .to_list()
3863 .len(),
3864 1
3865 );
3866 let mut h = IndexMap::new();
3867 h.insert("k".into(), PerlValue::integer(1));
3868 let list = PerlValue::hash(h).to_list();
3869 assert_eq!(list.len(), 2);
3870 let one = PerlValue::integer(99).to_list();
3871 assert_eq!(one.len(), 1);
3872 assert_eq!(one[0].to_int(), 99);
3873 }
3874
3875 #[test]
3876 fn type_name_and_ref_type_for_core_kinds() {
3877 assert_eq!(PerlValue::integer(0).type_name(), "INTEGER");
3878 assert_eq!(PerlValue::UNDEF.ref_type().to_string(), "");
3879 assert_eq!(
3880 PerlValue::array_ref(Arc::new(RwLock::new(vec![])))
3881 .ref_type()
3882 .to_string(),
3883 "ARRAY"
3884 );
3885 }
3886
3887 #[test]
3888 fn display_undef_is_empty_integer_is_decimal() {
3889 assert_eq!(PerlValue::UNDEF.to_string(), "");
3890 assert_eq!(PerlValue::integer(-7).to_string(), "-7");
3891 }
3892
3893 #[test]
3894 fn empty_array_is_false_nonempty_is_true() {
3895 assert!(!PerlValue::array(vec![]).is_true());
3896 assert!(PerlValue::array(vec![PerlValue::integer(0)]).is_true());
3897 }
3898
3899 #[test]
3900 fn to_number_undef_and_non_numeric_refs_are_zero() {
3901 use super::PerlSub;
3902
3903 assert_eq!(PerlValue::UNDEF.to_number(), 0.0);
3904 assert_eq!(
3905 PerlValue::code_ref(Arc::new(PerlSub {
3906 name: "f".into(),
3907 params: vec![],
3908 body: vec![],
3909 closure_env: None,
3910 prototype: None,
3911 fib_like: None,
3912 }))
3913 .to_number(),
3914 0.0
3915 );
3916 }
3917
3918 #[test]
3919 fn append_to_builds_string_without_extra_alloc_for_int_and_string() {
3920 let mut buf = String::new();
3921 PerlValue::integer(-12).append_to(&mut buf);
3922 PerlValue::string("ab".into()).append_to(&mut buf);
3923 assert_eq!(buf, "-12ab");
3924 let mut u = String::new();
3925 PerlValue::UNDEF.append_to(&mut u);
3926 assert!(u.is_empty());
3927 }
3928
3929 #[test]
3930 fn append_to_atomic_delegates_to_inner() {
3931 use parking_lot::Mutex;
3932 let a = PerlValue::atomic(Arc::new(Mutex::new(PerlValue::string("z".into()))));
3933 let mut buf = String::new();
3934 a.append_to(&mut buf);
3935 assert_eq!(buf, "z");
3936 }
3937
3938 #[test]
3939 fn unwrap_atomic_reads_inner_other_variants_clone() {
3940 use parking_lot::Mutex;
3941 let a = PerlValue::atomic(Arc::new(Mutex::new(PerlValue::integer(9))));
3942 assert_eq!(a.unwrap_atomic().to_int(), 9);
3943 assert_eq!(PerlValue::integer(3).unwrap_atomic().to_int(), 3);
3944 }
3945
3946 #[test]
3947 fn is_atomic_only_true_for_atomic_variant() {
3948 use parking_lot::Mutex;
3949 assert!(PerlValue::atomic(Arc::new(Mutex::new(PerlValue::UNDEF))).is_atomic());
3950 assert!(!PerlValue::integer(0).is_atomic());
3951 }
3952
3953 #[test]
3954 fn as_str_only_on_string_variant() {
3955 assert_eq!(
3956 PerlValue::string("x".into()).as_str(),
3957 Some("x".to_string())
3958 );
3959 assert_eq!(PerlValue::integer(1).as_str(), None);
3960 }
3961
3962 #[test]
3963 fn as_str_or_empty_defaults_non_string() {
3964 assert_eq!(PerlValue::string("z".into()).as_str_or_empty(), "z");
3965 assert_eq!(PerlValue::integer(1).as_str_or_empty(), "");
3966 }
3967
3968 #[test]
3969 fn to_int_truncates_float_toward_zero() {
3970 assert_eq!(PerlValue::float(3.9).to_int(), 3);
3971 assert_eq!(PerlValue::float(-2.1).to_int(), -2);
3972 }
3973
3974 #[test]
3975 fn to_number_array_is_length() {
3976 assert_eq!(
3977 PerlValue::array(vec![PerlValue::integer(1), PerlValue::integer(2)]).to_number(),
3978 2.0
3979 );
3980 }
3981
3982 #[test]
3983 fn scalar_context_empty_hash_is_zero() {
3984 let h = IndexMap::new();
3985 assert_eq!(PerlValue::hash(h).scalar_context().to_int(), 0);
3986 }
3987
3988 #[test]
3989 fn scalar_context_nonhash_nonarray_clones() {
3990 let v = PerlValue::integer(8);
3991 assert_eq!(v.scalar_context().to_int(), 8);
3992 }
3993
3994 #[test]
3995 fn display_float_integer_like_omits_decimal() {
3996 assert_eq!(PerlValue::float(4.0).to_string(), "4");
3997 }
3998
3999 #[test]
4000 fn display_array_concatenates_element_displays() {
4001 let a = PerlValue::array(vec![PerlValue::integer(1), PerlValue::string("b".into())]);
4002 assert_eq!(a.to_string(), "1b");
4003 }
4004
4005 #[test]
4006 fn display_code_ref_includes_sub_name() {
4007 use super::PerlSub;
4008 let c = PerlValue::code_ref(Arc::new(PerlSub {
4009 name: "foo".into(),
4010 params: vec![],
4011 body: vec![],
4012 closure_env: None,
4013 prototype: None,
4014 fib_like: None,
4015 }));
4016 assert!(c.to_string().contains("foo"));
4017 }
4018
4019 #[test]
4020 fn display_regex_shows_non_capturing_prefix() {
4021 let r = PerlValue::regex(
4022 PerlCompiledRegex::compile("x+").unwrap(),
4023 "x+".into(),
4024 "".into(),
4025 );
4026 assert_eq!(r.to_string(), "(?:x+)");
4027 }
4028
4029 #[test]
4030 fn display_iohandle_is_name() {
4031 assert_eq!(PerlValue::io_handle("STDOUT".into()).to_string(), "STDOUT");
4032 }
4033
4034 #[test]
4035 fn ref_type_blessed_uses_class_name() {
4036 let b = PerlValue::blessed(Arc::new(super::BlessedRef::new_blessed(
4037 "Pkg".into(),
4038 PerlValue::UNDEF,
4039 )));
4040 assert_eq!(b.ref_type().to_string(), "Pkg");
4041 }
4042
4043 #[test]
4044 fn blessed_drop_enqueues_pending_destroy() {
4045 let v = PerlValue::blessed(Arc::new(super::BlessedRef::new_blessed(
4046 "Z".into(),
4047 PerlValue::integer(7),
4048 )));
4049 drop(v);
4050 let q = crate::pending_destroy::take_queue();
4051 assert_eq!(q.len(), 1);
4052 assert_eq!(q[0].0, "Z");
4053 assert_eq!(q[0].1.to_int(), 7);
4054 }
4055
4056 #[test]
4057 fn type_name_iohandle_is_glob() {
4058 assert_eq!(PerlValue::io_handle("FH".into()).type_name(), "GLOB");
4059 }
4060
4061 #[test]
4062 fn empty_hash_is_false() {
4063 assert!(!PerlValue::hash(IndexMap::new()).is_true());
4064 }
4065
4066 #[test]
4067 fn hash_nonempty_is_true() {
4068 let mut h = IndexMap::new();
4069 h.insert("k".into(), PerlValue::UNDEF);
4070 assert!(PerlValue::hash(h).is_true());
4071 }
4072
4073 #[test]
4074 fn num_cmp_equal_integers() {
4075 assert_eq!(
4076 PerlValue::integer(5).num_cmp(&PerlValue::integer(5)),
4077 Ordering::Equal
4078 );
4079 }
4080
4081 #[test]
4082 fn str_cmp_compares_lexicographic_string_forms() {
4083 assert_eq!(
4085 PerlValue::integer(2).str_cmp(&PerlValue::integer(10)),
4086 Ordering::Greater
4087 );
4088 }
4089
4090 #[test]
4091 fn to_list_undef_empty() {
4092 assert!(PerlValue::UNDEF.to_list().is_empty());
4093 }
4094
4095 #[test]
4096 fn unwrap_atomic_nested_atomic() {
4097 use parking_lot::Mutex;
4098 let inner = PerlValue::atomic(Arc::new(Mutex::new(PerlValue::integer(2))));
4099 let outer = PerlValue::atomic(Arc::new(Mutex::new(inner)));
4100 assert_eq!(outer.unwrap_atomic().to_int(), 2);
4101 }
4102
4103 #[test]
4104 fn errno_dual_parts_extracts_code_and_message() {
4105 let v = PerlValue::errno_dual(-2, "oops".into());
4106 assert_eq!(v.errno_dual_parts(), Some((-2, "oops".into())));
4107 }
4108
4109 #[test]
4110 fn errno_dual_parts_none_for_plain_string() {
4111 assert!(PerlValue::string("hi".into()).errno_dual_parts().is_none());
4112 }
4113
4114 #[test]
4115 fn errno_dual_parts_none_for_integer() {
4116 assert!(PerlValue::integer(1).errno_dual_parts().is_none());
4117 }
4118
4119 #[test]
4120 fn errno_dual_numeric_context_uses_code_string_uses_msg() {
4121 let v = PerlValue::errno_dual(5, "five".into());
4122 assert_eq!(v.to_int(), 5);
4123 assert_eq!(v.to_string(), "five");
4124 }
4125
4126 #[test]
4127 fn list_range_alpha_joins_like_perl() {
4128 use super::perl_list_range_expand;
4129 let v =
4130 perl_list_range_expand(PerlValue::string("a".into()), PerlValue::string("z".into()));
4131 let s: String = v.iter().map(|x| x.to_string()).collect();
4132 assert_eq!(s, "abcdefghijklmnopqrstuvwxyz");
4133 }
4134
4135 #[test]
4136 fn list_range_numeric_string_endpoints() {
4137 use super::perl_list_range_expand;
4138 let v = perl_list_range_expand(
4139 PerlValue::string("9".into()),
4140 PerlValue::string("11".into()),
4141 );
4142 assert_eq!(v.len(), 3);
4143 assert_eq!(
4144 v.iter().map(|x| x.to_int()).collect::<Vec<_>>(),
4145 vec![9, 10, 11]
4146 );
4147 }
4148
4149 #[test]
4150 fn list_range_leading_zero_is_string_mode() {
4151 use super::perl_list_range_expand;
4152 let v = perl_list_range_expand(
4153 PerlValue::string("01".into()),
4154 PerlValue::string("05".into()),
4155 );
4156 assert_eq!(v.len(), 5);
4157 assert_eq!(
4158 v.iter().map(|x| x.to_string()).collect::<Vec<_>>(),
4159 vec!["01", "02", "03", "04", "05"]
4160 );
4161 }
4162
4163 #[test]
4164 fn list_range_empty_to_letter_one_element() {
4165 use super::perl_list_range_expand;
4166 let v = perl_list_range_expand(
4167 PerlValue::string(String::new()),
4168 PerlValue::string("c".into()),
4169 );
4170 assert_eq!(v.len(), 1);
4171 assert_eq!(v[0].to_string(), "");
4172 }
4173
4174 #[test]
4175 fn magic_string_inc_z_wraps_aa() {
4176 use super::{perl_magic_string_increment_for_range, PerlListRangeIncOutcome};
4177 let mut s = "z".to_string();
4178 assert_eq!(
4179 perl_magic_string_increment_for_range(&mut s),
4180 PerlListRangeIncOutcome::Continue
4181 );
4182 assert_eq!(s, "aa");
4183 }
4184
4185 #[test]
4186 fn test_boxed_numeric_stringification() {
4187 let large_int = 10_000_000_000i64;
4189 let v_int = PerlValue::integer(large_int);
4190 assert_eq!(v_int.to_string(), "10000000000");
4191
4192 let v_inf = PerlValue::float(f64::INFINITY);
4194 assert_eq!(v_inf.to_string(), "inf");
4195 }
4196
4197 #[test]
4198 fn magic_string_inc_nine_to_ten() {
4199 use super::{perl_magic_string_increment_for_range, PerlListRangeIncOutcome};
4200 let mut s = "9".to_string();
4201 assert_eq!(
4202 perl_magic_string_increment_for_range(&mut s),
4203 PerlListRangeIncOutcome::Continue
4204 );
4205 assert_eq!(s, "10");
4206 }
4207}