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 ScalarBindingRef(String),
546 ArrayBindingRef(String),
548 HashBindingRef(String),
550 CodeRef(Arc<PerlSub>),
551 Regex(Arc<PerlCompiledRegex>, String, String),
553 Blessed(Arc<BlessedRef>),
554 IOHandle(String),
555 Atomic(Arc<Mutex<PerlValue>>),
556 Set(Arc<PerlSet>),
557 ChannelTx(Arc<Sender<PerlValue>>),
558 ChannelRx(Arc<Receiver<PerlValue>>),
559 AsyncTask(Arc<PerlAsyncTask>),
560 Generator(Arc<PerlGenerator>),
561 Deque(Arc<Mutex<VecDeque<PerlValue>>>),
562 Heap(Arc<Mutex<PerlHeap>>),
563 Pipeline(Arc<Mutex<PipelineInner>>),
564 Capture(Arc<CaptureResult>),
565 Ppool(PerlPpool),
566 RemoteCluster(Arc<RemoteCluster>),
567 Barrier(PerlBarrier),
568 SqliteConn(Arc<Mutex<rusqlite::Connection>>),
569 StructInst(Arc<StructInstance>),
570 DataFrame(Arc<Mutex<PerlDataFrame>>),
571 EnumInst(Arc<EnumInstance>),
572 ClassInst(Arc<ClassInstance>),
573 Iterator(Arc<dyn PerlIterator>),
575 ErrnoDual {
577 code: i32,
578 msg: String,
579 },
580}
581
582#[repr(transparent)]
584pub struct PerlValue(pub(crate) u64);
585
586impl Default for PerlValue {
587 fn default() -> Self {
588 Self::UNDEF
589 }
590}
591
592impl Clone for PerlValue {
593 fn clone(&self) -> Self {
594 if nanbox::is_heap(self.0) {
595 let arc = self.heap_arc();
596 match &*arc {
597 HeapObject::Array(v) => {
598 PerlValue::from_heap(Arc::new(HeapObject::Array(v.clone())))
599 }
600 HeapObject::Hash(h) => PerlValue::from_heap(Arc::new(HeapObject::Hash(h.clone()))),
601 HeapObject::String(s) => {
602 PerlValue::from_heap(Arc::new(HeapObject::String(s.clone())))
603 }
604 HeapObject::Integer(n) => PerlValue::integer(*n),
605 HeapObject::Float(f) => PerlValue::float(*f),
606 _ => PerlValue::from_heap(Arc::clone(&arc)),
607 }
608 } else {
609 PerlValue(self.0)
610 }
611 }
612}
613
614impl PerlValue {
615 #[inline]
618 pub fn dup_stack(&self) -> Self {
619 if nanbox::is_heap(self.0) {
620 let arc = self.heap_arc();
621 match &*arc {
622 HeapObject::Array(_) | HeapObject::Hash(_) => {
623 PerlValue::from_heap(Arc::clone(&arc))
624 }
625 _ => self.clone(),
626 }
627 } else {
628 PerlValue(self.0)
629 }
630 }
631
632 #[inline]
643 pub fn shallow_clone(&self) -> Self {
644 if nanbox::is_heap(self.0) {
645 PerlValue::from_heap(self.heap_arc())
646 } else {
647 PerlValue(self.0)
648 }
649 }
650}
651
652impl Drop for PerlValue {
653 fn drop(&mut self) {
654 if nanbox::is_heap(self.0) {
655 unsafe {
656 let p = nanbox::decode_heap_ptr::<HeapObject>(self.0) as *mut HeapObject;
657 drop(Arc::from_raw(p));
658 }
659 }
660 }
661}
662
663impl fmt::Debug for PerlValue {
664 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
665 write!(f, "{self}")
666 }
667}
668
669#[derive(Clone)]
672pub struct PerlPpool(pub(crate) Arc<crate::ppool::PpoolInner>);
673
674impl fmt::Debug for PerlPpool {
675 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
676 f.write_str("PerlPpool")
677 }
678}
679
680#[derive(Debug, Clone, PartialEq, Eq)]
683pub struct FibLikeRecAddPattern {
684 pub param: String,
686 pub base_k: i64,
688 pub left_k: i64,
690 pub right_k: i64,
692}
693
694#[derive(Debug, Clone)]
695pub struct PerlSub {
696 pub name: String,
697 pub params: Vec<SubSigParam>,
698 pub body: Block,
699 pub closure_env: Option<Vec<(String, PerlValue)>>,
701 pub prototype: Option<String>,
703 pub fib_like: Option<FibLikeRecAddPattern>,
706}
707
708#[derive(Debug, Clone)]
710pub enum PipelineOp {
711 Filter(Arc<PerlSub>),
712 Map(Arc<PerlSub>),
713 Tap(Arc<PerlSub>),
715 Take(i64),
716 PMap {
718 sub: Arc<PerlSub>,
719 progress: bool,
720 },
721 PGrep {
723 sub: Arc<PerlSub>,
724 progress: bool,
725 },
726 PFor {
728 sub: Arc<PerlSub>,
729 progress: bool,
730 },
731 PMapChunked {
733 chunk: i64,
734 sub: Arc<PerlSub>,
735 progress: bool,
736 },
737 PSort {
739 cmp: Option<Arc<PerlSub>>,
740 progress: bool,
741 },
742 PCache {
744 sub: Arc<PerlSub>,
745 progress: bool,
746 },
747 PReduce {
749 sub: Arc<PerlSub>,
750 progress: bool,
751 },
752 PReduceInit {
754 init: PerlValue,
755 sub: Arc<PerlSub>,
756 progress: bool,
757 },
758 PMapReduce {
760 map: Arc<PerlSub>,
761 reduce: Arc<PerlSub>,
762 progress: bool,
763 },
764}
765
766#[derive(Debug)]
767pub struct PipelineInner {
768 pub source: Vec<PerlValue>,
769 pub ops: Vec<PipelineOp>,
770 pub has_scalar_terminal: bool,
772 pub par_stream: bool,
774 pub streaming: bool,
777 pub streaming_workers: usize,
779 pub streaming_buffer: usize,
781}
782
783#[derive(Debug)]
784pub struct BlessedRef {
785 pub class: String,
786 pub data: RwLock<PerlValue>,
787 pub(crate) suppress_destroy_queue: AtomicBool,
789}
790
791impl BlessedRef {
792 pub(crate) fn new_blessed(class: String, data: PerlValue) -> Self {
793 Self {
794 class,
795 data: RwLock::new(data),
796 suppress_destroy_queue: AtomicBool::new(false),
797 }
798 }
799
800 pub(crate) fn new_for_destroy_invocant(class: String, data: PerlValue) -> Self {
802 Self {
803 class,
804 data: RwLock::new(data),
805 suppress_destroy_queue: AtomicBool::new(true),
806 }
807 }
808}
809
810impl Clone for BlessedRef {
811 fn clone(&self) -> Self {
812 Self {
813 class: self.class.clone(),
814 data: RwLock::new(self.data.read().clone()),
815 suppress_destroy_queue: AtomicBool::new(false),
816 }
817 }
818}
819
820impl Drop for BlessedRef {
821 fn drop(&mut self) {
822 if self.suppress_destroy_queue.load(AtomicOrdering::Acquire) {
823 return;
824 }
825 let inner = {
826 let mut g = self.data.write();
827 std::mem::take(&mut *g)
828 };
829 crate::pending_destroy::enqueue(self.class.clone(), inner);
830 }
831}
832
833#[derive(Debug)]
835pub struct StructInstance {
836 pub def: Arc<StructDef>,
837 pub values: RwLock<Vec<PerlValue>>,
838}
839
840impl StructInstance {
841 pub fn new(def: Arc<StructDef>, values: Vec<PerlValue>) -> Self {
843 Self {
844 def,
845 values: RwLock::new(values),
846 }
847 }
848
849 #[inline]
851 pub fn get_field(&self, idx: usize) -> Option<PerlValue> {
852 self.values.read().get(idx).cloned()
853 }
854
855 #[inline]
857 pub fn set_field(&self, idx: usize, val: PerlValue) {
858 if let Some(slot) = self.values.write().get_mut(idx) {
859 *slot = val;
860 }
861 }
862
863 #[inline]
865 pub fn get_values(&self) -> Vec<PerlValue> {
866 self.values.read().clone()
867 }
868}
869
870impl Clone for StructInstance {
871 fn clone(&self) -> Self {
872 Self {
873 def: Arc::clone(&self.def),
874 values: RwLock::new(self.values.read().clone()),
875 }
876 }
877}
878
879#[derive(Debug)]
881pub struct EnumInstance {
882 pub def: Arc<EnumDef>,
883 pub variant_idx: usize,
884 pub data: PerlValue,
886}
887
888impl EnumInstance {
889 pub fn new(def: Arc<EnumDef>, variant_idx: usize, data: PerlValue) -> Self {
890 Self {
891 def,
892 variant_idx,
893 data,
894 }
895 }
896
897 pub fn variant_name(&self) -> &str {
898 &self.def.variants[self.variant_idx].name
899 }
900}
901
902impl Clone for EnumInstance {
903 fn clone(&self) -> Self {
904 Self {
905 def: Arc::clone(&self.def),
906 variant_idx: self.variant_idx,
907 data: self.data.clone(),
908 }
909 }
910}
911
912#[derive(Debug)]
914pub struct ClassInstance {
915 pub def: Arc<ClassDef>,
916 pub values: RwLock<Vec<PerlValue>>,
917 pub isa_chain: Vec<String>,
919}
920
921impl ClassInstance {
922 pub fn new(def: Arc<ClassDef>, values: Vec<PerlValue>) -> Self {
923 Self {
924 def,
925 values: RwLock::new(values),
926 isa_chain: Vec::new(),
927 }
928 }
929
930 pub fn new_with_isa(
931 def: Arc<ClassDef>,
932 values: Vec<PerlValue>,
933 isa_chain: Vec<String>,
934 ) -> Self {
935 Self {
936 def,
937 values: RwLock::new(values),
938 isa_chain,
939 }
940 }
941
942 #[inline]
944 pub fn isa(&self, name: &str) -> bool {
945 self.def.name == name || self.isa_chain.contains(&name.to_string())
946 }
947
948 #[inline]
949 pub fn get_field(&self, idx: usize) -> Option<PerlValue> {
950 self.values.read().get(idx).cloned()
951 }
952
953 #[inline]
954 pub fn set_field(&self, idx: usize, val: PerlValue) {
955 if let Some(slot) = self.values.write().get_mut(idx) {
956 *slot = val;
957 }
958 }
959
960 #[inline]
961 pub fn get_values(&self) -> Vec<PerlValue> {
962 self.values.read().clone()
963 }
964
965 pub fn get_field_by_name(&self, name: &str) -> Option<PerlValue> {
967 self.def
968 .field_index(name)
969 .and_then(|idx| self.get_field(idx))
970 }
971
972 pub fn set_field_by_name(&self, name: &str, val: PerlValue) -> bool {
974 if let Some(idx) = self.def.field_index(name) {
975 self.set_field(idx, val);
976 true
977 } else {
978 false
979 }
980 }
981}
982
983impl Clone for ClassInstance {
984 fn clone(&self) -> Self {
985 Self {
986 def: Arc::clone(&self.def),
987 values: RwLock::new(self.values.read().clone()),
988 isa_chain: self.isa_chain.clone(),
989 }
990 }
991}
992
993impl PerlValue {
994 pub const UNDEF: PerlValue = PerlValue(nanbox::encode_imm_undef());
995
996 #[inline]
997 fn from_heap(arc: Arc<HeapObject>) -> PerlValue {
998 let ptr = Arc::into_raw(arc);
999 PerlValue(nanbox::encode_heap_ptr(ptr))
1000 }
1001
1002 #[inline]
1003 pub(crate) fn heap_arc(&self) -> Arc<HeapObject> {
1004 debug_assert!(nanbox::is_heap(self.0));
1005 unsafe {
1006 let p = nanbox::decode_heap_ptr::<HeapObject>(self.0);
1007 Arc::increment_strong_count(p);
1008 Arc::from_raw(p as *mut HeapObject)
1009 }
1010 }
1011
1012 #[inline]
1017 pub(crate) unsafe fn heap_ref(&self) -> &HeapObject {
1018 &*nanbox::decode_heap_ptr::<HeapObject>(self.0)
1019 }
1020
1021 #[inline]
1022 pub(crate) fn with_heap<R>(&self, f: impl FnOnce(&HeapObject) -> R) -> Option<R> {
1023 if !nanbox::is_heap(self.0) {
1024 return None;
1025 }
1026 Some(f(unsafe { self.heap_ref() }))
1028 }
1029
1030 #[inline]
1032 pub(crate) fn raw_bits(&self) -> u64 {
1033 self.0
1034 }
1035
1036 #[inline]
1038 pub(crate) fn from_raw_bits(bits: u64) -> Self {
1039 Self(bits)
1040 }
1041
1042 #[inline]
1044 pub fn is_integer_like(&self) -> bool {
1045 nanbox::as_imm_int32(self.0).is_some()
1046 || matches!(
1047 self.with_heap(|h| matches!(h, HeapObject::Integer(_))),
1048 Some(true)
1049 )
1050 }
1051
1052 #[inline]
1054 pub fn is_float_like(&self) -> bool {
1055 nanbox::is_raw_float_bits(self.0)
1056 || matches!(
1057 self.with_heap(|h| matches!(h, HeapObject::Float(_))),
1058 Some(true)
1059 )
1060 }
1061
1062 #[inline]
1064 pub fn is_string_like(&self) -> bool {
1065 matches!(
1066 self.with_heap(|h| matches!(h, HeapObject::String(_))),
1067 Some(true)
1068 )
1069 }
1070
1071 #[inline]
1072 pub fn integer(n: i64) -> Self {
1073 if n >= i32::MIN as i64 && n <= i32::MAX as i64 {
1074 PerlValue(nanbox::encode_imm_int32(n as i32))
1075 } else {
1076 Self::from_heap(Arc::new(HeapObject::Integer(n)))
1077 }
1078 }
1079
1080 #[inline]
1081 pub fn float(f: f64) -> Self {
1082 if nanbox::float_needs_box(f) {
1083 Self::from_heap(Arc::new(HeapObject::Float(f)))
1084 } else {
1085 PerlValue(f.to_bits())
1086 }
1087 }
1088
1089 #[inline]
1090 pub fn string(s: String) -> Self {
1091 Self::from_heap(Arc::new(HeapObject::String(s)))
1092 }
1093
1094 #[inline]
1095 pub fn bytes(b: Arc<Vec<u8>>) -> Self {
1096 Self::from_heap(Arc::new(HeapObject::Bytes(b)))
1097 }
1098
1099 #[inline]
1100 pub fn array(v: Vec<PerlValue>) -> Self {
1101 Self::from_heap(Arc::new(HeapObject::Array(v)))
1102 }
1103
1104 #[inline]
1106 pub fn iterator(it: Arc<dyn PerlIterator>) -> Self {
1107 Self::from_heap(Arc::new(HeapObject::Iterator(it)))
1108 }
1109
1110 #[inline]
1112 pub fn is_iterator(&self) -> bool {
1113 if !nanbox::is_heap(self.0) {
1114 return false;
1115 }
1116 matches!(unsafe { self.heap_ref() }, HeapObject::Iterator(_))
1117 }
1118
1119 pub fn into_iterator(&self) -> Arc<dyn PerlIterator> {
1121 if nanbox::is_heap(self.0) {
1122 if let HeapObject::Iterator(it) = &*self.heap_arc() {
1123 return Arc::clone(it);
1124 }
1125 }
1126 panic!("into_iterator on non-iterator value");
1127 }
1128
1129 #[inline]
1130 pub fn hash(h: IndexMap<String, PerlValue>) -> Self {
1131 Self::from_heap(Arc::new(HeapObject::Hash(h)))
1132 }
1133
1134 #[inline]
1135 pub fn array_ref(a: Arc<RwLock<Vec<PerlValue>>>) -> Self {
1136 Self::from_heap(Arc::new(HeapObject::ArrayRef(a)))
1137 }
1138
1139 #[inline]
1140 pub fn hash_ref(h: Arc<RwLock<IndexMap<String, PerlValue>>>) -> Self {
1141 Self::from_heap(Arc::new(HeapObject::HashRef(h)))
1142 }
1143
1144 #[inline]
1145 pub fn scalar_ref(r: Arc<RwLock<PerlValue>>) -> Self {
1146 Self::from_heap(Arc::new(HeapObject::ScalarRef(r)))
1147 }
1148
1149 #[inline]
1150 pub fn scalar_binding_ref(name: String) -> Self {
1151 Self::from_heap(Arc::new(HeapObject::ScalarBindingRef(name)))
1152 }
1153
1154 #[inline]
1155 pub fn array_binding_ref(name: String) -> Self {
1156 Self::from_heap(Arc::new(HeapObject::ArrayBindingRef(name)))
1157 }
1158
1159 #[inline]
1160 pub fn hash_binding_ref(name: String) -> Self {
1161 Self::from_heap(Arc::new(HeapObject::HashBindingRef(name)))
1162 }
1163
1164 #[inline]
1165 pub fn code_ref(c: Arc<PerlSub>) -> Self {
1166 Self::from_heap(Arc::new(HeapObject::CodeRef(c)))
1167 }
1168
1169 #[inline]
1170 pub fn as_code_ref(&self) -> Option<Arc<PerlSub>> {
1171 self.with_heap(|h| match h {
1172 HeapObject::CodeRef(sub) => Some(Arc::clone(sub)),
1173 _ => None,
1174 })
1175 .flatten()
1176 }
1177
1178 #[inline]
1179 pub fn as_regex(&self) -> Option<Arc<PerlCompiledRegex>> {
1180 self.with_heap(|h| match h {
1181 HeapObject::Regex(re, _, _) => Some(Arc::clone(re)),
1182 _ => None,
1183 })
1184 .flatten()
1185 }
1186
1187 #[inline]
1188 pub fn as_blessed_ref(&self) -> Option<Arc<BlessedRef>> {
1189 self.with_heap(|h| match h {
1190 HeapObject::Blessed(b) => Some(Arc::clone(b)),
1191 _ => None,
1192 })
1193 .flatten()
1194 }
1195
1196 #[inline]
1198 pub fn hash_get(&self, key: &str) -> Option<PerlValue> {
1199 self.with_heap(|h| match h {
1200 HeapObject::Hash(h) => h.get(key).cloned(),
1201 _ => None,
1202 })
1203 .flatten()
1204 }
1205
1206 #[inline]
1207 pub fn is_undef(&self) -> bool {
1208 nanbox::is_imm_undef(self.0)
1209 }
1210
1211 pub fn is_simple_scalar(&self) -> bool {
1216 if self.is_undef() {
1217 return true;
1218 }
1219 if !nanbox::is_heap(self.0) {
1220 return true; }
1222 matches!(
1223 unsafe { self.heap_ref() },
1224 HeapObject::Integer(_)
1225 | HeapObject::Float(_)
1226 | HeapObject::String(_)
1227 | HeapObject::Bytes(_)
1228 )
1229 }
1230
1231 #[inline]
1233 pub fn as_integer(&self) -> Option<i64> {
1234 if let Some(n) = nanbox::as_imm_int32(self.0) {
1235 return Some(n as i64);
1236 }
1237 if nanbox::is_raw_float_bits(self.0) {
1238 return None;
1239 }
1240 self.with_heap(|h| match h {
1241 HeapObject::Integer(n) => Some(*n),
1242 _ => None,
1243 })
1244 .flatten()
1245 }
1246
1247 #[inline]
1248 pub fn as_float(&self) -> Option<f64> {
1249 if nanbox::is_raw_float_bits(self.0) {
1250 return Some(f64::from_bits(self.0));
1251 }
1252 self.with_heap(|h| match h {
1253 HeapObject::Float(f) => Some(*f),
1254 _ => None,
1255 })
1256 .flatten()
1257 }
1258
1259 #[inline]
1260 pub fn as_array_vec(&self) -> Option<Vec<PerlValue>> {
1261 self.with_heap(|h| match h {
1262 HeapObject::Array(v) => Some(v.clone()),
1263 _ => None,
1264 })
1265 .flatten()
1266 }
1267
1268 pub fn map_flatten_outputs(&self, peel_array_ref: bool) -> Vec<PerlValue> {
1272 if let Some(a) = self.as_array_vec() {
1273 return a;
1274 }
1275 if peel_array_ref {
1276 if let Some(r) = self.as_array_ref() {
1277 return r.read().clone();
1278 }
1279 }
1280 if self.is_iterator() {
1281 return self.into_iterator().collect_all();
1282 }
1283 vec![self.clone()]
1284 }
1285
1286 #[inline]
1287 pub fn as_hash_map(&self) -> Option<IndexMap<String, PerlValue>> {
1288 self.with_heap(|h| match h {
1289 HeapObject::Hash(h) => Some(h.clone()),
1290 _ => None,
1291 })
1292 .flatten()
1293 }
1294
1295 #[inline]
1296 pub fn as_bytes_arc(&self) -> Option<Arc<Vec<u8>>> {
1297 self.with_heap(|h| match h {
1298 HeapObject::Bytes(b) => Some(Arc::clone(b)),
1299 _ => None,
1300 })
1301 .flatten()
1302 }
1303
1304 #[inline]
1305 pub fn as_async_task(&self) -> Option<Arc<PerlAsyncTask>> {
1306 self.with_heap(|h| match h {
1307 HeapObject::AsyncTask(t) => Some(Arc::clone(t)),
1308 _ => None,
1309 })
1310 .flatten()
1311 }
1312
1313 #[inline]
1314 pub fn as_generator(&self) -> Option<Arc<PerlGenerator>> {
1315 self.with_heap(|h| match h {
1316 HeapObject::Generator(g) => Some(Arc::clone(g)),
1317 _ => None,
1318 })
1319 .flatten()
1320 }
1321
1322 #[inline]
1323 pub fn as_atomic_arc(&self) -> Option<Arc<Mutex<PerlValue>>> {
1324 self.with_heap(|h| match h {
1325 HeapObject::Atomic(a) => Some(Arc::clone(a)),
1326 _ => None,
1327 })
1328 .flatten()
1329 }
1330
1331 #[inline]
1332 pub fn as_io_handle_name(&self) -> Option<String> {
1333 self.with_heap(|h| match h {
1334 HeapObject::IOHandle(n) => Some(n.clone()),
1335 _ => None,
1336 })
1337 .flatten()
1338 }
1339
1340 #[inline]
1341 pub fn as_sqlite_conn(&self) -> Option<Arc<Mutex<rusqlite::Connection>>> {
1342 self.with_heap(|h| match h {
1343 HeapObject::SqliteConn(c) => Some(Arc::clone(c)),
1344 _ => None,
1345 })
1346 .flatten()
1347 }
1348
1349 #[inline]
1350 pub fn as_struct_inst(&self) -> Option<Arc<StructInstance>> {
1351 self.with_heap(|h| match h {
1352 HeapObject::StructInst(s) => Some(Arc::clone(s)),
1353 _ => None,
1354 })
1355 .flatten()
1356 }
1357
1358 #[inline]
1359 pub fn as_enum_inst(&self) -> Option<Arc<EnumInstance>> {
1360 self.with_heap(|h| match h {
1361 HeapObject::EnumInst(e) => Some(Arc::clone(e)),
1362 _ => None,
1363 })
1364 .flatten()
1365 }
1366
1367 #[inline]
1368 pub fn as_class_inst(&self) -> Option<Arc<ClassInstance>> {
1369 self.with_heap(|h| match h {
1370 HeapObject::ClassInst(c) => Some(Arc::clone(c)),
1371 _ => None,
1372 })
1373 .flatten()
1374 }
1375
1376 #[inline]
1377 pub fn as_dataframe(&self) -> Option<Arc<Mutex<PerlDataFrame>>> {
1378 self.with_heap(|h| match h {
1379 HeapObject::DataFrame(d) => Some(Arc::clone(d)),
1380 _ => None,
1381 })
1382 .flatten()
1383 }
1384
1385 #[inline]
1386 pub fn as_deque(&self) -> Option<Arc<Mutex<VecDeque<PerlValue>>>> {
1387 self.with_heap(|h| match h {
1388 HeapObject::Deque(d) => Some(Arc::clone(d)),
1389 _ => None,
1390 })
1391 .flatten()
1392 }
1393
1394 #[inline]
1395 pub fn as_heap_pq(&self) -> Option<Arc<Mutex<PerlHeap>>> {
1396 self.with_heap(|h| match h {
1397 HeapObject::Heap(h) => Some(Arc::clone(h)),
1398 _ => None,
1399 })
1400 .flatten()
1401 }
1402
1403 #[inline]
1404 pub fn as_pipeline(&self) -> Option<Arc<Mutex<PipelineInner>>> {
1405 self.with_heap(|h| match h {
1406 HeapObject::Pipeline(p) => Some(Arc::clone(p)),
1407 _ => None,
1408 })
1409 .flatten()
1410 }
1411
1412 #[inline]
1413 pub fn as_capture(&self) -> Option<Arc<CaptureResult>> {
1414 self.with_heap(|h| match h {
1415 HeapObject::Capture(c) => Some(Arc::clone(c)),
1416 _ => None,
1417 })
1418 .flatten()
1419 }
1420
1421 #[inline]
1422 pub fn as_ppool(&self) -> Option<PerlPpool> {
1423 self.with_heap(|h| match h {
1424 HeapObject::Ppool(p) => Some(p.clone()),
1425 _ => None,
1426 })
1427 .flatten()
1428 }
1429
1430 #[inline]
1431 pub fn as_remote_cluster(&self) -> Option<Arc<RemoteCluster>> {
1432 self.with_heap(|h| match h {
1433 HeapObject::RemoteCluster(c) => Some(Arc::clone(c)),
1434 _ => None,
1435 })
1436 .flatten()
1437 }
1438
1439 #[inline]
1440 pub fn as_barrier(&self) -> Option<PerlBarrier> {
1441 self.with_heap(|h| match h {
1442 HeapObject::Barrier(b) => Some(b.clone()),
1443 _ => None,
1444 })
1445 .flatten()
1446 }
1447
1448 #[inline]
1449 pub fn as_channel_tx(&self) -> Option<Arc<Sender<PerlValue>>> {
1450 self.with_heap(|h| match h {
1451 HeapObject::ChannelTx(t) => Some(Arc::clone(t)),
1452 _ => None,
1453 })
1454 .flatten()
1455 }
1456
1457 #[inline]
1458 pub fn as_channel_rx(&self) -> Option<Arc<Receiver<PerlValue>>> {
1459 self.with_heap(|h| match h {
1460 HeapObject::ChannelRx(r) => Some(Arc::clone(r)),
1461 _ => None,
1462 })
1463 .flatten()
1464 }
1465
1466 #[inline]
1467 pub fn as_scalar_ref(&self) -> Option<Arc<RwLock<PerlValue>>> {
1468 self.with_heap(|h| match h {
1469 HeapObject::ScalarRef(r) => Some(Arc::clone(r)),
1470 _ => None,
1471 })
1472 .flatten()
1473 }
1474
1475 #[inline]
1477 pub fn as_scalar_binding_name(&self) -> Option<String> {
1478 self.with_heap(|h| match h {
1479 HeapObject::ScalarBindingRef(s) => Some(s.clone()),
1480 _ => None,
1481 })
1482 .flatten()
1483 }
1484
1485 #[inline]
1487 pub fn as_array_binding_name(&self) -> Option<String> {
1488 self.with_heap(|h| match h {
1489 HeapObject::ArrayBindingRef(s) => Some(s.clone()),
1490 _ => None,
1491 })
1492 .flatten()
1493 }
1494
1495 #[inline]
1497 pub fn as_hash_binding_name(&self) -> Option<String> {
1498 self.with_heap(|h| match h {
1499 HeapObject::HashBindingRef(s) => Some(s.clone()),
1500 _ => None,
1501 })
1502 .flatten()
1503 }
1504
1505 #[inline]
1506 pub fn as_array_ref(&self) -> Option<Arc<RwLock<Vec<PerlValue>>>> {
1507 self.with_heap(|h| match h {
1508 HeapObject::ArrayRef(r) => Some(Arc::clone(r)),
1509 _ => None,
1510 })
1511 .flatten()
1512 }
1513
1514 #[inline]
1515 pub fn as_hash_ref(&self) -> Option<Arc<RwLock<IndexMap<String, PerlValue>>>> {
1516 self.with_heap(|h| match h {
1517 HeapObject::HashRef(r) => Some(Arc::clone(r)),
1518 _ => None,
1519 })
1520 .flatten()
1521 }
1522
1523 #[inline]
1525 pub fn is_mysync_deque_or_heap(&self) -> bool {
1526 matches!(
1527 self.with_heap(|h| matches!(h, HeapObject::Deque(_) | HeapObject::Heap(_))),
1528 Some(true)
1529 )
1530 }
1531
1532 #[inline]
1533 pub fn regex(rx: Arc<PerlCompiledRegex>, pattern_src: String, flags: String) -> Self {
1534 Self::from_heap(Arc::new(HeapObject::Regex(rx, pattern_src, flags)))
1535 }
1536
1537 #[inline]
1539 pub fn regex_src_and_flags(&self) -> Option<(String, String)> {
1540 self.with_heap(|h| match h {
1541 HeapObject::Regex(_, pat, fl) => Some((pat.clone(), fl.clone())),
1542 _ => None,
1543 })
1544 .flatten()
1545 }
1546
1547 #[inline]
1548 pub fn blessed(b: Arc<BlessedRef>) -> Self {
1549 Self::from_heap(Arc::new(HeapObject::Blessed(b)))
1550 }
1551
1552 #[inline]
1553 pub fn io_handle(name: String) -> Self {
1554 Self::from_heap(Arc::new(HeapObject::IOHandle(name)))
1555 }
1556
1557 #[inline]
1558 pub fn atomic(a: Arc<Mutex<PerlValue>>) -> Self {
1559 Self::from_heap(Arc::new(HeapObject::Atomic(a)))
1560 }
1561
1562 #[inline]
1563 pub fn set(s: Arc<PerlSet>) -> Self {
1564 Self::from_heap(Arc::new(HeapObject::Set(s)))
1565 }
1566
1567 #[inline]
1568 pub fn channel_tx(tx: Arc<Sender<PerlValue>>) -> Self {
1569 Self::from_heap(Arc::new(HeapObject::ChannelTx(tx)))
1570 }
1571
1572 #[inline]
1573 pub fn channel_rx(rx: Arc<Receiver<PerlValue>>) -> Self {
1574 Self::from_heap(Arc::new(HeapObject::ChannelRx(rx)))
1575 }
1576
1577 #[inline]
1578 pub fn async_task(t: Arc<PerlAsyncTask>) -> Self {
1579 Self::from_heap(Arc::new(HeapObject::AsyncTask(t)))
1580 }
1581
1582 #[inline]
1583 pub fn generator(g: Arc<PerlGenerator>) -> Self {
1584 Self::from_heap(Arc::new(HeapObject::Generator(g)))
1585 }
1586
1587 #[inline]
1588 pub fn deque(d: Arc<Mutex<VecDeque<PerlValue>>>) -> Self {
1589 Self::from_heap(Arc::new(HeapObject::Deque(d)))
1590 }
1591
1592 #[inline]
1593 pub fn heap(h: Arc<Mutex<PerlHeap>>) -> Self {
1594 Self::from_heap(Arc::new(HeapObject::Heap(h)))
1595 }
1596
1597 #[inline]
1598 pub fn pipeline(p: Arc<Mutex<PipelineInner>>) -> Self {
1599 Self::from_heap(Arc::new(HeapObject::Pipeline(p)))
1600 }
1601
1602 #[inline]
1603 pub fn capture(c: Arc<CaptureResult>) -> Self {
1604 Self::from_heap(Arc::new(HeapObject::Capture(c)))
1605 }
1606
1607 #[inline]
1608 pub fn ppool(p: PerlPpool) -> Self {
1609 Self::from_heap(Arc::new(HeapObject::Ppool(p)))
1610 }
1611
1612 #[inline]
1613 pub fn remote_cluster(c: Arc<RemoteCluster>) -> Self {
1614 Self::from_heap(Arc::new(HeapObject::RemoteCluster(c)))
1615 }
1616
1617 #[inline]
1618 pub fn barrier(b: PerlBarrier) -> Self {
1619 Self::from_heap(Arc::new(HeapObject::Barrier(b)))
1620 }
1621
1622 #[inline]
1623 pub fn sqlite_conn(c: Arc<Mutex<rusqlite::Connection>>) -> Self {
1624 Self::from_heap(Arc::new(HeapObject::SqliteConn(c)))
1625 }
1626
1627 #[inline]
1628 pub fn struct_inst(s: Arc<StructInstance>) -> Self {
1629 Self::from_heap(Arc::new(HeapObject::StructInst(s)))
1630 }
1631
1632 #[inline]
1633 pub fn enum_inst(e: Arc<EnumInstance>) -> Self {
1634 Self::from_heap(Arc::new(HeapObject::EnumInst(e)))
1635 }
1636
1637 #[inline]
1638 pub fn class_inst(c: Arc<ClassInstance>) -> Self {
1639 Self::from_heap(Arc::new(HeapObject::ClassInst(c)))
1640 }
1641
1642 #[inline]
1643 pub fn dataframe(df: Arc<Mutex<PerlDataFrame>>) -> Self {
1644 Self::from_heap(Arc::new(HeapObject::DataFrame(df)))
1645 }
1646
1647 #[inline]
1649 pub fn errno_dual(code: i32, msg: String) -> Self {
1650 Self::from_heap(Arc::new(HeapObject::ErrnoDual { code, msg }))
1651 }
1652
1653 #[inline]
1655 pub(crate) fn errno_dual_parts(&self) -> Option<(i32, String)> {
1656 if !nanbox::is_heap(self.0) {
1657 return None;
1658 }
1659 match unsafe { self.heap_ref() } {
1660 HeapObject::ErrnoDual { code, msg } => Some((*code, msg.clone())),
1661 _ => None,
1662 }
1663 }
1664
1665 #[inline]
1667 pub fn as_str(&self) -> Option<String> {
1668 if !nanbox::is_heap(self.0) {
1669 return None;
1670 }
1671 match unsafe { self.heap_ref() } {
1672 HeapObject::String(s) => Some(s.clone()),
1673 _ => None,
1674 }
1675 }
1676
1677 #[inline]
1678 pub fn append_to(&self, buf: &mut String) {
1679 if nanbox::is_imm_undef(self.0) {
1680 return;
1681 }
1682 if let Some(n) = nanbox::as_imm_int32(self.0) {
1683 let mut b = itoa::Buffer::new();
1684 buf.push_str(b.format(n));
1685 return;
1686 }
1687 if nanbox::is_raw_float_bits(self.0) {
1688 buf.push_str(&format_float(f64::from_bits(self.0)));
1689 return;
1690 }
1691 match unsafe { self.heap_ref() } {
1692 HeapObject::String(s) => buf.push_str(s),
1693 HeapObject::ErrnoDual { msg, .. } => buf.push_str(msg),
1694 HeapObject::Bytes(b) => buf.push_str(&decode_utf8_or_latin1(b)),
1695 HeapObject::Atomic(arc) => arc.lock().append_to(buf),
1696 HeapObject::Set(s) => {
1697 buf.push('{');
1698 let mut first = true;
1699 for v in s.values() {
1700 if !first {
1701 buf.push(',');
1702 }
1703 first = false;
1704 v.append_to(buf);
1705 }
1706 buf.push('}');
1707 }
1708 HeapObject::ChannelTx(_) => buf.push_str("PCHANNEL::Tx"),
1709 HeapObject::ChannelRx(_) => buf.push_str("PCHANNEL::Rx"),
1710 HeapObject::AsyncTask(_) => buf.push_str("AsyncTask"),
1711 HeapObject::Generator(_) => buf.push_str("Generator"),
1712 HeapObject::Pipeline(_) => buf.push_str("Pipeline"),
1713 HeapObject::DataFrame(d) => {
1714 let g = d.lock();
1715 buf.push_str(&format!("DataFrame({}x{})", g.nrows(), g.ncols()));
1716 }
1717 HeapObject::Capture(_) => buf.push_str("Capture"),
1718 HeapObject::Ppool(_) => buf.push_str("Ppool"),
1719 HeapObject::RemoteCluster(_) => buf.push_str("Cluster"),
1720 HeapObject::Barrier(_) => buf.push_str("Barrier"),
1721 HeapObject::SqliteConn(_) => buf.push_str("SqliteConn"),
1722 HeapObject::StructInst(s) => buf.push_str(&s.def.name),
1723 _ => buf.push_str(&self.to_string()),
1724 }
1725 }
1726
1727 #[inline]
1728 pub fn unwrap_atomic(&self) -> PerlValue {
1729 if !nanbox::is_heap(self.0) {
1730 return self.clone();
1731 }
1732 match unsafe { self.heap_ref() } {
1733 HeapObject::Atomic(a) => a.lock().clone(),
1734 _ => self.clone(),
1735 }
1736 }
1737
1738 #[inline]
1739 pub fn is_atomic(&self) -> bool {
1740 if !nanbox::is_heap(self.0) {
1741 return false;
1742 }
1743 matches!(unsafe { self.heap_ref() }, HeapObject::Atomic(_))
1744 }
1745
1746 #[inline]
1747 pub fn is_true(&self) -> bool {
1748 if nanbox::is_imm_undef(self.0) {
1749 return false;
1750 }
1751 if let Some(n) = nanbox::as_imm_int32(self.0) {
1752 return n != 0;
1753 }
1754 if nanbox::is_raw_float_bits(self.0) {
1755 return f64::from_bits(self.0) != 0.0;
1756 }
1757 match unsafe { self.heap_ref() } {
1758 HeapObject::ErrnoDual { code, msg } => *code != 0 || !msg.is_empty(),
1759 HeapObject::String(s) => !s.is_empty() && s != "0",
1760 HeapObject::Bytes(b) => !b.is_empty(),
1761 HeapObject::Array(a) => !a.is_empty(),
1762 HeapObject::Hash(h) => !h.is_empty(),
1763 HeapObject::Atomic(arc) => arc.lock().is_true(),
1764 HeapObject::Set(s) => !s.is_empty(),
1765 HeapObject::Deque(d) => !d.lock().is_empty(),
1766 HeapObject::Heap(h) => !h.lock().items.is_empty(),
1767 HeapObject::DataFrame(d) => d.lock().nrows() > 0,
1768 HeapObject::Pipeline(_) | HeapObject::Capture(_) => true,
1769 _ => true,
1770 }
1771 }
1772
1773 #[inline]
1776 pub(crate) fn concat_append_owned(self, rhs: &PerlValue) -> PerlValue {
1777 let mut s = self.into_string();
1778 rhs.append_to(&mut s);
1779 PerlValue::string(s)
1780 }
1781
1782 #[inline]
1788 pub(crate) fn try_concat_repeat_inplace(&mut self, rhs: &str, n: usize) -> bool {
1789 if !nanbox::is_heap(self.0) || n == 0 {
1790 return n == 0 && nanbox::is_heap(self.0);
1792 }
1793 unsafe {
1794 if !matches!(self.heap_ref(), HeapObject::String(_)) {
1795 return false;
1796 }
1797 let raw = nanbox::decode_heap_ptr::<HeapObject>(self.0) as *mut HeapObject
1798 as *const HeapObject;
1799 let mut arc: Arc<HeapObject> = Arc::from_raw(raw);
1800 let did = if let Some(HeapObject::String(s)) = Arc::get_mut(&mut arc) {
1801 if !rhs.is_empty() {
1802 s.reserve(rhs.len().saturating_mul(n));
1803 for _ in 0..n {
1804 s.push_str(rhs);
1805 }
1806 }
1807 true
1808 } else {
1809 false
1810 };
1811 let restored = Arc::into_raw(arc);
1812 self.0 = nanbox::encode_heap_ptr(restored);
1813 did
1814 }
1815 }
1816
1817 #[inline]
1827 pub(crate) fn try_concat_append_inplace(&mut self, rhs: &PerlValue) -> bool {
1828 if !nanbox::is_heap(self.0) {
1829 return false;
1830 }
1831 unsafe {
1835 if !matches!(self.heap_ref(), HeapObject::String(_)) {
1836 return false;
1837 }
1838 let raw = nanbox::decode_heap_ptr::<HeapObject>(self.0) as *mut HeapObject
1841 as *const HeapObject;
1842 let mut arc: Arc<HeapObject> = Arc::from_raw(raw);
1843 let did_append = if let Some(HeapObject::String(s)) = Arc::get_mut(&mut arc) {
1844 rhs.append_to(s);
1845 true
1846 } else {
1847 false
1848 };
1849 let restored = Arc::into_raw(arc);
1852 self.0 = nanbox::encode_heap_ptr(restored);
1853 did_append
1854 }
1855 }
1856
1857 #[inline]
1858 pub fn into_string(self) -> String {
1859 let bits = self.0;
1860 std::mem::forget(self);
1861 if nanbox::is_imm_undef(bits) {
1862 return String::new();
1863 }
1864 if let Some(n) = nanbox::as_imm_int32(bits) {
1865 let mut buf = itoa::Buffer::new();
1866 return buf.format(n).to_owned();
1867 }
1868 if nanbox::is_raw_float_bits(bits) {
1869 return format_float(f64::from_bits(bits));
1870 }
1871 if nanbox::is_heap(bits) {
1872 unsafe {
1873 let arc =
1874 Arc::from_raw(nanbox::decode_heap_ptr::<HeapObject>(bits) as *mut HeapObject);
1875 match Arc::try_unwrap(arc) {
1876 Ok(HeapObject::String(s)) => return s,
1877 Ok(o) => return PerlValue::from_heap(Arc::new(o)).to_string(),
1878 Err(arc) => {
1879 return match &*arc {
1880 HeapObject::String(s) => s.clone(),
1881 _ => PerlValue::from_heap(Arc::clone(&arc)).to_string(),
1882 };
1883 }
1884 }
1885 }
1886 }
1887 String::new()
1888 }
1889
1890 #[inline]
1891 pub fn as_str_or_empty(&self) -> String {
1892 if !nanbox::is_heap(self.0) {
1893 return String::new();
1894 }
1895 match unsafe { self.heap_ref() } {
1896 HeapObject::String(s) => s.clone(),
1897 HeapObject::ErrnoDual { msg, .. } => msg.clone(),
1898 _ => String::new(),
1899 }
1900 }
1901
1902 #[inline]
1903 pub fn to_number(&self) -> f64 {
1904 if nanbox::is_imm_undef(self.0) {
1905 return 0.0;
1906 }
1907 if let Some(n) = nanbox::as_imm_int32(self.0) {
1908 return n as f64;
1909 }
1910 if nanbox::is_raw_float_bits(self.0) {
1911 return f64::from_bits(self.0);
1912 }
1913 match unsafe { self.heap_ref() } {
1914 HeapObject::Integer(n) => *n as f64,
1915 HeapObject::Float(f) => *f,
1916 HeapObject::ErrnoDual { code, .. } => *code as f64,
1917 HeapObject::String(s) => parse_number(s),
1918 HeapObject::Bytes(b) => b.len() as f64,
1919 HeapObject::Array(a) => a.len() as f64,
1920 HeapObject::Atomic(arc) => arc.lock().to_number(),
1921 HeapObject::Set(s) => s.len() as f64,
1922 HeapObject::ChannelTx(_)
1923 | HeapObject::ChannelRx(_)
1924 | HeapObject::AsyncTask(_)
1925 | HeapObject::Generator(_) => 1.0,
1926 HeapObject::Deque(d) => d.lock().len() as f64,
1927 HeapObject::Heap(h) => h.lock().items.len() as f64,
1928 HeapObject::Pipeline(p) => p.lock().source.len() as f64,
1929 HeapObject::DataFrame(d) => d.lock().nrows() as f64,
1930 HeapObject::Capture(_)
1931 | HeapObject::Ppool(_)
1932 | HeapObject::RemoteCluster(_)
1933 | HeapObject::Barrier(_)
1934 | HeapObject::SqliteConn(_)
1935 | HeapObject::StructInst(_)
1936 | HeapObject::IOHandle(_) => 1.0,
1937 _ => 0.0,
1938 }
1939 }
1940
1941 #[inline]
1942 pub fn to_int(&self) -> i64 {
1943 if nanbox::is_imm_undef(self.0) {
1944 return 0;
1945 }
1946 if let Some(n) = nanbox::as_imm_int32(self.0) {
1947 return n as i64;
1948 }
1949 if nanbox::is_raw_float_bits(self.0) {
1950 return f64::from_bits(self.0) as i64;
1951 }
1952 match unsafe { self.heap_ref() } {
1953 HeapObject::Integer(n) => *n,
1954 HeapObject::Float(f) => *f as i64,
1955 HeapObject::ErrnoDual { code, .. } => *code as i64,
1956 HeapObject::String(s) => parse_number(s) as i64,
1957 HeapObject::Bytes(b) => b.len() as i64,
1958 HeapObject::Array(a) => a.len() as i64,
1959 HeapObject::Atomic(arc) => arc.lock().to_int(),
1960 HeapObject::Set(s) => s.len() as i64,
1961 HeapObject::ChannelTx(_)
1962 | HeapObject::ChannelRx(_)
1963 | HeapObject::AsyncTask(_)
1964 | HeapObject::Generator(_) => 1,
1965 HeapObject::Deque(d) => d.lock().len() as i64,
1966 HeapObject::Heap(h) => h.lock().items.len() as i64,
1967 HeapObject::Pipeline(p) => p.lock().source.len() as i64,
1968 HeapObject::DataFrame(d) => d.lock().nrows() as i64,
1969 HeapObject::Capture(_)
1970 | HeapObject::Ppool(_)
1971 | HeapObject::RemoteCluster(_)
1972 | HeapObject::Barrier(_)
1973 | HeapObject::SqliteConn(_)
1974 | HeapObject::StructInst(_)
1975 | HeapObject::IOHandle(_) => 1,
1976 _ => 0,
1977 }
1978 }
1979
1980 pub fn type_name(&self) -> String {
1981 if nanbox::is_imm_undef(self.0) {
1982 return "undef".to_string();
1983 }
1984 if nanbox::as_imm_int32(self.0).is_some() {
1985 return "INTEGER".to_string();
1986 }
1987 if nanbox::is_raw_float_bits(self.0) {
1988 return "FLOAT".to_string();
1989 }
1990 match unsafe { self.heap_ref() } {
1991 HeapObject::String(_) => "STRING".to_string(),
1992 HeapObject::Bytes(_) => "BYTES".to_string(),
1993 HeapObject::Array(_) => "ARRAY".to_string(),
1994 HeapObject::Hash(_) => "HASH".to_string(),
1995 HeapObject::ArrayRef(_) | HeapObject::ArrayBindingRef(_) => "ARRAY".to_string(),
1996 HeapObject::HashRef(_) | HeapObject::HashBindingRef(_) => "HASH".to_string(),
1997 HeapObject::ScalarRef(_) | HeapObject::ScalarBindingRef(_) => "SCALAR".to_string(),
1998 HeapObject::CodeRef(_) => "CODE".to_string(),
1999 HeapObject::Regex(_, _, _) => "Regexp".to_string(),
2000 HeapObject::Blessed(b) => b.class.clone(),
2001 HeapObject::IOHandle(_) => "GLOB".to_string(),
2002 HeapObject::Atomic(_) => "ATOMIC".to_string(),
2003 HeapObject::Set(_) => "Set".to_string(),
2004 HeapObject::ChannelTx(_) => "PCHANNEL::Tx".to_string(),
2005 HeapObject::ChannelRx(_) => "PCHANNEL::Rx".to_string(),
2006 HeapObject::AsyncTask(_) => "ASYNCTASK".to_string(),
2007 HeapObject::Generator(_) => "Generator".to_string(),
2008 HeapObject::Deque(_) => "Deque".to_string(),
2009 HeapObject::Heap(_) => "Heap".to_string(),
2010 HeapObject::Pipeline(_) => "Pipeline".to_string(),
2011 HeapObject::DataFrame(_) => "DataFrame".to_string(),
2012 HeapObject::Capture(_) => "Capture".to_string(),
2013 HeapObject::Ppool(_) => "Ppool".to_string(),
2014 HeapObject::RemoteCluster(_) => "Cluster".to_string(),
2015 HeapObject::Barrier(_) => "Barrier".to_string(),
2016 HeapObject::SqliteConn(_) => "SqliteConn".to_string(),
2017 HeapObject::StructInst(s) => s.def.name.to_string(),
2018 HeapObject::EnumInst(e) => e.def.name.to_string(),
2019 HeapObject::ClassInst(c) => c.def.name.to_string(),
2020 HeapObject::Iterator(_) => "Iterator".to_string(),
2021 HeapObject::ErrnoDual { .. } => "Errno".to_string(),
2022 HeapObject::Integer(_) => "INTEGER".to_string(),
2023 HeapObject::Float(_) => "FLOAT".to_string(),
2024 }
2025 }
2026
2027 pub fn ref_type(&self) -> PerlValue {
2028 if !nanbox::is_heap(self.0) {
2029 return PerlValue::string(String::new());
2030 }
2031 match unsafe { self.heap_ref() } {
2032 HeapObject::ArrayRef(_) | HeapObject::ArrayBindingRef(_) => {
2033 PerlValue::string("ARRAY".into())
2034 }
2035 HeapObject::HashRef(_) | HeapObject::HashBindingRef(_) => {
2036 PerlValue::string("HASH".into())
2037 }
2038 HeapObject::ScalarRef(_) | HeapObject::ScalarBindingRef(_) => {
2039 PerlValue::string("SCALAR".into())
2040 }
2041 HeapObject::CodeRef(_) => PerlValue::string("CODE".into()),
2042 HeapObject::Regex(_, _, _) => PerlValue::string("Regexp".into()),
2043 HeapObject::Atomic(_) => PerlValue::string("ATOMIC".into()),
2044 HeapObject::Set(_) => PerlValue::string("Set".into()),
2045 HeapObject::ChannelTx(_) => PerlValue::string("PCHANNEL::Tx".into()),
2046 HeapObject::ChannelRx(_) => PerlValue::string("PCHANNEL::Rx".into()),
2047 HeapObject::AsyncTask(_) => PerlValue::string("ASYNCTASK".into()),
2048 HeapObject::Generator(_) => PerlValue::string("Generator".into()),
2049 HeapObject::Deque(_) => PerlValue::string("Deque".into()),
2050 HeapObject::Heap(_) => PerlValue::string("Heap".into()),
2051 HeapObject::Pipeline(_) => PerlValue::string("Pipeline".into()),
2052 HeapObject::DataFrame(_) => PerlValue::string("DataFrame".into()),
2053 HeapObject::Capture(_) => PerlValue::string("Capture".into()),
2054 HeapObject::Ppool(_) => PerlValue::string("Ppool".into()),
2055 HeapObject::RemoteCluster(_) => PerlValue::string("Cluster".into()),
2056 HeapObject::Barrier(_) => PerlValue::string("Barrier".into()),
2057 HeapObject::SqliteConn(_) => PerlValue::string("SqliteConn".into()),
2058 HeapObject::StructInst(s) => PerlValue::string(s.def.name.clone()),
2059 HeapObject::EnumInst(e) => PerlValue::string(e.def.name.clone()),
2060 HeapObject::Bytes(_) => PerlValue::string("BYTES".into()),
2061 HeapObject::Blessed(b) => PerlValue::string(b.class.clone()),
2062 _ => PerlValue::string(String::new()),
2063 }
2064 }
2065
2066 pub fn num_cmp(&self, other: &PerlValue) -> Ordering {
2067 let a = self.to_number();
2068 let b = other.to_number();
2069 a.partial_cmp(&b).unwrap_or(Ordering::Equal)
2070 }
2071
2072 #[inline]
2074 pub fn str_eq(&self, other: &PerlValue) -> bool {
2075 if nanbox::is_heap(self.0) && nanbox::is_heap(other.0) {
2076 if let (HeapObject::String(a), HeapObject::String(b)) =
2077 unsafe { (self.heap_ref(), other.heap_ref()) }
2078 {
2079 return a == b;
2080 }
2081 }
2082 self.to_string() == other.to_string()
2083 }
2084
2085 pub fn str_cmp(&self, other: &PerlValue) -> Ordering {
2086 if nanbox::is_heap(self.0) && nanbox::is_heap(other.0) {
2087 if let (HeapObject::String(a), HeapObject::String(b)) =
2088 unsafe { (self.heap_ref(), other.heap_ref()) }
2089 {
2090 return a.cmp(b);
2091 }
2092 }
2093 self.to_string().cmp(&other.to_string())
2094 }
2095
2096 pub fn struct_field_eq(&self, other: &PerlValue) -> bool {
2098 if nanbox::is_imm_undef(self.0) && nanbox::is_imm_undef(other.0) {
2099 return true;
2100 }
2101 if let (Some(a), Some(b)) = (nanbox::as_imm_int32(self.0), nanbox::as_imm_int32(other.0)) {
2102 return a == b;
2103 }
2104 if nanbox::is_raw_float_bits(self.0) && nanbox::is_raw_float_bits(other.0) {
2105 return f64::from_bits(self.0) == f64::from_bits(other.0);
2106 }
2107 if !nanbox::is_heap(self.0) || !nanbox::is_heap(other.0) {
2108 return self.to_number() == other.to_number();
2109 }
2110 match (unsafe { self.heap_ref() }, unsafe { other.heap_ref() }) {
2111 (HeapObject::String(a), HeapObject::String(b)) => a == b,
2112 (HeapObject::Integer(a), HeapObject::Integer(b)) => a == b,
2113 (HeapObject::Float(a), HeapObject::Float(b)) => a == b,
2114 (HeapObject::Array(a), HeapObject::Array(b)) => {
2115 a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| x.struct_field_eq(y))
2116 }
2117 (HeapObject::ArrayRef(a), HeapObject::ArrayRef(b)) => {
2118 let ag = a.read();
2119 let bg = b.read();
2120 ag.len() == bg.len() && ag.iter().zip(bg.iter()).all(|(x, y)| x.struct_field_eq(y))
2121 }
2122 (HeapObject::Hash(a), HeapObject::Hash(b)) => {
2123 a.len() == b.len()
2124 && a.iter()
2125 .all(|(k, v)| b.get(k).is_some_and(|bv| v.struct_field_eq(bv)))
2126 }
2127 (HeapObject::HashRef(a), HeapObject::HashRef(b)) => {
2128 let ag = a.read();
2129 let bg = b.read();
2130 ag.len() == bg.len()
2131 && ag
2132 .iter()
2133 .all(|(k, v)| bg.get(k).is_some_and(|bv| v.struct_field_eq(bv)))
2134 }
2135 (HeapObject::StructInst(a), HeapObject::StructInst(b)) => {
2136 if a.def.name != b.def.name {
2137 false
2138 } else {
2139 let av = a.get_values();
2140 let bv = b.get_values();
2141 av.len() == bv.len()
2142 && av.iter().zip(bv.iter()).all(|(x, y)| x.struct_field_eq(y))
2143 }
2144 }
2145 _ => self.to_string() == other.to_string(),
2146 }
2147 }
2148
2149 pub fn deep_clone(&self) -> PerlValue {
2151 if !nanbox::is_heap(self.0) {
2152 return self.clone();
2153 }
2154 match unsafe { self.heap_ref() } {
2155 HeapObject::Array(a) => PerlValue::array(a.iter().map(|v| v.deep_clone()).collect()),
2156 HeapObject::ArrayRef(a) => {
2157 let cloned: Vec<PerlValue> = a.read().iter().map(|v| v.deep_clone()).collect();
2158 PerlValue::array_ref(Arc::new(RwLock::new(cloned)))
2159 }
2160 HeapObject::Hash(h) => {
2161 let mut cloned = IndexMap::new();
2162 for (k, v) in h.iter() {
2163 cloned.insert(k.clone(), v.deep_clone());
2164 }
2165 PerlValue::hash(cloned)
2166 }
2167 HeapObject::HashRef(h) => {
2168 let mut cloned = IndexMap::new();
2169 for (k, v) in h.read().iter() {
2170 cloned.insert(k.clone(), v.deep_clone());
2171 }
2172 PerlValue::hash_ref(Arc::new(RwLock::new(cloned)))
2173 }
2174 HeapObject::StructInst(s) => {
2175 let new_values = s.get_values().iter().map(|v| v.deep_clone()).collect();
2176 PerlValue::struct_inst(Arc::new(StructInstance::new(
2177 Arc::clone(&s.def),
2178 new_values,
2179 )))
2180 }
2181 _ => self.clone(),
2182 }
2183 }
2184
2185 pub fn to_list(&self) -> Vec<PerlValue> {
2186 if nanbox::is_imm_undef(self.0) {
2187 return vec![];
2188 }
2189 if !nanbox::is_heap(self.0) {
2190 return vec![self.clone()];
2191 }
2192 match unsafe { self.heap_ref() } {
2193 HeapObject::Array(a) => a.clone(),
2194 HeapObject::Hash(h) => h
2195 .iter()
2196 .flat_map(|(k, v)| vec![PerlValue::string(k.clone()), v.clone()])
2197 .collect(),
2198 HeapObject::Atomic(arc) => arc.lock().to_list(),
2199 HeapObject::Set(s) => s.values().cloned().collect(),
2200 HeapObject::Deque(d) => d.lock().iter().cloned().collect(),
2201 HeapObject::Iterator(it) => {
2202 let mut out = Vec::new();
2203 while let Some(v) = it.next_item() {
2204 out.push(v);
2205 }
2206 out
2207 }
2208 _ => vec![self.clone()],
2209 }
2210 }
2211
2212 pub fn scalar_context(&self) -> PerlValue {
2213 if !nanbox::is_heap(self.0) {
2214 return self.clone();
2215 }
2216 if let Some(arc) = self.as_atomic_arc() {
2217 return arc.lock().scalar_context();
2218 }
2219 match unsafe { self.heap_ref() } {
2220 HeapObject::Array(a) => PerlValue::integer(a.len() as i64),
2221 HeapObject::Hash(h) => {
2222 if h.is_empty() {
2223 PerlValue::integer(0)
2224 } else {
2225 PerlValue::string(format!("{}/{}", h.len(), h.capacity()))
2226 }
2227 }
2228 HeapObject::Set(s) => PerlValue::integer(s.len() as i64),
2229 HeapObject::Deque(d) => PerlValue::integer(d.lock().len() as i64),
2230 HeapObject::Heap(h) => PerlValue::integer(h.lock().items.len() as i64),
2231 HeapObject::Pipeline(p) => PerlValue::integer(p.lock().source.len() as i64),
2232 HeapObject::Capture(_)
2233 | HeapObject::Ppool(_)
2234 | HeapObject::RemoteCluster(_)
2235 | HeapObject::Barrier(_) => PerlValue::integer(1),
2236 HeapObject::Generator(_) => PerlValue::integer(1),
2237 _ => self.clone(),
2238 }
2239 }
2240}
2241
2242impl fmt::Display for PerlValue {
2243 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2244 if nanbox::is_imm_undef(self.0) {
2245 return Ok(());
2246 }
2247 if let Some(n) = nanbox::as_imm_int32(self.0) {
2248 return write!(f, "{n}");
2249 }
2250 if nanbox::is_raw_float_bits(self.0) {
2251 return write!(f, "{}", format_float(f64::from_bits(self.0)));
2252 }
2253 match unsafe { self.heap_ref() } {
2254 HeapObject::Integer(n) => write!(f, "{n}"),
2255 HeapObject::Float(val) => write!(f, "{}", format_float(*val)),
2256 HeapObject::ErrnoDual { msg, .. } => f.write_str(msg),
2257 HeapObject::String(s) => f.write_str(s),
2258 HeapObject::Bytes(b) => f.write_str(&decode_utf8_or_latin1(b)),
2259 HeapObject::Array(a) => {
2260 for v in a {
2261 write!(f, "{v}")?;
2262 }
2263 Ok(())
2264 }
2265 HeapObject::Hash(h) => write!(f, "{}/{}", h.len(), h.capacity()),
2266 HeapObject::ArrayRef(_) | HeapObject::ArrayBindingRef(_) => f.write_str("ARRAY(0x...)"),
2267 HeapObject::HashRef(_) | HeapObject::HashBindingRef(_) => f.write_str("HASH(0x...)"),
2268 HeapObject::ScalarRef(_) | HeapObject::ScalarBindingRef(_) => {
2269 f.write_str("SCALAR(0x...)")
2270 }
2271 HeapObject::CodeRef(sub) => write!(f, "CODE({})", sub.name),
2272 HeapObject::Regex(_, src, _) => write!(f, "(?:{src})"),
2273 HeapObject::Blessed(b) => write!(f, "{}=HASH(0x...)", b.class),
2274 HeapObject::IOHandle(name) => f.write_str(name),
2275 HeapObject::Atomic(arc) => write!(f, "{}", arc.lock()),
2276 HeapObject::Set(s) => {
2277 f.write_str("{")?;
2278 if !s.is_empty() {
2279 let mut iter = s.values();
2280 if let Some(v) = iter.next() {
2281 write!(f, "{v}")?;
2282 }
2283 for v in iter {
2284 write!(f, ",{v}")?;
2285 }
2286 }
2287 f.write_str("}")
2288 }
2289 HeapObject::ChannelTx(_) => f.write_str("PCHANNEL::Tx"),
2290 HeapObject::ChannelRx(_) => f.write_str("PCHANNEL::Rx"),
2291 HeapObject::AsyncTask(_) => f.write_str("AsyncTask"),
2292 HeapObject::Generator(g) => write!(f, "Generator({} stmts)", g.block.len()),
2293 HeapObject::Deque(d) => write!(f, "Deque({})", d.lock().len()),
2294 HeapObject::Heap(h) => write!(f, "Heap({})", h.lock().items.len()),
2295 HeapObject::Pipeline(p) => {
2296 let g = p.lock();
2297 write!(f, "Pipeline({} ops)", g.ops.len())
2298 }
2299 HeapObject::Capture(c) => write!(f, "Capture(exit={})", c.exitcode),
2300 HeapObject::Ppool(_) => f.write_str("Ppool"),
2301 HeapObject::RemoteCluster(c) => write!(f, "Cluster({} slots)", c.slots.len()),
2302 HeapObject::Barrier(_) => f.write_str("Barrier"),
2303 HeapObject::SqliteConn(_) => f.write_str("SqliteConn"),
2304 HeapObject::StructInst(s) => {
2305 write!(f, "{}(", s.def.name)?;
2307 let values = s.values.read();
2308 for (i, field) in s.def.fields.iter().enumerate() {
2309 if i > 0 {
2310 f.write_str(", ")?;
2311 }
2312 write!(
2313 f,
2314 "{} => {}",
2315 field.name,
2316 values.get(i).cloned().unwrap_or(PerlValue::UNDEF)
2317 )?;
2318 }
2319 f.write_str(")")
2320 }
2321 HeapObject::EnumInst(e) => {
2322 write!(f, "{}::{}", e.def.name, e.variant_name())?;
2324 if e.def.variants[e.variant_idx].ty.is_some() {
2325 write!(f, "({})", e.data)?;
2326 }
2327 Ok(())
2328 }
2329 HeapObject::ClassInst(c) => {
2330 write!(f, "{}(", c.def.name)?;
2332 let values = c.values.read();
2333 for (i, field) in c.def.fields.iter().enumerate() {
2334 if i > 0 {
2335 f.write_str(", ")?;
2336 }
2337 write!(
2338 f,
2339 "{} => {}",
2340 field.name,
2341 values.get(i).cloned().unwrap_or(PerlValue::UNDEF)
2342 )?;
2343 }
2344 f.write_str(")")
2345 }
2346 HeapObject::DataFrame(d) => {
2347 let g = d.lock();
2348 write!(f, "DataFrame({} rows)", g.nrows())
2349 }
2350 HeapObject::Iterator(_) => f.write_str("Iterator"),
2351 }
2352 }
2353}
2354
2355pub fn set_member_key(v: &PerlValue) -> String {
2357 if nanbox::is_imm_undef(v.0) {
2358 return "u:".to_string();
2359 }
2360 if let Some(n) = nanbox::as_imm_int32(v.0) {
2361 return format!("i:{n}");
2362 }
2363 if nanbox::is_raw_float_bits(v.0) {
2364 return format!("f:{}", f64::from_bits(v.0).to_bits());
2365 }
2366 match unsafe { v.heap_ref() } {
2367 HeapObject::String(s) => format!("s:{s}"),
2368 HeapObject::Bytes(b) => {
2369 use std::fmt::Write as _;
2370 let mut h = String::with_capacity(b.len() * 2);
2371 for &x in b.iter() {
2372 let _ = write!(&mut h, "{:02x}", x);
2373 }
2374 format!("by:{h}")
2375 }
2376 HeapObject::Array(a) => {
2377 let parts: Vec<_> = a.iter().map(set_member_key).collect();
2378 format!("a:{}", parts.join(","))
2379 }
2380 HeapObject::Hash(h) => {
2381 let mut keys: Vec<_> = h.keys().cloned().collect();
2382 keys.sort();
2383 let parts: Vec<_> = keys
2384 .iter()
2385 .map(|k| format!("{}={}", k, set_member_key(h.get(k).unwrap())))
2386 .collect();
2387 format!("h:{}", parts.join(","))
2388 }
2389 HeapObject::Set(inner) => {
2390 let mut keys: Vec<_> = inner.keys().cloned().collect();
2391 keys.sort();
2392 format!("S:{}", keys.join(","))
2393 }
2394 HeapObject::ArrayRef(a) => {
2395 let g = a.read();
2396 let parts: Vec<_> = g.iter().map(set_member_key).collect();
2397 format!("ar:{}", parts.join(","))
2398 }
2399 HeapObject::HashRef(h) => {
2400 let g = h.read();
2401 let mut keys: Vec<_> = g.keys().cloned().collect();
2402 keys.sort();
2403 let parts: Vec<_> = keys
2404 .iter()
2405 .map(|k| format!("{}={}", k, set_member_key(g.get(k).unwrap())))
2406 .collect();
2407 format!("hr:{}", parts.join(","))
2408 }
2409 HeapObject::Blessed(b) => {
2410 let d = b.data.read();
2411 format!("b:{}:{}", b.class, set_member_key(&d))
2412 }
2413 HeapObject::ScalarRef(_) | HeapObject::ScalarBindingRef(_) => format!("sr:{v}"),
2414 HeapObject::ArrayBindingRef(n) => format!("abind:{n}"),
2415 HeapObject::HashBindingRef(n) => format!("hbind:{n}"),
2416 HeapObject::CodeRef(_) => format!("c:{v}"),
2417 HeapObject::Regex(_, src, _) => format!("r:{src}"),
2418 HeapObject::IOHandle(s) => format!("io:{s}"),
2419 HeapObject::Atomic(arc) => format!("at:{}", set_member_key(&arc.lock())),
2420 HeapObject::ChannelTx(tx) => format!("chtx:{:p}", Arc::as_ptr(tx)),
2421 HeapObject::ChannelRx(rx) => format!("chrx:{:p}", Arc::as_ptr(rx)),
2422 HeapObject::AsyncTask(t) => format!("async:{:p}", Arc::as_ptr(t)),
2423 HeapObject::Generator(g) => format!("gen:{:p}", Arc::as_ptr(g)),
2424 HeapObject::Deque(d) => format!("dq:{:p}", Arc::as_ptr(d)),
2425 HeapObject::Heap(h) => format!("hp:{:p}", Arc::as_ptr(h)),
2426 HeapObject::Pipeline(p) => format!("pl:{:p}", Arc::as_ptr(p)),
2427 HeapObject::Capture(c) => format!("cap:{:p}", Arc::as_ptr(c)),
2428 HeapObject::Ppool(p) => format!("pp:{:p}", Arc::as_ptr(&p.0)),
2429 HeapObject::RemoteCluster(c) => format!("rcl:{:p}", Arc::as_ptr(c)),
2430 HeapObject::Barrier(b) => format!("br:{:p}", Arc::as_ptr(&b.0)),
2431 HeapObject::SqliteConn(c) => format!("sql:{:p}", Arc::as_ptr(c)),
2432 HeapObject::StructInst(s) => format!("st:{}:{:?}", s.def.name, s.values),
2433 HeapObject::EnumInst(e) => {
2434 format!("en:{}::{}:{}", e.def.name, e.variant_name(), e.data)
2435 }
2436 HeapObject::ClassInst(c) => format!("cl:{}:{:?}", c.def.name, c.values),
2437 HeapObject::DataFrame(d) => format!("df:{:p}", Arc::as_ptr(d)),
2438 HeapObject::Iterator(_) => "iter".to_string(),
2439 HeapObject::ErrnoDual { code, msg } => format!("e:{code}:{msg}"),
2440 HeapObject::Integer(n) => format!("i:{n}"),
2441 HeapObject::Float(fl) => format!("f:{}", fl.to_bits()),
2442 }
2443}
2444
2445pub fn set_from_elements<I: IntoIterator<Item = PerlValue>>(items: I) -> PerlValue {
2446 let mut map = PerlSet::new();
2447 for v in items {
2448 let k = set_member_key(&v);
2449 map.insert(k, v);
2450 }
2451 PerlValue::set(Arc::new(map))
2452}
2453
2454#[inline]
2456pub fn set_payload(v: &PerlValue) -> Option<Arc<PerlSet>> {
2457 if !nanbox::is_heap(v.0) {
2458 return None;
2459 }
2460 match unsafe { v.heap_ref() } {
2461 HeapObject::Set(s) => Some(Arc::clone(s)),
2462 HeapObject::Atomic(a) => set_payload(&a.lock()),
2463 _ => None,
2464 }
2465}
2466
2467pub fn set_union(a: &PerlValue, b: &PerlValue) -> Option<PerlValue> {
2468 let ia = set_payload(a)?;
2469 let ib = set_payload(b)?;
2470 let mut m = (*ia).clone();
2471 for (k, v) in ib.iter() {
2472 m.entry(k.clone()).or_insert_with(|| v.clone());
2473 }
2474 Some(PerlValue::set(Arc::new(m)))
2475}
2476
2477pub fn set_intersection(a: &PerlValue, b: &PerlValue) -> Option<PerlValue> {
2478 let ia = set_payload(a)?;
2479 let ib = set_payload(b)?;
2480 let mut m = PerlSet::new();
2481 for (k, v) in ia.iter() {
2482 if ib.contains_key(k) {
2483 m.insert(k.clone(), v.clone());
2484 }
2485 }
2486 Some(PerlValue::set(Arc::new(m)))
2487}
2488fn parse_number(s: &str) -> f64 {
2489 let s = s.trim();
2490 if s.is_empty() {
2491 return 0.0;
2492 }
2493 let mut end = 0;
2495 let bytes = s.as_bytes();
2496 if end < bytes.len() && (bytes[end] == b'+' || bytes[end] == b'-') {
2497 end += 1;
2498 }
2499 while end < bytes.len() && bytes[end].is_ascii_digit() {
2500 end += 1;
2501 }
2502 if end < bytes.len() && bytes[end] == b'.' {
2503 end += 1;
2504 while end < bytes.len() && bytes[end].is_ascii_digit() {
2505 end += 1;
2506 }
2507 }
2508 if end < bytes.len() && (bytes[end] == b'e' || bytes[end] == b'E') {
2509 end += 1;
2510 if end < bytes.len() && (bytes[end] == b'+' || bytes[end] == b'-') {
2511 end += 1;
2512 }
2513 while end < bytes.len() && bytes[end].is_ascii_digit() {
2514 end += 1;
2515 }
2516 }
2517 if end == 0 {
2518 return 0.0;
2519 }
2520 s[..end].parse::<f64>().unwrap_or(0.0)
2521}
2522
2523fn format_float(f: f64) -> String {
2524 if f.fract() == 0.0 && f.abs() < 1e16 {
2525 format!("{}", f as i64)
2526 } else {
2527 let mut buf = [0u8; 64];
2529 unsafe {
2530 libc::snprintf(
2531 buf.as_mut_ptr() as *mut libc::c_char,
2532 buf.len(),
2533 c"%.15g".as_ptr(),
2534 f,
2535 );
2536 std::ffi::CStr::from_ptr(buf.as_ptr() as *const libc::c_char)
2537 .to_string_lossy()
2538 .into_owned()
2539 }
2540 }
2541}
2542
2543#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2545pub(crate) enum PerlListRangeIncOutcome {
2546 Continue,
2547 BecameNumeric,
2549}
2550
2551fn perl_str_looks_like_number_for_range(s: &str) -> bool {
2554 let t = s.trim();
2555 if t.is_empty() {
2556 return s.is_empty();
2557 }
2558 let b = t.as_bytes();
2559 let mut i = 0usize;
2560 if i < b.len() && (b[i] == b'+' || b[i] == b'-') {
2561 i += 1;
2562 }
2563 if i >= b.len() {
2564 return false;
2565 }
2566 let mut saw_digit = false;
2567 while i < b.len() && b[i].is_ascii_digit() {
2568 saw_digit = true;
2569 i += 1;
2570 }
2571 if i < b.len() && b[i] == b'.' {
2572 i += 1;
2573 while i < b.len() && b[i].is_ascii_digit() {
2574 saw_digit = true;
2575 i += 1;
2576 }
2577 }
2578 if !saw_digit {
2579 return false;
2580 }
2581 if i < b.len() && (b[i] == b'e' || b[i] == b'E') {
2582 i += 1;
2583 if i < b.len() && (b[i] == b'+' || b[i] == b'-') {
2584 i += 1;
2585 }
2586 let exp0 = i;
2587 while i < b.len() && b[i].is_ascii_digit() {
2588 i += 1;
2589 }
2590 if i == exp0 {
2591 return false;
2592 }
2593 }
2594 i == b.len()
2595}
2596
2597pub(crate) fn perl_list_range_pair_is_numeric(left: &PerlValue, right: &PerlValue) -> bool {
2599 if left.is_integer_like() || left.is_float_like() {
2600 return true;
2601 }
2602 if !left.is_undef() && !left.is_string_like() {
2603 return true;
2604 }
2605 if right.is_integer_like() || right.is_float_like() {
2606 return true;
2607 }
2608 if !right.is_undef() && !right.is_string_like() {
2609 return true;
2610 }
2611
2612 let left_ok = !left.is_undef();
2613 let right_ok = !right.is_undef();
2614 let left_pok = left.is_string_like();
2615 let left_pv = left.as_str_or_empty();
2616 let right_pv = right.as_str_or_empty();
2617
2618 let left_n = perl_str_looks_like_number_for_range(&left_pv);
2619 let right_n = perl_str_looks_like_number_for_range(&right_pv);
2620
2621 let left_zero_prefix =
2622 left_pok && left_pv.len() > 1 && left_pv.as_bytes().first() == Some(&b'0');
2623
2624 let clause5_left =
2625 (!left_ok && right_ok) || ((!left_ok || left_n) && left_pok && !left_zero_prefix);
2626 clause5_left && (!right_ok || right_n)
2627}
2628
2629pub(crate) fn perl_magic_string_increment_for_range(s: &mut String) -> PerlListRangeIncOutcome {
2631 if s.is_empty() {
2632 return PerlListRangeIncOutcome::BecameNumeric;
2633 }
2634 let b = s.as_bytes();
2635 let mut i = 0usize;
2636 while i < b.len() && b[i].is_ascii_alphabetic() {
2637 i += 1;
2638 }
2639 while i < b.len() && b[i].is_ascii_digit() {
2640 i += 1;
2641 }
2642 if i < b.len() {
2643 let n = parse_number(s) + 1.0;
2644 *s = format_float(n);
2645 return PerlListRangeIncOutcome::BecameNumeric;
2646 }
2647
2648 let bytes = unsafe { s.as_mut_vec() };
2649 let mut idx = bytes.len() - 1;
2650 loop {
2651 if bytes[idx].is_ascii_digit() {
2652 bytes[idx] += 1;
2653 if bytes[idx] <= b'9' {
2654 return PerlListRangeIncOutcome::Continue;
2655 }
2656 bytes[idx] = b'0';
2657 if idx == 0 {
2658 bytes.insert(0, b'1');
2659 return PerlListRangeIncOutcome::Continue;
2660 }
2661 idx -= 1;
2662 } else {
2663 bytes[idx] = bytes[idx].wrapping_add(1);
2664 if bytes[idx].is_ascii_alphabetic() {
2665 return PerlListRangeIncOutcome::Continue;
2666 }
2667 bytes[idx] = bytes[idx].wrapping_sub(b'z' - b'a' + 1);
2668 if idx == 0 {
2669 let c = bytes[0];
2670 bytes.insert(0, if c.is_ascii_digit() { b'1' } else { c });
2671 return PerlListRangeIncOutcome::Continue;
2672 }
2673 idx -= 1;
2674 }
2675 }
2676}
2677
2678fn perl_list_range_max_bound(right: &str) -> usize {
2679 if right.is_ascii() {
2680 right.len()
2681 } else {
2682 right.chars().count()
2683 }
2684}
2685
2686fn perl_list_range_cur_bound(cur: &str, right_is_ascii: bool) -> usize {
2687 if right_is_ascii {
2688 cur.len()
2689 } else {
2690 cur.chars().count()
2691 }
2692}
2693
2694fn perl_list_range_expand_string_magic(from: PerlValue, to: PerlValue) -> Vec<PerlValue> {
2695 let mut cur = from.into_string();
2696 let right = to.into_string();
2697 let right_ascii = right.is_ascii();
2698 let max_bound = perl_list_range_max_bound(&right);
2699 let mut out = Vec::new();
2700 let mut guard = 0usize;
2701 loop {
2702 guard += 1;
2703 if guard > 50_000_000 {
2704 break;
2705 }
2706 let cur_bound = perl_list_range_cur_bound(&cur, right_ascii);
2707 if cur_bound > max_bound {
2708 break;
2709 }
2710 out.push(PerlValue::string(cur.clone()));
2711 if cur == right {
2712 break;
2713 }
2714 match perl_magic_string_increment_for_range(&mut cur) {
2715 PerlListRangeIncOutcome::Continue => {}
2716 PerlListRangeIncOutcome::BecameNumeric => break,
2717 }
2718 }
2719 out
2720}
2721
2722pub(crate) fn perl_list_range_expand(from: PerlValue, to: PerlValue) -> Vec<PerlValue> {
2724 if perl_list_range_pair_is_numeric(&from, &to) {
2725 let i = from.to_int();
2726 let j = to.to_int();
2727 if j >= i {
2728 (i..=j).map(PerlValue::integer).collect()
2729 } else {
2730 Vec::new()
2731 }
2732 } else {
2733 perl_list_range_expand_string_magic(from, to)
2734 }
2735}
2736
2737impl PerlDataFrame {
2738 pub fn row_hashref(&self, row: usize) -> PerlValue {
2740 let mut m = IndexMap::new();
2741 for (i, col) in self.columns.iter().enumerate() {
2742 m.insert(
2743 col.clone(),
2744 self.cols[i].get(row).cloned().unwrap_or(PerlValue::UNDEF),
2745 );
2746 }
2747 PerlValue::hash_ref(Arc::new(RwLock::new(m)))
2748 }
2749}
2750
2751#[cfg(test)]
2752mod tests {
2753 use super::PerlValue;
2754 use crate::perl_regex::PerlCompiledRegex;
2755 use indexmap::IndexMap;
2756 use parking_lot::RwLock;
2757 use std::cmp::Ordering;
2758 use std::sync::Arc;
2759
2760 #[test]
2761 fn undef_is_false() {
2762 assert!(!PerlValue::UNDEF.is_true());
2763 }
2764
2765 #[test]
2766 fn string_zero_is_false() {
2767 assert!(!PerlValue::string("0".into()).is_true());
2768 assert!(PerlValue::string("00".into()).is_true());
2769 }
2770
2771 #[test]
2772 fn empty_string_is_false() {
2773 assert!(!PerlValue::string(String::new()).is_true());
2774 }
2775
2776 #[test]
2777 fn integer_zero_is_false_nonzero_true() {
2778 assert!(!PerlValue::integer(0).is_true());
2779 assert!(PerlValue::integer(-1).is_true());
2780 }
2781
2782 #[test]
2783 fn float_zero_is_false_nonzero_true() {
2784 assert!(!PerlValue::float(0.0).is_true());
2785 assert!(PerlValue::float(0.1).is_true());
2786 }
2787
2788 #[test]
2789 fn num_cmp_orders_float_against_integer() {
2790 assert_eq!(
2791 PerlValue::float(2.5).num_cmp(&PerlValue::integer(3)),
2792 Ordering::Less
2793 );
2794 }
2795
2796 #[test]
2797 fn to_int_parses_leading_number_from_string() {
2798 assert_eq!(PerlValue::string("42xyz".into()).to_int(), 42);
2799 assert_eq!(PerlValue::string(" -3.7foo".into()).to_int(), -3);
2800 }
2801
2802 #[test]
2803 fn num_cmp_orders_as_numeric() {
2804 assert_eq!(
2805 PerlValue::integer(2).num_cmp(&PerlValue::integer(11)),
2806 Ordering::Less
2807 );
2808 assert_eq!(
2809 PerlValue::string("2foo".into()).num_cmp(&PerlValue::string("11".into())),
2810 Ordering::Less
2811 );
2812 }
2813
2814 #[test]
2815 fn str_cmp_orders_as_strings() {
2816 assert_eq!(
2817 PerlValue::string("2".into()).str_cmp(&PerlValue::string("11".into())),
2818 Ordering::Greater
2819 );
2820 }
2821
2822 #[test]
2823 fn str_eq_heap_strings_fast_path() {
2824 let a = PerlValue::string("hello".into());
2825 let b = PerlValue::string("hello".into());
2826 assert!(a.str_eq(&b));
2827 assert!(!a.str_eq(&PerlValue::string("hell".into())));
2828 }
2829
2830 #[test]
2831 fn str_eq_fallback_matches_stringified_equality() {
2832 let n = PerlValue::integer(42);
2833 let s = PerlValue::string("42".into());
2834 assert!(n.str_eq(&s));
2835 assert!(!PerlValue::integer(1).str_eq(&PerlValue::string("2".into())));
2836 }
2837
2838 #[test]
2839 fn str_cmp_heap_strings_fast_path() {
2840 assert_eq!(
2841 PerlValue::string("a".into()).str_cmp(&PerlValue::string("b".into())),
2842 Ordering::Less
2843 );
2844 }
2845
2846 #[test]
2847 fn scalar_context_array_and_hash() {
2848 let a =
2849 PerlValue::array(vec![PerlValue::integer(1), PerlValue::integer(2)]).scalar_context();
2850 assert_eq!(a.to_int(), 2);
2851 let mut h = IndexMap::new();
2852 h.insert("a".into(), PerlValue::integer(1));
2853 let sc = PerlValue::hash(h).scalar_context();
2854 assert!(sc.is_string_like());
2855 }
2856
2857 #[test]
2858 fn to_list_array_hash_and_scalar() {
2859 assert_eq!(
2860 PerlValue::array(vec![PerlValue::integer(7)])
2861 .to_list()
2862 .len(),
2863 1
2864 );
2865 let mut h = IndexMap::new();
2866 h.insert("k".into(), PerlValue::integer(1));
2867 let list = PerlValue::hash(h).to_list();
2868 assert_eq!(list.len(), 2);
2869 let one = PerlValue::integer(99).to_list();
2870 assert_eq!(one.len(), 1);
2871 assert_eq!(one[0].to_int(), 99);
2872 }
2873
2874 #[test]
2875 fn type_name_and_ref_type_for_core_kinds() {
2876 assert_eq!(PerlValue::integer(0).type_name(), "INTEGER");
2877 assert_eq!(PerlValue::UNDEF.ref_type().to_string(), "");
2878 assert_eq!(
2879 PerlValue::array_ref(Arc::new(RwLock::new(vec![])))
2880 .ref_type()
2881 .to_string(),
2882 "ARRAY"
2883 );
2884 }
2885
2886 #[test]
2887 fn display_undef_is_empty_integer_is_decimal() {
2888 assert_eq!(PerlValue::UNDEF.to_string(), "");
2889 assert_eq!(PerlValue::integer(-7).to_string(), "-7");
2890 }
2891
2892 #[test]
2893 fn empty_array_is_false_nonempty_is_true() {
2894 assert!(!PerlValue::array(vec![]).is_true());
2895 assert!(PerlValue::array(vec![PerlValue::integer(0)]).is_true());
2896 }
2897
2898 #[test]
2899 fn to_number_undef_and_non_numeric_refs_are_zero() {
2900 use super::PerlSub;
2901
2902 assert_eq!(PerlValue::UNDEF.to_number(), 0.0);
2903 assert_eq!(
2904 PerlValue::code_ref(Arc::new(PerlSub {
2905 name: "f".into(),
2906 params: vec![],
2907 body: vec![],
2908 closure_env: None,
2909 prototype: None,
2910 fib_like: None,
2911 }))
2912 .to_number(),
2913 0.0
2914 );
2915 }
2916
2917 #[test]
2918 fn append_to_builds_string_without_extra_alloc_for_int_and_string() {
2919 let mut buf = String::new();
2920 PerlValue::integer(-12).append_to(&mut buf);
2921 PerlValue::string("ab".into()).append_to(&mut buf);
2922 assert_eq!(buf, "-12ab");
2923 let mut u = String::new();
2924 PerlValue::UNDEF.append_to(&mut u);
2925 assert!(u.is_empty());
2926 }
2927
2928 #[test]
2929 fn append_to_atomic_delegates_to_inner() {
2930 use parking_lot::Mutex;
2931 let a = PerlValue::atomic(Arc::new(Mutex::new(PerlValue::string("z".into()))));
2932 let mut buf = String::new();
2933 a.append_to(&mut buf);
2934 assert_eq!(buf, "z");
2935 }
2936
2937 #[test]
2938 fn unwrap_atomic_reads_inner_other_variants_clone() {
2939 use parking_lot::Mutex;
2940 let a = PerlValue::atomic(Arc::new(Mutex::new(PerlValue::integer(9))));
2941 assert_eq!(a.unwrap_atomic().to_int(), 9);
2942 assert_eq!(PerlValue::integer(3).unwrap_atomic().to_int(), 3);
2943 }
2944
2945 #[test]
2946 fn is_atomic_only_true_for_atomic_variant() {
2947 use parking_lot::Mutex;
2948 assert!(PerlValue::atomic(Arc::new(Mutex::new(PerlValue::UNDEF))).is_atomic());
2949 assert!(!PerlValue::integer(0).is_atomic());
2950 }
2951
2952 #[test]
2953 fn as_str_only_on_string_variant() {
2954 assert_eq!(
2955 PerlValue::string("x".into()).as_str(),
2956 Some("x".to_string())
2957 );
2958 assert_eq!(PerlValue::integer(1).as_str(), None);
2959 }
2960
2961 #[test]
2962 fn as_str_or_empty_defaults_non_string() {
2963 assert_eq!(PerlValue::string("z".into()).as_str_or_empty(), "z");
2964 assert_eq!(PerlValue::integer(1).as_str_or_empty(), "");
2965 }
2966
2967 #[test]
2968 fn to_int_truncates_float_toward_zero() {
2969 assert_eq!(PerlValue::float(3.9).to_int(), 3);
2970 assert_eq!(PerlValue::float(-2.1).to_int(), -2);
2971 }
2972
2973 #[test]
2974 fn to_number_array_is_length() {
2975 assert_eq!(
2976 PerlValue::array(vec![PerlValue::integer(1), PerlValue::integer(2)]).to_number(),
2977 2.0
2978 );
2979 }
2980
2981 #[test]
2982 fn scalar_context_empty_hash_is_zero() {
2983 let h = IndexMap::new();
2984 assert_eq!(PerlValue::hash(h).scalar_context().to_int(), 0);
2985 }
2986
2987 #[test]
2988 fn scalar_context_nonhash_nonarray_clones() {
2989 let v = PerlValue::integer(8);
2990 assert_eq!(v.scalar_context().to_int(), 8);
2991 }
2992
2993 #[test]
2994 fn display_float_integer_like_omits_decimal() {
2995 assert_eq!(PerlValue::float(4.0).to_string(), "4");
2996 }
2997
2998 #[test]
2999 fn display_array_concatenates_element_displays() {
3000 let a = PerlValue::array(vec![PerlValue::integer(1), PerlValue::string("b".into())]);
3001 assert_eq!(a.to_string(), "1b");
3002 }
3003
3004 #[test]
3005 fn display_code_ref_includes_sub_name() {
3006 use super::PerlSub;
3007 let c = PerlValue::code_ref(Arc::new(PerlSub {
3008 name: "foo".into(),
3009 params: vec![],
3010 body: vec![],
3011 closure_env: None,
3012 prototype: None,
3013 fib_like: None,
3014 }));
3015 assert!(c.to_string().contains("foo"));
3016 }
3017
3018 #[test]
3019 fn display_regex_shows_non_capturing_prefix() {
3020 let r = PerlValue::regex(
3021 PerlCompiledRegex::compile("x+").unwrap(),
3022 "x+".into(),
3023 "".into(),
3024 );
3025 assert_eq!(r.to_string(), "(?:x+)");
3026 }
3027
3028 #[test]
3029 fn display_iohandle_is_name() {
3030 assert_eq!(PerlValue::io_handle("STDOUT".into()).to_string(), "STDOUT");
3031 }
3032
3033 #[test]
3034 fn ref_type_blessed_uses_class_name() {
3035 let b = PerlValue::blessed(Arc::new(super::BlessedRef::new_blessed(
3036 "Pkg".into(),
3037 PerlValue::UNDEF,
3038 )));
3039 assert_eq!(b.ref_type().to_string(), "Pkg");
3040 }
3041
3042 #[test]
3043 fn blessed_drop_enqueues_pending_destroy() {
3044 let v = PerlValue::blessed(Arc::new(super::BlessedRef::new_blessed(
3045 "Z".into(),
3046 PerlValue::integer(7),
3047 )));
3048 drop(v);
3049 let q = crate::pending_destroy::take_queue();
3050 assert_eq!(q.len(), 1);
3051 assert_eq!(q[0].0, "Z");
3052 assert_eq!(q[0].1.to_int(), 7);
3053 }
3054
3055 #[test]
3056 fn type_name_iohandle_is_glob() {
3057 assert_eq!(PerlValue::io_handle("FH".into()).type_name(), "GLOB");
3058 }
3059
3060 #[test]
3061 fn empty_hash_is_false() {
3062 assert!(!PerlValue::hash(IndexMap::new()).is_true());
3063 }
3064
3065 #[test]
3066 fn hash_nonempty_is_true() {
3067 let mut h = IndexMap::new();
3068 h.insert("k".into(), PerlValue::UNDEF);
3069 assert!(PerlValue::hash(h).is_true());
3070 }
3071
3072 #[test]
3073 fn num_cmp_equal_integers() {
3074 assert_eq!(
3075 PerlValue::integer(5).num_cmp(&PerlValue::integer(5)),
3076 Ordering::Equal
3077 );
3078 }
3079
3080 #[test]
3081 fn str_cmp_compares_lexicographic_string_forms() {
3082 assert_eq!(
3084 PerlValue::integer(2).str_cmp(&PerlValue::integer(10)),
3085 Ordering::Greater
3086 );
3087 }
3088
3089 #[test]
3090 fn to_list_undef_empty() {
3091 assert!(PerlValue::UNDEF.to_list().is_empty());
3092 }
3093
3094 #[test]
3095 fn unwrap_atomic_nested_atomic() {
3096 use parking_lot::Mutex;
3097 let inner = PerlValue::atomic(Arc::new(Mutex::new(PerlValue::integer(2))));
3098 let outer = PerlValue::atomic(Arc::new(Mutex::new(inner)));
3099 assert_eq!(outer.unwrap_atomic().to_int(), 2);
3100 }
3101
3102 #[test]
3103 fn errno_dual_parts_extracts_code_and_message() {
3104 let v = PerlValue::errno_dual(-2, "oops".into());
3105 assert_eq!(v.errno_dual_parts(), Some((-2, "oops".into())));
3106 }
3107
3108 #[test]
3109 fn errno_dual_parts_none_for_plain_string() {
3110 assert!(PerlValue::string("hi".into()).errno_dual_parts().is_none());
3111 }
3112
3113 #[test]
3114 fn errno_dual_parts_none_for_integer() {
3115 assert!(PerlValue::integer(1).errno_dual_parts().is_none());
3116 }
3117
3118 #[test]
3119 fn errno_dual_numeric_context_uses_code_string_uses_msg() {
3120 let v = PerlValue::errno_dual(5, "five".into());
3121 assert_eq!(v.to_int(), 5);
3122 assert_eq!(v.to_string(), "five");
3123 }
3124
3125 #[test]
3126 fn list_range_alpha_joins_like_perl() {
3127 use super::perl_list_range_expand;
3128 let v =
3129 perl_list_range_expand(PerlValue::string("a".into()), PerlValue::string("z".into()));
3130 let s: String = v.iter().map(|x| x.to_string()).collect();
3131 assert_eq!(s, "abcdefghijklmnopqrstuvwxyz");
3132 }
3133
3134 #[test]
3135 fn list_range_numeric_string_endpoints() {
3136 use super::perl_list_range_expand;
3137 let v = perl_list_range_expand(
3138 PerlValue::string("9".into()),
3139 PerlValue::string("11".into()),
3140 );
3141 assert_eq!(v.len(), 3);
3142 assert_eq!(
3143 v.iter().map(|x| x.to_int()).collect::<Vec<_>>(),
3144 vec![9, 10, 11]
3145 );
3146 }
3147
3148 #[test]
3149 fn list_range_leading_zero_is_string_mode() {
3150 use super::perl_list_range_expand;
3151 let v = perl_list_range_expand(
3152 PerlValue::string("01".into()),
3153 PerlValue::string("05".into()),
3154 );
3155 assert_eq!(v.len(), 5);
3156 assert_eq!(
3157 v.iter().map(|x| x.to_string()).collect::<Vec<_>>(),
3158 vec!["01", "02", "03", "04", "05"]
3159 );
3160 }
3161
3162 #[test]
3163 fn list_range_empty_to_letter_one_element() {
3164 use super::perl_list_range_expand;
3165 let v = perl_list_range_expand(
3166 PerlValue::string(String::new()),
3167 PerlValue::string("c".into()),
3168 );
3169 assert_eq!(v.len(), 1);
3170 assert_eq!(v[0].to_string(), "");
3171 }
3172
3173 #[test]
3174 fn magic_string_inc_z_wraps_aa() {
3175 use super::{perl_magic_string_increment_for_range, PerlListRangeIncOutcome};
3176 let mut s = "z".to_string();
3177 assert_eq!(
3178 perl_magic_string_increment_for_range(&mut s),
3179 PerlListRangeIncOutcome::Continue
3180 );
3181 assert_eq!(s, "aa");
3182 }
3183
3184 #[test]
3185 fn test_boxed_numeric_stringification() {
3186 let large_int = 10_000_000_000i64;
3188 let v_int = PerlValue::integer(large_int);
3189 assert_eq!(v_int.to_string(), "10000000000");
3190
3191 let v_inf = PerlValue::float(f64::INFINITY);
3193 assert_eq!(v_inf.to_string(), "inf");
3194 }
3195
3196 #[test]
3197 fn magic_string_inc_nine_to_ten() {
3198 use super::{perl_magic_string_increment_for_range, PerlListRangeIncOutcome};
3199 let mut s = "9".to_string();
3200 assert_eq!(
3201 perl_magic_string_increment_for_range(&mut s),
3202 PerlListRangeIncOutcome::Continue
3203 );
3204 assert_eq!(s, "10");
3205 }
3206}