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(_) | HeapObject::ScalarBindingRef(_) | HeapObject::CaptureCell(_) => "SCALAR".to_string(),
2017 HeapObject::CodeRef(_) => "CODE".to_string(),
2018 HeapObject::Regex(_, _, _) => "Regexp".to_string(),
2019 HeapObject::Blessed(b) => b.class.clone(),
2020 HeapObject::IOHandle(_) => "GLOB".to_string(),
2021 HeapObject::Atomic(_) => "ATOMIC".to_string(),
2022 HeapObject::Set(_) => "Set".to_string(),
2023 HeapObject::ChannelTx(_) => "PCHANNEL::Tx".to_string(),
2024 HeapObject::ChannelRx(_) => "PCHANNEL::Rx".to_string(),
2025 HeapObject::AsyncTask(_) => "ASYNCTASK".to_string(),
2026 HeapObject::Generator(_) => "Generator".to_string(),
2027 HeapObject::Deque(_) => "Deque".to_string(),
2028 HeapObject::Heap(_) => "Heap".to_string(),
2029 HeapObject::Pipeline(_) => "Pipeline".to_string(),
2030 HeapObject::DataFrame(_) => "DataFrame".to_string(),
2031 HeapObject::Capture(_) => "Capture".to_string(),
2032 HeapObject::Ppool(_) => "Ppool".to_string(),
2033 HeapObject::RemoteCluster(_) => "Cluster".to_string(),
2034 HeapObject::Barrier(_) => "Barrier".to_string(),
2035 HeapObject::SqliteConn(_) => "SqliteConn".to_string(),
2036 HeapObject::StructInst(s) => s.def.name.to_string(),
2037 HeapObject::EnumInst(e) => e.def.name.to_string(),
2038 HeapObject::ClassInst(c) => c.def.name.to_string(),
2039 HeapObject::Iterator(_) => "Iterator".to_string(),
2040 HeapObject::ErrnoDual { .. } => "Errno".to_string(),
2041 HeapObject::Integer(_) => "INTEGER".to_string(),
2042 HeapObject::Float(_) => "FLOAT".to_string(),
2043 }
2044 }
2045
2046 pub fn ref_type(&self) -> PerlValue {
2047 if !nanbox::is_heap(self.0) {
2048 return PerlValue::string(String::new());
2049 }
2050 match unsafe { self.heap_ref() } {
2051 HeapObject::ArrayRef(_) | HeapObject::ArrayBindingRef(_) => {
2052 PerlValue::string("ARRAY".into())
2053 }
2054 HeapObject::HashRef(_) | HeapObject::HashBindingRef(_) => {
2055 PerlValue::string("HASH".into())
2056 }
2057 HeapObject::ScalarRef(_) | HeapObject::ScalarBindingRef(_) => {
2058 PerlValue::string("SCALAR".into())
2059 }
2060 HeapObject::CodeRef(_) => PerlValue::string("CODE".into()),
2061 HeapObject::Regex(_, _, _) => PerlValue::string("Regexp".into()),
2062 HeapObject::Atomic(_) => PerlValue::string("ATOMIC".into()),
2063 HeapObject::Set(_) => PerlValue::string("Set".into()),
2064 HeapObject::ChannelTx(_) => PerlValue::string("PCHANNEL::Tx".into()),
2065 HeapObject::ChannelRx(_) => PerlValue::string("PCHANNEL::Rx".into()),
2066 HeapObject::AsyncTask(_) => PerlValue::string("ASYNCTASK".into()),
2067 HeapObject::Generator(_) => PerlValue::string("Generator".into()),
2068 HeapObject::Deque(_) => PerlValue::string("Deque".into()),
2069 HeapObject::Heap(_) => PerlValue::string("Heap".into()),
2070 HeapObject::Pipeline(_) => PerlValue::string("Pipeline".into()),
2071 HeapObject::DataFrame(_) => PerlValue::string("DataFrame".into()),
2072 HeapObject::Capture(_) => PerlValue::string("Capture".into()),
2073 HeapObject::Ppool(_) => PerlValue::string("Ppool".into()),
2074 HeapObject::RemoteCluster(_) => PerlValue::string("Cluster".into()),
2075 HeapObject::Barrier(_) => PerlValue::string("Barrier".into()),
2076 HeapObject::SqliteConn(_) => PerlValue::string("SqliteConn".into()),
2077 HeapObject::StructInst(s) => PerlValue::string(s.def.name.clone()),
2078 HeapObject::EnumInst(e) => PerlValue::string(e.def.name.clone()),
2079 HeapObject::Bytes(_) => PerlValue::string("BYTES".into()),
2080 HeapObject::Blessed(b) => PerlValue::string(b.class.clone()),
2081 _ => PerlValue::string(String::new()),
2082 }
2083 }
2084
2085 pub fn num_cmp(&self, other: &PerlValue) -> Ordering {
2086 let a = self.to_number();
2087 let b = other.to_number();
2088 a.partial_cmp(&b).unwrap_or(Ordering::Equal)
2089 }
2090
2091 #[inline]
2093 pub fn str_eq(&self, other: &PerlValue) -> bool {
2094 if nanbox::is_heap(self.0) && nanbox::is_heap(other.0) {
2095 if let (HeapObject::String(a), HeapObject::String(b)) =
2096 unsafe { (self.heap_ref(), other.heap_ref()) }
2097 {
2098 return a == b;
2099 }
2100 }
2101 self.to_string() == other.to_string()
2102 }
2103
2104 pub fn str_cmp(&self, other: &PerlValue) -> Ordering {
2105 if nanbox::is_heap(self.0) && nanbox::is_heap(other.0) {
2106 if let (HeapObject::String(a), HeapObject::String(b)) =
2107 unsafe { (self.heap_ref(), other.heap_ref()) }
2108 {
2109 return a.cmp(b);
2110 }
2111 }
2112 self.to_string().cmp(&other.to_string())
2113 }
2114
2115 pub fn struct_field_eq(&self, other: &PerlValue) -> bool {
2117 if nanbox::is_imm_undef(self.0) && nanbox::is_imm_undef(other.0) {
2118 return true;
2119 }
2120 if let (Some(a), Some(b)) = (nanbox::as_imm_int32(self.0), nanbox::as_imm_int32(other.0)) {
2121 return a == b;
2122 }
2123 if nanbox::is_raw_float_bits(self.0) && nanbox::is_raw_float_bits(other.0) {
2124 return f64::from_bits(self.0) == f64::from_bits(other.0);
2125 }
2126 if !nanbox::is_heap(self.0) || !nanbox::is_heap(other.0) {
2127 return self.to_number() == other.to_number();
2128 }
2129 match (unsafe { self.heap_ref() }, unsafe { other.heap_ref() }) {
2130 (HeapObject::String(a), HeapObject::String(b)) => a == b,
2131 (HeapObject::Integer(a), HeapObject::Integer(b)) => a == b,
2132 (HeapObject::Float(a), HeapObject::Float(b)) => a == b,
2133 (HeapObject::Array(a), HeapObject::Array(b)) => {
2134 a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| x.struct_field_eq(y))
2135 }
2136 (HeapObject::ArrayRef(a), HeapObject::ArrayRef(b)) => {
2137 let ag = a.read();
2138 let bg = b.read();
2139 ag.len() == bg.len() && ag.iter().zip(bg.iter()).all(|(x, y)| x.struct_field_eq(y))
2140 }
2141 (HeapObject::Hash(a), HeapObject::Hash(b)) => {
2142 a.len() == b.len()
2143 && a.iter()
2144 .all(|(k, v)| b.get(k).is_some_and(|bv| v.struct_field_eq(bv)))
2145 }
2146 (HeapObject::HashRef(a), HeapObject::HashRef(b)) => {
2147 let ag = a.read();
2148 let bg = b.read();
2149 ag.len() == bg.len()
2150 && ag
2151 .iter()
2152 .all(|(k, v)| bg.get(k).is_some_and(|bv| v.struct_field_eq(bv)))
2153 }
2154 (HeapObject::StructInst(a), HeapObject::StructInst(b)) => {
2155 if a.def.name != b.def.name {
2156 false
2157 } else {
2158 let av = a.get_values();
2159 let bv = b.get_values();
2160 av.len() == bv.len()
2161 && av.iter().zip(bv.iter()).all(|(x, y)| x.struct_field_eq(y))
2162 }
2163 }
2164 _ => self.to_string() == other.to_string(),
2165 }
2166 }
2167
2168 pub fn deep_clone(&self) -> PerlValue {
2170 if !nanbox::is_heap(self.0) {
2171 return self.clone();
2172 }
2173 match unsafe { self.heap_ref() } {
2174 HeapObject::Array(a) => PerlValue::array(a.iter().map(|v| v.deep_clone()).collect()),
2175 HeapObject::ArrayRef(a) => {
2176 let cloned: Vec<PerlValue> = a.read().iter().map(|v| v.deep_clone()).collect();
2177 PerlValue::array_ref(Arc::new(RwLock::new(cloned)))
2178 }
2179 HeapObject::Hash(h) => {
2180 let mut cloned = IndexMap::new();
2181 for (k, v) in h.iter() {
2182 cloned.insert(k.clone(), v.deep_clone());
2183 }
2184 PerlValue::hash(cloned)
2185 }
2186 HeapObject::HashRef(h) => {
2187 let mut cloned = IndexMap::new();
2188 for (k, v) in h.read().iter() {
2189 cloned.insert(k.clone(), v.deep_clone());
2190 }
2191 PerlValue::hash_ref(Arc::new(RwLock::new(cloned)))
2192 }
2193 HeapObject::StructInst(s) => {
2194 let new_values = s.get_values().iter().map(|v| v.deep_clone()).collect();
2195 PerlValue::struct_inst(Arc::new(StructInstance::new(
2196 Arc::clone(&s.def),
2197 new_values,
2198 )))
2199 }
2200 _ => self.clone(),
2201 }
2202 }
2203
2204 pub fn to_list(&self) -> Vec<PerlValue> {
2205 if nanbox::is_imm_undef(self.0) {
2206 return vec![];
2207 }
2208 if !nanbox::is_heap(self.0) {
2209 return vec![self.clone()];
2210 }
2211 match unsafe { self.heap_ref() } {
2212 HeapObject::Array(a) => a.clone(),
2213 HeapObject::Hash(h) => h
2214 .iter()
2215 .flat_map(|(k, v)| vec![PerlValue::string(k.clone()), v.clone()])
2216 .collect(),
2217 HeapObject::Atomic(arc) => arc.lock().to_list(),
2218 HeapObject::Set(s) => s.values().cloned().collect(),
2219 HeapObject::Deque(d) => d.lock().iter().cloned().collect(),
2220 HeapObject::Iterator(it) => {
2221 let mut out = Vec::new();
2222 while let Some(v) = it.next_item() {
2223 out.push(v);
2224 }
2225 out
2226 }
2227 _ => vec![self.clone()],
2228 }
2229 }
2230
2231 pub fn scalar_context(&self) -> PerlValue {
2232 if !nanbox::is_heap(self.0) {
2233 return self.clone();
2234 }
2235 if let Some(arc) = self.as_atomic_arc() {
2236 return arc.lock().scalar_context();
2237 }
2238 match unsafe { self.heap_ref() } {
2239 HeapObject::Array(a) => PerlValue::integer(a.len() as i64),
2240 HeapObject::Hash(h) => {
2241 if h.is_empty() {
2242 PerlValue::integer(0)
2243 } else {
2244 PerlValue::string(format!("{}/{}", h.len(), h.capacity()))
2245 }
2246 }
2247 HeapObject::Set(s) => PerlValue::integer(s.len() as i64),
2248 HeapObject::Deque(d) => PerlValue::integer(d.lock().len() as i64),
2249 HeapObject::Heap(h) => PerlValue::integer(h.lock().items.len() as i64),
2250 HeapObject::Pipeline(p) => PerlValue::integer(p.lock().source.len() as i64),
2251 HeapObject::Capture(_)
2252 | HeapObject::Ppool(_)
2253 | HeapObject::RemoteCluster(_)
2254 | HeapObject::Barrier(_) => PerlValue::integer(1),
2255 HeapObject::Generator(_) => PerlValue::integer(1),
2256 _ => self.clone(),
2257 }
2258 }
2259}
2260
2261impl fmt::Display for PerlValue {
2262 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2263 if nanbox::is_imm_undef(self.0) {
2264 return Ok(());
2265 }
2266 if let Some(n) = nanbox::as_imm_int32(self.0) {
2267 return write!(f, "{n}");
2268 }
2269 if nanbox::is_raw_float_bits(self.0) {
2270 return write!(f, "{}", format_float(f64::from_bits(self.0)));
2271 }
2272 match unsafe { self.heap_ref() } {
2273 HeapObject::Integer(n) => write!(f, "{n}"),
2274 HeapObject::Float(val) => write!(f, "{}", format_float(*val)),
2275 HeapObject::ErrnoDual { msg, .. } => f.write_str(msg),
2276 HeapObject::String(s) => f.write_str(s),
2277 HeapObject::Bytes(b) => f.write_str(&decode_utf8_or_latin1(b)),
2278 HeapObject::Array(a) => {
2279 for v in a {
2280 write!(f, "{v}")?;
2281 }
2282 Ok(())
2283 }
2284 HeapObject::Hash(h) => write!(f, "{}/{}", h.len(), h.capacity()),
2285 HeapObject::ArrayRef(_) | HeapObject::ArrayBindingRef(_) => f.write_str("ARRAY(0x...)"),
2286 HeapObject::HashRef(_) | HeapObject::HashBindingRef(_) => f.write_str("HASH(0x...)"),
2287 HeapObject::ScalarRef(_) | HeapObject::ScalarBindingRef(_) | HeapObject::CaptureCell(_) => {
2288 f.write_str("SCALAR(0x...)")
2289 }
2290 HeapObject::CodeRef(sub) => write!(f, "CODE({})", sub.name),
2291 HeapObject::Regex(_, src, _) => write!(f, "(?:{src})"),
2292 HeapObject::Blessed(b) => write!(f, "{}=HASH(0x...)", b.class),
2293 HeapObject::IOHandle(name) => f.write_str(name),
2294 HeapObject::Atomic(arc) => write!(f, "{}", arc.lock()),
2295 HeapObject::Set(s) => {
2296 f.write_str("{")?;
2297 if !s.is_empty() {
2298 let mut iter = s.values();
2299 if let Some(v) = iter.next() {
2300 write!(f, "{v}")?;
2301 }
2302 for v in iter {
2303 write!(f, ",{v}")?;
2304 }
2305 }
2306 f.write_str("}")
2307 }
2308 HeapObject::ChannelTx(_) => f.write_str("PCHANNEL::Tx"),
2309 HeapObject::ChannelRx(_) => f.write_str("PCHANNEL::Rx"),
2310 HeapObject::AsyncTask(_) => f.write_str("AsyncTask"),
2311 HeapObject::Generator(g) => write!(f, "Generator({} stmts)", g.block.len()),
2312 HeapObject::Deque(d) => write!(f, "Deque({})", d.lock().len()),
2313 HeapObject::Heap(h) => write!(f, "Heap({})", h.lock().items.len()),
2314 HeapObject::Pipeline(p) => {
2315 let g = p.lock();
2316 write!(f, "Pipeline({} ops)", g.ops.len())
2317 }
2318 HeapObject::Capture(c) => write!(f, "Capture(exit={})", c.exitcode),
2319 HeapObject::Ppool(_) => f.write_str("Ppool"),
2320 HeapObject::RemoteCluster(c) => write!(f, "Cluster({} slots)", c.slots.len()),
2321 HeapObject::Barrier(_) => f.write_str("Barrier"),
2322 HeapObject::SqliteConn(_) => f.write_str("SqliteConn"),
2323 HeapObject::StructInst(s) => {
2324 write!(f, "{}(", s.def.name)?;
2326 let values = s.values.read();
2327 for (i, field) in s.def.fields.iter().enumerate() {
2328 if i > 0 {
2329 f.write_str(", ")?;
2330 }
2331 write!(
2332 f,
2333 "{} => {}",
2334 field.name,
2335 values.get(i).cloned().unwrap_or(PerlValue::UNDEF)
2336 )?;
2337 }
2338 f.write_str(")")
2339 }
2340 HeapObject::EnumInst(e) => {
2341 write!(f, "{}::{}", e.def.name, e.variant_name())?;
2343 if e.def.variants[e.variant_idx].ty.is_some() {
2344 write!(f, "({})", e.data)?;
2345 }
2346 Ok(())
2347 }
2348 HeapObject::ClassInst(c) => {
2349 write!(f, "{}(", c.def.name)?;
2351 let values = c.values.read();
2352 for (i, field) in c.def.fields.iter().enumerate() {
2353 if i > 0 {
2354 f.write_str(", ")?;
2355 }
2356 write!(
2357 f,
2358 "{} => {}",
2359 field.name,
2360 values.get(i).cloned().unwrap_or(PerlValue::UNDEF)
2361 )?;
2362 }
2363 f.write_str(")")
2364 }
2365 HeapObject::DataFrame(d) => {
2366 let g = d.lock();
2367 write!(f, "DataFrame({} rows)", g.nrows())
2368 }
2369 HeapObject::Iterator(_) => f.write_str("Iterator"),
2370 }
2371 }
2372}
2373
2374pub fn set_member_key(v: &PerlValue) -> String {
2376 if nanbox::is_imm_undef(v.0) {
2377 return "u:".to_string();
2378 }
2379 if let Some(n) = nanbox::as_imm_int32(v.0) {
2380 return format!("i:{n}");
2381 }
2382 if nanbox::is_raw_float_bits(v.0) {
2383 return format!("f:{}", f64::from_bits(v.0).to_bits());
2384 }
2385 match unsafe { v.heap_ref() } {
2386 HeapObject::String(s) => format!("s:{s}"),
2387 HeapObject::Bytes(b) => {
2388 use std::fmt::Write as _;
2389 let mut h = String::with_capacity(b.len() * 2);
2390 for &x in b.iter() {
2391 let _ = write!(&mut h, "{:02x}", x);
2392 }
2393 format!("by:{h}")
2394 }
2395 HeapObject::Array(a) => {
2396 let parts: Vec<_> = a.iter().map(set_member_key).collect();
2397 format!("a:{}", parts.join(","))
2398 }
2399 HeapObject::Hash(h) => {
2400 let mut keys: Vec<_> = h.keys().cloned().collect();
2401 keys.sort();
2402 let parts: Vec<_> = keys
2403 .iter()
2404 .map(|k| format!("{}={}", k, set_member_key(h.get(k).unwrap())))
2405 .collect();
2406 format!("h:{}", parts.join(","))
2407 }
2408 HeapObject::Set(inner) => {
2409 let mut keys: Vec<_> = inner.keys().cloned().collect();
2410 keys.sort();
2411 format!("S:{}", keys.join(","))
2412 }
2413 HeapObject::ArrayRef(a) => {
2414 let g = a.read();
2415 let parts: Vec<_> = g.iter().map(set_member_key).collect();
2416 format!("ar:{}", parts.join(","))
2417 }
2418 HeapObject::HashRef(h) => {
2419 let g = h.read();
2420 let mut keys: Vec<_> = g.keys().cloned().collect();
2421 keys.sort();
2422 let parts: Vec<_> = keys
2423 .iter()
2424 .map(|k| format!("{}={}", k, set_member_key(g.get(k).unwrap())))
2425 .collect();
2426 format!("hr:{}", parts.join(","))
2427 }
2428 HeapObject::Blessed(b) => {
2429 let d = b.data.read();
2430 format!("b:{}:{}", b.class, set_member_key(&d))
2431 }
2432 HeapObject::ScalarRef(_) | HeapObject::ScalarBindingRef(_) | HeapObject::CaptureCell(_) => format!("sr:{v}"),
2433 HeapObject::ArrayBindingRef(n) => format!("abind:{n}"),
2434 HeapObject::HashBindingRef(n) => format!("hbind:{n}"),
2435 HeapObject::CodeRef(_) => format!("c:{v}"),
2436 HeapObject::Regex(_, src, _) => format!("r:{src}"),
2437 HeapObject::IOHandle(s) => format!("io:{s}"),
2438 HeapObject::Atomic(arc) => format!("at:{}", set_member_key(&arc.lock())),
2439 HeapObject::ChannelTx(tx) => format!("chtx:{:p}", Arc::as_ptr(tx)),
2440 HeapObject::ChannelRx(rx) => format!("chrx:{:p}", Arc::as_ptr(rx)),
2441 HeapObject::AsyncTask(t) => format!("async:{:p}", Arc::as_ptr(t)),
2442 HeapObject::Generator(g) => format!("gen:{:p}", Arc::as_ptr(g)),
2443 HeapObject::Deque(d) => format!("dq:{:p}", Arc::as_ptr(d)),
2444 HeapObject::Heap(h) => format!("hp:{:p}", Arc::as_ptr(h)),
2445 HeapObject::Pipeline(p) => format!("pl:{:p}", Arc::as_ptr(p)),
2446 HeapObject::Capture(c) => format!("cap:{:p}", Arc::as_ptr(c)),
2447 HeapObject::Ppool(p) => format!("pp:{:p}", Arc::as_ptr(&p.0)),
2448 HeapObject::RemoteCluster(c) => format!("rcl:{:p}", Arc::as_ptr(c)),
2449 HeapObject::Barrier(b) => format!("br:{:p}", Arc::as_ptr(&b.0)),
2450 HeapObject::SqliteConn(c) => format!("sql:{:p}", Arc::as_ptr(c)),
2451 HeapObject::StructInst(s) => format!("st:{}:{:?}", s.def.name, s.values),
2452 HeapObject::EnumInst(e) => {
2453 format!("en:{}::{}:{}", e.def.name, e.variant_name(), e.data)
2454 }
2455 HeapObject::ClassInst(c) => format!("cl:{}:{:?}", c.def.name, c.values),
2456 HeapObject::DataFrame(d) => format!("df:{:p}", Arc::as_ptr(d)),
2457 HeapObject::Iterator(_) => "iter".to_string(),
2458 HeapObject::ErrnoDual { code, msg } => format!("e:{code}:{msg}"),
2459 HeapObject::Integer(n) => format!("i:{n}"),
2460 HeapObject::Float(fl) => format!("f:{}", fl.to_bits()),
2461 }
2462}
2463
2464pub fn set_from_elements<I: IntoIterator<Item = PerlValue>>(items: I) -> PerlValue {
2465 let mut map = PerlSet::new();
2466 for v in items {
2467 let k = set_member_key(&v);
2468 map.insert(k, v);
2469 }
2470 PerlValue::set(Arc::new(map))
2471}
2472
2473#[inline]
2475pub fn set_payload(v: &PerlValue) -> Option<Arc<PerlSet>> {
2476 if !nanbox::is_heap(v.0) {
2477 return None;
2478 }
2479 match unsafe { v.heap_ref() } {
2480 HeapObject::Set(s) => Some(Arc::clone(s)),
2481 HeapObject::Atomic(a) => set_payload(&a.lock()),
2482 _ => None,
2483 }
2484}
2485
2486pub fn set_union(a: &PerlValue, b: &PerlValue) -> Option<PerlValue> {
2487 let ia = set_payload(a)?;
2488 let ib = set_payload(b)?;
2489 let mut m = (*ia).clone();
2490 for (k, v) in ib.iter() {
2491 m.entry(k.clone()).or_insert_with(|| v.clone());
2492 }
2493 Some(PerlValue::set(Arc::new(m)))
2494}
2495
2496pub fn set_intersection(a: &PerlValue, b: &PerlValue) -> Option<PerlValue> {
2497 let ia = set_payload(a)?;
2498 let ib = set_payload(b)?;
2499 let mut m = PerlSet::new();
2500 for (k, v) in ia.iter() {
2501 if ib.contains_key(k) {
2502 m.insert(k.clone(), v.clone());
2503 }
2504 }
2505 Some(PerlValue::set(Arc::new(m)))
2506}
2507fn parse_number(s: &str) -> f64 {
2508 let s = s.trim();
2509 if s.is_empty() {
2510 return 0.0;
2511 }
2512 let mut end = 0;
2514 let bytes = s.as_bytes();
2515 if end < bytes.len() && (bytes[end] == b'+' || bytes[end] == b'-') {
2516 end += 1;
2517 }
2518 while end < bytes.len() && bytes[end].is_ascii_digit() {
2519 end += 1;
2520 }
2521 if end < bytes.len() && bytes[end] == b'.' {
2522 end += 1;
2523 while end < bytes.len() && bytes[end].is_ascii_digit() {
2524 end += 1;
2525 }
2526 }
2527 if end < bytes.len() && (bytes[end] == b'e' || bytes[end] == b'E') {
2528 end += 1;
2529 if end < bytes.len() && (bytes[end] == b'+' || bytes[end] == b'-') {
2530 end += 1;
2531 }
2532 while end < bytes.len() && bytes[end].is_ascii_digit() {
2533 end += 1;
2534 }
2535 }
2536 if end == 0 {
2537 return 0.0;
2538 }
2539 s[..end].parse::<f64>().unwrap_or(0.0)
2540}
2541
2542fn format_float(f: f64) -> String {
2543 if f.fract() == 0.0 && f.abs() < 1e16 {
2544 format!("{}", f as i64)
2545 } else {
2546 let mut buf = [0u8; 64];
2548 unsafe {
2549 libc::snprintf(
2550 buf.as_mut_ptr() as *mut libc::c_char,
2551 buf.len(),
2552 c"%.15g".as_ptr(),
2553 f,
2554 );
2555 std::ffi::CStr::from_ptr(buf.as_ptr() as *const libc::c_char)
2556 .to_string_lossy()
2557 .into_owned()
2558 }
2559 }
2560}
2561
2562#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2564pub(crate) enum PerlListRangeIncOutcome {
2565 Continue,
2566 BecameNumeric,
2568}
2569
2570fn perl_str_looks_like_number_for_range(s: &str) -> bool {
2573 let t = s.trim();
2574 if t.is_empty() {
2575 return s.is_empty();
2576 }
2577 let b = t.as_bytes();
2578 let mut i = 0usize;
2579 if i < b.len() && (b[i] == b'+' || b[i] == b'-') {
2580 i += 1;
2581 }
2582 if i >= b.len() {
2583 return false;
2584 }
2585 let mut saw_digit = false;
2586 while i < b.len() && b[i].is_ascii_digit() {
2587 saw_digit = true;
2588 i += 1;
2589 }
2590 if i < b.len() && b[i] == b'.' {
2591 i += 1;
2592 while i < b.len() && b[i].is_ascii_digit() {
2593 saw_digit = true;
2594 i += 1;
2595 }
2596 }
2597 if !saw_digit {
2598 return false;
2599 }
2600 if i < b.len() && (b[i] == b'e' || b[i] == b'E') {
2601 i += 1;
2602 if i < b.len() && (b[i] == b'+' || b[i] == b'-') {
2603 i += 1;
2604 }
2605 let exp0 = i;
2606 while i < b.len() && b[i].is_ascii_digit() {
2607 i += 1;
2608 }
2609 if i == exp0 {
2610 return false;
2611 }
2612 }
2613 i == b.len()
2614}
2615
2616pub(crate) fn perl_list_range_pair_is_numeric(left: &PerlValue, right: &PerlValue) -> bool {
2618 if left.is_integer_like() || left.is_float_like() {
2619 return true;
2620 }
2621 if !left.is_undef() && !left.is_string_like() {
2622 return true;
2623 }
2624 if right.is_integer_like() || right.is_float_like() {
2625 return true;
2626 }
2627 if !right.is_undef() && !right.is_string_like() {
2628 return true;
2629 }
2630
2631 let left_ok = !left.is_undef();
2632 let right_ok = !right.is_undef();
2633 let left_pok = left.is_string_like();
2634 let left_pv = left.as_str_or_empty();
2635 let right_pv = right.as_str_or_empty();
2636
2637 let left_n = perl_str_looks_like_number_for_range(&left_pv);
2638 let right_n = perl_str_looks_like_number_for_range(&right_pv);
2639
2640 let left_zero_prefix =
2641 left_pok && left_pv.len() > 1 && left_pv.as_bytes().first() == Some(&b'0');
2642
2643 let clause5_left =
2644 (!left_ok && right_ok) || ((!left_ok || left_n) && left_pok && !left_zero_prefix);
2645 clause5_left && (!right_ok || right_n)
2646}
2647
2648pub(crate) fn perl_magic_string_increment_for_range(s: &mut String) -> PerlListRangeIncOutcome {
2650 if s.is_empty() {
2651 return PerlListRangeIncOutcome::BecameNumeric;
2652 }
2653 let b = s.as_bytes();
2654 let mut i = 0usize;
2655 while i < b.len() && b[i].is_ascii_alphabetic() {
2656 i += 1;
2657 }
2658 while i < b.len() && b[i].is_ascii_digit() {
2659 i += 1;
2660 }
2661 if i < b.len() {
2662 let n = parse_number(s) + 1.0;
2663 *s = format_float(n);
2664 return PerlListRangeIncOutcome::BecameNumeric;
2665 }
2666
2667 let bytes = unsafe { s.as_mut_vec() };
2668 let mut idx = bytes.len() - 1;
2669 loop {
2670 if bytes[idx].is_ascii_digit() {
2671 bytes[idx] += 1;
2672 if bytes[idx] <= b'9' {
2673 return PerlListRangeIncOutcome::Continue;
2674 }
2675 bytes[idx] = b'0';
2676 if idx == 0 {
2677 bytes.insert(0, b'1');
2678 return PerlListRangeIncOutcome::Continue;
2679 }
2680 idx -= 1;
2681 } else {
2682 bytes[idx] = bytes[idx].wrapping_add(1);
2683 if bytes[idx].is_ascii_alphabetic() {
2684 return PerlListRangeIncOutcome::Continue;
2685 }
2686 bytes[idx] = bytes[idx].wrapping_sub(b'z' - b'a' + 1);
2687 if idx == 0 {
2688 let c = bytes[0];
2689 bytes.insert(0, if c.is_ascii_digit() { b'1' } else { c });
2690 return PerlListRangeIncOutcome::Continue;
2691 }
2692 idx -= 1;
2693 }
2694 }
2695}
2696
2697fn perl_list_range_max_bound(right: &str) -> usize {
2698 if right.is_ascii() {
2699 right.len()
2700 } else {
2701 right.chars().count()
2702 }
2703}
2704
2705fn perl_list_range_cur_bound(cur: &str, right_is_ascii: bool) -> usize {
2706 if right_is_ascii {
2707 cur.len()
2708 } else {
2709 cur.chars().count()
2710 }
2711}
2712
2713fn perl_list_range_expand_string_magic(from: PerlValue, to: PerlValue) -> Vec<PerlValue> {
2714 let mut cur = from.into_string();
2715 let right = to.into_string();
2716 let right_ascii = right.is_ascii();
2717 let max_bound = perl_list_range_max_bound(&right);
2718 let mut out = Vec::new();
2719 let mut guard = 0usize;
2720 loop {
2721 guard += 1;
2722 if guard > 50_000_000 {
2723 break;
2724 }
2725 let cur_bound = perl_list_range_cur_bound(&cur, right_ascii);
2726 if cur_bound > max_bound {
2727 break;
2728 }
2729 out.push(PerlValue::string(cur.clone()));
2730 if cur == right {
2731 break;
2732 }
2733 match perl_magic_string_increment_for_range(&mut cur) {
2734 PerlListRangeIncOutcome::Continue => {}
2735 PerlListRangeIncOutcome::BecameNumeric => break,
2736 }
2737 }
2738 out
2739}
2740
2741pub(crate) fn perl_list_range_expand(from: PerlValue, to: PerlValue) -> Vec<PerlValue> {
2743 if perl_list_range_pair_is_numeric(&from, &to) {
2744 let i = from.to_int();
2745 let j = to.to_int();
2746 if j >= i {
2747 (i..=j).map(PerlValue::integer).collect()
2748 } else {
2749 Vec::new()
2750 }
2751 } else {
2752 perl_list_range_expand_string_magic(from, to)
2753 }
2754}
2755
2756impl PerlDataFrame {
2757 pub fn row_hashref(&self, row: usize) -> PerlValue {
2759 let mut m = IndexMap::new();
2760 for (i, col) in self.columns.iter().enumerate() {
2761 m.insert(
2762 col.clone(),
2763 self.cols[i].get(row).cloned().unwrap_or(PerlValue::UNDEF),
2764 );
2765 }
2766 PerlValue::hash_ref(Arc::new(RwLock::new(m)))
2767 }
2768}
2769
2770#[cfg(test)]
2771mod tests {
2772 use super::PerlValue;
2773 use crate::perl_regex::PerlCompiledRegex;
2774 use indexmap::IndexMap;
2775 use parking_lot::RwLock;
2776 use std::cmp::Ordering;
2777 use std::sync::Arc;
2778
2779 #[test]
2780 fn undef_is_false() {
2781 assert!(!PerlValue::UNDEF.is_true());
2782 }
2783
2784 #[test]
2785 fn string_zero_is_false() {
2786 assert!(!PerlValue::string("0".into()).is_true());
2787 assert!(PerlValue::string("00".into()).is_true());
2788 }
2789
2790 #[test]
2791 fn empty_string_is_false() {
2792 assert!(!PerlValue::string(String::new()).is_true());
2793 }
2794
2795 #[test]
2796 fn integer_zero_is_false_nonzero_true() {
2797 assert!(!PerlValue::integer(0).is_true());
2798 assert!(PerlValue::integer(-1).is_true());
2799 }
2800
2801 #[test]
2802 fn float_zero_is_false_nonzero_true() {
2803 assert!(!PerlValue::float(0.0).is_true());
2804 assert!(PerlValue::float(0.1).is_true());
2805 }
2806
2807 #[test]
2808 fn num_cmp_orders_float_against_integer() {
2809 assert_eq!(
2810 PerlValue::float(2.5).num_cmp(&PerlValue::integer(3)),
2811 Ordering::Less
2812 );
2813 }
2814
2815 #[test]
2816 fn to_int_parses_leading_number_from_string() {
2817 assert_eq!(PerlValue::string("42xyz".into()).to_int(), 42);
2818 assert_eq!(PerlValue::string(" -3.7foo".into()).to_int(), -3);
2819 }
2820
2821 #[test]
2822 fn num_cmp_orders_as_numeric() {
2823 assert_eq!(
2824 PerlValue::integer(2).num_cmp(&PerlValue::integer(11)),
2825 Ordering::Less
2826 );
2827 assert_eq!(
2828 PerlValue::string("2foo".into()).num_cmp(&PerlValue::string("11".into())),
2829 Ordering::Less
2830 );
2831 }
2832
2833 #[test]
2834 fn str_cmp_orders_as_strings() {
2835 assert_eq!(
2836 PerlValue::string("2".into()).str_cmp(&PerlValue::string("11".into())),
2837 Ordering::Greater
2838 );
2839 }
2840
2841 #[test]
2842 fn str_eq_heap_strings_fast_path() {
2843 let a = PerlValue::string("hello".into());
2844 let b = PerlValue::string("hello".into());
2845 assert!(a.str_eq(&b));
2846 assert!(!a.str_eq(&PerlValue::string("hell".into())));
2847 }
2848
2849 #[test]
2850 fn str_eq_fallback_matches_stringified_equality() {
2851 let n = PerlValue::integer(42);
2852 let s = PerlValue::string("42".into());
2853 assert!(n.str_eq(&s));
2854 assert!(!PerlValue::integer(1).str_eq(&PerlValue::string("2".into())));
2855 }
2856
2857 #[test]
2858 fn str_cmp_heap_strings_fast_path() {
2859 assert_eq!(
2860 PerlValue::string("a".into()).str_cmp(&PerlValue::string("b".into())),
2861 Ordering::Less
2862 );
2863 }
2864
2865 #[test]
2866 fn scalar_context_array_and_hash() {
2867 let a =
2868 PerlValue::array(vec![PerlValue::integer(1), PerlValue::integer(2)]).scalar_context();
2869 assert_eq!(a.to_int(), 2);
2870 let mut h = IndexMap::new();
2871 h.insert("a".into(), PerlValue::integer(1));
2872 let sc = PerlValue::hash(h).scalar_context();
2873 assert!(sc.is_string_like());
2874 }
2875
2876 #[test]
2877 fn to_list_array_hash_and_scalar() {
2878 assert_eq!(
2879 PerlValue::array(vec![PerlValue::integer(7)])
2880 .to_list()
2881 .len(),
2882 1
2883 );
2884 let mut h = IndexMap::new();
2885 h.insert("k".into(), PerlValue::integer(1));
2886 let list = PerlValue::hash(h).to_list();
2887 assert_eq!(list.len(), 2);
2888 let one = PerlValue::integer(99).to_list();
2889 assert_eq!(one.len(), 1);
2890 assert_eq!(one[0].to_int(), 99);
2891 }
2892
2893 #[test]
2894 fn type_name_and_ref_type_for_core_kinds() {
2895 assert_eq!(PerlValue::integer(0).type_name(), "INTEGER");
2896 assert_eq!(PerlValue::UNDEF.ref_type().to_string(), "");
2897 assert_eq!(
2898 PerlValue::array_ref(Arc::new(RwLock::new(vec![])))
2899 .ref_type()
2900 .to_string(),
2901 "ARRAY"
2902 );
2903 }
2904
2905 #[test]
2906 fn display_undef_is_empty_integer_is_decimal() {
2907 assert_eq!(PerlValue::UNDEF.to_string(), "");
2908 assert_eq!(PerlValue::integer(-7).to_string(), "-7");
2909 }
2910
2911 #[test]
2912 fn empty_array_is_false_nonempty_is_true() {
2913 assert!(!PerlValue::array(vec![]).is_true());
2914 assert!(PerlValue::array(vec![PerlValue::integer(0)]).is_true());
2915 }
2916
2917 #[test]
2918 fn to_number_undef_and_non_numeric_refs_are_zero() {
2919 use super::PerlSub;
2920
2921 assert_eq!(PerlValue::UNDEF.to_number(), 0.0);
2922 assert_eq!(
2923 PerlValue::code_ref(Arc::new(PerlSub {
2924 name: "f".into(),
2925 params: vec![],
2926 body: vec![],
2927 closure_env: None,
2928 prototype: None,
2929 fib_like: None,
2930 }))
2931 .to_number(),
2932 0.0
2933 );
2934 }
2935
2936 #[test]
2937 fn append_to_builds_string_without_extra_alloc_for_int_and_string() {
2938 let mut buf = String::new();
2939 PerlValue::integer(-12).append_to(&mut buf);
2940 PerlValue::string("ab".into()).append_to(&mut buf);
2941 assert_eq!(buf, "-12ab");
2942 let mut u = String::new();
2943 PerlValue::UNDEF.append_to(&mut u);
2944 assert!(u.is_empty());
2945 }
2946
2947 #[test]
2948 fn append_to_atomic_delegates_to_inner() {
2949 use parking_lot::Mutex;
2950 let a = PerlValue::atomic(Arc::new(Mutex::new(PerlValue::string("z".into()))));
2951 let mut buf = String::new();
2952 a.append_to(&mut buf);
2953 assert_eq!(buf, "z");
2954 }
2955
2956 #[test]
2957 fn unwrap_atomic_reads_inner_other_variants_clone() {
2958 use parking_lot::Mutex;
2959 let a = PerlValue::atomic(Arc::new(Mutex::new(PerlValue::integer(9))));
2960 assert_eq!(a.unwrap_atomic().to_int(), 9);
2961 assert_eq!(PerlValue::integer(3).unwrap_atomic().to_int(), 3);
2962 }
2963
2964 #[test]
2965 fn is_atomic_only_true_for_atomic_variant() {
2966 use parking_lot::Mutex;
2967 assert!(PerlValue::atomic(Arc::new(Mutex::new(PerlValue::UNDEF))).is_atomic());
2968 assert!(!PerlValue::integer(0).is_atomic());
2969 }
2970
2971 #[test]
2972 fn as_str_only_on_string_variant() {
2973 assert_eq!(
2974 PerlValue::string("x".into()).as_str(),
2975 Some("x".to_string())
2976 );
2977 assert_eq!(PerlValue::integer(1).as_str(), None);
2978 }
2979
2980 #[test]
2981 fn as_str_or_empty_defaults_non_string() {
2982 assert_eq!(PerlValue::string("z".into()).as_str_or_empty(), "z");
2983 assert_eq!(PerlValue::integer(1).as_str_or_empty(), "");
2984 }
2985
2986 #[test]
2987 fn to_int_truncates_float_toward_zero() {
2988 assert_eq!(PerlValue::float(3.9).to_int(), 3);
2989 assert_eq!(PerlValue::float(-2.1).to_int(), -2);
2990 }
2991
2992 #[test]
2993 fn to_number_array_is_length() {
2994 assert_eq!(
2995 PerlValue::array(vec![PerlValue::integer(1), PerlValue::integer(2)]).to_number(),
2996 2.0
2997 );
2998 }
2999
3000 #[test]
3001 fn scalar_context_empty_hash_is_zero() {
3002 let h = IndexMap::new();
3003 assert_eq!(PerlValue::hash(h).scalar_context().to_int(), 0);
3004 }
3005
3006 #[test]
3007 fn scalar_context_nonhash_nonarray_clones() {
3008 let v = PerlValue::integer(8);
3009 assert_eq!(v.scalar_context().to_int(), 8);
3010 }
3011
3012 #[test]
3013 fn display_float_integer_like_omits_decimal() {
3014 assert_eq!(PerlValue::float(4.0).to_string(), "4");
3015 }
3016
3017 #[test]
3018 fn display_array_concatenates_element_displays() {
3019 let a = PerlValue::array(vec![PerlValue::integer(1), PerlValue::string("b".into())]);
3020 assert_eq!(a.to_string(), "1b");
3021 }
3022
3023 #[test]
3024 fn display_code_ref_includes_sub_name() {
3025 use super::PerlSub;
3026 let c = PerlValue::code_ref(Arc::new(PerlSub {
3027 name: "foo".into(),
3028 params: vec![],
3029 body: vec![],
3030 closure_env: None,
3031 prototype: None,
3032 fib_like: None,
3033 }));
3034 assert!(c.to_string().contains("foo"));
3035 }
3036
3037 #[test]
3038 fn display_regex_shows_non_capturing_prefix() {
3039 let r = PerlValue::regex(
3040 PerlCompiledRegex::compile("x+").unwrap(),
3041 "x+".into(),
3042 "".into(),
3043 );
3044 assert_eq!(r.to_string(), "(?:x+)");
3045 }
3046
3047 #[test]
3048 fn display_iohandle_is_name() {
3049 assert_eq!(PerlValue::io_handle("STDOUT".into()).to_string(), "STDOUT");
3050 }
3051
3052 #[test]
3053 fn ref_type_blessed_uses_class_name() {
3054 let b = PerlValue::blessed(Arc::new(super::BlessedRef::new_blessed(
3055 "Pkg".into(),
3056 PerlValue::UNDEF,
3057 )));
3058 assert_eq!(b.ref_type().to_string(), "Pkg");
3059 }
3060
3061 #[test]
3062 fn blessed_drop_enqueues_pending_destroy() {
3063 let v = PerlValue::blessed(Arc::new(super::BlessedRef::new_blessed(
3064 "Z".into(),
3065 PerlValue::integer(7),
3066 )));
3067 drop(v);
3068 let q = crate::pending_destroy::take_queue();
3069 assert_eq!(q.len(), 1);
3070 assert_eq!(q[0].0, "Z");
3071 assert_eq!(q[0].1.to_int(), 7);
3072 }
3073
3074 #[test]
3075 fn type_name_iohandle_is_glob() {
3076 assert_eq!(PerlValue::io_handle("FH".into()).type_name(), "GLOB");
3077 }
3078
3079 #[test]
3080 fn empty_hash_is_false() {
3081 assert!(!PerlValue::hash(IndexMap::new()).is_true());
3082 }
3083
3084 #[test]
3085 fn hash_nonempty_is_true() {
3086 let mut h = IndexMap::new();
3087 h.insert("k".into(), PerlValue::UNDEF);
3088 assert!(PerlValue::hash(h).is_true());
3089 }
3090
3091 #[test]
3092 fn num_cmp_equal_integers() {
3093 assert_eq!(
3094 PerlValue::integer(5).num_cmp(&PerlValue::integer(5)),
3095 Ordering::Equal
3096 );
3097 }
3098
3099 #[test]
3100 fn str_cmp_compares_lexicographic_string_forms() {
3101 assert_eq!(
3103 PerlValue::integer(2).str_cmp(&PerlValue::integer(10)),
3104 Ordering::Greater
3105 );
3106 }
3107
3108 #[test]
3109 fn to_list_undef_empty() {
3110 assert!(PerlValue::UNDEF.to_list().is_empty());
3111 }
3112
3113 #[test]
3114 fn unwrap_atomic_nested_atomic() {
3115 use parking_lot::Mutex;
3116 let inner = PerlValue::atomic(Arc::new(Mutex::new(PerlValue::integer(2))));
3117 let outer = PerlValue::atomic(Arc::new(Mutex::new(inner)));
3118 assert_eq!(outer.unwrap_atomic().to_int(), 2);
3119 }
3120
3121 #[test]
3122 fn errno_dual_parts_extracts_code_and_message() {
3123 let v = PerlValue::errno_dual(-2, "oops".into());
3124 assert_eq!(v.errno_dual_parts(), Some((-2, "oops".into())));
3125 }
3126
3127 #[test]
3128 fn errno_dual_parts_none_for_plain_string() {
3129 assert!(PerlValue::string("hi".into()).errno_dual_parts().is_none());
3130 }
3131
3132 #[test]
3133 fn errno_dual_parts_none_for_integer() {
3134 assert!(PerlValue::integer(1).errno_dual_parts().is_none());
3135 }
3136
3137 #[test]
3138 fn errno_dual_numeric_context_uses_code_string_uses_msg() {
3139 let v = PerlValue::errno_dual(5, "five".into());
3140 assert_eq!(v.to_int(), 5);
3141 assert_eq!(v.to_string(), "five");
3142 }
3143
3144 #[test]
3145 fn list_range_alpha_joins_like_perl() {
3146 use super::perl_list_range_expand;
3147 let v =
3148 perl_list_range_expand(PerlValue::string("a".into()), PerlValue::string("z".into()));
3149 let s: String = v.iter().map(|x| x.to_string()).collect();
3150 assert_eq!(s, "abcdefghijklmnopqrstuvwxyz");
3151 }
3152
3153 #[test]
3154 fn list_range_numeric_string_endpoints() {
3155 use super::perl_list_range_expand;
3156 let v = perl_list_range_expand(
3157 PerlValue::string("9".into()),
3158 PerlValue::string("11".into()),
3159 );
3160 assert_eq!(v.len(), 3);
3161 assert_eq!(
3162 v.iter().map(|x| x.to_int()).collect::<Vec<_>>(),
3163 vec![9, 10, 11]
3164 );
3165 }
3166
3167 #[test]
3168 fn list_range_leading_zero_is_string_mode() {
3169 use super::perl_list_range_expand;
3170 let v = perl_list_range_expand(
3171 PerlValue::string("01".into()),
3172 PerlValue::string("05".into()),
3173 );
3174 assert_eq!(v.len(), 5);
3175 assert_eq!(
3176 v.iter().map(|x| x.to_string()).collect::<Vec<_>>(),
3177 vec!["01", "02", "03", "04", "05"]
3178 );
3179 }
3180
3181 #[test]
3182 fn list_range_empty_to_letter_one_element() {
3183 use super::perl_list_range_expand;
3184 let v = perl_list_range_expand(
3185 PerlValue::string(String::new()),
3186 PerlValue::string("c".into()),
3187 );
3188 assert_eq!(v.len(), 1);
3189 assert_eq!(v[0].to_string(), "");
3190 }
3191
3192 #[test]
3193 fn magic_string_inc_z_wraps_aa() {
3194 use super::{perl_magic_string_increment_for_range, PerlListRangeIncOutcome};
3195 let mut s = "z".to_string();
3196 assert_eq!(
3197 perl_magic_string_increment_for_range(&mut s),
3198 PerlListRangeIncOutcome::Continue
3199 );
3200 assert_eq!(s, "aa");
3201 }
3202
3203 #[test]
3204 fn test_boxed_numeric_stringification() {
3205 let large_int = 10_000_000_000i64;
3207 let v_int = PerlValue::integer(large_int);
3208 assert_eq!(v_int.to_string(), "10000000000");
3209
3210 let v_inf = PerlValue::float(f64::INFINITY);
3212 assert_eq!(v_inf.to_string(), "inf");
3213 }
3214
3215 #[test]
3216 fn magic_string_inc_nine_to_ten() {
3217 use super::{perl_magic_string_increment_for_range, PerlListRangeIncOutcome};
3218 let mut s = "9".to_string();
3219 assert_eq!(
3220 perl_magic_string_increment_for_range(&mut s),
3221 PerlListRangeIncOutcome::Continue
3222 );
3223 assert_eq!(s, "10");
3224 }
3225}