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() {
2753 "-Inf".to_string()
2754 } else {
2755 "Inf".to_string()
2756 };
2757 }
2758 if f.fract() == 0.0 && f.abs() < 1e16 {
2759 format!("{}", f as i64)
2760 } else {
2761 let mut buf = [0u8; 64];
2763 unsafe {
2764 libc::snprintf(
2765 buf.as_mut_ptr() as *mut libc::c_char,
2766 buf.len(),
2767 c"%.15g".as_ptr(),
2768 f,
2769 );
2770 std::ffi::CStr::from_ptr(buf.as_ptr() as *const libc::c_char)
2771 .to_string_lossy()
2772 .into_owned()
2773 }
2774 }
2775}
2776
2777#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2779pub(crate) enum PerlListRangeIncOutcome {
2780 Continue,
2781 BecameNumeric,
2783}
2784
2785fn perl_str_looks_like_number_for_range(s: &str) -> bool {
2788 let t = s.trim();
2789 if t.is_empty() {
2790 return s.is_empty();
2791 }
2792 let b = t.as_bytes();
2793 let mut i = 0usize;
2794 if i < b.len() && (b[i] == b'+' || b[i] == b'-') {
2795 i += 1;
2796 }
2797 if i >= b.len() {
2798 return false;
2799 }
2800 let mut saw_digit = false;
2801 while i < b.len() && b[i].is_ascii_digit() {
2802 saw_digit = true;
2803 i += 1;
2804 }
2805 if i < b.len() && b[i] == b'.' {
2806 i += 1;
2807 while i < b.len() && b[i].is_ascii_digit() {
2808 saw_digit = true;
2809 i += 1;
2810 }
2811 }
2812 if !saw_digit {
2813 return false;
2814 }
2815 if i < b.len() && (b[i] == b'e' || b[i] == b'E') {
2816 i += 1;
2817 if i < b.len() && (b[i] == b'+' || b[i] == b'-') {
2818 i += 1;
2819 }
2820 let exp0 = i;
2821 while i < b.len() && b[i].is_ascii_digit() {
2822 i += 1;
2823 }
2824 if i == exp0 {
2825 return false;
2826 }
2827 }
2828 i == b.len()
2829}
2830
2831pub(crate) fn perl_list_range_pair_is_numeric(left: &PerlValue, right: &PerlValue) -> bool {
2833 if left.is_integer_like() || left.is_float_like() {
2834 return true;
2835 }
2836 if !left.is_undef() && !left.is_string_like() {
2837 return true;
2838 }
2839 if right.is_integer_like() || right.is_float_like() {
2840 return true;
2841 }
2842 if !right.is_undef() && !right.is_string_like() {
2843 return true;
2844 }
2845
2846 let left_ok = !left.is_undef();
2847 let right_ok = !right.is_undef();
2848 let left_pok = left.is_string_like();
2849 let left_pv = left.as_str_or_empty();
2850 let right_pv = right.as_str_or_empty();
2851
2852 let left_n = perl_str_looks_like_number_for_range(&left_pv);
2853 let right_n = perl_str_looks_like_number_for_range(&right_pv);
2854
2855 let left_zero_prefix =
2856 left_pok && left_pv.len() > 1 && left_pv.as_bytes().first() == Some(&b'0');
2857
2858 let clause5_left =
2859 (!left_ok && right_ok) || ((!left_ok || left_n) && left_pok && !left_zero_prefix);
2860 clause5_left && (!right_ok || right_n)
2861}
2862
2863pub(crate) fn perl_magic_string_increment_for_range(s: &mut String) -> PerlListRangeIncOutcome {
2865 if s.is_empty() {
2866 return PerlListRangeIncOutcome::BecameNumeric;
2867 }
2868 let b = s.as_bytes();
2869 let mut i = 0usize;
2870 while i < b.len() && b[i].is_ascii_alphabetic() {
2871 i += 1;
2872 }
2873 while i < b.len() && b[i].is_ascii_digit() {
2874 i += 1;
2875 }
2876 if i < b.len() {
2877 let n = parse_number(s) + 1.0;
2878 *s = format_float(n);
2879 return PerlListRangeIncOutcome::BecameNumeric;
2880 }
2881
2882 let bytes = unsafe { s.as_mut_vec() };
2883 let mut idx = bytes.len() - 1;
2884 loop {
2885 if bytes[idx].is_ascii_digit() {
2886 bytes[idx] += 1;
2887 if bytes[idx] <= b'9' {
2888 return PerlListRangeIncOutcome::Continue;
2889 }
2890 bytes[idx] = b'0';
2891 if idx == 0 {
2892 bytes.insert(0, b'1');
2893 return PerlListRangeIncOutcome::Continue;
2894 }
2895 idx -= 1;
2896 } else {
2897 bytes[idx] = bytes[idx].wrapping_add(1);
2898 if bytes[idx].is_ascii_alphabetic() {
2899 return PerlListRangeIncOutcome::Continue;
2900 }
2901 bytes[idx] = bytes[idx].wrapping_sub(b'z' - b'a' + 1);
2902 if idx == 0 {
2903 let c = bytes[0];
2904 bytes.insert(0, if c.is_ascii_digit() { b'1' } else { c });
2905 return PerlListRangeIncOutcome::Continue;
2906 }
2907 idx -= 1;
2908 }
2909 }
2910}
2911
2912pub(crate) fn perl_magic_string_decrement_for_range(s: &mut String) -> Option<()> {
2915 if s.is_empty() {
2916 return None;
2917 }
2918 let b = s.as_bytes();
2920 let mut i = 0usize;
2921 while i < b.len() && b[i].is_ascii_alphabetic() {
2922 i += 1;
2923 }
2924 while i < b.len() && b[i].is_ascii_digit() {
2925 i += 1;
2926 }
2927 if i < b.len() {
2928 return None; }
2930
2931 let bytes = unsafe { s.as_mut_vec() };
2932 let mut idx = bytes.len() - 1;
2933 loop {
2934 if bytes[idx].is_ascii_digit() {
2935 if bytes[idx] > b'0' {
2936 bytes[idx] -= 1;
2937 return Some(());
2938 }
2939 bytes[idx] = b'9';
2941 if idx == 0 {
2942 if bytes.len() == 1 {
2944 bytes[0] = b'0'; return None;
2946 }
2947 bytes.remove(0);
2948 return Some(());
2949 }
2950 idx -= 1;
2951 } else if bytes[idx].is_ascii_lowercase() {
2952 if bytes[idx] > b'a' {
2953 bytes[idx] -= 1;
2954 return Some(());
2955 }
2956 bytes[idx] = b'z';
2958 if idx == 0 {
2959 if bytes.len() == 1 {
2961 bytes[0] = b'a'; return None;
2963 }
2964 bytes.remove(0);
2965 return Some(());
2966 }
2967 idx -= 1;
2968 } else if bytes[idx].is_ascii_uppercase() {
2969 if bytes[idx] > b'A' {
2970 bytes[idx] -= 1;
2971 return Some(());
2972 }
2973 bytes[idx] = b'Z';
2975 if idx == 0 {
2976 if bytes.len() == 1 {
2977 bytes[0] = b'A'; return None;
2979 }
2980 bytes.remove(0);
2981 return Some(());
2982 }
2983 idx -= 1;
2984 } else {
2985 return None;
2986 }
2987 }
2988}
2989
2990fn perl_list_range_max_bound(right: &str) -> usize {
2991 if right.is_ascii() {
2992 right.len()
2993 } else {
2994 right.chars().count()
2995 }
2996}
2997
2998fn perl_list_range_cur_bound(cur: &str, right_is_ascii: bool) -> usize {
2999 if right_is_ascii {
3000 cur.len()
3001 } else {
3002 cur.chars().count()
3003 }
3004}
3005
3006fn perl_list_range_expand_string_magic(from: PerlValue, to: PerlValue) -> Vec<PerlValue> {
3007 let mut cur = from.into_string();
3008 let right = to.into_string();
3009 let right_ascii = right.is_ascii();
3010 let max_bound = perl_list_range_max_bound(&right);
3011 let mut out = Vec::new();
3012 let mut guard = 0usize;
3013 loop {
3014 guard += 1;
3015 if guard > 50_000_000 {
3016 break;
3017 }
3018 let cur_bound = perl_list_range_cur_bound(&cur, right_ascii);
3019 if cur_bound > max_bound {
3020 break;
3021 }
3022 out.push(PerlValue::string(cur.clone()));
3023 if cur == right {
3024 break;
3025 }
3026 match perl_magic_string_increment_for_range(&mut cur) {
3027 PerlListRangeIncOutcome::Continue => {}
3028 PerlListRangeIncOutcome::BecameNumeric => break,
3029 }
3030 }
3031 out
3032}
3033
3034pub(crate) fn perl_list_range_expand(from: PerlValue, to: PerlValue) -> Vec<PerlValue> {
3036 if perl_list_range_pair_is_numeric(&from, &to) {
3037 let i = from.to_int();
3038 let j = to.to_int();
3039 if j >= i {
3040 (i..=j).map(PerlValue::integer).collect()
3041 } else {
3042 Vec::new()
3043 }
3044 } else {
3045 perl_list_range_expand_string_magic(from, to)
3046 }
3047}
3048
3049fn is_roman_numeral(s: &str) -> bool {
3055 if s.is_empty() {
3056 return false;
3057 }
3058 let upper = s.to_ascii_uppercase();
3059 upper
3060 .chars()
3061 .all(|c| matches!(c, 'I' | 'V' | 'X' | 'L' | 'C' | 'D' | 'M'))
3062}
3063
3064fn is_ipv4(s: &str) -> bool {
3066 let parts: Vec<&str> = s.split('.').collect();
3067 parts.len() == 4 && parts.iter().all(|p| p.parse::<u8>().is_ok())
3068}
3069
3070fn ipv4_to_u32(s: &str) -> Option<u32> {
3072 let parts: Vec<u8> = s.split('.').filter_map(|p| p.parse().ok()).collect();
3073 if parts.len() != 4 {
3074 return None;
3075 }
3076 Some(
3077 ((parts[0] as u32) << 24)
3078 | ((parts[1] as u32) << 16)
3079 | ((parts[2] as u32) << 8)
3080 | (parts[3] as u32),
3081 )
3082}
3083
3084fn u32_to_ipv4(n: u32) -> String {
3086 format!(
3087 "{}.{}.{}.{}",
3088 (n >> 24) & 0xFF,
3089 (n >> 16) & 0xFF,
3090 (n >> 8) & 0xFF,
3091 n & 0xFF
3092 )
3093}
3094
3095fn ipv4_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
3097 let Some(start) = ipv4_to_u32(from) else {
3098 return vec![];
3099 };
3100 let Some(end) = ipv4_to_u32(to) else {
3101 return vec![];
3102 };
3103 let mut out = Vec::new();
3104 if step > 0 {
3105 let mut cur = start as i64;
3106 while cur <= end as i64 {
3107 out.push(PerlValue::string(u32_to_ipv4(cur as u32)));
3108 cur += step;
3109 }
3110 } else {
3111 let mut cur = start as i64;
3112 while cur >= end as i64 {
3113 out.push(PerlValue::string(u32_to_ipv4(cur as u32)));
3114 cur += step;
3115 }
3116 }
3117 out
3118}
3119
3120fn is_ipv6(s: &str) -> bool {
3123 s.parse::<std::net::Ipv6Addr>().is_ok()
3124}
3125
3126fn is_hex_source_literal(s: &str) -> bool {
3131 let bytes = s.as_bytes();
3132 bytes.len() > 2
3133 && bytes[0] == b'0'
3134 && (bytes[1] == b'x' || bytes[1] == b'X')
3135 && bytes[2..].iter().all(|b| b.is_ascii_hexdigit())
3136}
3137
3138fn hex_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
3145 let from_body = &from[2..];
3146 let to_body = &to[2..];
3147 let Ok(start) = i64::from_str_radix(from_body, 16) else {
3148 return vec![];
3149 };
3150 let Ok(end) = i64::from_str_radix(to_body, 16) else {
3151 return vec![];
3152 };
3153 let prefix = &from[..2];
3154 let width = from_body.len().max(to_body.len());
3155 let upper = from_body.bytes().any(|b| b.is_ascii_uppercase())
3156 || to_body.bytes().any(|b| b.is_ascii_uppercase());
3157 let mut out = Vec::new();
3158 let format_one = |n: i64, width: usize, upper: bool, prefix: &str| -> String {
3159 if upper {
3160 format!("{}{:0>w$X}", prefix, n, w = width)
3161 } else {
3162 format!("{}{:0>w$x}", prefix, n, w = width)
3163 }
3164 };
3165 if step > 0 {
3166 if start > end {
3167 return out;
3168 }
3169 let mut cur = start;
3170 while cur <= end {
3171 out.push(PerlValue::string(format_one(cur, width, upper, prefix)));
3172 if (end - cur) < step {
3173 break;
3174 }
3175 cur += step;
3176 }
3177 } else if step < 0 {
3178 if start < end {
3179 return out;
3180 }
3181 let mut cur = start;
3182 while cur >= end {
3183 out.push(PerlValue::string(format_one(cur, width, upper, prefix)));
3184 if (cur - end) < (-step) {
3185 break;
3186 }
3187 cur += step;
3188 }
3189 }
3190 out
3191}
3192
3193fn ipv6_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
3194 let Ok(start) = from.parse::<std::net::Ipv6Addr>() else {
3195 return vec![];
3196 };
3197 let Ok(end) = to.parse::<std::net::Ipv6Addr>() else {
3198 return vec![];
3199 };
3200 let s = u128::from(start);
3201 let e = u128::from(end);
3202 let mut out = Vec::new();
3203 if step > 0 {
3204 if s > e {
3205 return out; }
3207 let step = step as u128;
3208 let mut cur = s;
3209 loop {
3210 out.push(PerlValue::string(std::net::Ipv6Addr::from(cur).to_string()));
3211 if cur == e || e.saturating_sub(cur) < step {
3212 break;
3213 }
3214 cur += step;
3215 }
3216 } else if step < 0 {
3217 if s < e {
3218 return out; }
3220 let step = (-step) as u128;
3221 let mut cur = s;
3222 loop {
3223 out.push(PerlValue::string(std::net::Ipv6Addr::from(cur).to_string()));
3224 if cur == e || cur.saturating_sub(e) < step {
3225 break;
3226 }
3227 cur -= step;
3228 }
3229 }
3230 out
3231}
3232
3233fn is_iso_date(s: &str) -> bool {
3235 if s.len() != 10 {
3236 return false;
3237 }
3238 let parts: Vec<&str> = s.split('-').collect();
3239 parts.len() == 3
3240 && parts[0].len() == 4
3241 && parts[0].parse::<u16>().is_ok()
3242 && parts[1].len() == 2
3243 && parts[1]
3244 .parse::<u8>()
3245 .map(|m| (1..=12).contains(&m))
3246 .unwrap_or(false)
3247 && parts[2].len() == 2
3248 && parts[2]
3249 .parse::<u8>()
3250 .map(|d| (1..=31).contains(&d))
3251 .unwrap_or(false)
3252}
3253
3254fn is_year_month(s: &str) -> bool {
3256 if s.len() != 7 {
3257 return false;
3258 }
3259 let parts: Vec<&str> = s.split('-').collect();
3260 parts.len() == 2
3261 && parts[0].len() == 4
3262 && parts[0].parse::<u16>().is_ok()
3263 && parts[1].len() == 2
3264 && parts[1]
3265 .parse::<u8>()
3266 .map(|m| (1..=12).contains(&m))
3267 .unwrap_or(false)
3268}
3269
3270fn parse_iso_date(s: &str) -> Option<(i32, u32, u32)> {
3272 let parts: Vec<&str> = s.split('-').collect();
3273 if parts.len() != 3 {
3274 return None;
3275 }
3276 Some((
3277 parts[0].parse().ok()?,
3278 parts[1].parse().ok()?,
3279 parts[2].parse().ok()?,
3280 ))
3281}
3282
3283fn parse_year_month(s: &str) -> Option<(i32, u32)> {
3285 let parts: Vec<&str> = s.split('-').collect();
3286 if parts.len() != 2 {
3287 return None;
3288 }
3289 Some((parts[0].parse().ok()?, parts[1].parse().ok()?))
3290}
3291
3292fn days_in_month(year: i32, month: u32) -> u32 {
3294 match month {
3295 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
3296 4 | 6 | 9 | 11 => 30,
3297 2 => {
3298 if (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 {
3299 29
3300 } else {
3301 28
3302 }
3303 }
3304 _ => 30,
3305 }
3306}
3307
3308fn add_days(mut year: i32, mut month: u32, mut day: u32, mut delta: i64) -> (i32, u32, u32) {
3310 if delta > 0 {
3311 while delta > 0 {
3312 let dim = days_in_month(year, month);
3313 let remaining = dim - day;
3314 if delta <= remaining as i64 {
3315 day += delta as u32;
3316 break;
3317 }
3318 delta -= (remaining + 1) as i64;
3319 day = 1;
3320 month += 1;
3321 if month > 12 {
3322 month = 1;
3323 year += 1;
3324 }
3325 }
3326 } else {
3327 while delta < 0 {
3328 if (-delta) < day as i64 {
3329 day = (day as i64 + delta) as u32;
3330 break;
3331 }
3332 delta += day as i64;
3333 month -= 1;
3334 if month == 0 {
3335 month = 12;
3336 year -= 1;
3337 }
3338 day = days_in_month(year, month);
3339 }
3340 }
3341 (year, month, day)
3342}
3343
3344fn iso_date_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
3346 let Some((mut y, mut m, mut d)) = parse_iso_date(from) else {
3347 return vec![];
3348 };
3349 let Some((ey, em, ed)) = parse_iso_date(to) else {
3350 return vec![];
3351 };
3352 let mut out = Vec::new();
3353 let mut guard = 0;
3354 if step > 0 {
3355 while (y, m, d) <= (ey, em, ed) && guard < 50_000 {
3356 out.push(PerlValue::string(format!("{:04}-{:02}-{:02}", y, m, d)));
3357 (y, m, d) = add_days(y, m, d, step);
3358 guard += 1;
3359 }
3360 } else {
3361 while (y, m, d) >= (ey, em, ed) && guard < 50_000 {
3362 out.push(PerlValue::string(format!("{:04}-{:02}-{:02}", y, m, d)));
3363 (y, m, d) = add_days(y, m, d, step);
3364 guard += 1;
3365 }
3366 }
3367 out
3368}
3369
3370fn add_months(mut year: i32, mut month: u32, delta: i64) -> (i32, u32) {
3372 let total = (year as i64 * 12 + month as i64 - 1) + delta;
3373 year = (total / 12) as i32;
3374 month = ((total % 12) + 1) as u32;
3375 if month == 0 {
3376 month = 12;
3377 year -= 1;
3378 }
3379 (year, month)
3380}
3381
3382fn year_month_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
3384 let Some((mut y, mut m)) = parse_year_month(from) else {
3385 return vec![];
3386 };
3387 let Some((ey, em)) = parse_year_month(to) else {
3388 return vec![];
3389 };
3390 let mut out = Vec::new();
3391 let mut guard = 0;
3392 if step > 0 {
3393 while (y, m) <= (ey, em) && guard < 50_000 {
3394 out.push(PerlValue::string(format!("{:04}-{:02}", y, m)));
3395 (y, m) = add_months(y, m, step);
3396 guard += 1;
3397 }
3398 } else {
3399 while (y, m) >= (ey, em) && guard < 50_000 {
3400 out.push(PerlValue::string(format!("{:04}-{:02}", y, m)));
3401 (y, m) = add_months(y, m, step);
3402 guard += 1;
3403 }
3404 }
3405 out
3406}
3407
3408fn is_time_hhmm(s: &str) -> bool {
3410 if s.len() != 5 {
3411 return false;
3412 }
3413 let parts: Vec<&str> = s.split(':').collect();
3414 parts.len() == 2
3415 && parts[0].len() == 2
3416 && parts[0].parse::<u8>().map(|h| h < 24).unwrap_or(false)
3417 && parts[1].len() == 2
3418 && parts[1].parse::<u8>().map(|m| m < 60).unwrap_or(false)
3419}
3420
3421fn parse_time_hhmm(s: &str) -> Option<i32> {
3423 let parts: Vec<&str> = s.split(':').collect();
3424 if parts.len() != 2 {
3425 return None;
3426 }
3427 let h: i32 = parts[0].parse().ok()?;
3428 let m: i32 = parts[1].parse().ok()?;
3429 Some(h * 60 + m)
3430}
3431
3432fn minutes_to_hhmm(mins: i32) -> String {
3434 let h = (mins / 60) % 24;
3435 let m = mins % 60;
3436 format!("{:02}:{:02}", h, m)
3437}
3438
3439fn time_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
3441 let Some(start) = parse_time_hhmm(from) else {
3442 return vec![];
3443 };
3444 let Some(end) = parse_time_hhmm(to) else {
3445 return vec![];
3446 };
3447 let mut out = Vec::new();
3448 let mut guard = 0;
3449 if step > 0 {
3450 let mut cur = start;
3451 while cur <= end && guard < 50_000 {
3452 out.push(PerlValue::string(minutes_to_hhmm(cur)));
3453 cur += step as i32;
3454 guard += 1;
3455 }
3456 } else {
3457 let mut cur = start;
3458 while cur >= end && guard < 50_000 {
3459 out.push(PerlValue::string(minutes_to_hhmm(cur)));
3460 cur += step as i32;
3461 guard += 1;
3462 }
3463 }
3464 out
3465}
3466
3467const WEEKDAYS: [&str; 7] = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
3468const WEEKDAYS_FULL: [&str; 7] = [
3469 "Monday",
3470 "Tuesday",
3471 "Wednesday",
3472 "Thursday",
3473 "Friday",
3474 "Saturday",
3475 "Sunday",
3476];
3477
3478fn weekday_index(s: &str) -> Option<usize> {
3480 let lower = s.to_ascii_lowercase();
3481 for (i, &d) in WEEKDAYS.iter().enumerate() {
3482 if d.to_ascii_lowercase() == lower {
3483 return Some(i);
3484 }
3485 }
3486 for (i, &d) in WEEKDAYS_FULL.iter().enumerate() {
3487 if d.to_ascii_lowercase() == lower {
3488 return Some(i);
3489 }
3490 }
3491 None
3492}
3493
3494fn weekday_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
3496 let Some(start) = weekday_index(from) else {
3497 return vec![];
3498 };
3499 let Some(end) = weekday_index(to) else {
3500 return vec![];
3501 };
3502 let full = from.len() > 3;
3503 let names = if full { &WEEKDAYS_FULL } else { &WEEKDAYS };
3504 let mut out = Vec::new();
3505 if step > 0 {
3506 let mut cur = start as i64;
3507 let target = if end >= start {
3508 end as i64
3509 } else {
3510 end as i64 + 7
3511 };
3512 while cur <= target {
3513 out.push(PerlValue::string(names[(cur % 7) as usize].to_string()));
3514 cur += step;
3515 }
3516 } else {
3517 let mut cur = start as i64;
3518 let target = if end <= start {
3519 end as i64
3520 } else {
3521 end as i64 - 7
3522 };
3523 while cur >= target {
3524 out.push(PerlValue::string(
3525 names[((cur % 7 + 7) % 7) as usize].to_string(),
3526 ));
3527 cur += step;
3528 }
3529 }
3530 out
3531}
3532
3533const MONTHS: [&str; 12] = [
3534 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
3535];
3536const MONTHS_FULL: [&str; 12] = [
3537 "January",
3538 "February",
3539 "March",
3540 "April",
3541 "May",
3542 "June",
3543 "July",
3544 "August",
3545 "September",
3546 "October",
3547 "November",
3548 "December",
3549];
3550
3551fn month_name_index(s: &str) -> Option<usize> {
3553 let lower = s.to_ascii_lowercase();
3554 for (i, &m) in MONTHS.iter().enumerate() {
3555 if m.to_ascii_lowercase() == lower {
3556 return Some(i);
3557 }
3558 }
3559 for (i, &m) in MONTHS_FULL.iter().enumerate() {
3560 if m.to_ascii_lowercase() == lower {
3561 return Some(i);
3562 }
3563 }
3564 None
3565}
3566
3567fn month_name_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
3569 let Some(start) = month_name_index(from) else {
3570 return vec![];
3571 };
3572 let Some(end) = month_name_index(to) else {
3573 return vec![];
3574 };
3575 let full = from.len() > 3;
3576 let names = if full { &MONTHS_FULL } else { &MONTHS };
3577 let mut out = Vec::new();
3578 if step > 0 {
3579 let mut cur = start as i64;
3580 let target = if end >= start {
3581 end as i64
3582 } else {
3583 end as i64 + 12
3584 };
3585 while cur <= target {
3586 out.push(PerlValue::string(names[(cur % 12) as usize].to_string()));
3587 cur += step;
3588 }
3589 } else {
3590 let mut cur = start as i64;
3591 let target = if end <= start {
3592 end as i64
3593 } else {
3594 end as i64 - 12
3595 };
3596 while cur >= target {
3597 out.push(PerlValue::string(
3598 names[((cur % 12 + 12) % 12) as usize].to_string(),
3599 ));
3600 cur += step;
3601 }
3602 }
3603 out
3604}
3605
3606fn is_float_pair(from: &str, to: &str) -> bool {
3608 fn is_float(s: &str) -> bool {
3609 s.contains('.')
3610 && !s.contains(':')
3611 && s.matches('.').count() == 1
3612 && s.parse::<f64>().is_ok()
3613 }
3614 is_float(from) && is_float(to)
3615}
3616
3617fn float_range_stepped(from: &str, to: &str, step: f64) -> Vec<PerlValue> {
3619 let Ok(start) = from.parse::<f64>() else {
3620 return vec![];
3621 };
3622 let Ok(end) = to.parse::<f64>() else {
3623 return vec![];
3624 };
3625 let mut out = Vec::new();
3626 let mut guard = 0;
3627 if step > 0.0 {
3629 let mut i = 0i64;
3630 loop {
3631 let cur = start + (i as f64) * step;
3632 if cur > end + step.abs() * f64::EPSILON * 10.0 || guard >= 50_000 {
3633 break;
3634 }
3635 let rounded = (cur * 1e12).round() / 1e12;
3637 out.push(PerlValue::float(rounded));
3638 i += 1;
3639 guard += 1;
3640 }
3641 } else if step < 0.0 {
3642 let mut i = 0i64;
3643 loop {
3644 let cur = start + (i as f64) * step;
3645 if cur < end - step.abs() * f64::EPSILON * 10.0 || guard >= 50_000 {
3646 break;
3647 }
3648 let rounded = (cur * 1e12).round() / 1e12;
3649 out.push(PerlValue::float(rounded));
3650 i += 1;
3651 guard += 1;
3652 }
3653 }
3654 out
3655}
3656
3657fn roman_to_int(s: &str) -> Option<i64> {
3659 let upper = s.to_ascii_uppercase();
3660 let mut result = 0i64;
3661 let mut prev = 0i64;
3662 for c in upper.chars().rev() {
3663 let val = match c {
3664 'I' => 1,
3665 'V' => 5,
3666 'X' => 10,
3667 'L' => 50,
3668 'C' => 100,
3669 'D' => 500,
3670 'M' => 1000,
3671 _ => return None,
3672 };
3673 if val < prev {
3674 result -= val;
3675 } else {
3676 result += val;
3677 }
3678 prev = val;
3679 }
3680 if result > 0 {
3681 Some(result)
3682 } else {
3683 None
3684 }
3685}
3686
3687fn int_to_roman(mut n: i64, lowercase: bool) -> Option<String> {
3689 if n <= 0 || n > 3999 {
3690 return None;
3691 }
3692 let numerals = [
3693 (1000, "M"),
3694 (900, "CM"),
3695 (500, "D"),
3696 (400, "CD"),
3697 (100, "C"),
3698 (90, "XC"),
3699 (50, "L"),
3700 (40, "XL"),
3701 (10, "X"),
3702 (9, "IX"),
3703 (5, "V"),
3704 (4, "IV"),
3705 (1, "I"),
3706 ];
3707 let mut result = String::new();
3708 for (val, sym) in numerals {
3709 while n >= val {
3710 result.push_str(sym);
3711 n -= val;
3712 }
3713 }
3714 if lowercase {
3715 Some(result.to_ascii_lowercase())
3716 } else {
3717 Some(result)
3718 }
3719}
3720
3721fn roman_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
3723 let Some(start) = roman_to_int(from) else {
3724 return vec![];
3725 };
3726 let Some(end) = roman_to_int(to) else {
3727 return vec![];
3728 };
3729 let lowercase = from
3730 .chars()
3731 .next()
3732 .map(|c| c.is_ascii_lowercase())
3733 .unwrap_or(false);
3734
3735 let mut out = Vec::new();
3736 if step > 0 {
3737 let mut cur = start;
3738 while cur <= end {
3739 if let Some(r) = int_to_roman(cur, lowercase) {
3740 out.push(PerlValue::string(r));
3741 }
3742 cur += step;
3743 }
3744 } else {
3745 let mut cur = start;
3746 while cur >= end {
3747 if let Some(r) = int_to_roman(cur, lowercase) {
3748 out.push(PerlValue::string(r));
3749 }
3750 cur += step; }
3752 }
3753 out
3754}
3755
3756pub(crate) fn perl_list_range_expand_stepped(
3759 from: PerlValue,
3760 to: PerlValue,
3761 step_val: PerlValue,
3762) -> Vec<PerlValue> {
3763 let from_str = from.to_string();
3764 let to_str = to.to_string();
3765
3766 let is_float_range = is_float_pair(&from_str, &to_str);
3768
3769 let step_float = step_val.as_float().unwrap_or(step_val.to_int() as f64);
3771 let step_int = step_val.to_int();
3772
3773 if step_int == 0 && step_float == 0.0 {
3774 return vec![];
3775 }
3776
3777 if is_float_range {
3779 return float_range_stepped(&from_str, &to_str, step_float);
3780 }
3781
3782 if perl_list_range_pair_is_numeric(&from, &to) {
3784 let i = from.to_int();
3785 let j = to.to_int();
3786 if step_int > 0 {
3787 (i..=j)
3788 .step_by(step_int as usize)
3789 .map(PerlValue::integer)
3790 .collect()
3791 } else {
3792 std::iter::successors(Some(i), |&x| {
3793 let next = x + step_int;
3794 if next >= j {
3795 Some(next)
3796 } else {
3797 None
3798 }
3799 })
3800 .map(PerlValue::integer)
3801 .collect()
3802 }
3803 } else {
3804 if is_hex_source_literal(&from_str) && is_hex_source_literal(&to_str) {
3811 return hex_range_stepped(&from_str, &to_str, step_int);
3812 }
3813
3814 if is_ipv4(&from_str) && is_ipv4(&to_str) {
3816 return ipv4_range_stepped(&from_str, &to_str, step_int);
3817 }
3818
3819 if is_ipv6(&from_str) && is_ipv6(&to_str) {
3823 return ipv6_range_stepped(&from_str, &to_str, step_int);
3824 }
3825
3826 if is_iso_date(&from_str) && is_iso_date(&to_str) {
3828 return iso_date_range_stepped(&from_str, &to_str, step_int);
3829 }
3830
3831 if is_year_month(&from_str) && is_year_month(&to_str) {
3833 return year_month_range_stepped(&from_str, &to_str, step_int);
3834 }
3835
3836 if is_time_hhmm(&from_str) && is_time_hhmm(&to_str) {
3838 return time_range_stepped(&from_str, &to_str, step_int);
3839 }
3840
3841 if weekday_index(&from_str).is_some() && weekday_index(&to_str).is_some() {
3843 return weekday_range_stepped(&from_str, &to_str, step_int);
3844 }
3845
3846 if month_name_index(&from_str).is_some() && month_name_index(&to_str).is_some() {
3848 return month_name_range_stepped(&from_str, &to_str, step_int);
3849 }
3850
3851 if is_roman_numeral(&from_str) && is_roman_numeral(&to_str) {
3853 return roman_range_stepped(&from_str, &to_str, step_int);
3854 }
3855
3856 perl_list_range_expand_string_magic_stepped(from, to, step_int)
3858 }
3859}
3860
3861pub(crate) fn perl_slice_endpoint_to_strict_int(
3865 v: &PerlValue,
3866 where_: &str,
3867) -> Result<i64, String> {
3868 if let Some(n) = v.as_integer() {
3869 return Ok(n);
3870 }
3871 if let Some(f) = v.as_float() {
3872 if f.is_finite() && f.fract() == 0.0 && f >= i64::MIN as f64 && f <= i64::MAX as f64 {
3873 return Ok(f as i64);
3874 }
3875 return Err(format!(
3876 "array slice {}: non-integer float endpoint {}",
3877 where_, f
3878 ));
3879 }
3880 let s = v.as_str_or_empty();
3881 if !s.is_empty() {
3882 if let Ok(n) = s.trim().parse::<i64>() {
3883 return Ok(n);
3884 }
3885 return Err(format!(
3886 "array slice {}: non-integer string endpoint {:?}",
3887 where_, s
3888 ));
3889 }
3890 Err(format!(
3891 "array slice {}: endpoint must be an integer (got non-numeric value)",
3892 where_
3893 ))
3894}
3895
3896pub(crate) fn compute_array_slice_indices(
3906 arr_len: i64,
3907 from: &PerlValue,
3908 to: &PerlValue,
3909 step: &PerlValue,
3910) -> Result<Vec<i64>, String> {
3911 let step_i = if step.is_undef() {
3912 1i64
3913 } else {
3914 perl_slice_endpoint_to_strict_int(step, "step")?
3915 };
3916 if step_i == 0 {
3917 return Err("array slice step cannot be 0".into());
3918 }
3919
3920 let normalize = |i: i64| -> i64 {
3921 if i < 0 {
3922 i + arr_len
3923 } else {
3924 i
3925 }
3926 };
3927
3928 let any_undef = from.is_undef() || to.is_undef();
3934
3935 let from_raw = if from.is_undef() {
3936 if step_i > 0 {
3937 0
3938 } else {
3939 arr_len - 1
3940 }
3941 } else {
3942 perl_slice_endpoint_to_strict_int(from, "start")?
3943 };
3944
3945 let to_raw = if to.is_undef() {
3946 if step_i > 0 {
3947 arr_len - 1
3948 } else {
3949 0
3950 }
3951 } else {
3952 perl_slice_endpoint_to_strict_int(to, "stop")?
3953 };
3954
3955 let mut out = Vec::new();
3956 if arr_len == 0 {
3957 return Ok(out);
3958 }
3959
3960 let (from_i, to_i) = if any_undef {
3961 (normalize(from_raw), normalize(to_raw))
3962 } else {
3963 (from_raw, to_raw)
3964 };
3965
3966 if step_i > 0 {
3967 let mut i = from_i;
3968 while i <= to_i {
3969 out.push(if any_undef { i } else { normalize(i) });
3970 i += step_i;
3971 }
3972 } else {
3973 let mut i = from_i;
3974 while i >= to_i {
3975 out.push(if any_undef { i } else { normalize(i) });
3976 i += step_i; }
3978 }
3979 Ok(out)
3980}
3981
3982pub(crate) fn compute_hash_slice_keys(
3987 from: &PerlValue,
3988 to: &PerlValue,
3989 step: &PerlValue,
3990) -> Result<Vec<String>, String> {
3991 if from.is_undef() || to.is_undef() {
3992 return Err(
3993 "hash slice range requires both endpoints (open-ended forms not allowed)".into(),
3994 );
3995 }
3996 let step_val = if step.is_undef() {
3997 PerlValue::integer(1)
3998 } else {
3999 step.clone()
4000 };
4001 let expanded = perl_list_range_expand_stepped(from.clone(), to.clone(), step_val);
4002 Ok(expanded.into_iter().map(|v| v.to_string()).collect())
4003}
4004
4005fn perl_list_range_expand_string_magic_stepped(
4006 from: PerlValue,
4007 to: PerlValue,
4008 step: i64,
4009) -> Vec<PerlValue> {
4010 if step == 0 {
4011 return vec![];
4012 }
4013 let mut cur = from.into_string();
4014 let right = to.into_string();
4015
4016 if step > 0 {
4017 let step = step as usize;
4019 let right_ascii = right.is_ascii();
4020 let max_bound = perl_list_range_max_bound(&right);
4021 let mut out = Vec::new();
4022 let mut guard = 0usize;
4023 let mut idx = 0usize;
4024 loop {
4025 guard += 1;
4026 if guard > 50_000_000 {
4027 break;
4028 }
4029 let cur_bound = perl_list_range_cur_bound(&cur, right_ascii);
4030 if cur_bound > max_bound {
4031 break;
4032 }
4033 if idx.is_multiple_of(step) {
4034 out.push(PerlValue::string(cur.clone()));
4035 }
4036 if cur == right {
4037 break;
4038 }
4039 match perl_magic_string_increment_for_range(&mut cur) {
4040 PerlListRangeIncOutcome::Continue => {}
4041 PerlListRangeIncOutcome::BecameNumeric => break,
4042 }
4043 idx += 1;
4044 }
4045 out
4046 } else {
4047 let step = (-step) as usize;
4049 let mut out = Vec::new();
4050 let mut guard = 0usize;
4051 let mut idx = 0usize;
4052 loop {
4053 guard += 1;
4054 if guard > 50_000_000 {
4055 break;
4056 }
4057 if idx.is_multiple_of(step) {
4058 out.push(PerlValue::string(cur.clone()));
4059 }
4060 if cur == right {
4061 break;
4062 }
4063 if cur < right {
4065 break;
4066 }
4067 match perl_magic_string_decrement_for_range(&mut cur) {
4068 Some(()) => {}
4069 None => break, }
4071 idx += 1;
4072 }
4073 out
4074 }
4075}
4076
4077impl PerlDataFrame {
4078 pub fn row_hashref(&self, row: usize) -> PerlValue {
4080 let mut m = IndexMap::new();
4081 for (i, col) in self.columns.iter().enumerate() {
4082 m.insert(
4083 col.clone(),
4084 self.cols[i].get(row).cloned().unwrap_or(PerlValue::UNDEF),
4085 );
4086 }
4087 PerlValue::hash_ref(Arc::new(RwLock::new(m)))
4088 }
4089}
4090
4091#[cfg(test)]
4092mod tests {
4093 use super::PerlValue;
4094 use crate::perl_regex::PerlCompiledRegex;
4095 use indexmap::IndexMap;
4096 use parking_lot::RwLock;
4097 use std::cmp::Ordering;
4098 use std::sync::Arc;
4099
4100 #[test]
4101 fn undef_is_false() {
4102 assert!(!PerlValue::UNDEF.is_true());
4103 }
4104
4105 #[test]
4106 fn string_zero_is_false() {
4107 assert!(!PerlValue::string("0".into()).is_true());
4108 assert!(PerlValue::string("00".into()).is_true());
4109 }
4110
4111 #[test]
4112 fn empty_string_is_false() {
4113 assert!(!PerlValue::string(String::new()).is_true());
4114 }
4115
4116 #[test]
4117 fn integer_zero_is_false_nonzero_true() {
4118 assert!(!PerlValue::integer(0).is_true());
4119 assert!(PerlValue::integer(-1).is_true());
4120 }
4121
4122 #[test]
4123 fn float_zero_is_false_nonzero_true() {
4124 assert!(!PerlValue::float(0.0).is_true());
4125 assert!(PerlValue::float(0.1).is_true());
4126 }
4127
4128 #[test]
4129 fn num_cmp_orders_float_against_integer() {
4130 assert_eq!(
4131 PerlValue::float(2.5).num_cmp(&PerlValue::integer(3)),
4132 Ordering::Less
4133 );
4134 }
4135
4136 #[test]
4137 fn to_int_parses_leading_number_from_string() {
4138 assert_eq!(PerlValue::string("42xyz".into()).to_int(), 42);
4139 assert_eq!(PerlValue::string(" -3.7foo".into()).to_int(), -3);
4140 }
4141
4142 #[test]
4143 fn num_cmp_orders_as_numeric() {
4144 assert_eq!(
4145 PerlValue::integer(2).num_cmp(&PerlValue::integer(11)),
4146 Ordering::Less
4147 );
4148 assert_eq!(
4149 PerlValue::string("2foo".into()).num_cmp(&PerlValue::string("11".into())),
4150 Ordering::Less
4151 );
4152 }
4153
4154 #[test]
4155 fn str_cmp_orders_as_strings() {
4156 assert_eq!(
4157 PerlValue::string("2".into()).str_cmp(&PerlValue::string("11".into())),
4158 Ordering::Greater
4159 );
4160 }
4161
4162 #[test]
4163 fn str_eq_heap_strings_fast_path() {
4164 let a = PerlValue::string("hello".into());
4165 let b = PerlValue::string("hello".into());
4166 assert!(a.str_eq(&b));
4167 assert!(!a.str_eq(&PerlValue::string("hell".into())));
4168 }
4169
4170 #[test]
4171 fn str_eq_fallback_matches_stringified_equality() {
4172 let n = PerlValue::integer(42);
4173 let s = PerlValue::string("42".into());
4174 assert!(n.str_eq(&s));
4175 assert!(!PerlValue::integer(1).str_eq(&PerlValue::string("2".into())));
4176 }
4177
4178 #[test]
4179 fn str_cmp_heap_strings_fast_path() {
4180 assert_eq!(
4181 PerlValue::string("a".into()).str_cmp(&PerlValue::string("b".into())),
4182 Ordering::Less
4183 );
4184 }
4185
4186 #[test]
4187 fn scalar_context_array_and_hash() {
4188 let a =
4189 PerlValue::array(vec![PerlValue::integer(1), PerlValue::integer(2)]).scalar_context();
4190 assert_eq!(a.to_int(), 2);
4191 let mut h = IndexMap::new();
4192 h.insert("a".into(), PerlValue::integer(1));
4193 let sc = PerlValue::hash(h).scalar_context();
4194 assert!(sc.is_string_like());
4195 }
4196
4197 #[test]
4198 fn to_list_array_hash_and_scalar() {
4199 assert_eq!(
4200 PerlValue::array(vec![PerlValue::integer(7)])
4201 .to_list()
4202 .len(),
4203 1
4204 );
4205 let mut h = IndexMap::new();
4206 h.insert("k".into(), PerlValue::integer(1));
4207 let list = PerlValue::hash(h).to_list();
4208 assert_eq!(list.len(), 2);
4209 let one = PerlValue::integer(99).to_list();
4210 assert_eq!(one.len(), 1);
4211 assert_eq!(one[0].to_int(), 99);
4212 }
4213
4214 #[test]
4215 fn type_name_and_ref_type_for_core_kinds() {
4216 assert_eq!(PerlValue::integer(0).type_name(), "INTEGER");
4217 assert_eq!(PerlValue::UNDEF.ref_type().to_string(), "");
4218 assert_eq!(
4219 PerlValue::array_ref(Arc::new(RwLock::new(vec![])))
4220 .ref_type()
4221 .to_string(),
4222 "ARRAY"
4223 );
4224 }
4225
4226 #[test]
4227 fn display_undef_is_empty_integer_is_decimal() {
4228 assert_eq!(PerlValue::UNDEF.to_string(), "");
4229 assert_eq!(PerlValue::integer(-7).to_string(), "-7");
4230 }
4231
4232 #[test]
4233 fn empty_array_is_false_nonempty_is_true() {
4234 assert!(!PerlValue::array(vec![]).is_true());
4235 assert!(PerlValue::array(vec![PerlValue::integer(0)]).is_true());
4236 }
4237
4238 #[test]
4239 fn to_number_undef_and_non_numeric_refs_are_zero() {
4240 use super::PerlSub;
4241
4242 assert_eq!(PerlValue::UNDEF.to_number(), 0.0);
4243 assert_eq!(
4244 PerlValue::code_ref(Arc::new(PerlSub {
4245 name: "f".into(),
4246 params: vec![],
4247 body: vec![],
4248 closure_env: None,
4249 prototype: None,
4250 fib_like: None,
4251 }))
4252 .to_number(),
4253 0.0
4254 );
4255 }
4256
4257 #[test]
4258 fn append_to_builds_string_without_extra_alloc_for_int_and_string() {
4259 let mut buf = String::new();
4260 PerlValue::integer(-12).append_to(&mut buf);
4261 PerlValue::string("ab".into()).append_to(&mut buf);
4262 assert_eq!(buf, "-12ab");
4263 let mut u = String::new();
4264 PerlValue::UNDEF.append_to(&mut u);
4265 assert!(u.is_empty());
4266 }
4267
4268 #[test]
4269 fn append_to_atomic_delegates_to_inner() {
4270 use parking_lot::Mutex;
4271 let a = PerlValue::atomic(Arc::new(Mutex::new(PerlValue::string("z".into()))));
4272 let mut buf = String::new();
4273 a.append_to(&mut buf);
4274 assert_eq!(buf, "z");
4275 }
4276
4277 #[test]
4278 fn unwrap_atomic_reads_inner_other_variants_clone() {
4279 use parking_lot::Mutex;
4280 let a = PerlValue::atomic(Arc::new(Mutex::new(PerlValue::integer(9))));
4281 assert_eq!(a.unwrap_atomic().to_int(), 9);
4282 assert_eq!(PerlValue::integer(3).unwrap_atomic().to_int(), 3);
4283 }
4284
4285 #[test]
4286 fn is_atomic_only_true_for_atomic_variant() {
4287 use parking_lot::Mutex;
4288 assert!(PerlValue::atomic(Arc::new(Mutex::new(PerlValue::UNDEF))).is_atomic());
4289 assert!(!PerlValue::integer(0).is_atomic());
4290 }
4291
4292 #[test]
4293 fn as_str_only_on_string_variant() {
4294 assert_eq!(
4295 PerlValue::string("x".into()).as_str(),
4296 Some("x".to_string())
4297 );
4298 assert_eq!(PerlValue::integer(1).as_str(), None);
4299 }
4300
4301 #[test]
4302 fn as_str_or_empty_defaults_non_string() {
4303 assert_eq!(PerlValue::string("z".into()).as_str_or_empty(), "z");
4304 assert_eq!(PerlValue::integer(1).as_str_or_empty(), "");
4305 }
4306
4307 #[test]
4308 fn to_int_truncates_float_toward_zero() {
4309 assert_eq!(PerlValue::float(3.9).to_int(), 3);
4310 assert_eq!(PerlValue::float(-2.1).to_int(), -2);
4311 }
4312
4313 #[test]
4314 fn to_number_array_is_length() {
4315 assert_eq!(
4316 PerlValue::array(vec![PerlValue::integer(1), PerlValue::integer(2)]).to_number(),
4317 2.0
4318 );
4319 }
4320
4321 #[test]
4322 fn scalar_context_empty_hash_is_zero() {
4323 let h = IndexMap::new();
4324 assert_eq!(PerlValue::hash(h).scalar_context().to_int(), 0);
4325 }
4326
4327 #[test]
4328 fn scalar_context_nonhash_nonarray_clones() {
4329 let v = PerlValue::integer(8);
4330 assert_eq!(v.scalar_context().to_int(), 8);
4331 }
4332
4333 #[test]
4334 fn display_float_integer_like_omits_decimal() {
4335 assert_eq!(PerlValue::float(4.0).to_string(), "4");
4336 }
4337
4338 #[test]
4339 fn display_array_concatenates_element_displays() {
4340 let a = PerlValue::array(vec![PerlValue::integer(1), PerlValue::string("b".into())]);
4341 assert_eq!(a.to_string(), "1b");
4342 }
4343
4344 #[test]
4345 fn display_code_ref_includes_sub_name() {
4346 use super::PerlSub;
4347 let c = PerlValue::code_ref(Arc::new(PerlSub {
4348 name: "foo".into(),
4349 params: vec![],
4350 body: vec![],
4351 closure_env: None,
4352 prototype: None,
4353 fib_like: None,
4354 }));
4355 assert!(c.to_string().contains("foo"));
4356 }
4357
4358 #[test]
4359 fn display_regex_shows_non_capturing_prefix() {
4360 let r = PerlValue::regex(
4361 PerlCompiledRegex::compile("x+").unwrap(),
4362 "x+".into(),
4363 "".into(),
4364 );
4365 assert_eq!(r.to_string(), "(?:x+)");
4366 }
4367
4368 #[test]
4369 fn display_iohandle_is_name() {
4370 assert_eq!(PerlValue::io_handle("STDOUT".into()).to_string(), "STDOUT");
4371 }
4372
4373 #[test]
4374 fn ref_type_blessed_uses_class_name() {
4375 let b = PerlValue::blessed(Arc::new(super::BlessedRef::new_blessed(
4376 "Pkg".into(),
4377 PerlValue::UNDEF,
4378 )));
4379 assert_eq!(b.ref_type().to_string(), "Pkg");
4380 }
4381
4382 #[test]
4383 fn blessed_drop_enqueues_pending_destroy() {
4384 let v = PerlValue::blessed(Arc::new(super::BlessedRef::new_blessed(
4385 "Z".into(),
4386 PerlValue::integer(7),
4387 )));
4388 drop(v);
4389 let q = crate::pending_destroy::take_queue();
4390 assert_eq!(q.len(), 1);
4391 assert_eq!(q[0].0, "Z");
4392 assert_eq!(q[0].1.to_int(), 7);
4393 }
4394
4395 #[test]
4396 fn type_name_iohandle_is_glob() {
4397 assert_eq!(PerlValue::io_handle("FH".into()).type_name(), "GLOB");
4398 }
4399
4400 #[test]
4401 fn empty_hash_is_false() {
4402 assert!(!PerlValue::hash(IndexMap::new()).is_true());
4403 }
4404
4405 #[test]
4406 fn hash_nonempty_is_true() {
4407 let mut h = IndexMap::new();
4408 h.insert("k".into(), PerlValue::UNDEF);
4409 assert!(PerlValue::hash(h).is_true());
4410 }
4411
4412 #[test]
4413 fn num_cmp_equal_integers() {
4414 assert_eq!(
4415 PerlValue::integer(5).num_cmp(&PerlValue::integer(5)),
4416 Ordering::Equal
4417 );
4418 }
4419
4420 #[test]
4421 fn str_cmp_compares_lexicographic_string_forms() {
4422 assert_eq!(
4424 PerlValue::integer(2).str_cmp(&PerlValue::integer(10)),
4425 Ordering::Greater
4426 );
4427 }
4428
4429 #[test]
4430 fn to_list_undef_empty() {
4431 assert!(PerlValue::UNDEF.to_list().is_empty());
4432 }
4433
4434 #[test]
4435 fn unwrap_atomic_nested_atomic() {
4436 use parking_lot::Mutex;
4437 let inner = PerlValue::atomic(Arc::new(Mutex::new(PerlValue::integer(2))));
4438 let outer = PerlValue::atomic(Arc::new(Mutex::new(inner)));
4439 assert_eq!(outer.unwrap_atomic().to_int(), 2);
4440 }
4441
4442 #[test]
4443 fn errno_dual_parts_extracts_code_and_message() {
4444 let v = PerlValue::errno_dual(-2, "oops".into());
4445 assert_eq!(v.errno_dual_parts(), Some((-2, "oops".into())));
4446 }
4447
4448 #[test]
4449 fn errno_dual_parts_none_for_plain_string() {
4450 assert!(PerlValue::string("hi".into()).errno_dual_parts().is_none());
4451 }
4452
4453 #[test]
4454 fn errno_dual_parts_none_for_integer() {
4455 assert!(PerlValue::integer(1).errno_dual_parts().is_none());
4456 }
4457
4458 #[test]
4459 fn errno_dual_numeric_context_uses_code_string_uses_msg() {
4460 let v = PerlValue::errno_dual(5, "five".into());
4461 assert_eq!(v.to_int(), 5);
4462 assert_eq!(v.to_string(), "five");
4463 }
4464
4465 #[test]
4466 fn list_range_alpha_joins_like_perl() {
4467 use super::perl_list_range_expand;
4468 let v =
4469 perl_list_range_expand(PerlValue::string("a".into()), PerlValue::string("z".into()));
4470 let s: String = v.iter().map(|x| x.to_string()).collect();
4471 assert_eq!(s, "abcdefghijklmnopqrstuvwxyz");
4472 }
4473
4474 #[test]
4475 fn list_range_numeric_string_endpoints() {
4476 use super::perl_list_range_expand;
4477 let v = perl_list_range_expand(
4478 PerlValue::string("9".into()),
4479 PerlValue::string("11".into()),
4480 );
4481 assert_eq!(v.len(), 3);
4482 assert_eq!(
4483 v.iter().map(|x| x.to_int()).collect::<Vec<_>>(),
4484 vec![9, 10, 11]
4485 );
4486 }
4487
4488 #[test]
4489 fn list_range_leading_zero_is_string_mode() {
4490 use super::perl_list_range_expand;
4491 let v = perl_list_range_expand(
4492 PerlValue::string("01".into()),
4493 PerlValue::string("05".into()),
4494 );
4495 assert_eq!(v.len(), 5);
4496 assert_eq!(
4497 v.iter().map(|x| x.to_string()).collect::<Vec<_>>(),
4498 vec!["01", "02", "03", "04", "05"]
4499 );
4500 }
4501
4502 #[test]
4503 fn list_range_empty_to_letter_one_element() {
4504 use super::perl_list_range_expand;
4505 let v = perl_list_range_expand(
4506 PerlValue::string(String::new()),
4507 PerlValue::string("c".into()),
4508 );
4509 assert_eq!(v.len(), 1);
4510 assert_eq!(v[0].to_string(), "");
4511 }
4512
4513 #[test]
4514 fn magic_string_inc_z_wraps_aa() {
4515 use super::{perl_magic_string_increment_for_range, PerlListRangeIncOutcome};
4516 let mut s = "z".to_string();
4517 assert_eq!(
4518 perl_magic_string_increment_for_range(&mut s),
4519 PerlListRangeIncOutcome::Continue
4520 );
4521 assert_eq!(s, "aa");
4522 }
4523
4524 #[test]
4525 fn test_boxed_numeric_stringification() {
4526 let large_int = 10_000_000_000i64;
4528 let v_int = PerlValue::integer(large_int);
4529 assert_eq!(v_int.to_string(), "10000000000");
4530
4531 let v_inf = PerlValue::float(f64::INFINITY);
4533 assert_eq!(v_inf.to_string(), "Inf");
4534 }
4535
4536 #[test]
4537 fn magic_string_inc_nine_to_ten() {
4538 use super::{perl_magic_string_increment_for_range, PerlListRangeIncOutcome};
4539 let mut s = "9".to_string();
4540 assert_eq!(
4541 perl_magic_string_increment_for_range(&mut s),
4542 PerlListRangeIncOutcome::Continue
4543 );
4544 assert_eq!(s, "10");
4545 }
4546}