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::ClassInst(c) => PerlValue::string(c.def.name.clone()),
2155 HeapObject::Bytes(_) => PerlValue::string("BYTES".into()),
2156 HeapObject::Blessed(b) => PerlValue::string(b.class.clone()),
2157 _ => PerlValue::string(String::new()),
2158 }
2159 }
2160
2161 pub fn num_cmp(&self, other: &PerlValue) -> Ordering {
2162 let a = self.to_number();
2163 let b = other.to_number();
2164 a.partial_cmp(&b).unwrap_or(Ordering::Equal)
2165 }
2166
2167 #[inline]
2169 pub fn str_eq(&self, other: &PerlValue) -> bool {
2170 if nanbox::is_heap(self.0) && nanbox::is_heap(other.0) {
2171 if let (HeapObject::String(a), HeapObject::String(b)) =
2172 unsafe { (self.heap_ref(), other.heap_ref()) }
2173 {
2174 return a == b;
2175 }
2176 }
2177 self.to_string() == other.to_string()
2178 }
2179
2180 pub fn str_cmp(&self, other: &PerlValue) -> Ordering {
2181 if nanbox::is_heap(self.0) && nanbox::is_heap(other.0) {
2182 if let (HeapObject::String(a), HeapObject::String(b)) =
2183 unsafe { (self.heap_ref(), other.heap_ref()) }
2184 {
2185 return a.cmp(b);
2186 }
2187 }
2188 self.to_string().cmp(&other.to_string())
2189 }
2190
2191 pub fn struct_field_eq(&self, other: &PerlValue) -> bool {
2193 if nanbox::is_imm_undef(self.0) && nanbox::is_imm_undef(other.0) {
2194 return true;
2195 }
2196 if let (Some(a), Some(b)) = (nanbox::as_imm_int32(self.0), nanbox::as_imm_int32(other.0)) {
2197 return a == b;
2198 }
2199 if nanbox::is_raw_float_bits(self.0) && nanbox::is_raw_float_bits(other.0) {
2200 return f64::from_bits(self.0) == f64::from_bits(other.0);
2201 }
2202 if !nanbox::is_heap(self.0) || !nanbox::is_heap(other.0) {
2203 return self.to_number() == other.to_number();
2204 }
2205 match (unsafe { self.heap_ref() }, unsafe { other.heap_ref() }) {
2206 (HeapObject::String(a), HeapObject::String(b)) => a == b,
2207 (HeapObject::Integer(a), HeapObject::Integer(b)) => a == b,
2208 (HeapObject::BigInt(a), HeapObject::BigInt(b)) => a == b,
2209 (HeapObject::BigInt(a), HeapObject::Integer(b))
2210 | (HeapObject::Integer(b), HeapObject::BigInt(a)) => a.as_ref() == &BigInt::from(*b),
2211 (HeapObject::Float(a), HeapObject::Float(b)) => a == b,
2212 (HeapObject::Array(a), HeapObject::Array(b)) => {
2213 a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| x.struct_field_eq(y))
2214 }
2215 (HeapObject::ArrayRef(a), HeapObject::ArrayRef(b)) => {
2216 let ag = a.read();
2217 let bg = b.read();
2218 ag.len() == bg.len() && ag.iter().zip(bg.iter()).all(|(x, y)| x.struct_field_eq(y))
2219 }
2220 (HeapObject::Hash(a), HeapObject::Hash(b)) => {
2221 a.len() == b.len()
2222 && a.iter()
2223 .all(|(k, v)| b.get(k).is_some_and(|bv| v.struct_field_eq(bv)))
2224 }
2225 (HeapObject::HashRef(a), HeapObject::HashRef(b)) => {
2226 let ag = a.read();
2227 let bg = b.read();
2228 ag.len() == bg.len()
2229 && ag
2230 .iter()
2231 .all(|(k, v)| bg.get(k).is_some_and(|bv| v.struct_field_eq(bv)))
2232 }
2233 (HeapObject::StructInst(a), HeapObject::StructInst(b)) => {
2234 if a.def.name != b.def.name {
2235 false
2236 } else {
2237 let av = a.get_values();
2238 let bv = b.get_values();
2239 av.len() == bv.len()
2240 && av.iter().zip(bv.iter()).all(|(x, y)| x.struct_field_eq(y))
2241 }
2242 }
2243 _ => self.to_string() == other.to_string(),
2244 }
2245 }
2246
2247 pub fn deep_clone(&self) -> PerlValue {
2249 if !nanbox::is_heap(self.0) {
2250 return self.clone();
2251 }
2252 match unsafe { self.heap_ref() } {
2253 HeapObject::Array(a) => PerlValue::array(a.iter().map(|v| v.deep_clone()).collect()),
2254 HeapObject::ArrayRef(a) => {
2255 let cloned: Vec<PerlValue> = a.read().iter().map(|v| v.deep_clone()).collect();
2256 PerlValue::array_ref(Arc::new(RwLock::new(cloned)))
2257 }
2258 HeapObject::Hash(h) => {
2259 let mut cloned = IndexMap::new();
2260 for (k, v) in h.iter() {
2261 cloned.insert(k.clone(), v.deep_clone());
2262 }
2263 PerlValue::hash(cloned)
2264 }
2265 HeapObject::HashRef(h) => {
2266 let mut cloned = IndexMap::new();
2267 for (k, v) in h.read().iter() {
2268 cloned.insert(k.clone(), v.deep_clone());
2269 }
2270 PerlValue::hash_ref(Arc::new(RwLock::new(cloned)))
2271 }
2272 HeapObject::StructInst(s) => {
2273 let new_values = s.get_values().iter().map(|v| v.deep_clone()).collect();
2274 PerlValue::struct_inst(Arc::new(StructInstance::new(
2275 Arc::clone(&s.def),
2276 new_values,
2277 )))
2278 }
2279 _ => self.clone(),
2280 }
2281 }
2282
2283 pub fn to_list(&self) -> Vec<PerlValue> {
2284 if nanbox::is_imm_undef(self.0) {
2285 return vec![];
2286 }
2287 if !nanbox::is_heap(self.0) {
2288 return vec![self.clone()];
2289 }
2290 match unsafe { self.heap_ref() } {
2291 HeapObject::Array(a) => a.clone(),
2292 HeapObject::Hash(h) => h
2293 .iter()
2294 .flat_map(|(k, v)| vec![PerlValue::string(k.clone()), v.clone()])
2295 .collect(),
2296 HeapObject::Atomic(arc) => arc.lock().to_list(),
2297 HeapObject::Set(s) => s.values().cloned().collect(),
2298 HeapObject::Deque(d) => d.lock().iter().cloned().collect(),
2299 HeapObject::Iterator(it) => {
2300 let mut out = Vec::new();
2301 while let Some(v) = it.next_item() {
2302 out.push(v);
2303 }
2304 out
2305 }
2306 _ => vec![self.clone()],
2307 }
2308 }
2309
2310 pub fn scalar_context(&self) -> PerlValue {
2311 if !nanbox::is_heap(self.0) {
2312 return self.clone();
2313 }
2314 if let Some(arc) = self.as_atomic_arc() {
2315 return arc.lock().scalar_context();
2316 }
2317 match unsafe { self.heap_ref() } {
2318 HeapObject::Array(a) => PerlValue::integer(a.len() as i64),
2319 HeapObject::Hash(h) => {
2320 if h.is_empty() {
2321 PerlValue::integer(0)
2322 } else {
2323 PerlValue::string(format!("{}/{}", h.len(), h.capacity()))
2324 }
2325 }
2326 HeapObject::Set(s) => PerlValue::integer(s.len() as i64),
2327 HeapObject::Deque(d) => PerlValue::integer(d.lock().len() as i64),
2328 HeapObject::Heap(h) => PerlValue::integer(h.lock().items.len() as i64),
2329 HeapObject::Pipeline(p) => PerlValue::integer(p.lock().source.len() as i64),
2330 HeapObject::Capture(_)
2331 | HeapObject::Ppool(_)
2332 | HeapObject::RemoteCluster(_)
2333 | HeapObject::Barrier(_) => PerlValue::integer(1),
2334 HeapObject::Generator(_) => PerlValue::integer(1),
2335 _ => self.clone(),
2336 }
2337 }
2338}
2339
2340impl fmt::Display for PerlValue {
2341 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2342 if nanbox::is_imm_undef(self.0) {
2343 return Ok(());
2344 }
2345 if let Some(n) = nanbox::as_imm_int32(self.0) {
2346 return write!(f, "{n}");
2347 }
2348 if nanbox::is_raw_float_bits(self.0) {
2349 return write!(f, "{}", format_float(f64::from_bits(self.0)));
2350 }
2351 match unsafe { self.heap_ref() } {
2352 HeapObject::Integer(n) => write!(f, "{n}"),
2353 HeapObject::BigInt(b) => write!(f, "{b}"),
2354 HeapObject::Float(val) => write!(f, "{}", format_float(*val)),
2355 HeapObject::ErrnoDual { msg, .. } => f.write_str(msg),
2356 HeapObject::String(s) => f.write_str(s),
2357 HeapObject::Bytes(b) => f.write_str(&decode_utf8_or_latin1(b)),
2358 HeapObject::Array(a) => {
2359 for v in a {
2360 write!(f, "{v}")?;
2361 }
2362 Ok(())
2363 }
2364 HeapObject::Hash(h) => write!(f, "{}/{}", h.len(), h.capacity()),
2365 HeapObject::ArrayRef(_) | HeapObject::ArrayBindingRef(_) => f.write_str("ARRAY(0x...)"),
2366 HeapObject::HashRef(_) | HeapObject::HashBindingRef(_) => f.write_str("HASH(0x...)"),
2367 HeapObject::ScalarRef(_)
2368 | HeapObject::ScalarBindingRef(_)
2369 | HeapObject::CaptureCell(_) => f.write_str("SCALAR(0x...)"),
2370 HeapObject::CodeRef(sub) => write!(f, "CODE({})", sub.name),
2371 HeapObject::Regex(_, src, _) => write!(f, "(?:{src})"),
2372 HeapObject::Blessed(b) => write!(f, "{}=HASH(0x...)", b.class),
2373 HeapObject::IOHandle(name) => f.write_str(name),
2374 HeapObject::Atomic(arc) => write!(f, "{}", arc.lock()),
2375 HeapObject::Set(s) => {
2376 f.write_str("{")?;
2377 if !s.is_empty() {
2378 let mut iter = s.values();
2379 if let Some(v) = iter.next() {
2380 write!(f, "{v}")?;
2381 }
2382 for v in iter {
2383 write!(f, ",{v}")?;
2384 }
2385 }
2386 f.write_str("}")
2387 }
2388 HeapObject::ChannelTx(_) => f.write_str("PCHANNEL::Tx"),
2389 HeapObject::ChannelRx(_) => f.write_str("PCHANNEL::Rx"),
2390 HeapObject::AsyncTask(_) => f.write_str("AsyncTask"),
2391 HeapObject::Generator(g) => write!(f, "Generator({} stmts)", g.block.len()),
2392 HeapObject::Deque(d) => write!(f, "Deque({})", d.lock().len()),
2393 HeapObject::Heap(h) => write!(f, "Heap({})", h.lock().items.len()),
2394 HeapObject::Pipeline(p) => {
2395 let g = p.lock();
2396 write!(f, "Pipeline({} ops)", g.ops.len())
2397 }
2398 HeapObject::Capture(c) => write!(f, "Capture(exit={})", c.exitcode),
2399 HeapObject::Ppool(_) => f.write_str("Ppool"),
2400 HeapObject::RemoteCluster(c) => write!(f, "Cluster({} slots)", c.slots.len()),
2401 HeapObject::Barrier(_) => f.write_str("Barrier"),
2402 HeapObject::SqliteConn(_) => f.write_str("SqliteConn"),
2403 HeapObject::StructInst(s) => {
2404 write!(f, "{}(", s.def.name)?;
2406 let values = s.values.read();
2407 for (i, field) in s.def.fields.iter().enumerate() {
2408 if i > 0 {
2409 f.write_str(", ")?;
2410 }
2411 write!(
2412 f,
2413 "{} => {}",
2414 field.name,
2415 values.get(i).cloned().unwrap_or(PerlValue::UNDEF)
2416 )?;
2417 }
2418 f.write_str(")")
2419 }
2420 HeapObject::EnumInst(e) => {
2421 write!(f, "{}::{}", e.def.name, e.variant_name())?;
2423 if e.def.variants[e.variant_idx].ty.is_some() {
2424 write!(f, "({})", e.data)?;
2425 }
2426 Ok(())
2427 }
2428 HeapObject::ClassInst(c) => {
2429 write!(f, "{}(", c.def.name)?;
2431 let values = c.values.read();
2432 for (i, field) in c.def.fields.iter().enumerate() {
2433 if i > 0 {
2434 f.write_str(", ")?;
2435 }
2436 write!(
2437 f,
2438 "{} => {}",
2439 field.name,
2440 values.get(i).cloned().unwrap_or(PerlValue::UNDEF)
2441 )?;
2442 }
2443 f.write_str(")")
2444 }
2445 HeapObject::DataFrame(d) => {
2446 let g = d.lock();
2447 write!(f, "DataFrame({} rows)", g.nrows())
2448 }
2449 HeapObject::Iterator(_) => f.write_str("Iterator"),
2450 }
2451 }
2452}
2453
2454pub fn set_member_key(v: &PerlValue) -> String {
2456 if nanbox::is_imm_undef(v.0) {
2457 return "u:".to_string();
2458 }
2459 if let Some(n) = nanbox::as_imm_int32(v.0) {
2460 return format!("i:{n}");
2461 }
2462 if nanbox::is_raw_float_bits(v.0) {
2463 return format!("f:{}", f64::from_bits(v.0).to_bits());
2464 }
2465 match unsafe { v.heap_ref() } {
2466 HeapObject::String(s) => format!("s:{s}"),
2467 HeapObject::Bytes(b) => {
2468 use std::fmt::Write as _;
2469 let mut h = String::with_capacity(b.len() * 2);
2470 for &x in b.iter() {
2471 let _ = write!(&mut h, "{:02x}", x);
2472 }
2473 format!("by:{h}")
2474 }
2475 HeapObject::Array(a) => {
2476 let parts: Vec<_> = a.iter().map(set_member_key).collect();
2477 format!("a:{}", parts.join(","))
2478 }
2479 HeapObject::Hash(h) => {
2480 let mut keys: Vec<_> = h.keys().cloned().collect();
2481 keys.sort();
2482 let parts: Vec<_> = keys
2483 .iter()
2484 .map(|k| format!("{}={}", k, set_member_key(h.get(k).unwrap())))
2485 .collect();
2486 format!("h:{}", parts.join(","))
2487 }
2488 HeapObject::Set(inner) => {
2489 let mut keys: Vec<_> = inner.keys().cloned().collect();
2490 keys.sort();
2491 format!("S:{}", keys.join(","))
2492 }
2493 HeapObject::ArrayRef(a) => {
2494 let g = a.read();
2495 let parts: Vec<_> = g.iter().map(set_member_key).collect();
2496 format!("ar:{}", parts.join(","))
2497 }
2498 HeapObject::HashRef(h) => {
2499 let g = h.read();
2500 let mut keys: Vec<_> = g.keys().cloned().collect();
2501 keys.sort();
2502 let parts: Vec<_> = keys
2503 .iter()
2504 .map(|k| format!("{}={}", k, set_member_key(g.get(k).unwrap())))
2505 .collect();
2506 format!("hr:{}", parts.join(","))
2507 }
2508 HeapObject::Blessed(b) => {
2509 let d = b.data.read();
2510 format!("b:{}:{}", b.class, set_member_key(&d))
2511 }
2512 HeapObject::ScalarRef(_) | HeapObject::ScalarBindingRef(_) | HeapObject::CaptureCell(_) => {
2513 format!("sr:{v}")
2514 }
2515 HeapObject::ArrayBindingRef(n) => format!("abind:{n}"),
2516 HeapObject::HashBindingRef(n) => format!("hbind:{n}"),
2517 HeapObject::CodeRef(_) => format!("c:{v}"),
2518 HeapObject::Regex(_, src, _) => format!("r:{src}"),
2519 HeapObject::IOHandle(s) => format!("io:{s}"),
2520 HeapObject::Atomic(arc) => format!("at:{}", set_member_key(&arc.lock())),
2521 HeapObject::ChannelTx(tx) => format!("chtx:{:p}", Arc::as_ptr(tx)),
2522 HeapObject::ChannelRx(rx) => format!("chrx:{:p}", Arc::as_ptr(rx)),
2523 HeapObject::AsyncTask(t) => format!("async:{:p}", Arc::as_ptr(t)),
2524 HeapObject::Generator(g) => format!("gen:{:p}", Arc::as_ptr(g)),
2525 HeapObject::Deque(d) => format!("dq:{:p}", Arc::as_ptr(d)),
2526 HeapObject::Heap(h) => format!("hp:{:p}", Arc::as_ptr(h)),
2527 HeapObject::Pipeline(p) => format!("pl:{:p}", Arc::as_ptr(p)),
2528 HeapObject::Capture(c) => format!("cap:{:p}", Arc::as_ptr(c)),
2529 HeapObject::Ppool(p) => format!("pp:{:p}", Arc::as_ptr(&p.0)),
2530 HeapObject::RemoteCluster(c) => format!("rcl:{:p}", Arc::as_ptr(c)),
2531 HeapObject::Barrier(b) => format!("br:{:p}", Arc::as_ptr(&b.0)),
2532 HeapObject::SqliteConn(c) => format!("sql:{:p}", Arc::as_ptr(c)),
2533 HeapObject::StructInst(s) => format!("st:{}:{:?}", s.def.name, s.values),
2534 HeapObject::EnumInst(e) => {
2535 format!("en:{}::{}:{}", e.def.name, e.variant_name(), e.data)
2536 }
2537 HeapObject::ClassInst(c) => format!("cl:{}:{:?}", c.def.name, c.values),
2538 HeapObject::DataFrame(d) => format!("df:{:p}", Arc::as_ptr(d)),
2539 HeapObject::Iterator(_) => "iter".to_string(),
2540 HeapObject::ErrnoDual { code, msg } => format!("e:{code}:{msg}"),
2541 HeapObject::Integer(n) => format!("i:{n}"),
2542 HeapObject::BigInt(b) => format!("bi:{b}"),
2543 HeapObject::Float(fl) => format!("f:{}", fl.to_bits()),
2544 }
2545}
2546
2547#[inline]
2558pub fn perl_mod_i64(a: i64, b: i64) -> i64 {
2559 debug_assert_ne!(b, 0);
2560 let r = a.wrapping_rem(b);
2561 if r != 0 && (r ^ b) < 0 {
2564 r + b
2565 } else {
2566 r
2567 }
2568}
2569
2570#[inline]
2574pub fn compat_mul(a: &PerlValue, b: &PerlValue) -> PerlValue {
2575 if a.as_bigint().is_some() || b.as_bigint().is_some() {
2576 return PerlValue::bigint(a.to_bigint() * b.to_bigint());
2577 }
2578 let (Some(x), Some(y)) = (a.as_integer(), b.as_integer()) else {
2579 return PerlValue::float(a.to_number() * b.to_number());
2580 };
2581 if crate::compat_mode() || crate::bigint_pragma() {
2582 match x.checked_mul(y) {
2583 Some(r) => PerlValue::integer(r),
2584 None => PerlValue::bigint(BigInt::from(x) * BigInt::from(y)),
2585 }
2586 } else {
2587 PerlValue::integer(x.wrapping_mul(y))
2588 }
2589}
2590
2591#[inline]
2592pub fn compat_add(a: &PerlValue, b: &PerlValue) -> PerlValue {
2593 if a.as_bigint().is_some() || b.as_bigint().is_some() {
2594 return PerlValue::bigint(a.to_bigint() + b.to_bigint());
2595 }
2596 let (Some(x), Some(y)) = (a.as_integer(), b.as_integer()) else {
2597 return PerlValue::float(a.to_number() + b.to_number());
2598 };
2599 if crate::compat_mode() || crate::bigint_pragma() {
2600 match x.checked_add(y) {
2601 Some(r) => PerlValue::integer(r),
2602 None => PerlValue::bigint(BigInt::from(x) + BigInt::from(y)),
2603 }
2604 } else {
2605 PerlValue::integer(x.wrapping_add(y))
2606 }
2607}
2608
2609#[inline]
2610pub fn compat_sub(a: &PerlValue, b: &PerlValue) -> PerlValue {
2611 if a.as_bigint().is_some() || b.as_bigint().is_some() {
2612 return PerlValue::bigint(a.to_bigint() - b.to_bigint());
2613 }
2614 let (Some(x), Some(y)) = (a.as_integer(), b.as_integer()) else {
2615 return PerlValue::float(a.to_number() - b.to_number());
2616 };
2617 if crate::compat_mode() || crate::bigint_pragma() {
2618 match x.checked_sub(y) {
2619 Some(r) => PerlValue::integer(r),
2620 None => PerlValue::bigint(BigInt::from(x) - BigInt::from(y)),
2621 }
2622 } else {
2623 PerlValue::integer(x.wrapping_sub(y))
2624 }
2625}
2626
2627#[inline]
2632pub fn compat_pow(a: &PerlValue, b: &PerlValue) -> PerlValue {
2633 let (Some(base), Some(exp)) = (a.as_integer(), b.as_integer()) else {
2634 return PerlValue::float(a.to_number().powf(b.to_number()));
2635 };
2636 let bigint_active = crate::compat_mode() || crate::bigint_pragma();
2637 if !bigint_active {
2638 return PerlValue::float((base as f64).powf(exp as f64));
2641 }
2642 if exp < 0 {
2643 return PerlValue::float((base as f64).powf(exp as f64));
2644 }
2645 use num_traits::Pow;
2646 let result = BigInt::from(base).pow(exp as u32);
2647 PerlValue::bigint(result)
2648}
2649
2650pub fn set_from_elements<I: IntoIterator<Item = PerlValue>>(items: I) -> PerlValue {
2651 let mut map = PerlSet::new();
2652 for v in items {
2653 let k = set_member_key(&v);
2654 map.insert(k, v);
2655 }
2656 PerlValue::set(Arc::new(map))
2657}
2658
2659#[inline]
2661pub fn set_payload(v: &PerlValue) -> Option<Arc<PerlSet>> {
2662 if !nanbox::is_heap(v.0) {
2663 return None;
2664 }
2665 match unsafe { v.heap_ref() } {
2666 HeapObject::Set(s) => Some(Arc::clone(s)),
2667 HeapObject::Atomic(a) => set_payload(&a.lock()),
2668 _ => None,
2669 }
2670}
2671
2672pub fn set_union(a: &PerlValue, b: &PerlValue) -> Option<PerlValue> {
2673 let ia = set_payload(a)?;
2674 let ib = set_payload(b)?;
2675 let mut m = (*ia).clone();
2676 for (k, v) in ib.iter() {
2677 m.entry(k.clone()).or_insert_with(|| v.clone());
2678 }
2679 Some(PerlValue::set(Arc::new(m)))
2680}
2681
2682pub fn set_intersection(a: &PerlValue, b: &PerlValue) -> Option<PerlValue> {
2683 let ia = set_payload(a)?;
2684 let ib = set_payload(b)?;
2685 let mut m = PerlSet::new();
2686 for (k, v) in ia.iter() {
2687 if ib.contains_key(k) {
2688 m.insert(k.clone(), v.clone());
2689 }
2690 }
2691 Some(PerlValue::set(Arc::new(m)))
2692}
2693fn parse_number(s: &str) -> f64 {
2694 let s = s.trim();
2695 if s.is_empty() {
2696 return 0.0;
2697 }
2698 {
2701 let bytes = s.as_bytes();
2702 let (sign, rest) = match bytes.first() {
2703 Some(b'+') => (1.0_f64, &s[1..]),
2704 Some(b'-') => (-1.0_f64, &s[1..]),
2705 _ => (1.0_f64, s),
2706 };
2707 if rest.eq_ignore_ascii_case("inf") || rest.eq_ignore_ascii_case("infinity") {
2708 return sign * f64::INFINITY;
2709 }
2710 if rest.eq_ignore_ascii_case("nan") {
2711 return f64::NAN;
2715 }
2716 }
2717 let mut end = 0;
2719 let bytes = s.as_bytes();
2720 if end < bytes.len() && (bytes[end] == b'+' || bytes[end] == b'-') {
2721 end += 1;
2722 }
2723 while end < bytes.len() && bytes[end].is_ascii_digit() {
2724 end += 1;
2725 }
2726 if end < bytes.len() && bytes[end] == b'.' {
2727 end += 1;
2728 while end < bytes.len() && bytes[end].is_ascii_digit() {
2729 end += 1;
2730 }
2731 }
2732 if end < bytes.len() && (bytes[end] == b'e' || bytes[end] == b'E') {
2733 end += 1;
2734 if end < bytes.len() && (bytes[end] == b'+' || bytes[end] == b'-') {
2735 end += 1;
2736 }
2737 while end < bytes.len() && bytes[end].is_ascii_digit() {
2738 end += 1;
2739 }
2740 }
2741 if end == 0 {
2742 return 0.0;
2743 }
2744 s[..end].parse::<f64>().unwrap_or(0.0)
2745}
2746
2747fn format_float(f: f64) -> String {
2748 if f.is_nan() {
2750 return "NaN".to_string();
2751 }
2752 if f.is_infinite() {
2753 return if f.is_sign_negative() {
2754 "-Inf".to_string()
2755 } else {
2756 "Inf".to_string()
2757 };
2758 }
2759 if f.fract() == 0.0 && f.abs() < 1e16 {
2760 format!("{}", f as i64)
2761 } else {
2762 let mut buf = [0u8; 64];
2764 unsafe {
2765 libc::snprintf(
2766 buf.as_mut_ptr() as *mut libc::c_char,
2767 buf.len(),
2768 c"%.15g".as_ptr(),
2769 f,
2770 );
2771 std::ffi::CStr::from_ptr(buf.as_ptr() as *const libc::c_char)
2772 .to_string_lossy()
2773 .into_owned()
2774 }
2775 }
2776}
2777
2778#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2780pub(crate) enum PerlListRangeIncOutcome {
2781 Continue,
2782 BecameNumeric,
2784}
2785
2786fn perl_str_looks_like_number_for_range(s: &str) -> bool {
2789 let t = s.trim();
2790 if t.is_empty() {
2791 return s.is_empty();
2792 }
2793 let b = t.as_bytes();
2794 let mut i = 0usize;
2795 if i < b.len() && (b[i] == b'+' || b[i] == b'-') {
2796 i += 1;
2797 }
2798 if i >= b.len() {
2799 return false;
2800 }
2801 let mut saw_digit = false;
2802 while i < b.len() && b[i].is_ascii_digit() {
2803 saw_digit = true;
2804 i += 1;
2805 }
2806 if i < b.len() && b[i] == b'.' {
2807 i += 1;
2808 while i < b.len() && b[i].is_ascii_digit() {
2809 saw_digit = true;
2810 i += 1;
2811 }
2812 }
2813 if !saw_digit {
2814 return false;
2815 }
2816 if i < b.len() && (b[i] == b'e' || b[i] == b'E') {
2817 i += 1;
2818 if i < b.len() && (b[i] == b'+' || b[i] == b'-') {
2819 i += 1;
2820 }
2821 let exp0 = i;
2822 while i < b.len() && b[i].is_ascii_digit() {
2823 i += 1;
2824 }
2825 if i == exp0 {
2826 return false;
2827 }
2828 }
2829 i == b.len()
2830}
2831
2832pub(crate) fn perl_list_range_pair_is_numeric(left: &PerlValue, right: &PerlValue) -> bool {
2834 if left.is_integer_like() || left.is_float_like() {
2835 return true;
2836 }
2837 if !left.is_undef() && !left.is_string_like() {
2838 return true;
2839 }
2840 if right.is_integer_like() || right.is_float_like() {
2841 return true;
2842 }
2843 if !right.is_undef() && !right.is_string_like() {
2844 return true;
2845 }
2846
2847 let left_ok = !left.is_undef();
2848 let right_ok = !right.is_undef();
2849 let left_pok = left.is_string_like();
2850 let left_pv = left.as_str_or_empty();
2851 let right_pv = right.as_str_or_empty();
2852
2853 let left_n = perl_str_looks_like_number_for_range(&left_pv);
2854 let right_n = perl_str_looks_like_number_for_range(&right_pv);
2855
2856 let left_zero_prefix =
2857 left_pok && left_pv.len() > 1 && left_pv.as_bytes().first() == Some(&b'0');
2858
2859 let clause5_left =
2860 (!left_ok && right_ok) || ((!left_ok || left_n) && left_pok && !left_zero_prefix);
2861 clause5_left && (!right_ok || right_n)
2862}
2863
2864pub(crate) fn perl_magic_string_increment_for_range(s: &mut String) -> PerlListRangeIncOutcome {
2866 if s.is_empty() {
2867 return PerlListRangeIncOutcome::BecameNumeric;
2868 }
2869 let b = s.as_bytes();
2870 let mut i = 0usize;
2871 while i < b.len() && b[i].is_ascii_alphabetic() {
2872 i += 1;
2873 }
2874 while i < b.len() && b[i].is_ascii_digit() {
2875 i += 1;
2876 }
2877 if i < b.len() {
2878 let n = parse_number(s) + 1.0;
2879 *s = format_float(n);
2880 return PerlListRangeIncOutcome::BecameNumeric;
2881 }
2882
2883 let bytes = unsafe { s.as_mut_vec() };
2884 let mut idx = bytes.len() - 1;
2885 loop {
2886 if bytes[idx].is_ascii_digit() {
2887 bytes[idx] += 1;
2888 if bytes[idx] <= b'9' {
2889 return PerlListRangeIncOutcome::Continue;
2890 }
2891 bytes[idx] = b'0';
2892 if idx == 0 {
2893 bytes.insert(0, b'1');
2894 return PerlListRangeIncOutcome::Continue;
2895 }
2896 idx -= 1;
2897 } else {
2898 bytes[idx] = bytes[idx].wrapping_add(1);
2899 if bytes[idx].is_ascii_alphabetic() {
2900 return PerlListRangeIncOutcome::Continue;
2901 }
2902 bytes[idx] = bytes[idx].wrapping_sub(b'z' - b'a' + 1);
2903 if idx == 0 {
2904 let c = bytes[0];
2905 bytes.insert(0, if c.is_ascii_digit() { b'1' } else { c });
2906 return PerlListRangeIncOutcome::Continue;
2907 }
2908 idx -= 1;
2909 }
2910 }
2911}
2912
2913pub(crate) fn perl_magic_string_decrement_for_range(s: &mut String) -> Option<()> {
2916 if s.is_empty() {
2917 return None;
2918 }
2919 let b = s.as_bytes();
2921 let mut i = 0usize;
2922 while i < b.len() && b[i].is_ascii_alphabetic() {
2923 i += 1;
2924 }
2925 while i < b.len() && b[i].is_ascii_digit() {
2926 i += 1;
2927 }
2928 if i < b.len() {
2929 return None; }
2931
2932 let bytes = unsafe { s.as_mut_vec() };
2933 let mut idx = bytes.len() - 1;
2934 loop {
2935 if bytes[idx].is_ascii_digit() {
2936 if bytes[idx] > b'0' {
2937 bytes[idx] -= 1;
2938 return Some(());
2939 }
2940 bytes[idx] = b'9';
2942 if idx == 0 {
2943 if bytes.len() == 1 {
2945 bytes[0] = b'0'; return None;
2947 }
2948 bytes.remove(0);
2949 return Some(());
2950 }
2951 idx -= 1;
2952 } else if bytes[idx].is_ascii_lowercase() {
2953 if bytes[idx] > b'a' {
2954 bytes[idx] -= 1;
2955 return Some(());
2956 }
2957 bytes[idx] = b'z';
2959 if idx == 0 {
2960 if bytes.len() == 1 {
2962 bytes[0] = b'a'; return None;
2964 }
2965 bytes.remove(0);
2966 return Some(());
2967 }
2968 idx -= 1;
2969 } else if bytes[idx].is_ascii_uppercase() {
2970 if bytes[idx] > b'A' {
2971 bytes[idx] -= 1;
2972 return Some(());
2973 }
2974 bytes[idx] = b'Z';
2976 if idx == 0 {
2977 if bytes.len() == 1 {
2978 bytes[0] = b'A'; return None;
2980 }
2981 bytes.remove(0);
2982 return Some(());
2983 }
2984 idx -= 1;
2985 } else {
2986 return None;
2987 }
2988 }
2989}
2990
2991fn perl_list_range_max_bound(right: &str) -> usize {
2992 if right.is_ascii() {
2993 right.len()
2994 } else {
2995 right.chars().count()
2996 }
2997}
2998
2999fn perl_list_range_cur_bound(cur: &str, right_is_ascii: bool) -> usize {
3000 if right_is_ascii {
3001 cur.len()
3002 } else {
3003 cur.chars().count()
3004 }
3005}
3006
3007fn perl_list_range_expand_string_magic(from: PerlValue, to: PerlValue) -> Vec<PerlValue> {
3008 let mut cur = from.into_string();
3009 let right = to.into_string();
3010 let right_ascii = right.is_ascii();
3011 let max_bound = perl_list_range_max_bound(&right);
3012 let mut out = Vec::new();
3013 let mut guard = 0usize;
3014 loop {
3015 guard += 1;
3016 if guard > 50_000_000 {
3017 break;
3018 }
3019 let cur_bound = perl_list_range_cur_bound(&cur, right_ascii);
3020 if cur_bound > max_bound {
3021 break;
3022 }
3023 out.push(PerlValue::string(cur.clone()));
3024 if cur == right {
3025 break;
3026 }
3027 match perl_magic_string_increment_for_range(&mut cur) {
3028 PerlListRangeIncOutcome::Continue => {}
3029 PerlListRangeIncOutcome::BecameNumeric => break,
3030 }
3031 }
3032 out
3033}
3034
3035pub(crate) fn perl_list_range_expand(from: PerlValue, to: PerlValue) -> Vec<PerlValue> {
3037 if perl_list_range_pair_is_numeric(&from, &to) {
3038 let i = from.to_int();
3039 let j = to.to_int();
3040 if j >= i {
3041 (i..=j).map(PerlValue::integer).collect()
3042 } else {
3043 Vec::new()
3044 }
3045 } else {
3046 perl_list_range_expand_string_magic(from, to)
3047 }
3048}
3049
3050fn is_roman_numeral(s: &str) -> bool {
3056 if s.is_empty() {
3057 return false;
3058 }
3059 let upper = s.to_ascii_uppercase();
3060 upper
3061 .chars()
3062 .all(|c| matches!(c, 'I' | 'V' | 'X' | 'L' | 'C' | 'D' | 'M'))
3063}
3064
3065fn is_ipv4(s: &str) -> bool {
3067 let parts: Vec<&str> = s.split('.').collect();
3068 parts.len() == 4 && parts.iter().all(|p| p.parse::<u8>().is_ok())
3069}
3070
3071fn ipv4_to_u32(s: &str) -> Option<u32> {
3073 let parts: Vec<u8> = s.split('.').filter_map(|p| p.parse().ok()).collect();
3074 if parts.len() != 4 {
3075 return None;
3076 }
3077 Some(
3078 ((parts[0] as u32) << 24)
3079 | ((parts[1] as u32) << 16)
3080 | ((parts[2] as u32) << 8)
3081 | (parts[3] as u32),
3082 )
3083}
3084
3085fn u32_to_ipv4(n: u32) -> String {
3087 format!(
3088 "{}.{}.{}.{}",
3089 (n >> 24) & 0xFF,
3090 (n >> 16) & 0xFF,
3091 (n >> 8) & 0xFF,
3092 n & 0xFF
3093 )
3094}
3095
3096fn ipv4_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
3098 let Some(start) = ipv4_to_u32(from) else {
3099 return vec![];
3100 };
3101 let Some(end) = ipv4_to_u32(to) else {
3102 return vec![];
3103 };
3104 let mut out = Vec::new();
3105 if step > 0 {
3106 let mut cur = start as i64;
3107 while cur <= end as i64 {
3108 out.push(PerlValue::string(u32_to_ipv4(cur as u32)));
3109 cur += step;
3110 }
3111 } else {
3112 let mut cur = start as i64;
3113 while cur >= end as i64 {
3114 out.push(PerlValue::string(u32_to_ipv4(cur as u32)));
3115 cur += step;
3116 }
3117 }
3118 out
3119}
3120
3121fn is_ipv6(s: &str) -> bool {
3124 s.parse::<std::net::Ipv6Addr>().is_ok()
3125}
3126
3127fn is_hex_source_literal(s: &str) -> bool {
3132 let bytes = s.as_bytes();
3133 bytes.len() > 2
3134 && bytes[0] == b'0'
3135 && (bytes[1] == b'x' || bytes[1] == b'X')
3136 && bytes[2..].iter().all(|b| b.is_ascii_hexdigit())
3137}
3138
3139fn hex_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
3146 let from_body = &from[2..];
3147 let to_body = &to[2..];
3148 let Ok(start) = i64::from_str_radix(from_body, 16) else {
3149 return vec![];
3150 };
3151 let Ok(end) = i64::from_str_radix(to_body, 16) else {
3152 return vec![];
3153 };
3154 let prefix = &from[..2];
3155 let width = from_body.len().max(to_body.len());
3156 let upper = from_body.bytes().any(|b| b.is_ascii_uppercase())
3157 || to_body.bytes().any(|b| b.is_ascii_uppercase());
3158 let mut out = Vec::new();
3159 let format_one = |n: i64, width: usize, upper: bool, prefix: &str| -> String {
3160 if upper {
3161 format!("{}{:0>w$X}", prefix, n, w = width)
3162 } else {
3163 format!("{}{:0>w$x}", prefix, n, w = width)
3164 }
3165 };
3166 if step > 0 {
3167 if start > end {
3168 return out;
3169 }
3170 let mut cur = start;
3171 while cur <= end {
3172 out.push(PerlValue::string(format_one(cur, width, upper, prefix)));
3173 if (end - cur) < step {
3174 break;
3175 }
3176 cur += step;
3177 }
3178 } else if step < 0 {
3179 if start < end {
3180 return out;
3181 }
3182 let mut cur = start;
3183 while cur >= end {
3184 out.push(PerlValue::string(format_one(cur, width, upper, prefix)));
3185 if (cur - end) < (-step) {
3186 break;
3187 }
3188 cur += step;
3189 }
3190 }
3191 out
3192}
3193
3194fn ipv6_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
3195 let Ok(start) = from.parse::<std::net::Ipv6Addr>() else {
3196 return vec![];
3197 };
3198 let Ok(end) = to.parse::<std::net::Ipv6Addr>() else {
3199 return vec![];
3200 };
3201 let s = u128::from(start);
3202 let e = u128::from(end);
3203 let mut out = Vec::new();
3204 if step > 0 {
3205 if s > e {
3206 return out; }
3208 let step = step as u128;
3209 let mut cur = s;
3210 loop {
3211 out.push(PerlValue::string(std::net::Ipv6Addr::from(cur).to_string()));
3212 if cur == e || e.saturating_sub(cur) < step {
3213 break;
3214 }
3215 cur += step;
3216 }
3217 } else if step < 0 {
3218 if s < e {
3219 return out; }
3221 let step = (-step) as u128;
3222 let mut cur = s;
3223 loop {
3224 out.push(PerlValue::string(std::net::Ipv6Addr::from(cur).to_string()));
3225 if cur == e || cur.saturating_sub(e) < step {
3226 break;
3227 }
3228 cur -= step;
3229 }
3230 }
3231 out
3232}
3233
3234fn is_iso_date(s: &str) -> bool {
3236 if s.len() != 10 {
3237 return false;
3238 }
3239 let parts: Vec<&str> = s.split('-').collect();
3240 parts.len() == 3
3241 && parts[0].len() == 4
3242 && parts[0].parse::<u16>().is_ok()
3243 && parts[1].len() == 2
3244 && parts[1]
3245 .parse::<u8>()
3246 .map(|m| (1..=12).contains(&m))
3247 .unwrap_or(false)
3248 && parts[2].len() == 2
3249 && parts[2]
3250 .parse::<u8>()
3251 .map(|d| (1..=31).contains(&d))
3252 .unwrap_or(false)
3253}
3254
3255fn is_year_month(s: &str) -> bool {
3257 if s.len() != 7 {
3258 return false;
3259 }
3260 let parts: Vec<&str> = s.split('-').collect();
3261 parts.len() == 2
3262 && parts[0].len() == 4
3263 && parts[0].parse::<u16>().is_ok()
3264 && parts[1].len() == 2
3265 && parts[1]
3266 .parse::<u8>()
3267 .map(|m| (1..=12).contains(&m))
3268 .unwrap_or(false)
3269}
3270
3271fn parse_iso_date(s: &str) -> Option<(i32, u32, u32)> {
3273 let parts: Vec<&str> = s.split('-').collect();
3274 if parts.len() != 3 {
3275 return None;
3276 }
3277 Some((
3278 parts[0].parse().ok()?,
3279 parts[1].parse().ok()?,
3280 parts[2].parse().ok()?,
3281 ))
3282}
3283
3284fn parse_year_month(s: &str) -> Option<(i32, u32)> {
3286 let parts: Vec<&str> = s.split('-').collect();
3287 if parts.len() != 2 {
3288 return None;
3289 }
3290 Some((parts[0].parse().ok()?, parts[1].parse().ok()?))
3291}
3292
3293fn days_in_month(year: i32, month: u32) -> u32 {
3295 match month {
3296 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
3297 4 | 6 | 9 | 11 => 30,
3298 2 => {
3299 if (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 {
3300 29
3301 } else {
3302 28
3303 }
3304 }
3305 _ => 30,
3306 }
3307}
3308
3309fn add_days(mut year: i32, mut month: u32, mut day: u32, mut delta: i64) -> (i32, u32, u32) {
3311 if delta > 0 {
3312 while delta > 0 {
3313 let dim = days_in_month(year, month);
3314 let remaining = dim - day;
3315 if delta <= remaining as i64 {
3316 day += delta as u32;
3317 break;
3318 }
3319 delta -= (remaining + 1) as i64;
3320 day = 1;
3321 month += 1;
3322 if month > 12 {
3323 month = 1;
3324 year += 1;
3325 }
3326 }
3327 } else {
3328 while delta < 0 {
3329 if (-delta) < day as i64 {
3330 day = (day as i64 + delta) as u32;
3331 break;
3332 }
3333 delta += day as i64;
3334 month -= 1;
3335 if month == 0 {
3336 month = 12;
3337 year -= 1;
3338 }
3339 day = days_in_month(year, month);
3340 }
3341 }
3342 (year, month, day)
3343}
3344
3345fn iso_date_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
3347 let Some((mut y, mut m, mut d)) = parse_iso_date(from) else {
3348 return vec![];
3349 };
3350 let Some((ey, em, ed)) = parse_iso_date(to) else {
3351 return vec![];
3352 };
3353 let mut out = Vec::new();
3354 let mut guard = 0;
3355 if step > 0 {
3356 while (y, m, d) <= (ey, em, ed) && guard < 50_000 {
3357 out.push(PerlValue::string(format!("{:04}-{:02}-{:02}", y, m, d)));
3358 (y, m, d) = add_days(y, m, d, step);
3359 guard += 1;
3360 }
3361 } else {
3362 while (y, m, d) >= (ey, em, ed) && guard < 50_000 {
3363 out.push(PerlValue::string(format!("{:04}-{:02}-{:02}", y, m, d)));
3364 (y, m, d) = add_days(y, m, d, step);
3365 guard += 1;
3366 }
3367 }
3368 out
3369}
3370
3371fn add_months(mut year: i32, mut month: u32, delta: i64) -> (i32, u32) {
3373 let total = (year as i64 * 12 + month as i64 - 1) + delta;
3374 year = (total / 12) as i32;
3375 month = ((total % 12) + 1) as u32;
3376 if month == 0 {
3377 month = 12;
3378 year -= 1;
3379 }
3380 (year, month)
3381}
3382
3383fn year_month_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
3385 let Some((mut y, mut m)) = parse_year_month(from) else {
3386 return vec![];
3387 };
3388 let Some((ey, em)) = parse_year_month(to) else {
3389 return vec![];
3390 };
3391 let mut out = Vec::new();
3392 let mut guard = 0;
3393 if step > 0 {
3394 while (y, m) <= (ey, em) && guard < 50_000 {
3395 out.push(PerlValue::string(format!("{:04}-{:02}", y, m)));
3396 (y, m) = add_months(y, m, step);
3397 guard += 1;
3398 }
3399 } else {
3400 while (y, m) >= (ey, em) && guard < 50_000 {
3401 out.push(PerlValue::string(format!("{:04}-{:02}", y, m)));
3402 (y, m) = add_months(y, m, step);
3403 guard += 1;
3404 }
3405 }
3406 out
3407}
3408
3409fn is_time_hhmm(s: &str) -> bool {
3411 if s.len() != 5 {
3412 return false;
3413 }
3414 let parts: Vec<&str> = s.split(':').collect();
3415 parts.len() == 2
3416 && parts[0].len() == 2
3417 && parts[0].parse::<u8>().map(|h| h < 24).unwrap_or(false)
3418 && parts[1].len() == 2
3419 && parts[1].parse::<u8>().map(|m| m < 60).unwrap_or(false)
3420}
3421
3422fn parse_time_hhmm(s: &str) -> Option<i32> {
3424 let parts: Vec<&str> = s.split(':').collect();
3425 if parts.len() != 2 {
3426 return None;
3427 }
3428 let h: i32 = parts[0].parse().ok()?;
3429 let m: i32 = parts[1].parse().ok()?;
3430 Some(h * 60 + m)
3431}
3432
3433fn minutes_to_hhmm(mins: i32) -> String {
3435 let h = (mins / 60) % 24;
3436 let m = mins % 60;
3437 format!("{:02}:{:02}", h, m)
3438}
3439
3440fn time_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
3442 let Some(start) = parse_time_hhmm(from) else {
3443 return vec![];
3444 };
3445 let Some(end) = parse_time_hhmm(to) else {
3446 return vec![];
3447 };
3448 let mut out = Vec::new();
3449 let mut guard = 0;
3450 if step > 0 {
3451 let mut cur = start;
3452 while cur <= end && guard < 50_000 {
3453 out.push(PerlValue::string(minutes_to_hhmm(cur)));
3454 cur += step as i32;
3455 guard += 1;
3456 }
3457 } else {
3458 let mut cur = start;
3459 while cur >= end && guard < 50_000 {
3460 out.push(PerlValue::string(minutes_to_hhmm(cur)));
3461 cur += step as i32;
3462 guard += 1;
3463 }
3464 }
3465 out
3466}
3467
3468const WEEKDAYS: [&str; 7] = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
3469const WEEKDAYS_FULL: [&str; 7] = [
3470 "Monday",
3471 "Tuesday",
3472 "Wednesday",
3473 "Thursday",
3474 "Friday",
3475 "Saturday",
3476 "Sunday",
3477];
3478
3479fn weekday_index(s: &str) -> Option<usize> {
3481 let lower = s.to_ascii_lowercase();
3482 for (i, &d) in WEEKDAYS.iter().enumerate() {
3483 if d.to_ascii_lowercase() == lower {
3484 return Some(i);
3485 }
3486 }
3487 for (i, &d) in WEEKDAYS_FULL.iter().enumerate() {
3488 if d.to_ascii_lowercase() == lower {
3489 return Some(i);
3490 }
3491 }
3492 None
3493}
3494
3495fn weekday_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
3497 let Some(start) = weekday_index(from) else {
3498 return vec![];
3499 };
3500 let Some(end) = weekday_index(to) else {
3501 return vec![];
3502 };
3503 let full = from.len() > 3;
3504 let names = if full { &WEEKDAYS_FULL } else { &WEEKDAYS };
3505 let mut out = Vec::new();
3506 if step > 0 {
3507 let mut cur = start as i64;
3508 let target = if end >= start {
3509 end as i64
3510 } else {
3511 end as i64 + 7
3512 };
3513 while cur <= target {
3514 out.push(PerlValue::string(names[(cur % 7) as usize].to_string()));
3515 cur += step;
3516 }
3517 } else {
3518 let mut cur = start as i64;
3519 let target = if end <= start {
3520 end as i64
3521 } else {
3522 end as i64 - 7
3523 };
3524 while cur >= target {
3525 out.push(PerlValue::string(
3526 names[((cur % 7 + 7) % 7) as usize].to_string(),
3527 ));
3528 cur += step;
3529 }
3530 }
3531 out
3532}
3533
3534const MONTHS: [&str; 12] = [
3535 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
3536];
3537const MONTHS_FULL: [&str; 12] = [
3538 "January",
3539 "February",
3540 "March",
3541 "April",
3542 "May",
3543 "June",
3544 "July",
3545 "August",
3546 "September",
3547 "October",
3548 "November",
3549 "December",
3550];
3551
3552fn month_name_index(s: &str) -> Option<usize> {
3554 let lower = s.to_ascii_lowercase();
3555 for (i, &m) in MONTHS.iter().enumerate() {
3556 if m.to_ascii_lowercase() == lower {
3557 return Some(i);
3558 }
3559 }
3560 for (i, &m) in MONTHS_FULL.iter().enumerate() {
3561 if m.to_ascii_lowercase() == lower {
3562 return Some(i);
3563 }
3564 }
3565 None
3566}
3567
3568fn month_name_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
3570 let Some(start) = month_name_index(from) else {
3571 return vec![];
3572 };
3573 let Some(end) = month_name_index(to) else {
3574 return vec![];
3575 };
3576 let full = from.len() > 3;
3577 let names = if full { &MONTHS_FULL } else { &MONTHS };
3578 let mut out = Vec::new();
3579 if step > 0 {
3580 let mut cur = start as i64;
3581 let target = if end >= start {
3582 end as i64
3583 } else {
3584 end as i64 + 12
3585 };
3586 while cur <= target {
3587 out.push(PerlValue::string(names[(cur % 12) as usize].to_string()));
3588 cur += step;
3589 }
3590 } else {
3591 let mut cur = start as i64;
3592 let target = if end <= start {
3593 end as i64
3594 } else {
3595 end as i64 - 12
3596 };
3597 while cur >= target {
3598 out.push(PerlValue::string(
3599 names[((cur % 12 + 12) % 12) as usize].to_string(),
3600 ));
3601 cur += step;
3602 }
3603 }
3604 out
3605}
3606
3607fn is_float_pair(from: &str, to: &str) -> bool {
3609 fn is_float(s: &str) -> bool {
3610 s.contains('.')
3611 && !s.contains(':')
3612 && s.matches('.').count() == 1
3613 && s.parse::<f64>().is_ok()
3614 }
3615 is_float(from) && is_float(to)
3616}
3617
3618fn float_range_stepped(from: &str, to: &str, step: f64) -> Vec<PerlValue> {
3620 let Ok(start) = from.parse::<f64>() else {
3621 return vec![];
3622 };
3623 let Ok(end) = to.parse::<f64>() else {
3624 return vec![];
3625 };
3626 let mut out = Vec::new();
3627 let mut guard = 0;
3628 if step > 0.0 {
3630 let mut i = 0i64;
3631 loop {
3632 let cur = start + (i as f64) * step;
3633 if cur > end + step.abs() * f64::EPSILON * 10.0 || guard >= 50_000 {
3634 break;
3635 }
3636 let rounded = (cur * 1e12).round() / 1e12;
3638 out.push(PerlValue::float(rounded));
3639 i += 1;
3640 guard += 1;
3641 }
3642 } else if step < 0.0 {
3643 let mut i = 0i64;
3644 loop {
3645 let cur = start + (i as f64) * step;
3646 if cur < end - step.abs() * f64::EPSILON * 10.0 || guard >= 50_000 {
3647 break;
3648 }
3649 let rounded = (cur * 1e12).round() / 1e12;
3650 out.push(PerlValue::float(rounded));
3651 i += 1;
3652 guard += 1;
3653 }
3654 }
3655 out
3656}
3657
3658fn roman_to_int(s: &str) -> Option<i64> {
3660 let upper = s.to_ascii_uppercase();
3661 let mut result = 0i64;
3662 let mut prev = 0i64;
3663 for c in upper.chars().rev() {
3664 let val = match c {
3665 'I' => 1,
3666 'V' => 5,
3667 'X' => 10,
3668 'L' => 50,
3669 'C' => 100,
3670 'D' => 500,
3671 'M' => 1000,
3672 _ => return None,
3673 };
3674 if val < prev {
3675 result -= val;
3676 } else {
3677 result += val;
3678 }
3679 prev = val;
3680 }
3681 if result > 0 {
3682 Some(result)
3683 } else {
3684 None
3685 }
3686}
3687
3688fn int_to_roman(mut n: i64, lowercase: bool) -> Option<String> {
3690 if n <= 0 || n > 3999 {
3691 return None;
3692 }
3693 let numerals = [
3694 (1000, "M"),
3695 (900, "CM"),
3696 (500, "D"),
3697 (400, "CD"),
3698 (100, "C"),
3699 (90, "XC"),
3700 (50, "L"),
3701 (40, "XL"),
3702 (10, "X"),
3703 (9, "IX"),
3704 (5, "V"),
3705 (4, "IV"),
3706 (1, "I"),
3707 ];
3708 let mut result = String::new();
3709 for (val, sym) in numerals {
3710 while n >= val {
3711 result.push_str(sym);
3712 n -= val;
3713 }
3714 }
3715 if lowercase {
3716 Some(result.to_ascii_lowercase())
3717 } else {
3718 Some(result)
3719 }
3720}
3721
3722fn roman_range_stepped(from: &str, to: &str, step: i64) -> Vec<PerlValue> {
3724 let Some(start) = roman_to_int(from) else {
3725 return vec![];
3726 };
3727 let Some(end) = roman_to_int(to) else {
3728 return vec![];
3729 };
3730 let lowercase = from
3731 .chars()
3732 .next()
3733 .map(|c| c.is_ascii_lowercase())
3734 .unwrap_or(false);
3735
3736 let mut out = Vec::new();
3737 if step > 0 {
3738 let mut cur = start;
3739 while cur <= end {
3740 if let Some(r) = int_to_roman(cur, lowercase) {
3741 out.push(PerlValue::string(r));
3742 }
3743 cur += step;
3744 }
3745 } else {
3746 let mut cur = start;
3747 while cur >= end {
3748 if let Some(r) = int_to_roman(cur, lowercase) {
3749 out.push(PerlValue::string(r));
3750 }
3751 cur += step; }
3753 }
3754 out
3755}
3756
3757pub(crate) fn perl_list_range_expand_stepped(
3760 from: PerlValue,
3761 to: PerlValue,
3762 step_val: PerlValue,
3763) -> Vec<PerlValue> {
3764 let from_str = from.to_string();
3765 let to_str = to.to_string();
3766
3767 let is_float_range = is_float_pair(&from_str, &to_str);
3769
3770 let step_float = step_val.as_float().unwrap_or(step_val.to_int() as f64);
3772 let step_int = step_val.to_int();
3773
3774 if step_int == 0 && step_float == 0.0 {
3775 return vec![];
3776 }
3777
3778 if is_float_range {
3780 return float_range_stepped(&from_str, &to_str, step_float);
3781 }
3782
3783 if perl_list_range_pair_is_numeric(&from, &to) {
3785 let i = from.to_int();
3786 let j = to.to_int();
3787 if step_int > 0 {
3788 (i..=j)
3789 .step_by(step_int as usize)
3790 .map(PerlValue::integer)
3791 .collect()
3792 } else {
3793 std::iter::successors(Some(i), |&x| {
3794 let next = x + step_int;
3795 if next >= j {
3796 Some(next)
3797 } else {
3798 None
3799 }
3800 })
3801 .map(PerlValue::integer)
3802 .collect()
3803 }
3804 } else {
3805 if is_hex_source_literal(&from_str) && is_hex_source_literal(&to_str) {
3812 return hex_range_stepped(&from_str, &to_str, step_int);
3813 }
3814
3815 if is_ipv4(&from_str) && is_ipv4(&to_str) {
3817 return ipv4_range_stepped(&from_str, &to_str, step_int);
3818 }
3819
3820 if is_ipv6(&from_str) && is_ipv6(&to_str) {
3824 return ipv6_range_stepped(&from_str, &to_str, step_int);
3825 }
3826
3827 if is_iso_date(&from_str) && is_iso_date(&to_str) {
3829 return iso_date_range_stepped(&from_str, &to_str, step_int);
3830 }
3831
3832 if is_year_month(&from_str) && is_year_month(&to_str) {
3834 return year_month_range_stepped(&from_str, &to_str, step_int);
3835 }
3836
3837 if is_time_hhmm(&from_str) && is_time_hhmm(&to_str) {
3839 return time_range_stepped(&from_str, &to_str, step_int);
3840 }
3841
3842 if weekday_index(&from_str).is_some() && weekday_index(&to_str).is_some() {
3844 return weekday_range_stepped(&from_str, &to_str, step_int);
3845 }
3846
3847 if month_name_index(&from_str).is_some() && month_name_index(&to_str).is_some() {
3849 return month_name_range_stepped(&from_str, &to_str, step_int);
3850 }
3851
3852 if is_roman_numeral(&from_str) && is_roman_numeral(&to_str) {
3854 return roman_range_stepped(&from_str, &to_str, step_int);
3855 }
3856
3857 perl_list_range_expand_string_magic_stepped(from, to, step_int)
3859 }
3860}
3861
3862pub(crate) fn perl_slice_endpoint_to_strict_int(
3866 v: &PerlValue,
3867 where_: &str,
3868) -> Result<i64, String> {
3869 if let Some(n) = v.as_integer() {
3870 return Ok(n);
3871 }
3872 if let Some(f) = v.as_float() {
3873 if f.is_finite() && f.fract() == 0.0 && f >= i64::MIN as f64 && f <= i64::MAX as f64 {
3874 return Ok(f as i64);
3875 }
3876 return Err(format!(
3877 "array slice {}: non-integer float endpoint {}",
3878 where_, f
3879 ));
3880 }
3881 let s = v.as_str_or_empty();
3882 if !s.is_empty() {
3883 if let Ok(n) = s.trim().parse::<i64>() {
3884 return Ok(n);
3885 }
3886 return Err(format!(
3887 "array slice {}: non-integer string endpoint {:?}",
3888 where_, s
3889 ));
3890 }
3891 Err(format!(
3892 "array slice {}: endpoint must be an integer (got non-numeric value)",
3893 where_
3894 ))
3895}
3896
3897pub(crate) fn compute_array_slice_indices(
3907 arr_len: i64,
3908 from: &PerlValue,
3909 to: &PerlValue,
3910 step: &PerlValue,
3911) -> Result<Vec<i64>, String> {
3912 let step_i = if step.is_undef() {
3913 1i64
3914 } else {
3915 perl_slice_endpoint_to_strict_int(step, "step")?
3916 };
3917 if step_i == 0 {
3918 return Err("array slice step cannot be 0".into());
3919 }
3920
3921 let normalize = |i: i64| -> i64 {
3922 if i < 0 {
3923 i + arr_len
3924 } else {
3925 i
3926 }
3927 };
3928
3929 let any_undef = from.is_undef() || to.is_undef();
3935
3936 let from_raw = if from.is_undef() {
3937 if step_i > 0 {
3938 0
3939 } else {
3940 arr_len - 1
3941 }
3942 } else {
3943 perl_slice_endpoint_to_strict_int(from, "start")?
3944 };
3945
3946 let to_raw = if to.is_undef() {
3947 if step_i > 0 {
3948 arr_len - 1
3949 } else {
3950 0
3951 }
3952 } else {
3953 perl_slice_endpoint_to_strict_int(to, "stop")?
3954 };
3955
3956 let mut out = Vec::new();
3957 if arr_len == 0 {
3958 return Ok(out);
3959 }
3960
3961 let (from_i, to_i) = if any_undef {
3962 (normalize(from_raw), normalize(to_raw))
3963 } else {
3964 (from_raw, to_raw)
3965 };
3966
3967 if step_i > 0 {
3968 let mut i = from_i;
3969 while i <= to_i {
3970 out.push(if any_undef { i } else { normalize(i) });
3971 i += step_i;
3972 }
3973 } else {
3974 let mut i = from_i;
3975 while i >= to_i {
3976 out.push(if any_undef { i } else { normalize(i) });
3977 i += step_i; }
3979 }
3980 Ok(out)
3981}
3982
3983pub(crate) fn compute_hash_slice_keys(
3988 from: &PerlValue,
3989 to: &PerlValue,
3990 step: &PerlValue,
3991) -> Result<Vec<String>, String> {
3992 if from.is_undef() || to.is_undef() {
3993 return Err(
3994 "hash slice range requires both endpoints (open-ended forms not allowed)".into(),
3995 );
3996 }
3997 let step_val = if step.is_undef() {
3998 PerlValue::integer(1)
3999 } else {
4000 step.clone()
4001 };
4002 let expanded = perl_list_range_expand_stepped(from.clone(), to.clone(), step_val);
4003 Ok(expanded.into_iter().map(|v| v.to_string()).collect())
4004}
4005
4006fn perl_list_range_expand_string_magic_stepped(
4007 from: PerlValue,
4008 to: PerlValue,
4009 step: i64,
4010) -> Vec<PerlValue> {
4011 if step == 0 {
4012 return vec![];
4013 }
4014 let mut cur = from.into_string();
4015 let right = to.into_string();
4016
4017 if step > 0 {
4018 let step = step as usize;
4020 let right_ascii = right.is_ascii();
4021 let max_bound = perl_list_range_max_bound(&right);
4022 let mut out = Vec::new();
4023 let mut guard = 0usize;
4024 let mut idx = 0usize;
4025 loop {
4026 guard += 1;
4027 if guard > 50_000_000 {
4028 break;
4029 }
4030 let cur_bound = perl_list_range_cur_bound(&cur, right_ascii);
4031 if cur_bound > max_bound {
4032 break;
4033 }
4034 if idx.is_multiple_of(step) {
4035 out.push(PerlValue::string(cur.clone()));
4036 }
4037 if cur == right {
4038 break;
4039 }
4040 match perl_magic_string_increment_for_range(&mut cur) {
4041 PerlListRangeIncOutcome::Continue => {}
4042 PerlListRangeIncOutcome::BecameNumeric => break,
4043 }
4044 idx += 1;
4045 }
4046 out
4047 } else {
4048 let step = (-step) as usize;
4050 let mut out = Vec::new();
4051 let mut guard = 0usize;
4052 let mut idx = 0usize;
4053 loop {
4054 guard += 1;
4055 if guard > 50_000_000 {
4056 break;
4057 }
4058 if idx.is_multiple_of(step) {
4059 out.push(PerlValue::string(cur.clone()));
4060 }
4061 if cur == right {
4062 break;
4063 }
4064 if cur < right {
4066 break;
4067 }
4068 match perl_magic_string_decrement_for_range(&mut cur) {
4069 Some(()) => {}
4070 None => break, }
4072 idx += 1;
4073 }
4074 out
4075 }
4076}
4077
4078impl PerlDataFrame {
4079 pub fn row_hashref(&self, row: usize) -> PerlValue {
4081 let mut m = IndexMap::new();
4082 for (i, col) in self.columns.iter().enumerate() {
4083 m.insert(
4084 col.clone(),
4085 self.cols[i].get(row).cloned().unwrap_or(PerlValue::UNDEF),
4086 );
4087 }
4088 PerlValue::hash_ref(Arc::new(RwLock::new(m)))
4089 }
4090}
4091
4092#[cfg(test)]
4093mod tests {
4094 use super::PerlValue;
4095 use crate::perl_regex::PerlCompiledRegex;
4096 use indexmap::IndexMap;
4097 use parking_lot::RwLock;
4098 use std::cmp::Ordering;
4099 use std::sync::Arc;
4100
4101 #[test]
4102 fn undef_is_false() {
4103 assert!(!PerlValue::UNDEF.is_true());
4104 }
4105
4106 #[test]
4107 fn string_zero_is_false() {
4108 assert!(!PerlValue::string("0".into()).is_true());
4109 assert!(PerlValue::string("00".into()).is_true());
4110 }
4111
4112 #[test]
4113 fn empty_string_is_false() {
4114 assert!(!PerlValue::string(String::new()).is_true());
4115 }
4116
4117 #[test]
4118 fn integer_zero_is_false_nonzero_true() {
4119 assert!(!PerlValue::integer(0).is_true());
4120 assert!(PerlValue::integer(-1).is_true());
4121 }
4122
4123 #[test]
4124 fn float_zero_is_false_nonzero_true() {
4125 assert!(!PerlValue::float(0.0).is_true());
4126 assert!(PerlValue::float(0.1).is_true());
4127 }
4128
4129 #[test]
4130 fn num_cmp_orders_float_against_integer() {
4131 assert_eq!(
4132 PerlValue::float(2.5).num_cmp(&PerlValue::integer(3)),
4133 Ordering::Less
4134 );
4135 }
4136
4137 #[test]
4138 fn to_int_parses_leading_number_from_string() {
4139 assert_eq!(PerlValue::string("42xyz".into()).to_int(), 42);
4140 assert_eq!(PerlValue::string(" -3.7foo".into()).to_int(), -3);
4141 }
4142
4143 #[test]
4144 fn num_cmp_orders_as_numeric() {
4145 assert_eq!(
4146 PerlValue::integer(2).num_cmp(&PerlValue::integer(11)),
4147 Ordering::Less
4148 );
4149 assert_eq!(
4150 PerlValue::string("2foo".into()).num_cmp(&PerlValue::string("11".into())),
4151 Ordering::Less
4152 );
4153 }
4154
4155 #[test]
4156 fn str_cmp_orders_as_strings() {
4157 assert_eq!(
4158 PerlValue::string("2".into()).str_cmp(&PerlValue::string("11".into())),
4159 Ordering::Greater
4160 );
4161 }
4162
4163 #[test]
4164 fn str_eq_heap_strings_fast_path() {
4165 let a = PerlValue::string("hello".into());
4166 let b = PerlValue::string("hello".into());
4167 assert!(a.str_eq(&b));
4168 assert!(!a.str_eq(&PerlValue::string("hell".into())));
4169 }
4170
4171 #[test]
4172 fn str_eq_fallback_matches_stringified_equality() {
4173 let n = PerlValue::integer(42);
4174 let s = PerlValue::string("42".into());
4175 assert!(n.str_eq(&s));
4176 assert!(!PerlValue::integer(1).str_eq(&PerlValue::string("2".into())));
4177 }
4178
4179 #[test]
4180 fn str_cmp_heap_strings_fast_path() {
4181 assert_eq!(
4182 PerlValue::string("a".into()).str_cmp(&PerlValue::string("b".into())),
4183 Ordering::Less
4184 );
4185 }
4186
4187 #[test]
4188 fn scalar_context_array_and_hash() {
4189 let a =
4190 PerlValue::array(vec![PerlValue::integer(1), PerlValue::integer(2)]).scalar_context();
4191 assert_eq!(a.to_int(), 2);
4192 let mut h = IndexMap::new();
4193 h.insert("a".into(), PerlValue::integer(1));
4194 let sc = PerlValue::hash(h).scalar_context();
4195 assert!(sc.is_string_like());
4196 }
4197
4198 #[test]
4199 fn to_list_array_hash_and_scalar() {
4200 assert_eq!(
4201 PerlValue::array(vec![PerlValue::integer(7)])
4202 .to_list()
4203 .len(),
4204 1
4205 );
4206 let mut h = IndexMap::new();
4207 h.insert("k".into(), PerlValue::integer(1));
4208 let list = PerlValue::hash(h).to_list();
4209 assert_eq!(list.len(), 2);
4210 let one = PerlValue::integer(99).to_list();
4211 assert_eq!(one.len(), 1);
4212 assert_eq!(one[0].to_int(), 99);
4213 }
4214
4215 #[test]
4216 fn type_name_and_ref_type_for_core_kinds() {
4217 assert_eq!(PerlValue::integer(0).type_name(), "INTEGER");
4218 assert_eq!(PerlValue::UNDEF.ref_type().to_string(), "");
4219 assert_eq!(
4220 PerlValue::array_ref(Arc::new(RwLock::new(vec![])))
4221 .ref_type()
4222 .to_string(),
4223 "ARRAY"
4224 );
4225 }
4226
4227 #[test]
4228 fn display_undef_is_empty_integer_is_decimal() {
4229 assert_eq!(PerlValue::UNDEF.to_string(), "");
4230 assert_eq!(PerlValue::integer(-7).to_string(), "-7");
4231 }
4232
4233 #[test]
4234 fn empty_array_is_false_nonempty_is_true() {
4235 assert!(!PerlValue::array(vec![]).is_true());
4236 assert!(PerlValue::array(vec![PerlValue::integer(0)]).is_true());
4237 }
4238
4239 #[test]
4240 fn to_number_undef_and_non_numeric_refs_are_zero() {
4241 use super::PerlSub;
4242
4243 assert_eq!(PerlValue::UNDEF.to_number(), 0.0);
4244 assert_eq!(
4245 PerlValue::code_ref(Arc::new(PerlSub {
4246 name: "f".into(),
4247 params: vec![],
4248 body: vec![],
4249 closure_env: None,
4250 prototype: None,
4251 fib_like: None,
4252 }))
4253 .to_number(),
4254 0.0
4255 );
4256 }
4257
4258 #[test]
4259 fn append_to_builds_string_without_extra_alloc_for_int_and_string() {
4260 let mut buf = String::new();
4261 PerlValue::integer(-12).append_to(&mut buf);
4262 PerlValue::string("ab".into()).append_to(&mut buf);
4263 assert_eq!(buf, "-12ab");
4264 let mut u = String::new();
4265 PerlValue::UNDEF.append_to(&mut u);
4266 assert!(u.is_empty());
4267 }
4268
4269 #[test]
4270 fn append_to_atomic_delegates_to_inner() {
4271 use parking_lot::Mutex;
4272 let a = PerlValue::atomic(Arc::new(Mutex::new(PerlValue::string("z".into()))));
4273 let mut buf = String::new();
4274 a.append_to(&mut buf);
4275 assert_eq!(buf, "z");
4276 }
4277
4278 #[test]
4279 fn unwrap_atomic_reads_inner_other_variants_clone() {
4280 use parking_lot::Mutex;
4281 let a = PerlValue::atomic(Arc::new(Mutex::new(PerlValue::integer(9))));
4282 assert_eq!(a.unwrap_atomic().to_int(), 9);
4283 assert_eq!(PerlValue::integer(3).unwrap_atomic().to_int(), 3);
4284 }
4285
4286 #[test]
4287 fn is_atomic_only_true_for_atomic_variant() {
4288 use parking_lot::Mutex;
4289 assert!(PerlValue::atomic(Arc::new(Mutex::new(PerlValue::UNDEF))).is_atomic());
4290 assert!(!PerlValue::integer(0).is_atomic());
4291 }
4292
4293 #[test]
4294 fn as_str_only_on_string_variant() {
4295 assert_eq!(
4296 PerlValue::string("x".into()).as_str(),
4297 Some("x".to_string())
4298 );
4299 assert_eq!(PerlValue::integer(1).as_str(), None);
4300 }
4301
4302 #[test]
4303 fn as_str_or_empty_defaults_non_string() {
4304 assert_eq!(PerlValue::string("z".into()).as_str_or_empty(), "z");
4305 assert_eq!(PerlValue::integer(1).as_str_or_empty(), "");
4306 }
4307
4308 #[test]
4309 fn to_int_truncates_float_toward_zero() {
4310 assert_eq!(PerlValue::float(3.9).to_int(), 3);
4311 assert_eq!(PerlValue::float(-2.1).to_int(), -2);
4312 }
4313
4314 #[test]
4315 fn to_number_array_is_length() {
4316 assert_eq!(
4317 PerlValue::array(vec![PerlValue::integer(1), PerlValue::integer(2)]).to_number(),
4318 2.0
4319 );
4320 }
4321
4322 #[test]
4323 fn scalar_context_empty_hash_is_zero() {
4324 let h = IndexMap::new();
4325 assert_eq!(PerlValue::hash(h).scalar_context().to_int(), 0);
4326 }
4327
4328 #[test]
4329 fn scalar_context_nonhash_nonarray_clones() {
4330 let v = PerlValue::integer(8);
4331 assert_eq!(v.scalar_context().to_int(), 8);
4332 }
4333
4334 #[test]
4335 fn display_float_integer_like_omits_decimal() {
4336 assert_eq!(PerlValue::float(4.0).to_string(), "4");
4337 }
4338
4339 #[test]
4340 fn display_array_concatenates_element_displays() {
4341 let a = PerlValue::array(vec![PerlValue::integer(1), PerlValue::string("b".into())]);
4342 assert_eq!(a.to_string(), "1b");
4343 }
4344
4345 #[test]
4346 fn display_code_ref_includes_sub_name() {
4347 use super::PerlSub;
4348 let c = PerlValue::code_ref(Arc::new(PerlSub {
4349 name: "foo".into(),
4350 params: vec![],
4351 body: vec![],
4352 closure_env: None,
4353 prototype: None,
4354 fib_like: None,
4355 }));
4356 assert!(c.to_string().contains("foo"));
4357 }
4358
4359 #[test]
4360 fn display_regex_shows_non_capturing_prefix() {
4361 let r = PerlValue::regex(
4362 PerlCompiledRegex::compile("x+").unwrap(),
4363 "x+".into(),
4364 "".into(),
4365 );
4366 assert_eq!(r.to_string(), "(?:x+)");
4367 }
4368
4369 #[test]
4370 fn display_iohandle_is_name() {
4371 assert_eq!(PerlValue::io_handle("STDOUT".into()).to_string(), "STDOUT");
4372 }
4373
4374 #[test]
4375 fn ref_type_blessed_uses_class_name() {
4376 let b = PerlValue::blessed(Arc::new(super::BlessedRef::new_blessed(
4377 "Pkg".into(),
4378 PerlValue::UNDEF,
4379 )));
4380 assert_eq!(b.ref_type().to_string(), "Pkg");
4381 }
4382
4383 #[test]
4384 fn blessed_drop_enqueues_pending_destroy() {
4385 let v = PerlValue::blessed(Arc::new(super::BlessedRef::new_blessed(
4386 "Z".into(),
4387 PerlValue::integer(7),
4388 )));
4389 drop(v);
4390 let q = crate::pending_destroy::take_queue();
4391 assert_eq!(q.len(), 1);
4392 assert_eq!(q[0].0, "Z");
4393 assert_eq!(q[0].1.to_int(), 7);
4394 }
4395
4396 #[test]
4397 fn type_name_iohandle_is_glob() {
4398 assert_eq!(PerlValue::io_handle("FH".into()).type_name(), "GLOB");
4399 }
4400
4401 #[test]
4402 fn empty_hash_is_false() {
4403 assert!(!PerlValue::hash(IndexMap::new()).is_true());
4404 }
4405
4406 #[test]
4407 fn hash_nonempty_is_true() {
4408 let mut h = IndexMap::new();
4409 h.insert("k".into(), PerlValue::UNDEF);
4410 assert!(PerlValue::hash(h).is_true());
4411 }
4412
4413 #[test]
4414 fn num_cmp_equal_integers() {
4415 assert_eq!(
4416 PerlValue::integer(5).num_cmp(&PerlValue::integer(5)),
4417 Ordering::Equal
4418 );
4419 }
4420
4421 #[test]
4422 fn str_cmp_compares_lexicographic_string_forms() {
4423 assert_eq!(
4425 PerlValue::integer(2).str_cmp(&PerlValue::integer(10)),
4426 Ordering::Greater
4427 );
4428 }
4429
4430 #[test]
4431 fn to_list_undef_empty() {
4432 assert!(PerlValue::UNDEF.to_list().is_empty());
4433 }
4434
4435 #[test]
4436 fn unwrap_atomic_nested_atomic() {
4437 use parking_lot::Mutex;
4438 let inner = PerlValue::atomic(Arc::new(Mutex::new(PerlValue::integer(2))));
4439 let outer = PerlValue::atomic(Arc::new(Mutex::new(inner)));
4440 assert_eq!(outer.unwrap_atomic().to_int(), 2);
4441 }
4442
4443 #[test]
4444 fn errno_dual_parts_extracts_code_and_message() {
4445 let v = PerlValue::errno_dual(-2, "oops".into());
4446 assert_eq!(v.errno_dual_parts(), Some((-2, "oops".into())));
4447 }
4448
4449 #[test]
4450 fn errno_dual_parts_none_for_plain_string() {
4451 assert!(PerlValue::string("hi".into()).errno_dual_parts().is_none());
4452 }
4453
4454 #[test]
4455 fn errno_dual_parts_none_for_integer() {
4456 assert!(PerlValue::integer(1).errno_dual_parts().is_none());
4457 }
4458
4459 #[test]
4460 fn errno_dual_numeric_context_uses_code_string_uses_msg() {
4461 let v = PerlValue::errno_dual(5, "five".into());
4462 assert_eq!(v.to_int(), 5);
4463 assert_eq!(v.to_string(), "five");
4464 }
4465
4466 #[test]
4467 fn list_range_alpha_joins_like_perl() {
4468 use super::perl_list_range_expand;
4469 let v =
4470 perl_list_range_expand(PerlValue::string("a".into()), PerlValue::string("z".into()));
4471 let s: String = v.iter().map(|x| x.to_string()).collect();
4472 assert_eq!(s, "abcdefghijklmnopqrstuvwxyz");
4473 }
4474
4475 #[test]
4476 fn list_range_numeric_string_endpoints() {
4477 use super::perl_list_range_expand;
4478 let v = perl_list_range_expand(
4479 PerlValue::string("9".into()),
4480 PerlValue::string("11".into()),
4481 );
4482 assert_eq!(v.len(), 3);
4483 assert_eq!(
4484 v.iter().map(|x| x.to_int()).collect::<Vec<_>>(),
4485 vec![9, 10, 11]
4486 );
4487 }
4488
4489 #[test]
4490 fn list_range_leading_zero_is_string_mode() {
4491 use super::perl_list_range_expand;
4492 let v = perl_list_range_expand(
4493 PerlValue::string("01".into()),
4494 PerlValue::string("05".into()),
4495 );
4496 assert_eq!(v.len(), 5);
4497 assert_eq!(
4498 v.iter().map(|x| x.to_string()).collect::<Vec<_>>(),
4499 vec!["01", "02", "03", "04", "05"]
4500 );
4501 }
4502
4503 #[test]
4504 fn list_range_empty_to_letter_one_element() {
4505 use super::perl_list_range_expand;
4506 let v = perl_list_range_expand(
4507 PerlValue::string(String::new()),
4508 PerlValue::string("c".into()),
4509 );
4510 assert_eq!(v.len(), 1);
4511 assert_eq!(v[0].to_string(), "");
4512 }
4513
4514 #[test]
4515 fn magic_string_inc_z_wraps_aa() {
4516 use super::{perl_magic_string_increment_for_range, PerlListRangeIncOutcome};
4517 let mut s = "z".to_string();
4518 assert_eq!(
4519 perl_magic_string_increment_for_range(&mut s),
4520 PerlListRangeIncOutcome::Continue
4521 );
4522 assert_eq!(s, "aa");
4523 }
4524
4525 #[test]
4526 fn test_boxed_numeric_stringification() {
4527 let large_int = 10_000_000_000i64;
4529 let v_int = PerlValue::integer(large_int);
4530 assert_eq!(v_int.to_string(), "10000000000");
4531
4532 let v_inf = PerlValue::float(f64::INFINITY);
4534 assert_eq!(v_inf.to_string(), "Inf");
4535 }
4536
4537 #[test]
4538 fn magic_string_inc_nine_to_ten() {
4539 use super::{perl_magic_string_increment_for_range, PerlListRangeIncOutcome};
4540 let mut s = "9".to_string();
4541 assert_eq!(
4542 perl_magic_string_increment_for_range(&mut s),
4543 PerlListRangeIncOutcome::Continue
4544 );
4545 assert_eq!(s, "10");
4546 }
4547}