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